master
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}