| author | |
| committer | |
| log | 816565dd077f561a46a9f31d9ecce32f152f9553 |
| tree | 739359f959b2775beed8eb25887a2823de328abb |
| parent | 867501d9d2f757f99af59b1904b251aa63262e23 |
9 files changed, 263 insertions(+), 306 deletions(-)
lib/compiler/build_runner.zig-1| ... | ... | @@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies"); |
| 21 | 21 | pub const std_options: std.Options = .{ |
| 22 | 22 | .side_channels_mitigations = .none, |
| 23 | 23 | .http_disable_tls = true, |
| 24 | .crypto_fork_safety = false, | |
| 25 | 24 | }; |
| 26 | 25 | |
| 27 | 26 | pub fn main(init: process.Init.Minimal) !void { |
lib/std/Io.zig+9| ... | ... | @@ -731,6 +731,8 @@ pub const VTable = struct { |
| 731 | 731 | now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, |
| 732 | 732 | sleep: *const fn (?*anyopaque, Timeout) SleepError!void, |
| 733 | 733 | |
| 734 | random: *const fn (?*anyopaque, buffer: []u8) RandomError!void, | |
| 735 | ||
| 734 | 736 | netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server, |
| 735 | 737 | netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream, |
| 736 | 738 | netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket, |
| ... | ... | @@ -2242,3 +2244,10 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel |
| 2242 | 2244 | pub fn unlockStderr(io: Io) void { |
| 2243 | 2245 | return io.vtable.unlockStderr(io.userdata); |
| 2244 | 2246 | } |
| 2247 | ||
| 2248 | pub const RandomError = error{EntropyUnavailable} || Cancelable; | |
| 2249 | ||
| 2250 | /// Threadsafe. | |
| 2251 | pub fn random(io: Io, buffer: []u8) RandomError!void { | |
| 2252 | return io.vtable.random(io.userdata, buffer); | |
| 2253 | } |
lib/std/Io/Threaded.zig+242-15| ... | ... | @@ -65,6 +65,7 @@ argv0: Argv0, |
| 65 | 65 | environ: Environ, |
| 66 | 66 | |
| 67 | 67 | null_file: NullFile = .{}, |
| 68 | dev_urandom_fd: dev_urandom_fd_t, | |
| 68 | 69 | |
| 69 | 70 | pub const Argv0 = switch (native_os) { |
| 70 | 71 | .openbsd, .haiku => struct { |
| ... | ... | @@ -585,6 +586,10 @@ const Thread = struct { |
| 585 | 586 | /// Always released when `Status.cancelation` is set to `.parked`. |
| 586 | 587 | futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, |
| 587 | 588 | |
| 589 | random_buffer: [128]u8, | |
| 590 | /// How many bytes of `random_buffer` are filled. | |
| 591 | random_i: usize, | |
| 592 | ||
| 588 | 593 | const Handle = Handle: { |
| 589 | 594 | if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; |
| 590 | 595 | if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE; |
| ... | ... | @@ -1285,6 +1290,9 @@ pub fn deinit(t: *Threaded) void { |
| 1285 | 1290 | if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); |
| 1286 | 1291 | } |
| 1287 | 1292 | t.null_file.deinit(); |
| 1293 | if (use_dev_urandom and t.dev_urandom_fd != -1) { | |
| 1294 | posix.close(t.dev_urandom_fd); | |
| 1295 | } | |
| 1288 | 1296 | t.* = undefined; |
| 1289 | 1297 | } |
| 1290 | 1298 | |
| ... | ... | @@ -1466,6 +1474,8 @@ pub fn io(t: *Threaded) Io { |
| 1466 | 1474 | .now = now, |
| 1467 | 1475 | .sleep = sleep, |
| 1468 | 1476 | |
| 1477 | .random = random, | |
| 1478 | ||
| 1469 | 1479 | .netListenIp = switch (native_os) { |
| 1470 | 1480 | .windows => netListenIpWindows, |
| 1471 | 1481 | else => netListenIpPosix, |
| ... | ... | @@ -1614,6 +1624,8 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1614 | 1624 | .now = now, |
| 1615 | 1625 | .sleep = sleep, |
| 1616 | 1626 | |
| 1627 | .random = random, | |
| 1628 | ||
| 1617 | 1629 | .netListenIp = netListenIpUnavailable, |
| 1618 | 1630 | .netListenUnix = netListenUnixUnavailable, |
| 1619 | 1631 | .netAccept = netAcceptUnavailable, |
| ... | ... | @@ -1704,6 +1716,26 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid |
| 1704 | 1716 | }); |
| 1705 | 1717 | const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; |
| 1706 | 1718 | |
| 1719 | const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) | |
| 1720 | .{ .major = 30, .minor = 0, .patch = 0 } | |
| 1721 | else | |
| 1722 | .{ .major = 2, .minor = 28, .patch = 0 }); | |
| 1723 | ||
| 1724 | const getrandom_use_libc = @TypeOf(posix.system.getrandom) != void and (native_os != .linux or | |
| 1725 | std.c.versionCheck(if (builtin.abi.isAndroid()) .{ | |
| 1726 | .major = 28, | |
| 1727 | .minor = 0, | |
| 1728 | .patch = 0, | |
| 1729 | } else .{ | |
| 1730 | .major = 2, | |
| 1731 | .minor = 25, | |
| 1732 | .patch = 0, | |
| 1733 | })); | |
| 1734 | ||
| 1735 | const use_dev_urandom = getrandom_use_libc and native_os == .linux; | |
| 1736 | ||
| 1737 | const dev_urandom_fd_t = if (use_dev_urandom) posix.fd_t else void; | |
| 1738 | ||
| 1707 | 1739 | fn async( |
| 1708 | 1740 | userdata: ?*anyopaque, |
| 1709 | 1741 | result: []u8, |
| ... | ... | @@ -2538,11 +2570,7 @@ fn dirStatFileLinux( |
| 2538 | 2570 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2539 | 2571 | _ = t; |
| 2540 | 2572 | const linux = std.os.linux; |
| 2541 | const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) | |
| 2542 | .{ .major = 30, .minor = 0, .patch = 0 } | |
| 2543 | else | |
| 2544 | .{ .major = 2, .minor = 28, .patch = 0 }); | |
| 2545 | const sys = if (use_c) std.c else std.os.linux; | |
| 2573 | const sys = if (statx_use_c) std.c else std.os.linux; | |
| 2546 | 2574 | |
| 2547 | 2575 | var path_buffer: [posix.PATH_MAX]u8 = undefined; |
| 2548 | 2576 | const sub_path_posix = try pathToPosix(sub_path, &path_buffer); |
| ... | ... | @@ -2778,11 +2806,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { |
| 2778 | 2806 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2779 | 2807 | _ = t; |
| 2780 | 2808 | const linux = std.os.linux; |
| 2781 | const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) | |
| 2782 | .{ .major = 30, .minor = 0, .patch = 0 } | |
| 2783 | else | |
| 2784 | .{ .major = 2, .minor = 28, .patch = 0 }); | |
| 2785 | const sys = if (use_c) std.c else std.os.linux; | |
| 2809 | const sys = if (statx_use_c) std.c else std.os.linux; | |
| 2786 | 2810 | |
| 2787 | 2811 | const syscall: Syscall = try .start(); |
| 2788 | 2812 | while (true) { |
| ... | ... | @@ -6318,11 +6342,6 @@ fn fchmodatFallback( |
| 6318 | 6342 | mode: posix.mode_t, |
| 6319 | 6343 | ) Dir.SetFilePermissionsError!void { |
| 6320 | 6344 | comptime assert(native_os == .linux); |
| 6321 | const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) | |
| 6322 | .{ .major = 30, .minor = 0, .patch = 0 } | |
| 6323 | else | |
| 6324 | .{ .major = 2, .minor = 28, .patch = 0 }); | |
| 6325 | const sys = if (use_c) std.c else std.os.linux; | |
| 6326 | 6345 | |
| 6327 | 6346 | // Fallback to changing permissions using procfs: |
| 6328 | 6347 | // |
| ... | ... | @@ -6369,6 +6388,7 @@ fn fchmodatFallback( |
| 6369 | 6388 | defer posix.close(path_fd); |
| 6370 | 6389 | |
| 6371 | 6390 | const path_mode = mode: { |
| 6391 | const sys = if (statx_use_c) std.c else std.os.linux; | |
| 6372 | 6392 | const syscall: Syscall = try .start(); |
| 6373 | 6393 | while (true) { |
| 6374 | 6394 | var statx = std.mem.zeroes(std.os.linux.Statx); |
| ... | ... | @@ -14935,6 +14955,213 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { |
| 14935 | 14955 | return @field(t.environ.string, name); |
| 14936 | 14956 | } |
| 14937 | 14957 | |
| 14958 | fn random(userdata: ?*anyopaque, buffer: []u8) Io.RandomError!void { | |
| 14959 | const t: *Threaded = @ptrCast(@alignCast(userdata)); | |
| 14960 | ||
| 14961 | if (is_windows) { | |
| 14962 | // Call RtlGenRandom() instead of CryptGetRandom() on Windows | |
| 14963 | // https://github.com/rust-lang-nursery/rand/issues/111 | |
| 14964 | // https://bugzilla.mozilla.org/show_bug.cgi?id=504270 | |
| 14965 | const max_read_size: windows.ULONG = std.math.maxInt(windows.ULONG); | |
| 14966 | var i: usize = 0; | |
| 14967 | while (i < buffer.len) { | |
| 14968 | const buf = buffer[i..]; | |
| 14969 | const request_n: windows.ULONG = @min(buf.len, max_read_size); | |
| 14970 | const syscall: Syscall = try .start(); | |
| 14971 | const result = windows.advapi32.RtlGenRandom(buf.ptr, request_n); | |
| 14972 | syscall.finish(); | |
| 14973 | if (result == 0) { | |
| 14974 | // `RtlGenRandom` has been observed to fail in situations where | |
| 14975 | // the system is under heavy load. Unfortunately, it does not | |
| 14976 | // call `SetLastError`, so it is not possible to get more | |
| 14977 | // specific error information; it could actually be due to an | |
| 14978 | // out-of-memory condition, for example. | |
| 14979 | return error.EntropyUnavailable; | |
| 14980 | } | |
| 14981 | i += request_n; | |
| 14982 | } | |
| 14983 | return; | |
| 14984 | } | |
| 14985 | ||
| 14986 | if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) { | |
| 14987 | posix.system.arc4random_buf(buffer.ptr, buffer.len); | |
| 14988 | return; | |
| 14989 | } | |
| 14990 | ||
| 14991 | if (native_os == .wasi) { | |
| 14992 | const syscall: Syscall = try .start(); | |
| 14993 | while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) { | |
| 14994 | .SUCCESS => return syscall.finish(), | |
| 14995 | .INTR => { | |
| 14996 | try syscall.checkCancel(); | |
| 14997 | continue; | |
| 14998 | }, | |
| 14999 | else => return syscall.fail(error.EntropyUnavailable), | |
| 15000 | }; | |
| 15001 | } | |
| 15002 | ||
| 15003 | if (@TypeOf(posix.system.getrandom) != void) { | |
| 15004 | const getrandom = if (getrandom_use_libc) std.c.getrandom else std.os.linux.getrandom; | |
| 15005 | var i: usize = 0; | |
| 15006 | const syscall: Syscall = try .start(); | |
| 15007 | while (i < buffer.len) { | |
| 15008 | const buf = buffer[i..]; | |
| 15009 | const rc = getrandom(buf.ptr, buf.len, 0); | |
| 15010 | switch (posix.errno(rc)) { | |
| 15011 | .SUCCESS => { | |
| 15012 | syscall.finish(); | |
| 15013 | const n: usize = @intCast(rc); | |
| 15014 | i += n; | |
| 15015 | continue; | |
| 15016 | }, | |
| 15017 | .INTR => { | |
| 15018 | try syscall.checkCancel(); | |
| 15019 | continue; | |
| 15020 | }, | |
| 15021 | else => return syscall.fail(error.EntropyUnavailable), | |
| 15022 | } | |
| 15023 | } | |
| 15024 | return; | |
| 15025 | } | |
| 15026 | ||
| 15027 | if (native_os == .emscripten) { | |
| 15028 | const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len)); | |
| 15029 | switch (err) { | |
| 15030 | .SUCCESS => return, | |
| 15031 | else => return error.EntropyUnavailable, | |
| 15032 | } | |
| 15033 | } | |
| 15034 | ||
| 15035 | const urandom_fd = try getRandomFd(t); | |
| 15036 | ||
| 15037 | var i: usize = 0; | |
| 15038 | while (buffer.len - i != 0) { | |
| 15039 | const syscall: Syscall = try .start(); | |
| 15040 | const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i); | |
| 15041 | switch (posix.errno(rc)) { | |
| 15042 | .SUCCESS => { | |
| 15043 | syscall.finish(); | |
| 15044 | const n: usize = @intCast(rc); | |
| 15045 | if (n == 0) { | |
| 15046 | if (buffer.len - i != 0) { | |
| 15047 | return error.EntropyUnavailable; | |
| 15048 | } else { | |
| 15049 | return; | |
| 15050 | } | |
| 15051 | } | |
| 15052 | i += n; | |
| 15053 | continue; | |
| 15054 | }, | |
| 15055 | .INTR => { | |
| 15056 | try syscall.checkCancel(); | |
| 15057 | continue; | |
| 15058 | }, | |
| 15059 | else => return syscall.fail(error.EntropyUnavailable), | |
| 15060 | } | |
| 15061 | } | |
| 15062 | } | |
| 15063 | ||
| 15064 | fn getRandomFd(t: *Threaded) posix.fd_t { | |
| 15065 | { | |
| 15066 | t.mutex.lock(); | |
| 15067 | defer t.mutex.unlock(); | |
| 15068 | ||
| 15069 | if (t.dev_urandom_fd == -2) return error.EntropyUnavailable; | |
| 15070 | if (t.dev_urandom_fd != -1) return t.dev_urandom_fd; | |
| 15071 | } | |
| 15072 | ||
| 15073 | const fd: posix.fd_t = fd: { | |
| 15074 | const syscall: Syscall = try .start(); | |
| 15075 | while (true) { | |
| 15076 | const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{ | |
| 15077 | .ACCMODE = .RDONLY, | |
| 15078 | .CLOEXEC = true, | |
| 15079 | }, 0); | |
| 15080 | switch (posix.errno(rc)) { | |
| 15081 | .SUCCESS => { | |
| 15082 | syscall.finish(); | |
| 15083 | break :fd @intCast(rc); | |
| 15084 | }, | |
| 15085 | .INTR => { | |
| 15086 | try syscall.checkCancel(); | |
| 15087 | continue; | |
| 15088 | }, | |
| 15089 | else => { | |
| 15090 | syscall.endSyscall(); | |
| 15091 | t.dev_urandom_fd = -2; | |
| 15092 | return error.EntropyUnavailable; | |
| 15093 | }, | |
| 15094 | } | |
| 15095 | } | |
| 15096 | }; | |
| 15097 | errdefer posix.close(fd); | |
| 15098 | ||
| 15099 | switch (native_os) { | |
| 15100 | .linux => { | |
| 15101 | const sys = if (statx_use_c) std.c else std.os.linux; | |
| 15102 | const syscall: Syscall = try .start(); | |
| 15103 | while (true) { | |
| 15104 | var statx = std.mem.zeroes(std.os.linux.Statx); | |
| 15105 | switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { | |
| 15106 | .SUCCESS => { | |
| 15107 | syscall.finish(); | |
| 15108 | if (!statx.mask.TYPE) return error.Unexpected; | |
| 15109 | t.mutex.lock(); // Another thread might have won the race. | |
| 15110 | defer t.mutex.unlock(); | |
| 15111 | if (t.dev_urandom_fd >= 0) { | |
| 15112 | posix.close(fd); | |
| 15113 | return t.dev_urandom_fd; | |
| 15114 | } else if (!posix.S.ISCHR(statx.mode)) { | |
| 15115 | t.dev_urandom_fd = -2; | |
| 15116 | return error.EntropyUnavailable; | |
| 15117 | } else { | |
| 15118 | t.dev_urandom_fd = fd; | |
| 15119 | return fd; | |
| 15120 | } | |
| 15121 | }, | |
| 15122 | .INTR => { | |
| 15123 | try syscall.checkCancel(); | |
| 15124 | continue; | |
| 15125 | }, | |
| 15126 | else => { | |
| 15127 | t.dev_urandom_fd = -2; | |
| 15128 | return error.EntropyUnavailable; | |
| 15129 | }, | |
| 15130 | } | |
| 15131 | } | |
| 15132 | }, | |
| 15133 | else => { | |
| 15134 | const syscall: Syscall = try .start(); | |
| 15135 | while (true) { | |
| 15136 | var stat = std.mem.zeroes(posix.Stat); | |
| 15137 | switch (posix.errno(fstat_sym(fd, &stat))) { | |
| 15138 | .SUCCESS => { | |
| 15139 | syscall.finish(); | |
| 15140 | if (t.dev_urandom_fd >= 0) { | |
| 15141 | posix.close(fd); | |
| 15142 | return t.dev_urandom_fd; | |
| 15143 | } else if (!posix.S.ISCHR(stat.mode)) { | |
| 15144 | t.dev_urandom_fd = -2; | |
| 15145 | return error.EntropyUnavailable; | |
| 15146 | } else { | |
| 15147 | t.dev_urandom_fd = fd; | |
| 15148 | return fd; | |
| 15149 | } | |
| 15150 | }, | |
| 15151 | .INTR => { | |
| 15152 | try syscall.checkCancel(); | |
| 15153 | continue; | |
| 15154 | }, | |
| 15155 | else => { | |
| 15156 | t.dev_urandom_fd = -2; | |
| 15157 | return error.EntropyUnavailable; | |
| 15158 | }, | |
| 15159 | } | |
| 15160 | } | |
| 15161 | }, | |
| 15162 | } | |
| 15163 | } | |
| 15164 | ||
| 14938 | 15165 | test { |
| 14939 | 15166 | _ = @import("Threaded/test.zig"); |
| 14940 | 15167 | } |
lib/std/Io/test.zig+11| ... | ... | @@ -564,3 +564,14 @@ test "tasks spawned in group after Group.cancel are canceled" { |
| 564 | 564 | try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up |
| 565 | 565 | try group.concurrent(io, global.waitThenSpawn, .{ io, &group }); |
| 566 | 566 | } |
| 567 | ||
| 568 | test "CSPRNG" { | |
| 569 | const io = testing.io; | |
| 570 | ||
| 571 | var random = io.random(); | |
| 572 | ||
| 573 | const a = random.int(u64); | |
| 574 | const b = random.int(u64); | |
| 575 | const c = random.int(u64); | |
| 576 | try std.testing.expect(a ^ b ^ c != 0); | |
| 577 | } |
lib/std/Random.zig+1-3| ... | ... | @@ -1,15 +1,13 @@ |
| 1 | 1 | //! The engines provided here should be initialized from an external source. |
| 2 | //! For a thread-local cryptographically secure pseudo random number generator, | |
| 3 | //! use `std.crypto.random`. | |
| 4 | 2 | //! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will |
| 5 | 3 | //! be faster and use substantially less stack space. |
| 4 | const Random = @This(); | |
| 6 | 5 | |
| 7 | 6 | const std = @import("std.zig"); |
| 8 | 7 | const math = std.math; |
| 9 | 8 | const mem = std.mem; |
| 10 | 9 | const assert = std.debug.assert; |
| 11 | 10 | const maxInt = std.math.maxInt; |
| 12 | const Random = @This(); | |
| 13 | 11 | |
| 14 | 12 | /// Fast unbiased random numbers. |
| 15 | 13 | pub const DefaultPrng = Xoshiro256; |
lib/std/crypto.zig-11| ... | ... | @@ -235,9 +235,6 @@ pub const nacl = struct { |
| 235 | 235 | /// Finite-field arithmetic. |
| 236 | 236 | pub const ff = @import("crypto/ff.zig"); |
| 237 | 237 | |
| 238 | /// This is a thread-local, cryptographically secure pseudo random number generator. | |
| 239 | pub const random = @import("crypto/tlcsprng.zig").interface; | |
| 240 | ||
| 241 | 238 | /// Encoding and decoding |
| 242 | 239 | pub const codecs = @import("crypto/codecs.zig"); |
| 243 | 240 | |
| ... | ... | @@ -364,20 +361,12 @@ test { |
| 364 | 361 | _ = secureZero; |
| 365 | 362 | _ = timing_safe; |
| 366 | 363 | _ = ff; |
| 367 | _ = random; | |
| 368 | 364 | _ = errors; |
| 369 | 365 | _ = tls; |
| 370 | 366 | _ = Certificate; |
| 371 | 367 | _ = codecs; |
| 372 | 368 | } |
| 373 | 369 | |
| 374 | test "CSPRNG" { | |
| 375 | const a = random.int(u64); | |
| 376 | const b = random.int(u64); | |
| 377 | const c = random.int(u64); | |
| 378 | try std.testing.expect(a ^ b ^ c != 0); | |
| 379 | } | |
| 380 | ||
| 381 | 370 | test "issue #4532: no index out of bounds" { |
| 382 | 371 | const types = [_]type{ |
| 383 | 372 | hash.Md5, |
lib/std/crypto/tlcsprng.zig deleted-169| ... | ... | @@ -1,169 +0,0 @@ |
| 1 | //! Thread-local cryptographically secure pseudo-random number generator. | |
| 2 | //! This file has public declarations that are intended to be used internally | |
| 3 | //! by the standard library; this namespace is not intended to be exposed | |
| 4 | //! directly to standard library users. | |
| 5 | ||
| 6 | const std = @import("std"); | |
| 7 | const builtin = @import("builtin"); | |
| 8 | const mem = std.mem; | |
| 9 | const native_os = builtin.os.tag; | |
| 10 | const posix = std.posix; | |
| 11 | ||
| 12 | /// We use this as a layer of indirection because global const pointers cannot | |
| 13 | /// point to thread-local variables. | |
| 14 | pub const interface: std.Random = .{ | |
| 15 | .ptr = undefined, | |
| 16 | .fillFn = tlsCsprngFill, | |
| 17 | }; | |
| 18 | ||
| 19 | const os_has_fork = @TypeOf(posix.fork) != void; | |
| 20 | const os_has_arc4random = builtin.link_libc and (@TypeOf(std.c.arc4random_buf) != void); | |
| 21 | const want_fork_safety = os_has_fork and !os_has_arc4random and std.options.crypto_fork_safety; | |
| 22 | const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{ | |
| 23 | .major = 4, | |
| 24 | .minor = 14, | |
| 25 | .patch = 0, | |
| 26 | }) orelse true; | |
| 27 | ||
| 28 | const Rng = std.Random.DefaultCsprng; | |
| 29 | ||
| 30 | const Context = struct { | |
| 31 | init_state: enum(u8) { uninitialized = 0, initialized, failed }, | |
| 32 | rng: Rng, | |
| 33 | }; | |
| 34 | ||
| 35 | var install_atfork_handler = std.once(struct { | |
| 36 | // Install the global handler only once. | |
| 37 | // The same handler is shared among threads and is inherinted by fork()-ed | |
| 38 | // processes. | |
| 39 | fn do() void { | |
| 40 | const r = std.c.pthread_atfork(null, null, childAtForkHandler); | |
| 41 | std.debug.assert(r == 0); | |
| 42 | } | |
| 43 | }.do); | |
| 44 | ||
| 45 | threadlocal var wipe_mem: []align(std.heap.page_size_min) u8 = &[_]u8{}; | |
| 46 | ||
| 47 | fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void { | |
| 48 | if (os_has_arc4random) { | |
| 49 | // arc4random is already a thread-local CSPRNG. | |
| 50 | return std.c.arc4random_buf(buffer.ptr, buffer.len); | |
| 51 | } | |
| 52 | // Allow applications to decide they would prefer to have every call to | |
| 53 | // std.crypto.random always make an OS syscall, rather than rely on an | |
| 54 | // application implementation of a CSPRNG. | |
| 55 | if (std.options.crypto_always_getrandom) { | |
| 56 | return std.options.cryptoRandomSeed(buffer); | |
| 57 | } | |
| 58 | ||
| 59 | if (wipe_mem.len == 0) { | |
| 60 | // Not initialized yet. | |
| 61 | if (want_fork_safety and maybe_have_wipe_on_fork) { | |
| 62 | // Allocate a per-process page, madvise operates with page | |
| 63 | // granularity. | |
| 64 | wipe_mem = posix.mmap( | |
| 65 | null, | |
| 66 | @sizeOf(Context), | |
| 67 | posix.PROT.READ | posix.PROT.WRITE, | |
| 68 | .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, | |
| 69 | -1, | |
| 70 | 0, | |
| 71 | ) catch { | |
| 72 | // Could not allocate memory for the local state, fall back to | |
| 73 | // the OS syscall. | |
| 74 | return std.options.cryptoRandomSeed(buffer); | |
| 75 | }; | |
| 76 | // The memory is already zero-initialized. | |
| 77 | } else { | |
| 78 | // Use a static thread-local buffer. | |
| 79 | const S = struct { | |
| 80 | threadlocal var buf: Context align(std.heap.page_size_min) = .{ | |
| 81 | .init_state = .uninitialized, | |
| 82 | .rng = undefined, | |
| 83 | }; | |
| 84 | }; | |
| 85 | wipe_mem = mem.asBytes(&S.buf); | |
| 86 | } | |
| 87 | } | |
| 88 | const ctx: *Context = @ptrCast(wipe_mem.ptr); | |
| 89 | ||
| 90 | switch (ctx.init_state) { | |
| 91 | .uninitialized => { | |
| 92 | if (!want_fork_safety) { | |
| 93 | return initAndFill(buffer); | |
| 94 | } | |
| 95 | ||
| 96 | if (maybe_have_wipe_on_fork) wof: { | |
| 97 | // Qemu user-mode emulation ignores any valid/invalid madvise | |
| 98 | // hint and returns success. Check if this is the case by | |
| 99 | // passing bogus parameters, we expect EINVAL as result. | |
| 100 | if (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| { | |
| 101 | break :wof; | |
| 102 | } else |_| {} | |
| 103 | ||
| 104 | if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| { | |
| 105 | return initAndFill(buffer); | |
| 106 | } else |_| {} | |
| 107 | } | |
| 108 | ||
| 109 | if (std.Thread.use_pthreads) { | |
| 110 | return setupPthreadAtforkAndFill(buffer); | |
| 111 | } | |
| 112 | ||
| 113 | // Since we failed to set up fork safety, we fall back to always | |
| 114 | // calling getrandom every time. | |
| 115 | ctx.init_state = .failed; | |
| 116 | return std.options.cryptoRandomSeed(buffer); | |
| 117 | }, | |
| 118 | .initialized => { | |
| 119 | return fillWithCsprng(buffer); | |
| 120 | }, | |
| 121 | .failed => { | |
| 122 | if (want_fork_safety) { | |
| 123 | return std.options.cryptoRandomSeed(buffer); | |
| 124 | } else { | |
| 125 | unreachable; | |
| 126 | } | |
| 127 | }, | |
| 128 | } | |
| 129 | } | |
| 130 | ||
| 131 | fn setupPthreadAtforkAndFill(buffer: []u8) void { | |
| 132 | install_atfork_handler.call(); | |
| 133 | return initAndFill(buffer); | |
| 134 | } | |
| 135 | ||
| 136 | fn childAtForkHandler() callconv(.c) void { | |
| 137 | // The atfork handler is global, this function may be called after | |
| 138 | // fork()-ing threads that never initialized the CSPRNG context. | |
| 139 | if (wipe_mem.len == 0) return; | |
| 140 | std.crypto.secureZero(u8, wipe_mem); | |
| 141 | } | |
| 142 | ||
| 143 | fn fillWithCsprng(buffer: []u8) void { | |
| 144 | const ctx: *Context = @ptrCast(wipe_mem.ptr); | |
| 145 | return ctx.rng.fill(buffer); | |
| 146 | } | |
| 147 | ||
| 148 | pub fn defaultRandomSeed(buffer: []u8) void { | |
| 149 | posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); | |
| 150 | } | |
| 151 | ||
| 152 | fn initAndFill(buffer: []u8) void { | |
| 153 | var seed: [Rng.secret_seed_length]u8 = undefined; | |
| 154 | // Because we panic on getrandom() failing, we provide the opportunity | |
| 155 | // to override the default seed function. This also makes | |
| 156 | // `std.crypto.random` available on freestanding targets, provided that | |
| 157 | // the `std.options.cryptoRandomSeed` function is provided. | |
| 158 | std.options.cryptoRandomSeed(&seed); | |
| 159 | ||
| 160 | const ctx: *Context = @ptrCast(wipe_mem.ptr); | |
| 161 | ctx.rng = Rng.init(seed); | |
| 162 | std.crypto.secureZero(u8, &seed); | |
| 163 | ||
| 164 | // This is at the end so that accidental recursive dependencies result | |
| 165 | // in stack overflows instead of invalid random data. | |
| 166 | ctx.init_state = .initialized; | |
| 167 | ||
| 168 | return fillWithCsprng(buffer); | |
| 169 | } |
lib/std/posix.zig-101| ... | ... | @@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void { |
| 361 | 361 | } |
| 362 | 362 | } |
| 363 | 363 | |
| 364 | pub const GetRandomError = OpenError; | |
| 365 | ||
| 366 | /// Obtain a series of random bytes. These bytes can be used to seed user-space | |
| 367 | /// random number generators or for cryptographic purposes. | |
| 368 | /// When linking against libc, this calls the | |
| 369 | /// appropriate OS-specific library call. Otherwise it uses the zig standard | |
| 370 | /// library implementation. | |
| 371 | pub fn getrandom(buffer: []u8) GetRandomError!void { | |
| 372 | if (native_os == .windows) { | |
| 373 | return windows.ProcessPrng(buffer); | |
| 374 | } | |
| 375 | if (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) { | |
| 376 | system.arc4random_buf(buffer.ptr, buffer.len); | |
| 377 | return; | |
| 378 | } | |
| 379 | if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) { | |
| 380 | .SUCCESS => return, | |
| 381 | else => |err| return unexpectedErrno(err), | |
| 382 | }; | |
| 383 | if (@TypeOf(system.getrandom) != void) { | |
| 384 | var buf = buffer; | |
| 385 | const use_c = native_os != .linux or | |
| 386 | std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 25, .patch = 0 }); | |
| 387 | ||
| 388 | while (buf.len != 0) { | |
| 389 | const num_read: usize, const err = if (use_c) res: { | |
| 390 | const rc = std.c.getrandom(buf.ptr, buf.len, 0); | |
| 391 | break :res .{ @bitCast(rc), errno(rc) }; | |
| 392 | } else res: { | |
| 393 | const rc = linux.getrandom(buf.ptr, buf.len, 0); | |
| 394 | break :res .{ rc, linux.errno(rc) }; | |
| 395 | }; | |
| 396 | ||
| 397 | switch (err) { | |
| 398 | .SUCCESS => buf = buf[num_read..], | |
| 399 | .INVAL => unreachable, | |
| 400 | .FAULT => unreachable, | |
| 401 | .INTR => continue, | |
| 402 | else => return unexpectedErrno(err), | |
| 403 | } | |
| 404 | } | |
| 405 | return; | |
| 406 | } | |
| 407 | if (native_os == .emscripten) { | |
| 408 | const err = errno(std.c.getentropy(buffer.ptr, buffer.len)); | |
| 409 | switch (err) { | |
| 410 | .SUCCESS => return, | |
| 411 | else => return unexpectedErrno(err), | |
| 412 | } | |
| 413 | } | |
| 414 | return getRandomBytesDevURandom(buffer); | |
| 415 | } | |
| 416 | ||
| 417 | fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void { | |
| 418 | const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); | |
| 419 | defer close(fd); | |
| 420 | ||
| 421 | switch (native_os) { | |
| 422 | .linux => { | |
| 423 | var stx = std.mem.zeroes(linux.Statx); | |
| 424 | const rc = linux.statx( | |
| 425 | fd, | |
| 426 | "", | |
| 427 | linux.AT.EMPTY_PATH, | |
| 428 | .{ .TYPE = true }, | |
| 429 | &stx, | |
| 430 | ); | |
| 431 | switch (errno(rc)) { | |
| 432 | .SUCCESS => {}, | |
| 433 | .ACCES => unreachable, | |
| 434 | .BADF => unreachable, | |
| 435 | .FAULT => unreachable, | |
| 436 | .INVAL => unreachable, | |
| 437 | .LOOP => unreachable, | |
| 438 | .NAMETOOLONG => unreachable, | |
| 439 | .NOENT => unreachable, | |
| 440 | .NOMEM => return error.SystemResources, | |
| 441 | .NOTDIR => unreachable, | |
| 442 | else => |err| return unexpectedErrno(err), | |
| 443 | } | |
| 444 | if (!S.ISCHR(stx.mode)) { | |
| 445 | return error.NoDevice; | |
| 446 | } | |
| 447 | }, | |
| 448 | else => { | |
| 449 | const st = fstat(fd) catch |err| switch (err) { | |
| 450 | error.Streaming => return error.NoDevice, | |
| 451 | else => |e| return e, | |
| 452 | }; | |
| 453 | if (!S.ISCHR(st.mode)) { | |
| 454 | return error.NoDevice; | |
| 455 | } | |
| 456 | }, | |
| 457 | } | |
| 458 | ||
| 459 | var i: usize = 0; | |
| 460 | while (i < buf.len) { | |
| 461 | i += read(fd, buf[i..]) catch return error.Unexpected; | |
| 462 | } | |
| 463 | } | |
| 464 | ||
| 465 | 364 | pub const RaiseError = UnexpectedError; |
| 466 | 365 | |
| 467 | 366 | pub fn raise(sig: SIG) RaiseError!void { |
lib/std/std.zig-6| ... | ... | @@ -137,12 +137,6 @@ pub const Options = struct { |
| 137 | 137 | |
| 138 | 138 | fmt_max_depth: usize = fmt.default_max_depth, |
| 139 | 139 | |
| 140 | cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed, | |
| 141 | ||
| 142 | crypto_always_getrandom: bool = false, | |
| 143 | ||
| 144 | crypto_fork_safety: bool = true, | |
| 145 | ||
| 146 | 140 | /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to |
| 147 | 141 | /// disable TLS support. |
| 148 | 142 | /// |