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 {...@@ -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";
...@@ -8228,40 +8235,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us...@@ -8228,40 +8235,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
8228 const buffer = data[index];8235 const buffer = data[index];
82298236
8230 var io_status_block: windows.IO_STATUS_BLOCK = undefined;8237 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8231 var done: bool = false;8238 const syscall: Syscall = try .start();
8232 const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64);8239 while (true) {
82338240 io_status_block.u.Status = .PENDING;
8234 read: {8241 switch (windows.ntdll.NtReadFile(
8235 const syscall: Syscall = try .start();8242 file.handle,
8236 while (true) {8243 null, // event
8237 switch (windows.ntdll.NtReadFile(8244 noopApc, // apc callback
8238 file.handle,8245 null, // apc context
8239 null, // event8246 &io_status_block,
8240 flagApc, // apc callback8247 buffer.ptr,
8241 &done, // apc context8248 @min(std.math.maxInt(u32), buffer.len),
8242 &io_status_block,8249 null, // byte offset
8243 buffer.ptr,8250 null, // key
8244 @min(std.math.maxInt(u32), buffer.len),8251 )) {
8245 null, // byte offset8252 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {
8246 null, // key8253 syscall.finish();
8247 )) {8254 return io_status_block.Information;
8248 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(),8255 },
8249 .PENDING => break,8256 .PENDING => break,
8250 .CANCELLED => {8257 .CANCELLED => {
8251 try syscall.checkCancel();8258 try syscall.checkCancel();
8252 continue;8259 continue;
8253 },8260 },
8254 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),8261 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8255 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),8262 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8256 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8263 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8257 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file8264 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
8258 else => |status| return syscall.unexpectedNtstatus(status),8265 else => |status| return syscall.unexpectedNtstatus(status),
8259 }
8260 }8266 }
8267 }
8268 {
8261 // Once we get here we received PENDING so we must not return from the8269 // Once we get here we received PENDING so we must not return from the
8262 // function until the operation completes.8270 // function until the operation completes.
8263 defer while (!done) {8271 defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8264 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);8272 waitForApcOrAlert();
8265 };8273 };
82668274
8267 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {8275 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {
...@@ -8271,36 +8279,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us...@@ -8271,36 +8279,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us
8271 },8279 },
8272 };8280 };
8273 defer alertable_syscall.finish();8281 defer alertable_syscall.finish();
8274 while (!done) {8282 waitForApcOrAlert();
8275 _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval);8283 while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8276 alertable_syscall.checkCancel() catch |err| switch (err) {8284 alertable_syscall.checkCancel() catch |err| switch (err) {
8277 error.Canceled => |e| {8285 error.Canceled => |e| {
8278 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);8286 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
8279 return e;8287 return e;
8280 },8288 },
8281 };8289 };
8290 waitForApcOrAlert();
8282 }8291 }
8283 }8292 }
8284
8285 switch (io_status_block.u.Status) {8293 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
8287 .INVALID_DEVICE_REQUEST => return error.IsDir,8296 .INVALID_DEVICE_REQUEST => return error.IsDir,
8288 .LOCK_NOT_GRANTED => return error.LockViolation,8297 .LOCK_NOT_GRANTED => return error.LockViolation,
8289 .ACCESS_DENIED => return error.AccessDenied,8298 .ACCESS_DENIED => return error.AccessDenied,
8290 else => |status| return windows.unexpectedStatus(status),8299 else => |status| return windows.unexpectedStatus(status),
8291 }8300 }
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;
8304}8301}
83058302
8306fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {8303fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
...@@ -14407,7 +14404,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -14407,7 +14404,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
14407 t.mutex.lock(); // Another thread might have won the race.14404 t.mutex.lock(); // Another thread might have won the race.
14408 defer t.mutex.unlock();14405 defer t.mutex.unlock();
14409 if (t.random_file.handle) |prev_handle| {14406 if (t.random_file.handle) |prev_handle| {
14410 _ = windows.ntdll.NtClose(fresh_handle);14407 windows.CloseHandle(fresh_handle);
14411 return prev_handle;14408 return prev_handle;
14412 } else {14409 } else {
14413 t.random_file.handle = fresh_handle;14410 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 }