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}