// SPDX-License-Identifier: MIT // Copyright (c) 2015-2021 Zig Contributors // This file is part of [zig](https://ziglang.org/), which is MIT licensed. // The MIT license requires this copyright notice to be included in all copies // and substantial portions of the software. //! Thread-local cryptographically secure pseudo-random number generator. //! This file has public declarations that are intended to be used internally //! by the standard library; this namespace is not intended to be exposed //! directly to standard library users. const std = @import("std"); const root = @import("root"); const mem = std.mem; /// We use this as a layer of indirection because global const pointers cannot /// point to thread-local variables. pub var interface = std.rand.Random{ .fillFn = tlsCsprngFill }; const os_has_fork = switch (std.Target.current.os.tag) { .dragonfly, .freebsd, .ios, .kfreebsd, .linux, .macos, .netbsd, .openbsd, .solaris, .tvos, .watchos, => true, else => false, }; const os_has_arc4random = std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf"); const want_fork_safety = os_has_fork and !os_has_arc4random and (std.meta.globalOption("crypto_fork_safety", bool) orelse true); const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 14, }) orelse true; const WipeMe = struct { init_state: enum { uninitialized, initialized, failed }, gimli: std.crypto.core.Gimli, }; const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe); threadlocal var wipe_me: WipeMe align(wipe_align) = .{ .gimli = undefined, .init_state = .uninitialized, }; fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void { if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) { // arc4random is already a thread-local CSPRNG. return std.c.arc4random_buf(buffer.ptr, buffer.len); } // Allow applications to decide they would prefer to have every call to // std.crypto.random always make an OS syscall, rather than rely on an // application implementation of a CSPRNG. if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) { return fillWithOsEntropy(buffer); } switch (wipe_me.init_state) { .uninitialized => { if (want_fork_safety) { if (maybe_have_wipe_on_fork) { if (std.os.madvise( @ptrCast([*]align(mem.page_size) u8, &wipe_me), @sizeOf(@TypeOf(wipe_me)), std.os.MADV_WIPEONFORK, )) |_| { return initAndFill(buffer); } else |_| if (std.Thread.use_pthreads) { return setupPthreadAtforkAndFill(buffer); } else { // Since we failed to set up fork safety, we fall back to always // calling getrandom every time. wipe_me.init_state = .failed; return fillWithOsEntropy(buffer); } } else if (std.Thread.use_pthreads) { return setupPthreadAtforkAndFill(buffer); } else { // We have no mechanism to provide fork safety, but we want fork safety, // so we fall back to calling getrandom every time. wipe_me.init_state = .failed; return fillWithOsEntropy(buffer); } } else { return initAndFill(buffer); } }, .initialized => { return fillWithCsprng(buffer); }, .failed => { if (want_fork_safety) { return fillWithOsEntropy(buffer); } else { unreachable; } }, } } fn setupPthreadAtforkAndFill(buffer: []u8) void { const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0; if (failed) { wipe_me.init_state = .failed; return fillWithOsEntropy(buffer); } else { return initAndFill(buffer); } } fn childAtForkHandler() callconv(.C) void { const wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))]; std.crypto.utils.secureZero(u8, wipe_slice); } fn fillWithCsprng(buffer: []u8) void { if (buffer.len != 0) { wipe_me.gimli.squeeze(buffer); } else { wipe_me.gimli.permute(); } mem.set(u8, wipe_me.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0); } fn fillWithOsEntropy(buffer: []u8) void { std.os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); } fn initAndFill(buffer: []u8) void { var seed: [std.crypto.core.Gimli.BLOCKBYTES]u8 = undefined; // Because we panic on getrandom() failing, we provide the opportunity // to override the default seed function. This also makes // `std.crypto.random` available on freestanding targets, provided that // the `cryptoRandomSeed` function is provided. if (@hasDecl(root, "cryptoRandomSeed")) { root.cryptoRandomSeed(&seed); } else { fillWithOsEntropy(&seed); } wipe_me.gimli = std.crypto.core.Gimli.init(seed); // This is at the end so that accidental recursive dependencies result // in stack overflows instead of invalid random data. wipe_me.init_state = .initialized; return fillWithCsprng(buffer); }