authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-05 13:40:20-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 11:03:36-08:00
log2f639a45b401998a113174829839110ba8970dfa
tree57150266ddcdd69979ca44ab6c8bb13a914826ca
parent42ca9e5d8e45264ae39ac9dd7fd34e2d9f82a61a

std.Io.Threaded: implement windows random with \Device\CNG


2 files changed, 125 insertions(+), 97 deletions(-)

lib/std/Io/Threaded.zig+125-41
...@@ -65,7 +65,7 @@ argv0: Argv0,...@@ -65,7 +65,7 @@ argv0: Argv0,
65environ: Environ,65environ: Environ,
6666
67null_file: NullFile = .{},67null_file: NullFile = .{},
68dev_urandom_fd: dev_urandom_fd_t = if (use_dev_urandom) -1 else {},68random_file: RandomFile = .{},
6969
70pub const Argv0 = switch (native_os) {70pub const Argv0 = switch (native_os) {
71 .openbsd, .haiku => struct {71 .openbsd, .haiku => struct {
...@@ -152,6 +152,15 @@ pub const NullFile = switch (native_os) {...@@ -152,6 +152,15 @@ pub const NullFile = switch (native_os) {
152 },152 },
153};153};
154154
155pub const RandomFile = switch (native_os) {
156 .windows => NullFile,
157 else => if (use_dev_urandom) NullFile else struct {
158 fn deinit(this: @This()) void {
159 _ = this;
160 }
161 },
162};
163
155pub const Pid = if (native_os == .linux) enum(posix.pid_t) {164pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
156 unknown = 0,165 unknown = 0,
157 _,166 _,
...@@ -586,9 +595,7 @@ const Thread = struct {...@@ -586,9 +595,7 @@ const Thread = struct {
586 /// Always released when `Status.cancelation` is set to `.parked`.595 /// Always released when `Status.cancelation` is set to `.parked`.
587 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,596 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
588597
589 random_buffer: [128]u8,598 csprng: std.Random.DefaultCsprng,
590 /// How many bytes of `random_buffer` are filled.
591 random_i: usize,
592599
593 const Handle = Handle: {600 const Handle = Handle: {
594 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;601 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
...@@ -1290,9 +1297,7 @@ pub fn deinit(t: *Threaded) void {...@@ -1290,9 +1297,7 @@ pub fn deinit(t: *Threaded) void {
1290 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);1297 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
1291 }1298 }
1292 t.null_file.deinit();1299 t.null_file.deinit();
1293 if (use_dev_urandom and t.dev_urandom_fd != -1) {1300 t.random_file.deinit();
1294 posix.close(t.dev_urandom_fd);
1295 }
1296 t.* = undefined;1301 t.* = undefined;
1297}1302}
12981303
...@@ -1321,6 +1326,10 @@ fn worker(t: *Threaded) void {...@@ -1321,6 +1326,10 @@ fn worker(t: *Threaded) void {
1321 }),1326 }),
1322 .cancel_protection = .unblocked,1327 .cancel_protection = .unblocked,
1323 .futex_waiter = undefined,1328 .futex_waiter = undefined,
1329 .csprng = .{
1330 .state = undefined,
1331 .offset = std.math.maxInt(usize),
1332 },
1324 };1333 };
1325 Thread.current = &thread;1334 Thread.current = &thread;
13261335
...@@ -1734,8 +1743,6 @@ const getrandom_use_libc = @TypeOf(posix.system.getrandom) != void and (native_o...@@ -1734,8 +1743,6 @@ const getrandom_use_libc = @TypeOf(posix.system.getrandom) != void and (native_o
17341743
1735const use_dev_urandom = getrandom_use_libc and native_os == .linux;1744const use_dev_urandom = getrandom_use_libc and native_os == .linux;
17361745
1737const dev_urandom_fd_t = if (use_dev_urandom) posix.fd_t else void;
1738
1739fn async(1746fn async(
1740 userdata: ?*anyopaque,1747 userdata: ?*anyopaque,
1741 result: []u8,1748 result: []u8,
...@@ -13873,6 +13880,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -13873,6 +13880,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
13873 };13880 };
13874}13881}
1387513882
13883fn getCngHandle(t: *Threaded) !windows.HANDLE {
13884 {
13885 t.mutex.lock();
13886 defer t.mutex.unlock();
13887 if (t.random_file.handle) |handle| return handle;
13888 }
13889
13890 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
13891
13892 var nt_name: windows.UNICODE_STRING = .{
13893 .Length = device_path.len * 2,
13894 .MaximumLength = 0,
13895 .Buffer = @constCast(&device_path),
13896 };
13897 var fresh_handle: windows.HANDLE = undefined;
13898 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
13899 var syscall: Syscall = try .start();
13900 while (true) switch (windows.ntdll.NtOpenFile(
13901 &fresh_handle,
13902 .{
13903 .STANDARD = .{ .SYNCHRONIZE = true },
13904 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
13905 },
13906 &.{
13907 .Length = @sizeOf(windows.OBJECT_ATTRIBUTES),
13908 .RootDirectory = null,
13909 .ObjectName = &nt_name,
13910 .Attributes = .{},
13911 .SecurityDescriptor = null,
13912 .SecurityQualityOfService = null,
13913 },
13914 &io_status_block,
13915 .VALID_FLAGS,
13916 .{ .IO = .SYNCHRONOUS_NONALERT },
13917 )) {
13918 .SUCCESS => {
13919 syscall.finish();
13920 t.mutex.lock(); // Another thread might have won the race.
13921 defer t.mutex.unlock();
13922 if (t.random_file.handle) |prev_handle| {
13923 _ = windows.ntdll.NtClose(fresh_handle);
13924 return prev_handle;
13925 } else {
13926 t.random_file.handle = fresh_handle;
13927 return fresh_handle;
13928 }
13929 },
13930 .CANCELLED => {
13931 try syscall.checkCancel();
13932 continue;
13933 },
13934 .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.Unexpected), // Observed on wine 10.0
13935 else => |status| return syscall.unexpectedNtstatus(status),
13936 };
13937}
13938
13876fn getNulHandle(t: *Threaded) !windows.HANDLE {13939fn getNulHandle(t: *Threaded) !windows.HANDLE {
13877 {13940 {
13878 t.mutex.lock();13941 t.mutex.lock();
...@@ -14959,28 +15022,48 @@ fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {...@@ -14959,28 +15022,48 @@ fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {
14959 const t: *Threaded = @ptrCast(@alignCast(userdata));15022 const t: *Threaded = @ptrCast(@alignCast(userdata));
1496015023
14961 if (is_windows) {15024 if (is_windows) {
14962 // Call RtlGenRandom() instead of CryptGetRandom() on Windows15025 if (buffer.len == 0) return;
14963 // https://github.com/rust-lang-nursery/rand/issues/11115026 // ProcessPrng from bcryptprimitives.dll has the following properties:
14964 // https://bugzilla.mozilla.org/show_bug.cgi?id=50427015027 // * introduces a dependency on bcryptprimitives.dll, which apparently
14965 const max_read_size: windows.ULONG = std.math.maxInt(windows.ULONG);15028 // runs a test suite every time it is loaded
15029 // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL
15030 // despite the function being documented to always return TRUE
15031 // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG
15032 // Therefore, that function is avoided in favor of using the device directly.
15033 const cng_device = try getCngHandle(t);
15034 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
14966 var i: usize = 0;15035 var i: usize = 0;
14967 while (i < buffer.len) {15036 const syscall: Syscall = try .start();
14968 const buf = buffer[i..];15037 while (true) {
14969 const request_n: windows.ULONG = @min(buf.len, max_read_size);15038 const remaining_len = std.math.lossyCast(u32, buffer.len - i);
14970 const syscall: Syscall = try .start();15039 switch (windows.ntdll.NtDeviceIoControlFile(
14971 const result = windows.advapi32.RtlGenRandom(buf.ptr, request_n);15040 cng_device,
14972 syscall.finish();15041 null,
14973 if (result == 0) {15042 null,
14974 // `RtlGenRandom` has been observed to fail in situations where15043 null,
14975 // the system is under heavy load. Unfortunately, it does not15044 &io_status_block,
14976 // call `SetLastError`, so it is not possible to get more15045 windows.IOCTL.KSEC.GEN_RANDOM,
14977 // specific error information; it could actually be due to an15046 null,
14978 // out-of-memory condition, for example.15047 0,
14979 return error.EntropyUnavailable;15048 buffer[i..].ptr,
15049 remaining_len,
15050 )) {
15051 .SUCCESS => {
15052 i += remaining_len;
15053 if (buffer.len - i == 0) {
15054 return syscall.finish();
15055 } else {
15056 try syscall.checkCancel();
15057 continue;
15058 }
15059 },
15060 .CANCELLED => {
15061 try syscall.checkCancel();
15062 continue;
15063 },
15064 else => return syscall.fail(error.EntropyUnavailable),
14980 }15065 }
14981 i += request_n;
14982 }15066 }
14983 return;
14984 }15067 }
1498515068
14986 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {15069 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
...@@ -15032,6 +15115,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {...@@ -15032,6 +15115,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {
15032 }15115 }
15033 }15116 }
1503415117
15118 if (buffer.len == 0) return;
15035 const urandom_fd = try getRandomFd(t);15119 const urandom_fd = try getRandomFd(t);
1503615120
15037 var i: usize = 0;15121 var i: usize = 0;
...@@ -15066,8 +15150,8 @@ fn getRandomFd(t: *Threaded) posix.fd_t {...@@ -15066,8 +15150,8 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
15066 t.mutex.lock();15150 t.mutex.lock();
15067 defer t.mutex.unlock();15151 defer t.mutex.unlock();
1506815152
15069 if (t.dev_urandom_fd == -2) return error.EntropyUnavailable;15153 if (t.random_file.fd == -2) return error.EntropyUnavailable;
15070 if (t.dev_urandom_fd != -1) return t.dev_urandom_fd;15154 if (t.random_file.fd != -1) return t.random_file.fd;
15071 }15155 }
1507215156
15073 const fd: posix.fd_t = fd: {15157 const fd: posix.fd_t = fd: {
...@@ -15088,7 +15172,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {...@@ -15088,7 +15172,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
15088 },15172 },
15089 else => {15173 else => {
15090 syscall.endSyscall();15174 syscall.endSyscall();
15091 t.dev_urandom_fd = -2;15175 t.random_file.fd = -2;
15092 return error.EntropyUnavailable;15176 return error.EntropyUnavailable;
15093 },15177 },
15094 }15178 }
...@@ -15108,14 +15192,14 @@ fn getRandomFd(t: *Threaded) posix.fd_t {...@@ -15108,14 +15192,14 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
15108 if (!statx.mask.TYPE) return error.Unexpected;15192 if (!statx.mask.TYPE) return error.Unexpected;
15109 t.mutex.lock(); // Another thread might have won the race.15193 t.mutex.lock(); // Another thread might have won the race.
15110 defer t.mutex.unlock();15194 defer t.mutex.unlock();
15111 if (t.dev_urandom_fd >= 0) {15195 if (t.random_file.fd >= 0) {
15112 posix.close(fd);15196 posix.close(fd);
15113 return t.dev_urandom_fd;15197 return t.random_file.fd;
15114 } else if (!posix.S.ISCHR(statx.mode)) {15198 } else if (!posix.S.ISCHR(statx.mode)) {
15115 t.dev_urandom_fd = -2;15199 t.random_file.fd = -2;
15116 return error.EntropyUnavailable;15200 return error.EntropyUnavailable;
15117 } else {15201 } else {
15118 t.dev_urandom_fd = fd;15202 t.random_file.fd = fd;
15119 return fd;15203 return fd;
15120 }15204 }
15121 },15205 },
...@@ -15124,7 +15208,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {...@@ -15124,7 +15208,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
15124 continue;15208 continue;
15125 },15209 },
15126 else => {15210 else => {
15127 t.dev_urandom_fd = -2;15211 t.random_file.fd = -2;
15128 return error.EntropyUnavailable;15212 return error.EntropyUnavailable;
15129 },15213 },
15130 }15214 }
...@@ -15137,14 +15221,14 @@ fn getRandomFd(t: *Threaded) posix.fd_t {...@@ -15137,14 +15221,14 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
15137 switch (posix.errno(fstat_sym(fd, &stat))) {15221 switch (posix.errno(fstat_sym(fd, &stat))) {
15138 .SUCCESS => {15222 .SUCCESS => {
15139 syscall.finish();15223 syscall.finish();
15140 if (t.dev_urandom_fd >= 0) {15224 if (t.random_file.fd >= 0) {
15141 posix.close(fd);15225 posix.close(fd);
15142 return t.dev_urandom_fd;15226 return t.random_file.fd;
15143 } else if (!posix.S.ISCHR(stat.mode)) {15227 } else if (!posix.S.ISCHR(stat.mode)) {
15144 t.dev_urandom_fd = -2;15228 t.random_file.fd = -2;
15145 return error.EntropyUnavailable;15229 return error.EntropyUnavailable;
15146 } else {15230 } else {
15147 t.dev_urandom_fd = fd;15231 t.random_file.fd = fd;
15148 return fd;15232 return fd;
15149 }15233 }
15150 },15234 },
...@@ -15153,7 +15237,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {...@@ -15153,7 +15237,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
15153 continue;15237 continue;
15154 },15238 },
15155 else => {15239 else => {
15156 t.dev_urandom_fd = -2;15240 t.random_file.fd = -2;
15157 return error.EntropyUnavailable;15241 return error.EntropyUnavailable;
15158 },15242 },
15159 }15243 }
lib/std/os/windows.zig-56
...@@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor...@@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor
2647 }2647 }
2648}2648}
26492649
2650/// An alternate implementation of ProcessPrng from bcryptprimitives.dll
2651/// This one has the following differences:
2652/// * does not heap allocate `buffer`
2653/// * does not introduce a dependency on bcryptprimitives.dll, which apparently
2654/// runs a test suite every time it is loaded
2655/// * reads buffer.len bytes from "\\Device\\CNG" rather than seeding a per-CPU
2656/// AES csprng with 48 bytes.
2657pub fn ProcessPrng(buffer: []u8) error{Unexpected}!void {
2658 const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' };
2659 var nt_name: UNICODE_STRING = .{
2660 .Length = device_path.len * 2,
2661 .MaximumLength = 0,
2662 .Buffer = @constCast(&device_path),
2663 };
2664 var cng_device: HANDLE = undefined;
2665 var io_status_block: IO_STATUS_BLOCK = undefined;
2666 switch (ntdll.NtOpenFile(
2667 &cng_device,
2668 .{
2669 .STANDARD = .{ .SYNCHRONIZE = true },
2670 .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } },
2671 },
2672 &.{
2673 .Length = @sizeOf(OBJECT_ATTRIBUTES),
2674 .RootDirectory = null,
2675 .ObjectName = &nt_name,
2676 .Attributes = .{},
2677 .SecurityDescriptor = null,
2678 .SecurityQualityOfService = null,
2679 },
2680 &io_status_block,
2681 .VALID_FLAGS,
2682 .{ .IO = .SYNCHRONOUS_NONALERT },
2683 )) {
2684 .SUCCESS => {},
2685 .OBJECT_NAME_NOT_FOUND => return error.Unexpected, // Observed on wine 10.0
2686 else => |status| return unexpectedStatus(status),
2687 }
2688 defer _ = ntdll.NtClose(cng_device);
2689 switch (ntdll.NtDeviceIoControlFile(
2690 cng_device,
2691 null,
2692 null,
2693 null,
2694 &io_status_block,
2695 IOCTL.KSEC.GEN_RANDOM,
2696 null,
2697 0,
2698 buffer.ptr,
2699 @intCast(buffer.len),
2700 )) {
2701 .SUCCESS => {},
2702 else => |status| return unexpectedStatus(status),
2703 }
2704}
2705
2706pub const WaitForSingleObjectError = error{2650pub const WaitForSingleObjectError = error{
2707 WaitAbandoned,2651 WaitAbandoned,
2708 WaitTimeOut,2652 WaitTimeOut,