zig

fork of https://codeberg.org/ziglang/zig
Log | Files | Refs | README | LICENSE

commit 90890fcb5cf39e53dc470db8260964f95b607937 (tree)
parent 9862518797f79c946ccfad3339260597ce37ca18
Author: Jacob Young <jacobly0@users.noreply.github.com>
Date:   Sat, 24 Jan 2026 03:37:43 -0500

Io.Threaded: fix UAF-induced crashes during asynchronous operations

When `NtReadFile` returns `SUCCESS`, the APC routine still runs when
next alertable, which was previously clobbering an out of scope `done`.
Instead of adding an extra syscall to the success path, avoid all APC
side effects, allowing instant completions to return immediately.

Diffstat:
Mlib/std/Io/Threaded.zig | 93++++++++++++++++++++++++++++++++++++++-----------------------------------------
Msrc/codegen/c/Type.zig | 4++--
Msrc/link.zig | 11+++++++++--
3 files changed, 56 insertions(+), 52 deletions(-)

diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig @@ -1314,6 +1314,13 @@ const AlertableSyscall = struct { } }; +fn noopApc(_: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void {} + +fn waitForApcOrAlert() void { + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout); +} + const max_iovecs_len = 8; const splat_buffer_size = 64; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; @@ -8371,40 +8378,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us const buffer = data[index]; var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var done: bool = false; - const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64); - - read: { - const syscall: Syscall = try .start(); - while (true) { - switch (windows.ntdll.NtReadFile( - file.handle, - null, // event - flagApc, // apc callback - &done, // apc context - &io_status_block, - buffer.ptr, - @min(std.math.maxInt(u32), buffer.len), - null, // byte offset - null, // key - )) { - .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(), - .PENDING => break, - .CANCELLED => { - try syscall.checkCancel(); - continue; - }, - .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir), - .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file - else => |status| return syscall.unexpectedNtstatus(status), - } + const syscall: Syscall = try .start(); + while (true) { + io_status_block.u.Status = .PENDING; + switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + noopApc, // apc callback + null, // apc context + &io_status_block, + buffer.ptr, + @min(std.math.maxInt(u32), buffer.len), + null, // byte offset + null, // key + )) { + .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => { + syscall.finish(); + return io_status_block.Information; + }, + .PENDING => break, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir), + .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file + else => |status| return syscall.unexpectedNtstatus(status), } + } + { // Once we get here we received PENDING so we must not return from the // function until the operation completes. - defer while (!done) { - _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval); + defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { + waitForApcOrAlert(); }; const alertable_syscall = syscall.toAlertable() catch |err| switch (err) { @@ -8414,36 +8422,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us }, }; defer alertable_syscall.finish(); - while (!done) { - _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval); + waitForApcOrAlert(); + while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { alertable_syscall.checkCancel() catch |err| switch (err) { error.Canceled => |e| { _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); return e; }, }; + waitForApcOrAlert(); } } - switch (io_status_block.u.Status) { - .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {}, + .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information, + .PENDING => unreachable, // cannot return until the operation completes .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, else => |status| return windows.unexpectedStatus(status), } - return io_status_block.Information; -} - -fn flagApc( - apc_context: ?*anyopaque, - io_status_block: *windows.IO_STATUS_BLOCK, - unused: windows.ULONG, -) callconv(.winapi) void { - const flag: *bool = @ptrCast(apc_context); - flag.* = true; - _ = io_status_block; - _ = unused; } fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { @@ -14646,7 +14643,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { t.mutex.lock(); // Another thread might have won the race. defer t.mutex.unlock(); if (t.random_file.handle) |prev_handle| { - _ = windows.ntdll.NtClose(fresh_handle); + windows.CloseHandle(fresh_handle); return prev_handle; } else { t.random_file.handle = fresh_handle; diff --git a/src/codegen/c/Type.zig b/src/codegen/c/Type.zig @@ -2389,7 +2389,7 @@ pub const Pool = struct { .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) { .none => true, .zero_u8 => false, - else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq), + else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu), }, }); }, @@ -2438,7 +2438,7 @@ pub const Pool = struct { .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) { .none => true, .zero_u8 => false, - else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq), + else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu), }, }); if (!kind.isParameter()) return array_ctype; diff --git a/src/link.zig b/src/link.zig @@ -605,8 +605,8 @@ pub const File = struct { switch (base.tag) { .lld => assert(base.file == null), .elf, .macho, .wasm => { - if (base.file != null) return; dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker }); + if (base.file != null) return; const emit = base.emit; if (base.child_pid) |pid| { if (builtin.os.tag == .windows) { @@ -645,6 +645,7 @@ pub const File = struct { base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write }); }, .elf2, .coff2 => if (base.file == null) { + dev.checkAny(&.{ .elf2_linker, .coff2_linker }); const mf = if (base.cast(.elf2)) |elf| &elf.mf else if (base.cast(.coff2)) |coff| @@ -657,7 +658,13 @@ pub const File = struct { base.file = mf.memory_map.file; try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); }, - .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), + .c => if (base.file == null) { + dev.check(.c_linker); + base.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ + .mode = .write_only, + }); + }, + .spirv => dev.check(.spirv_linker), .plan9 => unreachable, } }