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 @@...@@ -12,6 +12,7 @@
12const std = @import("std");12const std = @import("std");
13const root = @import("root");13const root = @import("root");
14const mem = std.mem;14const mem = std.mem;
15const os = std.os;
1516
16/// We use this as a layer of indirection because global const pointers cannot17/// We use this as a layer of indirection because global const pointers cannot
17/// point to thread-local variables.18/// point to thread-local variables.
...@@ -42,16 +43,12 @@ const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{...@@ -42,16 +43,12 @@ const maybe_have_wipe_on_fork = std.Target.current.os.isAtLeast(.linux, .{
42 .minor = 14,43 .minor = 14,
43}) orelse true;44}) orelse true;
4445
45const WipeMe = struct {46const Context = struct {
46 init_state: enum { uninitialized, initialized, failed },47 init_state: enum(u8) { uninitialized = 0, initialized, failed },
47 gimli: std.crypto.core.Gimli,48 gimli: std.crypto.core.Gimli,
48};49};
49const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe);
5050
51threadlocal var wipe_me: WipeMe align(wipe_align) = .{51threadlocal var wipe_mem: []align(mem.page_size) u8 = &[_]u8{};
52 .gimli = undefined,
53 .init_state = .uninitialized,
54};
5552
56fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {53fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
57 if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {54 if (std.builtin.link_libc and @hasDecl(std.c, "arc4random_buf")) {
...@@ -64,35 +61,69 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {...@@ -64,35 +61,69 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
64 if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {61 if (comptime std.meta.globalOption("crypto_always_getrandom", bool) orelse false) {
65 return fillWithOsEntropy(buffer);62 return fillWithOsEntropy(buffer);
66 }63 }
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) {
68 .uninitialized => {97 .uninitialized => {
69 if (want_fork_safety) {98 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 {
94 return initAndFill(buffer);99 return initAndFill(buffer);
95 }100 }
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);
96 },127 },
97 .initialized => {128 .initialized => {
98 return fillWithCsprng(buffer);129 return fillWithCsprng(buffer);
...@@ -110,7 +141,8 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {...@@ -110,7 +141,8 @@ fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void {
110fn setupPthreadAtforkAndFill(buffer: []u8) void {141fn setupPthreadAtforkAndFill(buffer: []u8) void {
111 const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0;142 const failed = std.c.pthread_atfork(null, null, childAtForkHandler) != 0;
112 if (failed) {143 if (failed) {
113 wipe_me.init_state = .failed;144 const ctx = @ptrCast(*Context, wipe_mem.ptr);
145 ctx.init_state = .failed;
114 return fillWithOsEntropy(buffer);146 return fillWithOsEntropy(buffer);
115 } else {147 } else {
116 return initAndFill(buffer);148 return initAndFill(buffer);
...@@ -118,21 +150,21 @@ fn setupPthreadAtforkAndFill(buffer: []u8) void {...@@ -118,21 +150,21 @@ fn setupPthreadAtforkAndFill(buffer: []u8) void {
118}150}
119151
120fn childAtForkHandler() callconv(.C) void {152fn childAtForkHandler() callconv(.C) void {
121 const wipe_slice = @ptrCast([*]u8, &wipe_me)[0..@sizeOf(@TypeOf(wipe_me))];153 std.crypto.utils.secureZero(u8, wipe_mem);
122 std.crypto.utils.secureZero(u8, wipe_slice);
123}154}
124155
125fn fillWithCsprng(buffer: []u8) void {156fn fillWithCsprng(buffer: []u8) void {
157 const ctx = @ptrCast(*Context, wipe_mem.ptr);
126 if (buffer.len != 0) {158 if (buffer.len != 0) {
127 wipe_me.gimli.squeeze(buffer);159 ctx.gimli.squeeze(buffer);
128 } else {160 } else {
129 wipe_me.gimli.permute();161 ctx.gimli.permute();
130 }162 }
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);
132}164}
133165
134fn fillWithOsEntropy(buffer: []u8) void {166fn 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");
136}168}
137169
138fn initAndFill(buffer: []u8) void {170fn initAndFill(buffer: []u8) void {
...@@ -147,11 +179,12 @@ fn initAndFill(buffer: []u8) void {...@@ -147,11 +179,12 @@ fn initAndFill(buffer: []u8) void {
147 fillWithOsEntropy(&seed);179 fillWithOsEntropy(&seed);
148 }180 }
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
152 // This is at the end so that accidental recursive dependencies result185 // This is at the end so that accidental recursive dependencies result
153 // in stack overflows instead of invalid random data.186 // in stack overflows instead of invalid random data.
154 wipe_me.init_state = .initialized;187 ctx.init_state = .initialized;
155188
156 return fillWithCsprng(buffer);189 return fillWithCsprng(buffer);
157}190}