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 {
984984 };
985985
986986 // 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));
988989 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
989990
990991 for (output_placeholders.items) |placeholder| {
......@@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode(
11281129 }
11291130
11301131 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));
11321134 const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
11331135 try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{
11341136 .progress_node = prog_node,
lib/std/Build/Step/WriteFile.zig+2-1
......@@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void {
293293 .tmp => {
294294 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));
297298 const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int);
298299
299300 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 {
731731 now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp,
732732 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
736737 netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server,
737738 netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream,
......@@ -2245,9 +2246,34 @@ pub fn unlockStderr(io: Io) void {
22452246 return io.vtable.unlockStderr(io.userdata);
22462247}
22472248
2248pub const RandomError = error{EntropyUnavailable} || Cancelable;
2249
2249/// Obtains entropy.
2250///
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///
22502257/// Threadsafe.
2251pub fn random(io: Io, buffer: []u8) RandomError!void {
2258///
2259/// See also `randomSecure`.
2260pub fn random(io: Io, buffer: []u8) void {
22522261 return io.vtable.random(io.userdata, buffer);
22532262}
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(
10981098
10991099 const temp_path = temp_path_buf[0..temp_path_len];
11001100
1101 var random_integer: u64 = undefined;
1102
11011103 while (true) {
1102 const random_integer = std.crypto.random.int(u64);
1104 io.random(@ptrCast(&random_integer));
11031105 temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer);
11041106
11051107 if (dir.symLink(io, target_path, temp_path, flags)) {
lib/std/Io/Threaded.zig+79-8
......@@ -67,6 +67,21 @@ environ: Environ,
6767null_file: NullFile = .{},
6868random_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
7085pub const Argv0 = switch (native_os) {
7186 .openbsd, .haiku => struct {
7287 value: ?[*:0]const u8,
......@@ -595,7 +610,7 @@ const Thread = struct {
595610 /// Always released when `Status.cancelation` is set to `.parked`.
596611 futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn,
597612
598 csprng: std.Random.DefaultCsprng,
613 csprng: Csprng,
599614
600615 const Handle = Handle: {
601616 if (std.Thread.use_pthreads) break :Handle std.c.pthread_t;
......@@ -1326,10 +1341,7 @@ fn worker(t: *Threaded) void {
13261341 }),
13271342 .cancel_protection = .unblocked,
13281343 .futex_waiter = undefined,
1329 .csprng = .{
1330 .state = undefined,
1331 .offset = std.math.maxInt(usize),
1332 },
1344 .csprng = .{},
13331345 };
13341346 Thread.current = &thread;
13351347
......@@ -1484,6 +1496,7 @@ pub fn io(t: *Threaded) Io {
14841496 .sleep = sleep,
14851497
14861498 .random = random,
1499 .randomSecure = randomSecure,
14871500
14881501 .netListenIp = switch (native_os) {
14891502 .windows => netListenIpWindows,
......@@ -1634,6 +1647,7 @@ pub fn ioBasic(t: *Threaded) Io {
16341647 .sleep = sleep,
16351648
16361649 .random = random,
1650 .randomSecure = randomSecure,
16371651
16381652 .netListenIp = netListenIpUnavailable,
16391653 .netListenUnix = netListenUnixUnavailable,
......@@ -3584,8 +3598,9 @@ fn atomicFileInit(
35843598 dir: Dir,
35853599 close_dir_on_deinit: bool,
35863600) Dir.CreateFileAtomicError!File.Atomic {
3601 var random_integer: u64 = undefined;
35873602 while (true) {
3588 const random_integer = std.crypto.random.int(u64);
3603 t_io.random(@ptrCast(&random_integer));
35893604 const tmp_sub_path = std.fmt.hex(random_integer);
35903605 const file = dir.createFile(t_io, &tmp_sub_path, .{
35913606 .permissions = permissions,
......@@ -12468,7 +12483,8 @@ fn lookupDns(
1246812483
1246912484 for (family_records) |fr| {
1247012485 if (options.family != fr.af) {
12471 const entropy = std.crypto.random.array(u8, 2);
12486 var entropy: [2]u8 = undefined;
12487 random(t, &entropy);
1247212488 const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy);
1247312489 queries_buffer[nq] = query_buffers[nq][0..len];
1247412490 nq += 1;
......@@ -15018,7 +15034,62 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 {
1501815034 return @field(t.environ.string, name);
1501915035}
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 {
1502215093 const t: *Threaded = @ptrCast(@alignCast(userdata));
1502315094
1502415095 if (is_windows) {
lib/std/Random.zig+16
......@@ -33,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig");
3333ptr: *anyopaque,
3434fillFn: *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
3652pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random {
3753 const Ptr = @TypeOf(pointer);
3854 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;
2020
2121/// The seed must be uniform, secret and `secret_seed_length` bytes long.
2222pub 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 };
2424 Cipher.stream(&self.state, 0, secret_seed, nonce);
2525 return self;
2626}