std: Allocate tlscsprng memory as needed
Let mmap allocate a block of memory that's wide enough to use with MADV_WIPEONFORK, madvise granularity is the current system page size (using a static buffer of mem.page_size bytes would be wrong, that's the minimum page size). As a result, we don't zero some random chunk of memory every time we fork the process. Fixes #7609
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
const std = @import("std");
|
||||
const root = @import("root");
|
||||
const mem = std.mem;
|
||||
const os = std.os;
|
||||
|
||||
/// We use this as a layer of indirection because global const pointers cannot
|
||||
/// point to thread-local variables.
|
||||
@@ -42,16 +43,12 @@ const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{
|
||||
.minor = 14,
|
||||
}) orelse true;
|
||||
|
||||
const WipeMe = struct {
|
||||
init_state: enum { uninitialized, initialized, failed },
|
||||
const Context = struct {
|
||||
init_state: enum(u8) { uninitialized = 0, 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,
|
||||
};
|
||||
threadlocal var wipe_mem: []align(mem.page_size) u8 = &[_]u8{};
|
||||
|
||||
fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
|
||||
if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {
|
||||
@@ -64,35 +61,69 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
|
||||
if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {
|
||||
return fillWithOsEntropy(buffer);
|
||||
}
|
||||
switch (wipe_me.init_state) {
|
||||
|
||||
if (wipe_mem.len == 0) {
|
||||
// Not initialized yet.
|
||||
if (want_fork_safety and maybe_have_wipe_on_fork) {
|
||||
// Allocate a per-process page, madvise operates with page
|
||||
// granularity.
|
||||
wipe_mem = os.mmap(
|
||||
null,
|
||||
@sizeOf(Context),
|
||||
os.PROT_READ | os.PROT_WRITE,
|
||||
os.MAP_PRIVATE | os.MAP_ANONYMOUS,
|
||||
-1,
|
||||
0,
|
||||
) catch |err| {
|
||||
// Could not allocate memory for the local state, fall back to
|
||||
// the OS syscall.
|
||||
return fillWithOsEntropy(buffer);
|
||||
};
|
||||
// The memory is already zero-initialized.
|
||||
} else {
|
||||
// Use a static thread-local buffer.
|
||||
const S = struct {
|
||||
threadlocal var buf: Context align(mem.page_size) = .{
|
||||
.init_state = .uninitialized,
|
||||
.gimli = undefined,
|
||||
};
|
||||
};
|
||||
wipe_mem = mem.asBytes(&S.buf);
|
||||
}
|
||||
}
|
||||
const ctx = @ptrCast(*Context, wipe_mem.ptr);
|
||||
|
||||
switch (ctx.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 {
|
||||
if (!want_fork_safety) {
|
||||
return initAndFill(buffer);
|
||||
}
|
||||
|
||||
if (maybe_have_wipe_on_fork) wof: {
|
||||
// Qemu user-mode emulation ignores any valid/invalid madvise
|
||||
// hint and returns success. Check if this is the case by
|
||||
// passing bogus parameters, we expect EINVAL as result.
|
||||
if (os.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
|
||||
break :wof;
|
||||
} else |_| {}
|
||||
|
||||
os.madvise(
|
||||
wipe_mem.ptr,
|
||||
wipe_mem.len,
|
||||
os.MADV_WIPEONFORK,
|
||||
) catch |_| {
|
||||
return initAndFill(buffer);
|
||||
};
|
||||
}
|
||||
|
||||
if (std.Thread.use_pthreads) {
|
||||
return setupPthreadAtforkAndFill(buffer);
|
||||
}
|
||||
|
||||
// Since we failed to set up fork safety, we fall back to always
|
||||
// calling getrandom every time.
|
||||
ctx.init_state = .failed;
|
||||
return fillWithOsEntropy(buffer);
|
||||
},
|
||||
.initialized => {
|
||||
return fillWithCsprng(buffer);
|
||||
@@ -110,7 +141,8 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
|
||||
fn setupPthreadAtforkAndFill(buffer: []u8) void {
|
||||
const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0;
|
||||
if (failed) {
|
||||
wipe_me.init_state = .failed;
|
||||
const ctx = @ptrCast(*Context, wipe_mem.ptr);
|
||||
ctx.init_state = .failed;
|
||||
return fillWithOsEntropy(buffer);
|
||||
} else {
|
||||
return initAndFill(buffer);
|
||||
@@ -118,21 +150,21 @@ fn setupPthreadAtforkAndFill(buffer: []u8) void {
|
||||
}
|
||||
|
||||
fn childAtForkHandler() callconv(.C) void {
|
||||
const wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))];
|
||||
std.crypto.utils.secureZero(u8, wipe_slice);
|
||||
std.crypto.utils.secureZero(u8, wipe_mem);
|
||||
}
|
||||
|
||||
fn fillWithCsprng(buffer: []u8) void {
|
||||
const ctx = @ptrCast(*Context, wipe_mem.ptr);
|
||||
if (buffer.len != 0) {
|
||||
wipe_me.gimli.squeeze(buffer);
|
||||
ctx.gimli.squeeze(buffer);
|
||||
} else {
|
||||
wipe_me.gimli.permute();
|
||||
ctx.gimli.permute();
|
||||
}
|
||||
mem.set(u8, wipe_me.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
|
||||
mem.set(u8, ctx.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");
|
||||
os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
|
||||
}
|
||||
|
||||
fn initAndFill(buffer: []u8) void {
|
||||
@@ -147,11 +179,12 @@ fn initAndFill(buffer: []u8) void {
|
||||
fillWithOsEntropy(&seed);
|
||||
}
|
||||
|
||||
wipe_me.gimli = std.crypto.core.Gimli.init(seed);
|
||||
const ctx = @ptrCast(*Context, wipe_mem.ptr);
|
||||
ctx.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;
|
||||
ctx.init_state = .initialized;
|
||||
|
||||
return fillWithCsprng(buffer);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user