authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2021-05-16 11:51:39+02:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-17 17:16:38-04:00
logb7eab32f42040ecb80501111adb05bc8369625e8
tree7fe250870ed8690ec8eb71bf1a5878debb6d47e5
parentfe1a166589db0f2371429c93e1e1e622c19378f1

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

1 files changed, 75 insertions(+), 42 deletions(-)

lib/std/crypto/tlcsprng.zig+75-42
......@@ -12,6 +12,7 @@
1212const std = @import("std");
1313const root = @import("root");
1414const mem = std.mem;
15const os = std.os;
1516
1617/// We use this as a layer of indirection because global const pointers cannot
1718/// point to thread-local variables.
......@@ -42,16 +43,12 @@ const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{
4243 .minor = 14,
4344}) orelse true;
4445
45const WipeMe = struct {
46 init_state: enum { uninitialized, initialized, failed },
46const Context = struct {
47 init_state: enum(u8) { uninitialized = 0, initialized, failed },
4748 gimli: std.crypto.core.Gimli,
4849};
49const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe);
5050
51threadlocal var wipe_me: WipeMe align(wipe_align) = .{
52 .gimli = undefined,
53 .init_state = .uninitialized,
54};
51threadlocal var wipe_mem: []align(mem.page_size) u8 = &[_]u8{};
5552
5653fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
5754 if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {
......@@ -64,35 +61,69 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
6461 if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {
6562 return fillWithOsEntropy(buffer);
6663 }
67 switch (wipe_me.init_state) {
64
65 if (wipe_mem.len == 0) {
66 // Not initialized yet.
67 if (want_fork_safety and maybe_have_wipe_on_fork) {
68 // Allocate a per-process page, madvise operates with page
69 // granularity.
70 wipe_mem = os.mmap(
71 null,
72 @sizeOf(Context),
73 os.PROT_READ | os.PROT_WRITE,
74 os.MAP_PRIVATE | os.MAP_ANONYMOUS,
75 -1,
76 0,
77 ) catch |err| {
78 // Could not allocate memory for the local state, fall back to
79 // the OS syscall.
80 return fillWithOsEntropy(buffer);
81 };
82 // The memory is already zero-initialized.
83 } else {
84 // Use a static thread-local buffer.
85 const S = struct {
86 threadlocal var buf: Context align(mem.page_size) = .{
87 .init_state = .uninitialized,
88 .gimli = undefined,
89 };
90 };
91 wipe_mem = mem.asBytes(&S.buf);
92 }
93 }
94 const ctx = @ptrCast(*Context, wipe_mem.ptr);
95
96 switch (ctx.init_state) {
6897 .uninitialized => {
69 if (want_fork_safety) {
70 if (maybe_have_wipe_on_fork) {
71 if (std.os.madvise(
72 @ptrCast([*]align(mem.page_size) u8, &wipe_me),
73 @sizeOf(@TypeOf(wipe_me)),
74 std.os.MADV_WIPEONFORK,
75 )) |_| {
76 return initAndFill(buffer);
77 } else |_| if (std.Thread.use_pthreads) {
78 return setupPthreadAtforkAndFill(buffer);
79 } else {
80 // Since we failed to set up fork safety, we fall back to always
81 // calling getrandom every time.
82 wipe_me.init_state = .failed;
83 return fillWithOsEntropy(buffer);
84 }
85 } else if (std.Thread.use_pthreads) {
86 return setupPthreadAtforkAndFill(buffer);
87 } else {
88 // We have no mechanism to provide fork safety, but we want fork safety,
89 // so we fall back to calling getrandom every time.
90 wipe_me.init_state = .failed;
91 return fillWithOsEntropy(buffer);
92 }
93 } else {
98 if (!want_fork_safety) {
9499 return initAndFill(buffer);
95100 }
101
102 if (maybe_have_wipe_on_fork) wof: {
103 // Qemu user-mode emulation ignores any valid/invalid madvise
104 // hint and returns success. Check if this is the case by
105 // passing bogus parameters, we expect EINVAL as result.
106 if (os.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| {
107 break :wof;
108 } else |_| {}
109
110 os.madvise(
111 wipe_mem.ptr,
112 wipe_mem.len,
113 os.MADV_WIPEONFORK,
114 ) catch |_| {
115 return initAndFill(buffer);
116 };
117 }
118
119 if (std.Thread.use_pthreads) {
120 return setupPthreadAtforkAndFill(buffer);
121 }
122
123 // Since we failed to set up fork safety, we fall back to always
124 // calling getrandom every time.
125 ctx.init_state = .failed;
126 return fillWithOsEntropy(buffer);
96127 },
97128 .initialized => {
98129 return fillWithCsprng(buffer);
......@@ -110,7 +141,8 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
110141fn setupPthreadAtforkAndFill(buffer: []u8) void {
111142 const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0;
112143 if (failed) {
113 wipe_me.init_state = .failed;
144 const ctx = @ptrCast(*Context, wipe_mem.ptr);
145 ctx.init_state = .failed;
114146 return fillWithOsEntropy(buffer);
115147 } else {
116148 return initAndFill(buffer);
......@@ -118,21 +150,21 @@ fn setupPthreadAtforkAndFill(buffer: []u8) void {
118150}
119151
120152fn childAtForkHandler() callconv(.C) void {
121 const wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))];
122 std.crypto.utils.secureZero(u8, wipe_slice);
153 std.crypto.utils.secureZero(u8, wipe_mem);
123154}
124155
125156fn fillWithCsprng(buffer: []u8) void {
157 const ctx = @ptrCast(*Context, wipe_mem.ptr);
126158 if (buffer.len != 0) {
127 wipe_me.gimli.squeeze(buffer);
159 ctx.gimli.squeeze(buffer);
128160 } else {
129 wipe_me.gimli.permute();
161 ctx.gimli.permute();
130162 }
131 mem.set(u8, wipe_me.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
163 mem.set(u8, ctx.gimli.toSlice()[0..std.crypto.core.Gimli.RATE], 0);
132164}
133165
134166fn fillWithOsEntropy(buffer: []u8) void {
135 std.os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
167 os.getrandom(buffer) catch @panic("getrandom() failed to provide entropy");
136168}
137169
138170fn initAndFill(buffer: []u8) void {
......@@ -147,11 +179,12 @@ fn initAndFill(buffer: []u8) void {
147179 fillWithOsEntropy(&seed);
148180 }
149181
150 wipe_me.gimli = std.crypto.core.Gimli.init(seed);
182 const ctx = @ptrCast(*Context, wipe_mem.ptr);
183 ctx.gimli = std.crypto.core.Gimli.init(seed);
151184
152185 // This is at the end so that accidental recursive dependencies result
153186 // in stack overflows instead of invalid random data.
154 wipe_me.init_state = .initialized;
187 ctx.init_state = .initialized;
155188
156189 return fillWithCsprng(buffer);
157190}