authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-04 12:14:03-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 11:03:36-08:00
log816565dd077f561a46a9f31d9ecce32f152f9553
tree739359f959b2775beed8eb25887a2823de328abb
parent867501d9d2f757f99af59b1904b251aa63262e23

std: move entropy to Io


9 files changed, 263 insertions(+), 306 deletions(-)

lib/compiler/build_runner.zig-1
...@@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies");...@@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies");
21pub const std_options: std.Options = .{21pub const std_options: std.Options = .{
22 .side_channels_mitigations = .none,22 .side_channels_mitigations = .none,
23 .http_disable_tls = true,23 .http_disable_tls = true,
24 .crypto_fork_safety = false,
25};24};
2625
27pub fn main(init: process.Init.Minimal) !void {26pub fn main(init: process.Init.Minimal) !void {
lib/std/Io.zig+9
...@@ -731,6 +731,8 @@ pub const VTable = struct {...@@ -731,6 +731,8 @@ pub const VTable = struct {
731 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,731 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
732 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,732 sleep: *const fn (?*anyopaque, Timeout) SleepError!void,
733733
734 random: *const fn (?*anyopaque, buffer: []u8) RandomError!void,
735
734 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,736 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
735 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,737 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
736 netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,738 netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket,
...@@ -2242,3 +2244,10 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel...@@ -2242,3 +2244,10 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel
2242pub fn unlockStderr(io: Io) void {2244pub fn unlockStderr(io: Io) void {
2243 return io.vtable.unlockStderr(io.userdata);2245 return io.vtable.unlockStderr(io.userdata);
2244}2246}
2247
2248pub const RandomError = error{EntropyUnavailable} || Cancelable;
2249
2250/// Threadsafe.
2251pub fn random(io: Io, buffer: []u8) RandomError!void {
2252 return io.vtable.random(io.userdata, buffer);
2253}
lib/std/Io/Threaded.zig+242-15
...@@ -65,6 +65,7 @@ argv0: Argv0,...@@ -65,6 +65,7 @@ argv0: Argv0,
65environ: Environ,65environ: Environ,
6666
67null_file: NullFile = .{},67null_file: NullFile = .{},
68dev_urandom_fd: dev_urandom_fd_t,
6869
69pub const Argv0 = switch (native_os) {70pub const Argv0 = switch (native_os) {
70 .openbsd, .haiku => struct {71 .openbsd, .haiku => struct {
...@@ -585,6 +586,10 @@ const Thread = struct {...@@ -585,6 +586,10 @@ const Thread = struct {
585 /// Always released when `Status.cancelation` is set to `.parked`.586 /// Always released when `Status.cancelation` is set to `.parked`.
586 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,587 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
587588
589 random_buffer: [128]u8,
590 /// How many bytes of `random_buffer` are filled.
591 random_i: usize,
592
588 const Handle = Handle: {593 const Handle = Handle: {
589 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;594 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
590 if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;595 if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE;
...@@ -1285,6 +1290,9 @@ pub fn deinit(t: *Threaded) void {...@@ -1285,6 +1290,9 @@ pub fn deinit(t: *Threaded) void {
1285 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);1290 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
1286 }1291 }
1287 t.null_file.deinit();1292 t.null_file.deinit();
1293 if (use_dev_urandom and t.dev_urandom_fd != -1) {
1294 posix.close(t.dev_urandom_fd);
1295 }
1288 t.* = undefined;1296 t.* = undefined;
1289}1297}
12901298
...@@ -1466,6 +1474,8 @@ pub fn io(t: *Threaded) Io {...@@ -1466,6 +1474,8 @@ pub fn io(t: *Threaded) Io {
1466 .now = now,1474 .now = now,
1467 .sleep = sleep,1475 .sleep = sleep,
14681476
1477 .random = random,
1478
1469 .netListenIp = switch (native_os) {1479 .netListenIp = switch (native_os) {
1470 .windows => netListenIpWindows,1480 .windows => netListenIpWindows,
1471 else => netListenIpPosix,1481 else => netListenIpPosix,
...@@ -1614,6 +1624,8 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1614,6 +1624,8 @@ pub fn ioBasic(t: *Threaded) Io {
1614 .now = now,1624 .now = now,
1615 .sleep = sleep,1625 .sleep = sleep,
16161626
1627 .random = random,
1628
1617 .netListenIp = netListenIpUnavailable,1629 .netListenIp = netListenIpUnavailable,
1618 .netListenUnix = netListenUnixUnavailable,1630 .netListenUnix = netListenUnixUnavailable,
1619 .netAccept = netAcceptUnavailable,1631 .netAccept = netAcceptUnavailable,
...@@ -1704,6 +1716,26 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid...@@ -1704,6 +1716,26 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid
1704});1716});
1705const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;1717const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux;
17061718
1719const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
1720 .{ .major = 30, .minor = 0, .patch = 0 }
1721else
1722 .{ .major = 2, .minor = 28, .patch = 0 });
1723
1724const getrandom_use_libc = @TypeOf(posix.system.getrandom) != void and (native_os != .linux or
1725 std.c.versionCheck(if (builtin.abi.isAndroid()) .{
1726 .major = 28,
1727 .minor = 0,
1728 .patch = 0,
1729 } else .{
1730 .major = 2,
1731 .minor = 25,
1732 .patch = 0,
1733 }));
1734
1735const use_dev_urandom = getrandom_use_libc and native_os == .linux;
1736
1737const dev_urandom_fd_t = if (use_dev_urandom) posix.fd_t else void;
1738
1707fn async(1739fn async(
1708 userdata: ?*anyopaque,1740 userdata: ?*anyopaque,
1709 result: []u8,1741 result: []u8,
...@@ -2538,11 +2570,7 @@ fn dirStatFileLinux(...@@ -2538,11 +2570,7 @@ fn dirStatFileLinux(
2538 const t: *Threaded = @ptrCast(@alignCast(userdata));2570 const t: *Threaded = @ptrCast(@alignCast(userdata));
2539 _ = t;2571 _ = t;
2540 const linux = std.os.linux;2572 const linux = std.os.linux;
2541 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())2573 const sys = if (statx_use_c) std.c else std.os.linux;
2542 .{ .major = 30, .minor = 0, .patch = 0 }
2543 else
2544 .{ .major = 2, .minor = 28, .patch = 0 });
2545 const sys = if (use_c) std.c else std.os.linux;
25462574
2547 var path_buffer: [posix.PATH_MAX]u8 = undefined;2575 var path_buffer: [posix.PATH_MAX]u8 = undefined;
2548 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);2576 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
...@@ -2778,11 +2806,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {...@@ -2778,11 +2806,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat {
2778 const t: *Threaded = @ptrCast(@alignCast(userdata));2806 const t: *Threaded = @ptrCast(@alignCast(userdata));
2779 _ = t;2807 _ = t;
2780 const linux = std.os.linux;2808 const linux = std.os.linux;
2781 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())2809 const sys = if (statx_use_c) std.c else std.os.linux;
2782 .{ .major = 30, .minor = 0, .patch = 0 }
2783 else
2784 .{ .major = 2, .minor = 28, .patch = 0 });
2785 const sys = if (use_c) std.c else std.os.linux;
27862810
2787 const syscall: Syscall = try .start();2811 const syscall: Syscall = try .start();
2788 while (true) {2812 while (true) {
...@@ -6318,11 +6342,6 @@ fn fchmodatFallback(...@@ -6318,11 +6342,6 @@ fn fchmodatFallback(
6318 mode: posix.mode_t,6342 mode: posix.mode_t,
6319) Dir.SetFilePermissionsError!void {6343) Dir.SetFilePermissionsError!void {
6320 comptime assert(native_os == .linux);6344 comptime assert(native_os == .linux);
6321 const use_c = std.c.versionCheck(if (builtin.abi.isAndroid())
6322 .{ .major = 30, .minor = 0, .patch = 0 }
6323 else
6324 .{ .major = 2, .minor = 28, .patch = 0 });
6325 const sys = if (use_c) std.c else std.os.linux;
63266345
6327 // Fallback to changing permissions using procfs:6346 // Fallback to changing permissions using procfs:
6328 //6347 //
...@@ -6369,6 +6388,7 @@ fn fchmodatFallback(...@@ -6369,6 +6388,7 @@ fn fchmodatFallback(
6369 defer posix.close(path_fd);6388 defer posix.close(path_fd);
63706389
6371 const path_mode = mode: {6390 const path_mode = mode: {
6391 const sys = if (statx_use_c) std.c else std.os.linux;
6372 const syscall: Syscall = try .start();6392 const syscall: Syscall = try .start();
6373 while (true) {6393 while (true) {
6374 var statx = std.mem.zeroes(std.os.linux.Statx);6394 var statx = std.mem.zeroes(std.os.linux.Statx);
...@@ -14935,6 +14955,213 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {...@@ -14935,6 +14955,213 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
14935 return @field(t.environ.string, name);14955 return @field(t.environ.string, name);
14936}14956}
1493714957
14958fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {
14959 const t: *Threaded = @ptrCast(@alignCast(userdata));
14960
14961 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);
14966 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;
14980 }
14981 i += request_n;
14982 }
14983 return;
14984 }
14985
14986 if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) {
14987 posix.system.arc4random_buf(buffer.ptr, buffer.len);
14988 return;
14989 }
14990
14991 if (native_os == .wasi) {
14992 const syscall: Syscall = try .start();
14993 while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) {
14994 .SUCCESS => return syscall.finish(),
14995 .INTR => {
14996 try syscall.checkCancel();
14997 continue;
14998 },
14999 else => return syscall.fail(error.EntropyUnavailable),
15000 };
15001 }
15002
15003 if (@TypeOf(posix.system.getrandom) != void) {
15004 const getrandom = if (getrandom_use_libc) std.c.getrandom else std.os.linux.getrandom;
15005 var i: usize = 0;
15006 const syscall: Syscall = try .start();
15007 while (i < buffer.len) {
15008 const buf = buffer[i..];
15009 const rc = getrandom(buf.ptr, buf.len, 0);
15010 switch (posix.errno(rc)) {
15011 .SUCCESS => {
15012 syscall.finish();
15013 const n: usize = @intCast(rc);
15014 i += n;
15015 continue;
15016 },
15017 .INTR => {
15018 try syscall.checkCancel();
15019 continue;
15020 },
15021 else => return syscall.fail(error.EntropyUnavailable),
15022 }
15023 }
15024 return;
15025 }
15026
15027 if (native_os == .emscripten) {
15028 const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len));
15029 switch (err) {
15030 .SUCCESS => return,
15031 else => return error.EntropyUnavailable,
15032 }
15033 }
15034
15035 const urandom_fd = try getRandomFd(t);
15036
15037 var i: usize = 0;
15038 while (buffer.len - i != 0) {
15039 const syscall: Syscall = try .start();
15040 const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i);
15041 switch (posix.errno(rc)) {
15042 .SUCCESS => {
15043 syscall.finish();
15044 const n: usize = @intCast(rc);
15045 if (n == 0) {
15046 if (buffer.len - i != 0) {
15047 return error.EntropyUnavailable;
15048 } else {
15049 return;
15050 }
15051 }
15052 i += n;
15053 continue;
15054 },
15055 .INTR => {
15056 try syscall.checkCancel();
15057 continue;
15058 },
15059 else => return syscall.fail(error.EntropyUnavailable),
15060 }
15061 }
15062}
15063
15064fn getRandomFd(t: *Threaded) posix.fd_t {
15065 {
15066 t.mutex.lock();
15067 defer t.mutex.unlock();
15068
15069 if (t.dev_urandom_fd == -2) return error.EntropyUnavailable;
15070 if (t.dev_urandom_fd != -1) return t.dev_urandom_fd;
15071 }
15072
15073 const fd: posix.fd_t = fd: {
15074 const syscall: Syscall = try .start();
15075 while (true) {
15076 const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{
15077 .ACCMODE = .RDONLY,
15078 .CLOEXEC = true,
15079 }, 0);
15080 switch (posix.errno(rc)) {
15081 .SUCCESS => {
15082 syscall.finish();
15083 break :fd @intCast(rc);
15084 },
15085 .INTR => {
15086 try syscall.checkCancel();
15087 continue;
15088 },
15089 else => {
15090 syscall.endSyscall();
15091 t.dev_urandom_fd = -2;
15092 return error.EntropyUnavailable;
15093 },
15094 }
15095 }
15096 };
15097 errdefer posix.close(fd);
15098
15099 switch (native_os) {
15100 .linux => {
15101 const sys = if (statx_use_c) std.c else std.os.linux;
15102 const syscall: Syscall = try .start();
15103 while (true) {
15104 var statx = std.mem.zeroes(std.os.linux.Statx);
15105 switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) {
15106 .SUCCESS => {
15107 syscall.finish();
15108 if (!statx.mask.TYPE) return error.Unexpected;
15109 t.mutex.lock(); // Another thread might have won the race.
15110 defer t.mutex.unlock();
15111 if (t.dev_urandom_fd >= 0) {
15112 posix.close(fd);
15113 return t.dev_urandom_fd;
15114 } else if (!posix.S.ISCHR(statx.mode)) {
15115 t.dev_urandom_fd = -2;
15116 return error.EntropyUnavailable;
15117 } else {
15118 t.dev_urandom_fd = fd;
15119 return fd;
15120 }
15121 },
15122 .INTR => {
15123 try syscall.checkCancel();
15124 continue;
15125 },
15126 else => {
15127 t.dev_urandom_fd = -2;
15128 return error.EntropyUnavailable;
15129 },
15130 }
15131 }
15132 },
15133 else => {
15134 const syscall: Syscall = try .start();
15135 while (true) {
15136 var stat = std.mem.zeroes(posix.Stat);
15137 switch (posix.errno(fstat_sym(fd, &stat))) {
15138 .SUCCESS => {
15139 syscall.finish();
15140 if (t.dev_urandom_fd >= 0) {
15141 posix.close(fd);
15142 return t.dev_urandom_fd;
15143 } else if (!posix.S.ISCHR(stat.mode)) {
15144 t.dev_urandom_fd = -2;
15145 return error.EntropyUnavailable;
15146 } else {
15147 t.dev_urandom_fd = fd;
15148 return fd;
15149 }
15150 },
15151 .INTR => {
15152 try syscall.checkCancel();
15153 continue;
15154 },
15155 else => {
15156 t.dev_urandom_fd = -2;
15157 return error.EntropyUnavailable;
15158 },
15159 }
15160 }
15161 },
15162 }
15163}
15164
14938test {15165test {
14939 _ = @import("Threaded/test.zig");15166 _ = @import("Threaded/test.zig");
14940}15167}
lib/std/Io/test.zig+11
...@@ -564,3 +564,14 @@ test "tasks spawned in group after Group.cancel are canceled" {...@@ -564,3 +564,14 @@ test "tasks spawned in group after Group.cancel are canceled" {
564 try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up564 try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up
565 try group.concurrent(io, global.waitThenSpawn, .{ io, &group });565 try group.concurrent(io, global.waitThenSpawn, .{ io, &group });
566}566}
567
568test "CSPRNG" {
569 const io = testing.io;
570
571 var random = io.random();
572
573 const a = random.int(u64);
574 const b = random.int(u64);
575 const c = random.int(u64);
576 try std.testing.expect(a ^ b ^ c != 0);
577}
lib/std/Random.zig+1-3
...@@ -1,15 +1,13 @@...@@ -1,15 +1,13 @@
1//! The engines provided here should be initialized from an external source.1//! The engines provided here should be initialized from an external source.
2//! For a thread-local cryptographically secure pseudo random number generator,
3//! use `std.crypto.random`.
4//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will2//! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will
5//! be faster and use substantially less stack space.3//! be faster and use substantially less stack space.
4const Random = @This();
65
7const std = @import("std.zig");6const std = @import("std.zig");
8const math = std.math;7const math = std.math;
9const mem = std.mem;8const mem = std.mem;
10const assert = std.debug.assert;9const assert = std.debug.assert;
11const maxInt = std.math.maxInt;10const maxInt = std.math.maxInt;
12const Random = @This();
1311
14/// Fast unbiased random numbers.12/// Fast unbiased random numbers.
15pub const DefaultPrng = Xoshiro256;13pub const DefaultPrng = Xoshiro256;
lib/std/crypto.zig-11
...@@ -235,9 +235,6 @@ pub const nacl = struct {...@@ -235,9 +235,6 @@ pub const nacl = struct {
235/// Finite-field arithmetic.235/// Finite-field arithmetic.
236pub const ff = @import("crypto/ff.zig");236pub const ff = @import("crypto/ff.zig");
237237
238/// This is a thread-local, cryptographically secure pseudo random number generator.
239pub const random = @import("crypto/tlcsprng.zig").interface;
240
241/// Encoding and decoding238/// Encoding and decoding
242pub const codecs = @import("crypto/codecs.zig");239pub const codecs = @import("crypto/codecs.zig");
243240
...@@ -364,20 +361,12 @@ test {...@@ -364,20 +361,12 @@ test {
364 _ = secureZero;361 _ = secureZero;
365 _ = timing_safe;362 _ = timing_safe;
366 _ = ff;363 _ = ff;
367 _ = random;
368 _ = errors;364 _ = errors;
369 _ = tls;365 _ = tls;
370 _ = Certificate;366 _ = Certificate;
371 _ = codecs;367 _ = codecs;
372}368}
373369
374test "CSPRNG" {
375 const a = random.int(u64);
376 const b = random.int(u64);
377 const c = random.int(u64);
378 try std.testing.expect(a ^ b ^ c != 0);
379}
380
381test "issue #4532: no index out of bounds" {370test "issue #4532: no index out of bounds" {
382 const types = [_]type{371 const types = [_]type{
383 hash.Md5,372 hash.Md5,
lib/std/crypto/tlcsprng.zig deleted-169
...@@ -1,169 +0,0 @@
1//! Thread-local cryptographically secure pseudo-random number generator.
2//! This file has public declarations that are intended to be used internally
3//! by the standard library; this namespace is not intended to be exposed
4//! directly to standard library users.
5
6const std = @import("std");
7const builtin = @import("builtin");
8const mem = std.mem;
9const native_os = builtin.os.tag;
10const posix = std.posix;
11
12/// We use this as a layer of indirection because global const pointers cannot
13/// point to thread-local variables.
14pub const interface: std.Random = .{
15 .ptr = undefined,
16 .fillFn = tlsCsprngFill,
17};
18
19const os_has_fork = @TypeOf(posix.fork) != void;
20const os_has_arc4random = builtin.link_libc and (@TypeOf(std.c.arc4random_buf) != void);
21const want_fork_safety = os_has_fork and !os_has_arc4random and std.options.crypto_fork_safety;
22const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{
23 .major = 4,
24 .minor = 14,
25 .patch = 0,
26}) orelse true;
27
28const Rng = std.Random.DefaultCsprng;
29
30const Context = struct {
31 init_state: enum(u8) { uninitialized = 0, initialized, failed },
32 rng: Rng,
33};
34
35var install_atfork_handler = std.once(struct {
36 // Install the global handler only once.
37 // The same handler is shared among threads and is inherinted by fork()-ed
38 // processes.
39 fn do() void {
40 const r = std.c.pthread_atfork(null, null, childAtForkHandler);
41 std.debug.assert(r == 0);
42 }
43}.do);
44
45threadlocal var wipe_mem: []align(std.heap.page_size_min) u8 = &[_]u8{};
46
47fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void {
48 if (os_has_arc4random) {
49 // arc4random is already a thread-local CSPRNG.
50 return std.c.arc4random_buf(buffer.ptr, buffer.len);
51 }
52 // Allow applications to decide they would prefer to have every call to
53 // std.crypto.random always make an OS syscall, rather than rely on an
54 // application implementation of a CSPRNG.
55 if (std.options.crypto_always_getrandom) {
56 return std.options.cryptoRandomSeed(buffer);
57 }
58
59 if (wipe_mem.len == 0) {
60 // Not initialized yet.
61 if (want_fork_safety and maybe_have_wipe_on_fork) {
62 // Allocate a per-process page, madvise operates with page
63 // granularity.
64 wipe_mem = posix.mmap(
65 null,
66 @sizeOf(Context),
67 posix.PROT.READ | posix.PROT.WRITE,
68 .{ .TYPE = .PRIVATE, .ANONYMOUS = true },
69 -1,
70 0,
71 ) catch {
72 // Could not allocate memory for the local state, fall back to
73 // the OS syscall.
74 return std.options.cryptoRandomSeed(buffer);
75 };
76 // The memory is already zero-initialized.
77 } else {
78 // Use a static thread-local buffer.
79 const S = struct {
80 threadlocal var buf: Context align(std.heap.page_size_min) = .{
81 .init_state = .uninitialized,
82 .rng = undefined,
83 };
84 };
85 wipe_mem = mem.asBytes(&S.buf);
86 }
87 }
88 const ctx: *Context = @ptrCast(wipe_mem.ptr);
89
90 switch (ctx.init_state) {
91 .uninitialized => {
92 if (!want_fork_safety) {
93 return initAndFill(buffer);
94 }
95
96 if (maybe_have_wipe_on_fork) wof: {
97 // Qemu user-mode emulation ignores any valid/invalid madvise
98 // hint and returns success. Check if this is the case by
99 // passing bogus parameters, we expect EINVAL as result.
100 if (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
101 break :wof;
102 } else |_| {}
103
104 if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| {
105 return initAndFill(buffer);
106 } else |_| {}
107 }
108
109 if (std.Thread.use_pthreads) {
110 return setupPthreadAtforkAndFill(buffer);
111 }
112
113 // Since we failed to set up fork safety, we fall back to always
114 // calling getrandom every time.
115 ctx.init_state = .failed;
116 return std.options.cryptoRandomSeed(buffer);
117 },
118 .initialized => {
119 return fillWithCsprng(buffer);
120 },
121 .failed => {
122 if (want_fork_safety) {
123 return std.options.cryptoRandomSeed(buffer);
124 } else {
125 unreachable;
126 }
127 },
128 }
129}
130
131fn setupPthreadAtforkAndFill(buffer: []u8) void {
132 install_atfork_handler.call();
133 return initAndFill(buffer);
134}
135
136fn childAtForkHandler() callconv(.c) void {
137 // The atfork handler is global, this function may be called after
138 // fork()-ing threads that never initialized the CSPRNG context.
139 if (wipe_mem.len == 0) return;
140 std.crypto.secureZero(u8, wipe_mem);
141}
142
143fn fillWithCsprng(buffer: []u8) void {
144 const ctx: *Context = @ptrCast(wipe_mem.ptr);
145 return ctx.rng.fill(buffer);
146}
147
148pub fn defaultRandomSeed(buffer: []u8) void {
149 posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
150}
151
152fn initAndFill(buffer: []u8) void {
153 var seed: [Rng.secret_seed_length]u8 = undefined;
154 // Because we panic on getrandom() failing, we provide the opportunity
155 // to override the default seed function. This also makes
156 // `std.crypto.random` available on freestanding targets, provided that
157 // the `std.options.cryptoRandomSeed` function is provided.
158 std.options.cryptoRandomSeed(&seed);
159
160 const ctx: *Context = @ptrCast(wipe_mem.ptr);
161 ctx.rng = Rng.init(seed);
162 std.crypto.secureZero(u8, &seed);
163
164 // This is at the end so that accidental recursive dependencies result
165 // in stack overflows instead of invalid random data.
166 ctx.init_state = .initialized;
167
168 return fillWithCsprng(buffer);
169}
lib/std/posix.zig-101
...@@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void {...@@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void {
361 }361 }
362}362}
363363
364pub const GetRandomError = OpenError;
365
366/// Obtain a series of random bytes. These bytes can be used to seed user-space
367/// random number generators or for cryptographic purposes.
368/// When linking against libc, this calls the
369/// appropriate OS-specific library call. Otherwise it uses the zig standard
370/// library implementation.
371pub fn getrandom(buffer: []u8) GetRandomError!void {
372 if (native_os == .windows) {
373 return windows.ProcessPrng(buffer);
374 }
375 if (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) {
376 system.arc4random_buf(buffer.ptr, buffer.len);
377 return;
378 }
379 if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) {
380 .SUCCESS => return,
381 else => |err| return unexpectedErrno(err),
382 };
383 if (@TypeOf(system.getrandom) != void) {
384 var buf = buffer;
385 const use_c = native_os != .linux or
386 std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 25, .patch = 0 });
387
388 while (buf.len != 0) {
389 const num_read: usize, const err = if (use_c) res: {
390 const rc = std.c.getrandom(buf.ptr, buf.len, 0);
391 break :res .{ @bitCast(rc), errno(rc) };
392 } else res: {
393 const rc = linux.getrandom(buf.ptr, buf.len, 0);
394 break :res .{ rc, linux.errno(rc) };
395 };
396
397 switch (err) {
398 .SUCCESS => buf = buf[num_read..],
399 .INVAL => unreachable,
400 .FAULT => unreachable,
401 .INTR => continue,
402 else => return unexpectedErrno(err),
403 }
404 }
405 return;
406 }
407 if (native_os == .emscripten) {
408 const err = errno(std.c.getentropy(buffer.ptr, buffer.len));
409 switch (err) {
410 .SUCCESS => return,
411 else => return unexpectedErrno(err),
412 }
413 }
414 return getRandomBytesDevURandom(buffer);
415}
416
417fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void {
418 const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
419 defer close(fd);
420
421 switch (native_os) {
422 .linux => {
423 var stx = std.mem.zeroes(linux.Statx);
424 const rc = linux.statx(
425 fd,
426 "",
427 linux.AT.EMPTY_PATH,
428 .{ .TYPE = true },
429 &stx,
430 );
431 switch (errno(rc)) {
432 .SUCCESS => {},
433 .ACCES => unreachable,
434 .BADF => unreachable,
435 .FAULT => unreachable,
436 .INVAL => unreachable,
437 .LOOP => unreachable,
438 .NAMETOOLONG => unreachable,
439 .NOENT => unreachable,
440 .NOMEM => return error.SystemResources,
441 .NOTDIR => unreachable,
442 else => |err| return unexpectedErrno(err),
443 }
444 if (!S.ISCHR(stx.mode)) {
445 return error.NoDevice;
446 }
447 },
448 else => {
449 const st = fstat(fd) catch |err| switch (err) {
450 error.Streaming => return error.NoDevice,
451 else => |e| return e,
452 };
453 if (!S.ISCHR(st.mode)) {
454 return error.NoDevice;
455 }
456 },
457 }
458
459 var i: usize = 0;
460 while (i < buf.len) {
461 i += read(fd, buf[i..]) catch return error.Unexpected;
462 }
463}
464
465pub const RaiseError = UnexpectedError;364pub const RaiseError = UnexpectedError;
466365
467pub fn raise(sig: SIG) RaiseError!void {366pub fn raise(sig: SIG) RaiseError!void {
lib/std/std.zig-6
...@@ -137,12 +137,6 @@ pub const Options = struct {...@@ -137,12 +137,6 @@ pub const Options = struct {
137137
138 fmt_max_depth: usize = fmt.default_max_depth,138 fmt_max_depth: usize = fmt.default_max_depth,
139139
140 cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed,
141
142 crypto_always_getrandom: bool = false,
143
144 crypto_fork_safety: bool = true,
145
146 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to140 /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to
147 /// disable TLS support.141 /// disable TLS support.
148 ///142 ///