authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-05 17:06:03-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-07 11:03:36-08:00
log81a35a86ea9385016f371213a2b72b14902bb955
tree730e69aff40ca5abd5f1ac8b6b51295f3521afe4
parente3e9c7c33c029368ad644619ed20f3d7cccd3a47

std.Io: introduce random and randomSecure

and use a thread-local CSPRNG for the former.

7 files changed, 135 insertions(+), 17 deletions(-)

lib/std/Build/Step/Run.zig+4-2
...@@ -984,7 +984,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -984,7 +984,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
984 };984 };
985985
986 // We do not know the final output paths yet, use temp paths to run the command.986 // We do not know the final output paths yet, use temp paths to run the command.
987 const rand_int = std.crypto.random.int(u64);987 var rand_int: u64 = undefined;
988 io.random(@ptrCast(&rand_int));
988 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);989 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
989990
990 for (output_placeholders.items) |placeholder| {991 for (output_placeholders.items) |placeholder| {
...@@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode(...@@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode(
1128 }1129 }
11291130
1130 const has_side_effects = false;1131 const has_side_effects = false;
1131 const rand_int = std.crypto.random.int(u64);1132 var rand_int: u64 = undefined;
1133 io.random(@ptrCast(&rand_int));
1132 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);1134 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
1133 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{1135 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
1134 .progress_node = prog_node,1136 .progress_node = prog_node,
lib/std/Build/Step/WriteFile.zig+2-1
...@@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {...@@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
293 .tmp => {293 .tmp => {
294 step.result_cached = false;294 step.result_cached = false;
295295
296 const rand_int = std.crypto.random.int(u64);296 var rand_int: u64 = undefined;
297 io.random(@ptrCast(&rand_int));
297 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);298 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
298299
299 write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});300 write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path});
lib/std/Io.zig+30-4
...@@ -731,7 +731,8 @@ pub const VTable = struct {...@@ -731,7 +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,734 random: *const fn (?*anyopaque, buffer: []u8) void,
735 randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void,
735736
736 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,737 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
737 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,738 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
...@@ -2245,9 +2246,34 @@ pub fn unlockStderr(io: Io) void {...@@ -2245,9 +2246,34 @@ pub fn unlockStderr(io: Io) void {
2245 return io.vtable.unlockStderr(io.userdata);2246 return io.vtable.unlockStderr(io.userdata);
2246}2247}
22472248
2248pub const RandomError = error{EntropyUnavailable} || Cancelable;2249/// Obtains entropy.
22492250///
2251/// The implementation *may* store RNG state in process memory and use it to
2252/// fill `buffer`.
2253///
2254/// The degree to which the entropy is cryptographically secure is determined
2255/// by the `Io` implementation.
2256///
2250/// Threadsafe.2257/// Threadsafe.
2251pub fn random(io: Io, buffer: []u8) RandomError!void {2258///
2259/// See also `randomSecure`.
2260pub fn random(io: Io, buffer: []u8) void {
2252 return io.vtable.random(io.userdata, buffer);2261 return io.vtable.random(io.userdata, buffer);
2253}2262}
2263
2264pub const RandomSecureError = error{EntropyUnavailable} || Cancelable;
2265
2266/// Obtains cryptographically secure entropy from outside the process.
2267///
2268/// Always makes a syscall, or otherwise avoids dependency on process memory,
2269/// in order to obtain fresh randomness. Does not rely on stored RNG state.
2270///
2271/// Does not have any fallback mechanisms; returns `error.EntropyUnavailable`
2272/// if any problems occur.
2273///
2274/// Threadsafe.
2275///
2276/// See also `random`.
2277pub fn randomSecure(io: Io, buffer: []u8) RandomSecureError!void {
2278 return io.vtable.randomSecure(io.userdata, buffer);
2279}
lib/std/Io/Dir.zig+3-1
...@@ -1098,8 +1098,10 @@ pub fn symLinkAtomic(...@@ -1098,8 +1098,10 @@ pub fn symLinkAtomic(
10981098
1099 const temp_path = temp_path_buf[0..temp_path_len];1099 const temp_path = temp_path_buf[0..temp_path_len];
11001100
1101 var random_integer: u64 = undefined;
1102
1101 while (true) {1103 while (true) {
1102 const random_integer = std.crypto.random.int(u64);1104 io.random(@ptrCast(&random_integer));
1103 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);1105 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
11041106
1105 if (dir.symLink(io, target_path, temp_path, flags)) {1107 if (dir.symLink(io, target_path, temp_path, flags)) {
lib/std/Io/Threaded.zig+79-8
...@@ -67,6 +67,21 @@ environ: Environ,...@@ -67,6 +67,21 @@ environ: Environ,
67null_file: NullFile = .{},67null_file: NullFile = .{},
68random_file: RandomFile = .{},68random_file: RandomFile = .{},
6969
70csprng: Csprng = .{},
71
72pub const Csprng = struct {
73 rng: std.Random.DefaultCsprng = .{
74 .state = undefined,
75 .offset = std.math.maxInt(usize),
76 },
77
78 pub const seed_len = std.Random.DefaultCsprng.secret_seed_length;
79
80 pub fn isInitialized(c: *const Csprng) bool {
81 return c.rng.offset == std.math.maxInt(usize);
82 }
83};
84
70pub const Argv0 = switch (native_os) {85pub const Argv0 = switch (native_os) {
71 .openbsd, .haiku => struct {86 .openbsd, .haiku => struct {
72 value: ?[*:0]const u8,87 value: ?[*:0]const u8,
...@@ -595,7 +610,7 @@ const Thread = struct {...@@ -595,7 +610,7 @@ const Thread = struct {
595 /// Always released when `Status.cancelation` is set to `.parked`.610 /// Always released when `Status.cancelation` is set to `.parked`.
596 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,611 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
597612
598 csprng: std.Random.DefaultCsprng,613 csprng: Csprng,
599614
600 const Handle = Handle: {615 const Handle = Handle: {
601 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;616 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
...@@ -1326,10 +1341,7 @@ fn worker(t: *Threaded) void {...@@ -1326,10 +1341,7 @@ fn worker(t: *Threaded) void {
1326 }),1341 }),
1327 .cancel_protection = .unblocked,1342 .cancel_protection = .unblocked,
1328 .futex_waiter = undefined,1343 .futex_waiter = undefined,
1329 .csprng = .{1344 .csprng = .{},
1330 .state = undefined,
1331 .offset = std.math.maxInt(usize),
1332 },
1333 };1345 };
1334 Thread.current = &thread;1346 Thread.current = &thread;
13351347
...@@ -1484,6 +1496,7 @@ pub fn io(t: *Threaded) Io {...@@ -1484,6 +1496,7 @@ pub fn io(t: *Threaded) Io {
1484 .sleep = sleep,1496 .sleep = sleep,
14851497
1486 .random = random,1498 .random = random,
1499 .randomSecure = randomSecure,
14871500
1488 .netListenIp = switch (native_os) {1501 .netListenIp = switch (native_os) {
1489 .windows => netListenIpWindows,1502 .windows => netListenIpWindows,
...@@ -1634,6 +1647,7 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1634,6 +1647,7 @@ pub fn ioBasic(t: *Threaded) Io {
1634 .sleep = sleep,1647 .sleep = sleep,
16351648
1636 .random = random,1649 .random = random,
1650 .randomSecure = randomSecure,
16371651
1638 .netListenIp = netListenIpUnavailable,1652 .netListenIp = netListenIpUnavailable,
1639 .netListenUnix = netListenUnixUnavailable,1653 .netListenUnix = netListenUnixUnavailable,
...@@ -3584,8 +3598,9 @@ fn atomicFileInit(...@@ -3584,8 +3598,9 @@ fn atomicFileInit(
3584 dir: Dir,3598 dir: Dir,
3585 close_dir_on_deinit: bool,3599 close_dir_on_deinit: bool,
3586) Dir.CreateFileAtomicError!File.Atomic {3600) Dir.CreateFileAtomicError!File.Atomic {
3601 var random_integer: u64 = undefined;
3587 while (true) {3602 while (true) {
3588 const random_integer = std.crypto.random.int(u64);3603 t_io.random(@ptrCast(&random_integer));
3589 const tmp_sub_path = std.fmt.hex(random_integer);3604 const tmp_sub_path = std.fmt.hex(random_integer);
3590 const file = dir.createFile(t_io, &tmp_sub_path, .{3605 const file = dir.createFile(t_io, &tmp_sub_path, .{
3591 .permissions = permissions,3606 .permissions = permissions,
...@@ -12468,7 +12483,8 @@ fn lookupDns(...@@ -12468,7 +12483,8 @@ fn lookupDns(
1246812483
12469 for (family_records) |fr| {12484 for (family_records) |fr| {
12470 if (options.family != fr.af) {12485 if (options.family != fr.af) {
12471 const entropy = std.crypto.random.array(u8, 2);12486 var entropy: [2]u8 = undefined;
12487 random(t, &entropy);
12472 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);12488 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
12473 queries_buffer[nq] = query_buffers[nq][0..len];12489 queries_buffer[nq] = query_buffers[nq][0..len];
12474 nq += 1;12490 nq += 1;
...@@ -15018,7 +15034,62 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {...@@ -15018,7 +15034,62 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
15018 return @field(t.environ.string, name);15034 return @field(t.environ.string, name);
15019}15035}
1502015036
15021fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void {15037fn random(userdata: ?*anyopaque, buffer: []u8) void {
15038 const t: *Threaded = @ptrCast(@alignCast(userdata));
15039 const thread = Thread.current orelse return randomMainThread(t, buffer);
15040 if (!thread.csprng.isInitialized()) {
15041 @branchHint(.unlikely);
15042 var seed: [Csprng.seed_len]u8 = undefined;
15043 randomMainThread(t, &seed);
15044 thread.csprng.rng = .init(seed);
15045 }
15046 thread.csprng.rng.fill(buffer);
15047}
15048
15049fn randomMainThread(t: *Threaded, buffer: []u8) void {
15050 t.mutex.lock();
15051 defer t.mutex.unlock();
15052
15053 if (!t.csprng.isInitialized()) {
15054 @branchHint(.unlikely);
15055 var seed: [Csprng.seed_len]u8 = undefined;
15056 {
15057 t.mutex.unlock();
15058 defer t.mutex.lock();
15059
15060 const prev = swapCancelProtection(t, .blocked);
15061 defer _ = swapCancelProtection(t, prev);
15062
15063 randomSecure(t, &seed) catch |err| switch (err) {
15064 error.Canceled => unreachable,
15065 error.EntropyUnavailable => {
15066 seed = @splat(0);
15067 std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native);
15068 const i_1 = @sizeOf(posix.pid_t);
15069
15070 var ts: posix.timespec = undefined;
15071 const Sec = @TypeOf(ts.sec);
15072 const Nsec = @TypeOf(ts.nsec);
15073 const i_2 = i_1 + @sizeOf(Sec);
15074 const i_3 = i_2 + @sizeOf(Nsec);
15075 switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) {
15076 .SUCCESS => {
15077 std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native);
15078 std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native);
15079 },
15080 else => {},
15081 }
15082 std.mem.writeInt(usize, seed[i_3..][0..@sizeOf(usize)], @intFromPtr(t), .native);
15083 },
15084 };
15085 }
15086 t.csprng.rng = .init(seed);
15087 }
15088
15089 t.csprng.rng.fill(buffer);
15090}
15091
15092fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void {
15022 const t: *Threaded = @ptrCast(@alignCast(userdata));15093 const t: *Threaded = @ptrCast(@alignCast(userdata));
1502315094
15024 if (is_windows) {15095 if (is_windows) {
lib/std/Random.zig+16
...@@ -33,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig");...@@ -33,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig");
33ptr: *anyopaque,33ptr: *anyopaque,
34fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,34fillFn: *const fn (ptr: *anyopaque, buf: []u8) void,
3535
36pub const IoSource = struct {
37 io: std.Io,
38
39 pub fn interface(this: *const @This()) std.Random {
40 return .{
41 .ptr = this,
42 .fillFn = fill,
43 };
44 }
45
46 fn fill(ptr: *anyopaque, buffer: []u8) void {
47 const this: *const @This() = @ptrCast(@alignCast(ptr));
48 this.io.random(buffer);
49 }
50};
51
36pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {52pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
37 const Ptr = @TypeOf(pointer);53 const Ptr = @TypeOf(pointer);
38 assert(@typeInfo(Ptr) == .pointer); // Must be a pointer54 assert(@typeInfo(Ptr) == .pointer); // Must be a pointer
lib/std/Random/ChaCha.zig+1-1
...@@ -20,7 +20,7 @@ pub const secret_seed_length = Cipher.key_length;...@@ -20,7 +20,7 @@ pub const secret_seed_length = Cipher.key_length;
2020
21/// The seed must be uniform, secret and `secret_seed_length` bytes long.21/// The seed must be uniform, secret and `secret_seed_length` bytes long.
22pub fn init(secret_seed: [secret_seed_length]u8) Self {22pub fn init(secret_seed: [secret_seed_length]u8) Self {
23 var self = Self{ .state = undefined, .offset = 0 };23 var self: Self = .{ .state = undefined, .offset = 0 };
24 Cipher.stream(&self.state, 0, secret_seed, nonce);24 Cipher.stream(&self.state, 0, secret_seed, nonce);
25 return self;25 return self;
26}26}