mirror of
https://codeberg.org/ziglang/zig.git
synced 2026-05-15 20:45:56 +03:00
84549b4267
Don't use the instantiation argument types to build the function
parameter array.
f416535768 worked around the problem, this
commit solves it.
157 lines
5.4 KiB
Zig
157 lines
5.4 KiB
Zig
// SPDX-License-Identifier: MIT
|
|
// Copyright (c) 2015-2020 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);
|
|
}
|