authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-01-24 03:37:43-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-27 15:32:34-08:00
log456e0492f271957738a6502713c5382e45fb6296
tree9cd5ad47ea8b3ffdcfa3aecf24c0a6025da7e485
parent041add45ad37522f1b3dea8daec0808b5dc55d1d

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.

3 files changed, 56 insertions(+), 52 deletions(-)

lib/std/Io/Threaded.zig+45-48
......@@ -1314,6 +1314,13 @@ const AlertableSyscall = struct {
13141314 }
13151315};
13161316
1317fn noopApc(_: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void {}
1318
1319fn waitForApcOrAlert() void {
1320 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
1321 _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout);
1322}
1323
13171324const max_iovecs_len = 8;
13181325const splat_buffer_size = 64;
13191326const default_PATH = "/usr/local/bin:/bin/:/usr/bin";
......@@ -8228,40 +8235,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
82288235 const buffer = data[index];
82298236
82308237 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8231 var done: bool = false;
8232 const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64);
8233
8234 read: {
8235 const syscall: Syscall = try .start();
8236 while (true) {
8237 switch (windows.ntdll.NtReadFile(
8238 file.handle,
8239 null, // event
8240 flagApc, // apc callback
8241 &done, // apc context
8242 &io_status_block,
8243 buffer.ptr,
8244 @min(std.math.maxInt(u32), buffer.len),
8245 null, // byte offset
8246 null, // key
8247 )) {
8248 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(),
8249 .PENDING => break,
8250 .CANCELLED => {
8251 try syscall.checkCancel();
8252 continue;
8253 },
8254 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8255 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8256 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8257 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
8258 else => |status| return syscall.unexpectedNtstatus(status),
8259 }
8238 const syscall: Syscall = try .start();
8239 while (true) {
8240 io_status_block.u.Status = .PENDING;
8241 switch (windows.ntdll.NtReadFile(
8242 file.handle,
8243 null, // event
8244 noopApc, // apc callback
8245 null, // apc context
8246 &io_status_block,
8247 buffer.ptr,
8248 @min(std.math.maxInt(u32), buffer.len),
8249 null, // byte offset
8250 null, // key
8251 )) {
8252 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {
8253 syscall.finish();
8254 return io_status_block.Information;
8255 },
8256 .PENDING => break,
8257 .CANCELLED => {
8258 try syscall.checkCancel();
8259 continue;
8260 },
8261 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8262 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8263 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8264 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
8265 else => |status| return syscall.unexpectedNtstatus(status),
82608266 }
8267 }
8268 {
82618269 // Once we get here we received PENDING so we must not return from the
82628270 // function until the operation completes.
8263 defer while (!done) {
8264 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);
8271 defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8272 waitForApcOrAlert();
82658273 };
82668274
82678275 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {
......@@ -8271,36 +8279,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
82718279 },
82728280 };
82738281 defer alertable_syscall.finish();
8274 while (!done) {
8275 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);
8282 waitForApcOrAlert();
8283 while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
82768284 alertable_syscall.checkCancel() catch |err| switch (err) {
82778285 error.Canceled => |e| {
82788286 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
82798287 return e;
82808288 },
82818289 };
8290 waitForApcOrAlert();
82828291 }
82838292 }
8284
82858293 switch (io_status_block.u.Status) {
8286 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {},
8294 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information,
8295 .PENDING => unreachable, // cannot return until the operation completes
82878296 .INVALID_DEVICE_REQUEST => return error.IsDir,
82888297 .LOCK_NOT_GRANTED => return error.LockViolation,
82898298 .ACCESS_DENIED => return error.AccessDenied,
82908299 else => |status| return windows.unexpectedStatus(status),
82918300 }
8292 return io_status_block.Information;
8293}
8294
8295fn flagApc(
8296 apc_context: ?*anyopaque,
8297 io_status_block: *windows.IO_STATUS_BLOCK,
8298 unused: windows.ULONG,
8299) callconv(.winapi) void {
8300 const flag: *bool = @ptrCast(apc_context);
8301 flag.* = true;
8302 _ = io_status_block;
8303 _ = unused;
83048301}
83058302
83068303fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
......@@ -14407,7 +14404,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1440714404 t.mutex.lock(); // Another thread might have won the race.
1440814405 defer t.mutex.unlock();
1440914406 if (t.random_file.handle) |prev_handle| {
14410 _ = windows.ntdll.NtClose(fresh_handle);
14407 windows.CloseHandle(fresh_handle);
1441114408 return prev_handle;
1441214409 } else {
1441314410 t.random_file.handle = fresh_handle;
src/codegen/c/Type.zig+2-2
......@@ -2389,7 +2389,7 @@ pub const Pool = struct {
23892389 .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) {
23902390 .none => true,
23912391 .zero_u8 => false,
2392 else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq),
2392 else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu),
23932393 },
23942394 });
23952395 },
......@@ -2438,7 +2438,7 @@ pub const Pool = struct {
24382438 .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) {
24392439 .none => true,
24402440 .zero_u8 => false,
2441 else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq),
2441 else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu),
24422442 },
24432443 });
24442444 if (!kind.isParameter()) return array_ctype;
src/link.zig+9-2
......@@ -605,8 +605,8 @@ pub const File = struct {
605605 switch (base.tag) {
606606 .lld => assert(base.file == null),
607607 .elf, .macho, .wasm => {
608 if (base.file != null) return;
609608 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });
609 if (base.file != null) return;
610610 const emit = base.emit;
611611 if (base.child_pid) |pid| {
612612 if (builtin.os.tag == .windows) {
......@@ -645,6 +645,7 @@ pub const File = struct {
645645 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
646646 },
647647 .elf2, .coff2 => if (base.file == null) {
648 dev.checkAny(&.{ .elf2_linker, .coff2_linker });
648649 const mf = if (base.cast(.elf2)) |elf|
649650 &elf.mf
650651 else if (base.cast(.coff2)) |coff|
......@@ -657,7 +658,13 @@ pub const File = struct {
657658 base.file = mf.memory_map.file;
658659 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
659660 },
660 .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),
661 .c => if (base.file == null) {
662 dev.check(.c_linker);
663 base.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
664 .mode = .write_only,
665 });
666 },
667 .spirv => dev.check(.spirv_linker),
661668 .plan9 => unreachable,
662669 }
663670 }