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-30 12:10:01-08:00
logb5174455f8049b926b9e712ed8f77c13327ffe8c
tree91e7c23f828804f240eff4587c735a0ba6607ad2
parent29f44952c118be655e152b8f6c4ae78a0cc619a9

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 {...@@ -1314,6 +1314,13 @@ const AlertableSyscall = struct {
1314 }1314 }
1315};1315};
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
1317const max_iovecs_len = 8;1324const max_iovecs_len = 8;
1318const splat_buffer_size = 64;1325const splat_buffer_size = 64;
1319const default_PATH = "/usr/local/bin:/bin/:/usr/bin";1326const default_PATH = "/usr/local/bin:/bin/:/usr/bin";
...@@ -8371,40 +8378,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us...@@ -8371,40 +8378,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
8371 const buffer = data[index];8378 const buffer = data[index];
83728379
8373 var io_status_block: windows.IO_STATUS_BLOCK = undefined;8380 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8374 var done: bool = false;8381 const syscall: Syscall = try .start();
8375 const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64);8382 while (true) {
83768383 io_status_block.u.Status = .PENDING;
8377 read: {8384 switch (windows.ntdll.NtReadFile(
8378 const syscall: Syscall = try .start();8385 file.handle,
8379 while (true) {8386 null, // event
8380 switch (windows.ntdll.NtReadFile(8387 noopApc, // apc callback
8381 file.handle,8388 null, // apc context
8382 null, // event8389 &io_status_block,
8383 flagApc, // apc callback8390 buffer.ptr,
8384 &done, // apc context8391 @min(std.math.maxInt(u32), buffer.len),
8385 &io_status_block,8392 null, // byte offset
8386 buffer.ptr,8393 null, // key
8387 @min(std.math.maxInt(u32), buffer.len),8394 )) {
8388 null, // byte offset8395 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {
8389 null, // key8396 syscall.finish();
8390 )) {8397 return io_status_block.Information;
8391 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(),8398 },
8392 .PENDING => break,8399 .PENDING => break,
8393 .CANCELLED => {8400 .CANCELLED => {
8394 try syscall.checkCancel();8401 try syscall.checkCancel();
8395 continue;8402 continue;
8396 },8403 },
8397 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),8404 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8398 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),8405 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8399 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8406 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8400 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file8407 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
8401 else => |status| return syscall.unexpectedNtstatus(status),8408 else => |status| return syscall.unexpectedNtstatus(status),
8402 }
8403 }8409 }
8410 }
8411 {
8404 // Once we get here we received PENDING so we must not return from the8412 // Once we get here we received PENDING so we must not return from the
8405 // function until the operation completes.8413 // function until the operation completes.
8406 defer while (!done) {8414 defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8407 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);8415 waitForApcOrAlert();
8408 };8416 };
84098417
8410 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {8418 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {
...@@ -8414,36 +8422,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us...@@ -8414,36 +8422,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
8414 },8422 },
8415 };8423 };
8416 defer alertable_syscall.finish();8424 defer alertable_syscall.finish();
8417 while (!done) {8425 waitForApcOrAlert();
8418 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);8426 while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8419 alertable_syscall.checkCancel() catch |err| switch (err) {8427 alertable_syscall.checkCancel() catch |err| switch (err) {
8420 error.Canceled => |e| {8428 error.Canceled => |e| {
8421 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);8429 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
8422 return e;8430 return e;
8423 },8431 },
8424 };8432 };
8433 waitForApcOrAlert();
8425 }8434 }
8426 }8435 }
8427
8428 switch (io_status_block.u.Status) {8436 switch (io_status_block.u.Status) {
8429 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {},8437 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information,
8438 .PENDING => unreachable, // cannot return until the operation completes
8430 .INVALID_DEVICE_REQUEST => return error.IsDir,8439 .INVALID_DEVICE_REQUEST => return error.IsDir,
8431 .LOCK_NOT_GRANTED => return error.LockViolation,8440 .LOCK_NOT_GRANTED => return error.LockViolation,
8432 .ACCESS_DENIED => return error.AccessDenied,8441 .ACCESS_DENIED => return error.AccessDenied,
8433 else => |status| return windows.unexpectedStatus(status),8442 else => |status| return windows.unexpectedStatus(status),
8434 }8443 }
8435 return io_status_block.Information;
8436}
8437
8438fn flagApc(
8439 apc_context: ?*anyopaque,
8440 io_status_block: *windows.IO_STATUS_BLOCK,
8441 unused: windows.ULONG,
8442) callconv(.winapi) void {
8443 const flag: *bool = @ptrCast(apc_context);
8444 flag.* = true;
8445 _ = io_status_block;
8446 _ = unused;
8447}8444}
84488445
8449fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {8446fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
...@@ -14646,7 +14643,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -14646,7 +14643,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
14646 t.mutex.lock(); // Another thread might have won the race.14643 t.mutex.lock(); // Another thread might have won the race.
14647 defer t.mutex.unlock();14644 defer t.mutex.unlock();
14648 if (t.random_file.handle) |prev_handle| {14645 if (t.random_file.handle) |prev_handle| {
14649 _ = windows.ntdll.NtClose(fresh_handle);14646 windows.CloseHandle(fresh_handle);
14650 return prev_handle;14647 return prev_handle;
14651 } else {14648 } else {
14652 t.random_file.handle = fresh_handle;14649 t.random_file.handle = fresh_handle;
src/codegen/c/Type.zig+2-2
...@@ -2389,7 +2389,7 @@ pub const Pool = struct {...@@ -2389,7 +2389,7 @@ pub const Pool = struct {
2389 .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) {2389 .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) {
2390 .none => true,2390 .none => true,
2391 .zero_u8 => false,2391 .zero_u8 => false,
2392 else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq),2392 else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu),
2393 },2393 },
2394 });2394 });
2395 },2395 },
...@@ -2438,7 +2438,7 @@ pub const Pool = struct {...@@ -2438,7 +2438,7 @@ pub const Pool = struct {
2438 .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) {2438 .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) {
2439 .none => true,2439 .none => true,
2440 .zero_u8 => false,2440 .zero_u8 => false,
2441 else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq),2441 else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu),
2442 },2442 },
2443 });2443 });
2444 if (!kind.isParameter()) return array_ctype;2444 if (!kind.isParameter()) return array_ctype;
src/link.zig+9-2
...@@ -605,8 +605,8 @@ pub const File = struct {...@@ -605,8 +605,8 @@ pub const File = struct {
605 switch (base.tag) {605 switch (base.tag) {
606 .lld => assert(base.file == null),606 .lld => assert(base.file == null),
607 .elf, .macho, .wasm => {607 .elf, .macho, .wasm => {
608 if (base.file != null) return;
609 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });608 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });
609 if (base.file != null) return;
610 const emit = base.emit;610 const emit = base.emit;
611 if (base.child_pid) |pid| {611 if (base.child_pid) |pid| {
612 if (builtin.os.tag == .windows) {612 if (builtin.os.tag == .windows) {
...@@ -645,6 +645,7 @@ pub const File = struct {...@@ -645,6 +645,7 @@ pub const File = struct {
645 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });645 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
646 },646 },
647 .elf2, .coff2 => if (base.file == null) {647 .elf2, .coff2 => if (base.file == null) {
648 dev.checkAny(&.{ .elf2_linker, .coff2_linker });
648 const mf = if (base.cast(.elf2)) |elf|649 const mf = if (base.cast(.elf2)) |elf|
649 &elf.mf650 &elf.mf
650 else if (base.cast(.coff2)) |coff|651 else if (base.cast(.coff2)) |coff|
...@@ -657,7 +658,13 @@ pub const File = struct {...@@ -657,7 +658,13 @@ pub const File = struct {
657 base.file = mf.memory_map.file;658 base.file = mf.memory_map.file;
658 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));659 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
659 },660 },
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),
661 .plan9 => unreachable,668 .plan9 => unreachable,
662 }669 }
663 }670 }