| ... | @@ -12,6 +12,7 @@ | ... | @@ -12,6 +12,7 @@ |
| 12 | const std = @import("std"); | 12 | const std = @import("std"); |
| 13 | const root = @import("root"); | 13 | const root = @import("root"); |
| 14 | const mem = std.mem; | 14 | const mem = std.mem; |
| | 15 | const os = std.os; |
| 15 | | 16 | |
| 16 | /// We use this as a layer of indirection because global const pointers cannot | 17 | /// 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; |
| 44 | | 45 | |
| 45 | const WipeMe = struct { | 46 | const 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 | }; |
| 49 | const wipe_align = if (maybe_have_wipe_on_fork) mem.page_size else @alignOf(WipeMe); | | |
| 50 | | 50 | |
| 51 | threadlocal var wipe_me: WipeMe align(wipe_align) = .{ | 51 | threadlocal var wipe_mem: []align(mem.page_size) u8 = &[_]u8{}; |
| 52 | .gimli = undefined, | | |
| 53 | .init_state = .uninitialized, | | |
| 54 | }; | | |
| 55 | | 52 | |
| 56 | fn tlsCsprngFill(_: *const std.rand.Random, buffer: []u8) void { | 53 | fn 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 { |
| 110 | fn setupPthreadAtforkAndFill(buffer: []u8) void { | 141 | fn 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 | } |
| 119 | | 151 | |
| 120 | fn childAtForkHandler() callconv(.C) void { | 152 | fn 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 | } |
| 124 | | 155 | |
| 125 | fn fillWithCsprng(buffer: []u8) void { | 156 | fn 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 | } |
| 133 | | 165 | |
| 134 | fn fillWithOsEntropy(buffer: []u8) void { | 166 | fn 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 | } |
| 137 | | 169 | |
| 138 | fn initAndFill(buffer: []u8) void { | 170 | fn 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 | } |
| 149 | | 181 | |
| 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); |
| 151 | | 184 | |
| 152 | // This is at the end so that accidental recursive dependencies result | 185 | // 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; |
| 155 | | 188 | |
| 156 | return fillWithCsprng(buffer); | 189 | return fillWithCsprng(buffer); |
| 157 | } | 190 | } |