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,
6565environ: Environ,
6666
6767null_file: NullFile = .{},
68dev_urandom_fd: dev_urandom_fd_t = if (use_dev_urandom) -1 else {},
68random_file: RandomFile = .{},
6969
7070pub const Argv0 = switch (native_os) {
7171 .openbsd, .haiku => struct {
......@@ -152,6 +152,15 @@ pub const NullFile = switch (native_os) {
152152 },
153153};
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
155164pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
156165 unknown = 0,
157166 _,
......@@ -586,9 +595,7 @@ const Thread = struct {
586595 /// Always released when `Status.cancelation` is set to `.parked`.
587596 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
588597
589 random_buffer: [128]u8,
590 /// How many bytes of `random_buffer` are filled.
591 random_i: usize,
598 csprng: std.Random.DefaultCsprng,
592599
593600 const Handle = Handle: {
594601 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
......@@ -1290,9 +1297,7 @@ pub fn deinit(t: *Threaded) void {
12901297 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
12911298 }
12921299 t.null_file.deinit();
1293 if (use_dev_urandom and t.dev_urandom_fd != -1) {
1294 posix.close(t.dev_urandom_fd);
1295 }
1300 t.random_file.deinit();
12961301 t.* = undefined;
12971302}
12981303
......@@ -1321,6 +1326,10 @@ fn worker(t: *Threaded) void {
13211326 }),
13221327 .cancel_protection = .unblocked,
13231328 .futex_waiter = undefined,
1329 .csprng = .{
1330 .state = undefined,
1331 .offset = std.math.maxInt(usize),
1332 },
13241333 };
13251334 Thread.current = &thread;
13261335
......@@ -1734,8 +1743,6 @@ const getrandom_use_libc = @TypeOf(posix.system.getrandom) != void and (native_o
17341743
17351744const 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
17391746fn async(
17401747 userdata: ?*anyopaque,
17411748 result: []u8,
......@@ -13873,6 +13880,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
1387313880 };
1387413881}
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
1387613939fn getNulHandle(t: *Threaded) !windows.HANDLE {
1387713940 {
1387813941 t.mutex.lock();
......@@ -14959,28 +15022,48 @@ fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {
1495915022 const t: *Threaded = @ptrCast(@alignCast(userdata));
1496015023
1496115024 if (is_windows) {
14962 // Call RtlGenRandom() instead of CryptGetRandom() on Windows
14963 // https://github.com/rust-lang-nursery/rand/issues/111
14964 // https://bugzilla.mozilla.org/show_bug.cgi?id=504270
14965 const max_read_size: windows.ULONG = std.math.maxInt(windows.ULONG);
15025 if (buffer.len == 0) return;
15026 // ProcessPrng from bcryptprimitives.dll has the following properties:
15027 // * introduces a dependency on bcryptprimitives.dll, which apparently
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;
1496615035 var i: usize = 0;
14967 while (i < buffer.len) {
14968 const buf = buffer[i..];
14969 const request_n: windows.ULONG = @min(buf.len, max_read_size);
14970 const syscall: Syscall = try .start();
14971 const result = windows.advapi32.RtlGenRandom(buf.ptr, request_n);
14972 syscall.finish();
14973 if (result == 0) {
14974 // `RtlGenRandom` has been observed to fail in situations where
14975 // the system is under heavy load. Unfortunately, it does not
14976 // call `SetLastError`, so it is not possible to get more
14977 // specific error information; it could actually be due to an
14978 // out-of-memory condition, for example.
14979 return error.EntropyUnavailable;
15036 const syscall: Syscall = try .start();
15037 while (true) {
15038 const remaining_len = std.math.lossyCast(u32, buffer.len - i);
15039 switch (windows.ntdll.NtDeviceIoControlFile(
15040 cng_device,
15041 null,
15042 null,
15043 null,
15044 &io_status_block,
15045 windows.IOCTL.KSEC.GEN_RANDOM,
15046 null,
15047 0,
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),
1498015065 }
14981 i += request_n;
1498215066 }
14983 return;
1498415067 }
1498515068
1498615069 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
......@@ -15032,6 +15115,7 @@ fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {
1503215115 }
1503315116 }
1503415117
15118 if (buffer.len == 0) return;
1503515119 const urandom_fd = try getRandomFd(t);
1503615120
1503715121 var i: usize = 0;
......@@ -15066,8 +15150,8 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
1506615150 t.mutex.lock();
1506715151 defer t.mutex.unlock();
1506815152
15069 if (t.dev_urandom_fd == -2) return error.EntropyUnavailable;
15070 if (t.dev_urandom_fd != -1) return t.dev_urandom_fd;
15153 if (t.random_file.fd == -2) return error.EntropyUnavailable;
15154 if (t.random_file.fd != -1) return t.random_file.fd;
1507115155 }
1507215156
1507315157 const fd: posix.fd_t = fd: {
......@@ -15088,7 +15172,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
1508815172 },
1508915173 else => {
1509015174 syscall.endSyscall();
15091 t.dev_urandom_fd = -2;
15175 t.random_file.fd = -2;
1509215176 return error.EntropyUnavailable;
1509315177 },
1509415178 }
......@@ -15108,14 +15192,14 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
1510815192 if (!statx.mask.TYPE) return error.Unexpected;
1510915193 t.mutex.lock(); // Another thread might have won the race.
1511015194 defer t.mutex.unlock();
15111 if (t.dev_urandom_fd >= 0) {
15195 if (t.random_file.fd >= 0) {
1511215196 posix.close(fd);
15113 return t.dev_urandom_fd;
15197 return t.random_file.fd;
1511415198 } else if (!posix.S.ISCHR(statx.mode)) {
15115 t.dev_urandom_fd = -2;
15199 t.random_file.fd = -2;
1511615200 return error.EntropyUnavailable;
1511715201 } else {
15118 t.dev_urandom_fd = fd;
15202 t.random_file.fd = fd;
1511915203 return fd;
1512015204 }
1512115205 },
......@@ -15124,7 +15208,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
1512415208 continue;
1512515209 },
1512615210 else => {
15127 t.dev_urandom_fd = -2;
15211 t.random_file.fd = -2;
1512815212 return error.EntropyUnavailable;
1512915213 },
1513015214 }
......@@ -15137,14 +15221,14 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
1513715221 switch (posix.errno(fstat_sym(fd, &stat))) {
1513815222 .SUCCESS => {
1513915223 syscall.finish();
15140 if (t.dev_urandom_fd >= 0) {
15224 if (t.random_file.fd >= 0) {
1514115225 posix.close(fd);
15142 return t.dev_urandom_fd;
15226 return t.random_file.fd;
1514315227 } else if (!posix.S.ISCHR(stat.mode)) {
15144 t.dev_urandom_fd = -2;
15228 t.random_file.fd = -2;
1514515229 return error.EntropyUnavailable;
1514615230 } else {
15147 t.dev_urandom_fd = fd;
15231 t.random_file.fd = fd;
1514815232 return fd;
1514915233 }
1515015234 },
......@@ -15153,7 +15237,7 @@ fn getRandomFd(t: *Threaded) posix.fd_t {
1515315237 continue;
1515415238 },
1515515239 else => {
15156 t.dev_urandom_fd = -2;
15240 t.random_file.fd = -2;
1515715241 return error.EntropyUnavailable;
1515815242 },
1515915243 }
lib/std/os/windows.zig-56
......@@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor
26472647 }
26482648}
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
27062650pub const WaitForSingleObjectError = error{
27072651 WaitAbandoned,
27082652 WaitTimeOut,