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 {
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";
......@@ -8371,40 +8378,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
83718378 const buffer = data[index];
83728379
83738380 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8374 var done: bool = false;
8375 const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64);
8376
8377 read: {
8378 const syscall: Syscall = try .start();
8379 while (true) {
8380 switch (windows.ntdll.NtReadFile(
8381 file.handle,
8382 null, // event
8383 flagApc, // apc callback
8384 &done, // apc context
8385 &io_status_block,
8386 buffer.ptr,
8387 @min(std.math.maxInt(u32), buffer.len),
8388 null, // byte offset
8389 null, // key
8390 )) {
8391 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(),
8392 .PENDING => break,
8393 .CANCELLED => {
8394 try syscall.checkCancel();
8395 continue;
8396 },
8397 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8398 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8399 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8400 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
8401 else => |status| return syscall.unexpectedNtstatus(status),
8402 }
8381 const syscall: Syscall = try .start();
8382 while (true) {
8383 io_status_block.u.Status = .PENDING;
8384 switch (windows.ntdll.NtReadFile(
8385 file.handle,
8386 null, // event
8387 noopApc, // apc callback
8388 null, // apc context
8389 &io_status_block,
8390 buffer.ptr,
8391 @min(std.math.maxInt(u32), buffer.len),
8392 null, // byte offset
8393 null, // key
8394 )) {
8395 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {
8396 syscall.finish();
8397 return io_status_block.Information;
8398 },
8399 .PENDING => break,
8400 .CANCELLED => {
8401 try syscall.checkCancel();
8402 continue;
8403 },
8404 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8405 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8406 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8407 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
8408 else => |status| return syscall.unexpectedNtstatus(status),
84038409 }
8410 }
8411 {
84048412 // Once we get here we received PENDING so we must not return from the
84058413 // function until the operation completes.
8406 defer while (!done) {
8407 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);
8414 defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8415 waitForApcOrAlert();
84088416 };
84098417
84108418 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {
......@@ -8414,36 +8422,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
84148422 },
84158423 };
84168424 defer alertable_syscall.finish();
8417 while (!done) {
8418 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);
8425 waitForApcOrAlert();
8426 while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
84198427 alertable_syscall.checkCancel() catch |err| switch (err) {
84208428 error.Canceled => |e| {
84218429 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
84228430 return e;
84238431 },
84248432 };
8433 waitForApcOrAlert();
84258434 }
84268435 }
8427
84288436 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
84308439 .INVALID_DEVICE_REQUEST => return error.IsDir,
84318440 .LOCK_NOT_GRANTED => return error.LockViolation,
84328441 .ACCESS_DENIED => return error.AccessDenied,
84338442 else => |status| return windows.unexpectedStatus(status),
84348443 }
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;
84478444}
84488445
84498446fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
......@@ -14646,7 +14643,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
1464614643 t.mutex.lock(); // Another thread might have won the race.
1464714644 defer t.mutex.unlock();
1464814645 if (t.random_file.handle) |prev_handle| {
14649 _ = windows.ntdll.NtClose(fresh_handle);
14646 windows.CloseHandle(fresh_handle);
1465014647 return prev_handle;
1465114648 } else {
1465214649 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 }