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:
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,
}
}