diff --git a/build.zig b/build.zig index 58a5c5def3..aaa67f458d 100644 --- a/build.zig +++ b/build.zig @@ -593,15 +593,7 @@ pub fn build(b: *std.Build) !void { .x86_64 => 3_756_422_348, else => 3_800_000_000, }, - .linux => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 6_732_817_203, - .loongarch64 => 3_216_349_593, - .powerpc64le => 3_090_179_276, - .riscv64 => 4_052_670_054, - .s390x => 3_652_514_201, - .x86_64 => 3_249_546_854, - else => 6_800_000_000, - }, + .linux => 6_800_000_000, .macos => switch (b.graph.host.result.cpu.arch) { .aarch64 => 8_273_795_481, else => 8_300_000_000, diff --git a/lib/compiler/aro/aro/Driver.zig b/lib/compiler/aro/aro/Driver.zig index 400a1ef6f6..154b4d56a5 100644 --- a/lib/compiler/aro/aro/Driver.zig +++ b/lib/compiler/aro/aro/Driver.zig @@ -1217,11 +1217,12 @@ pub fn getDepFileName(d: *Driver, source: Source, buf: *[std.fs.max_name_bytes]u } fn getRandomFilename(d: *Driver, buf: *[std.fs.max_name_bytes]u8, extension: []const u8) ![]const u8 { + const io = d.comp.io; const random_bytes_count = 12; const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count); var random_bytes: [random_bytes_count]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var random_name: [sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&random_name, &random_bytes); diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index e479ca725a..752f1fdae3 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -21,7 +21,6 @@ pub const dependencies = @import("@dependencies"); pub const std_options: std.Options = .{ .side_channels_mitigations = .none, .http_disable_tls = true, - .crypto_fork_safety = false, }; pub fn main(init: process.Init.Minimal) !void { diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 007765c4d5..ec931306cd 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -111,12 +111,7 @@ pub const TestResults = struct { pub const MakeOptions = struct { progress_node: std.Progress.Node, watch: bool, - web_server: switch (builtin.target.cpu.arch) { - else => ?*Build.WebServer, - // WASM code references `Build.abi` which happens to incidentally reference this type, but - // it currently breaks because `std.net.Address` doesn't work there. Work around for now. - .wasm32 => void, - }, + web_server: ?*Build.WebServer, /// If set, this is a timeout to enforce on all individual unit tests, in nanoseconds. unit_test_timeout_ns: ?u64, /// Not to be confused with `Build.allocator`, which is an alias of `Build.graph.arena`. diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 2403436c73..92db0ca0a0 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1706,18 +1706,29 @@ fn getZigArgs(compile: *Compile, fuzz: bool) ![][]const u8 { // The args file is already present from a previous run. } else |err| switch (err) { error.FileNotFound => { - try b.cache_root.handle.createDirPath(io, "tmp"); - const rand_int = std.crypto.random.int(u64); - const tmp_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); - try b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_path, .data = args }); - defer b.cache_root.handle.deleteFile(io, tmp_path) catch { - // It's fine if the temporary file can't be cleaned up. + var af = b.cache_root.handle.createFileAtomic(io, args_file, .{ + .replace = false, + .make_path = true, + }) catch |e| return step.fail("failed creating tmp args file {f}{s}: {t}", .{ + b.cache_root, args_file, e, + }); + defer af.deinit(io); + + af.file.writeStreamingAll(io, args) catch |e| { + return step.fail("failed writing args data to tmp file {f}{s}: {t}", .{ + b.cache_root, args_file, e, + }); }; - b.cache_root.handle.rename(tmp_path, b.cache_root.handle, args_file, io) catch |rename_err| switch (rename_err) { + // Note we can't clean up this file, not even after build + // success, because that might interfere with another build + // process that needs the same file. + af.link(io) catch |e| switch (e) { error.PathAlreadyExists => { // The args file was created by another concurrent build process. }, - else => |other_err| return other_err, + else => |other_err| return step.fail("failed linking tmp file {f}{s}: {t}", .{ + b.cache_root, args_file, other_err, + }), }; }, else => |other_err| return other_err, diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 9bb9160620..6c316b35fc 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -476,46 +476,28 @@ fn make(step: *Step, make_options: Step.MakeOptions) !void { return; } else |outer_err| switch (outer_err) { error.FileNotFound => { - const sub_dirname = fs.path.dirname(sub_path).?; - b.cache_root.handle.createDirPath(io, sub_dirname) catch |e| - return step.fail("unable to make path '{f}{s}': {t}", .{ b.cache_root, sub_dirname, e }); + var atomic_file = b.cache_root.handle.createFileAtomic(io, sub_path, .{ + .replace = false, + .make_path = true, + }) catch |err| return step.fail("failed to create temporary path for '{f}{s}': {t}", .{ + b.cache_root, sub_path, err, + }); + defer atomic_file.deinit(io); - const rand_int = std.crypto.random.int(u64); - const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ - std.fmt.hex(rand_int) ++ fs.path.sep_str ++ - basename; - const tmp_sub_path_dirname = fs.path.dirname(tmp_sub_path).?; - - b.cache_root.handle.createDirPath(io, tmp_sub_path_dirname) catch |err| { - return step.fail("unable to make temporary directory '{f}{s}': {t}", .{ - b.cache_root, tmp_sub_path_dirname, err, + atomic_file.file.writeStreamingAll(io, options.contents.items) catch |err| { + return step.fail("failed to write options to temporary path for '{f}{s}': {t}", .{ + b.cache_root, sub_path, err, }); }; - b.cache_root.handle.writeFile(io, .{ .sub_path = tmp_sub_path, .data = options.contents.items }) catch |err| { - return step.fail("unable to write options to '{f}{s}': {t}", .{ - b.cache_root, tmp_sub_path, err, - }); - }; - - b.cache_root.handle.rename(tmp_sub_path, b.cache_root.handle, sub_path, io) catch |err| switch (err) { + atomic_file.link(io) catch |err| switch (err) { error.PathAlreadyExists => { - // Other process beat us to it. Clean up the temp file. - b.cache_root.handle.deleteFile(io, tmp_sub_path) catch |e| { - try step.addError("warning: unable to delete temp file '{f}{s}': {t}", .{ - b.cache_root, tmp_sub_path, e, - }); - }; step.result_cached = true; return; }, - else => { - return step.fail("unable to rename options from '{f}{s}' to '{f}{s}': {t}", .{ - b.cache_root, tmp_sub_path, - b.cache_root, sub_path, - err, - }); - }, + else => return step.fail("failed to link temporary file into '{f}{s}': {t}", .{ + b.cache_root, sub_path, err, + }), }; }, else => |e| return step.fail("unable to access options file '{f}{s}': {t}", .{ diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 1084c7fbfd..d822697ae5 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -984,7 +984,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { }; // We do not know the final output paths yet, use temp paths to run the command. - const rand_int = std.crypto.random.int(u64); + var rand_int: u64 = undefined; + io.random(@ptrCast(&rand_int)); const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); for (output_placeholders.items) |placeholder| { @@ -1128,7 +1129,8 @@ pub fn rerunInFuzzMode( } const has_side_effects = false; - const rand_int = std.crypto.random.int(u64); + var rand_int: u64 = undefined; + io.random(@ptrCast(&rand_int)); const tmp_dir_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); try runCommand(run, argv_list.items, has_side_effects, tmp_dir_path, .{ .progress_node = prog_node, diff --git a/lib/std/Build/Step/WriteFile.zig b/lib/std/Build/Step/WriteFile.zig index d2c5587283..0c0e2b5d86 100644 --- a/lib/std/Build/Step/WriteFile.zig +++ b/lib/std/Build/Step/WriteFile.zig @@ -293,7 +293,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { .tmp => { step.result_cached = false; - const rand_int = std.crypto.random.int(u64); + var rand_int: u64 = undefined; + io.random(@ptrCast(&rand_int)); const tmp_dir_sub_path = "tmp" ++ Dir.path.sep_str ++ std.fmt.hex(rand_int); write_file.generated_directory.path = try b.cache_root.join(arena, &.{tmp_dir_sub_path}); diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 7ef4b85142..5796ee7eb0 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -676,6 +676,7 @@ pub const VTable = struct { dirDeleteFile: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteFileError!void, dirDeleteDir: *const fn (?*anyopaque, Dir, []const u8) Dir.DeleteDirError!void, dirRename: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenameError!void, + dirRenamePreserve: *const fn (?*anyopaque, old_dir: Dir, old_sub_path: []const u8, new_dir: Dir, new_sub_path: []const u8) Dir.RenamePreserveError!void, dirSymLink: *const fn (?*anyopaque, Dir, target_path: []const u8, sym_link_path: []const u8, Dir.SymLinkFlags) Dir.SymLinkError!void, dirReadLink: *const fn (?*anyopaque, Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize, dirSetOwner: *const fn (?*anyopaque, Dir, ?File.Uid, ?File.Gid) Dir.SetOwnerError!void, @@ -731,6 +732,9 @@ pub const VTable = struct { now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, sleep: *const fn (?*anyopaque, Timeout) SleepError!void, + random: *const fn (?*anyopaque, buffer: []u8) void, + randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void, + netListenIp: *const fn (?*anyopaque, address: net.IpAddress, net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server, netAccept: *const fn (?*anyopaque, server: net.Socket.Handle) net.Server.AcceptError!net.Stream, netBindIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket, @@ -2242,3 +2246,36 @@ pub fn tryLockStderr(io: Io, buffer: []u8, terminal_mode: ?Terminal.Mode) Cancel pub fn unlockStderr(io: Io) void { return io.vtable.unlockStderr(io.userdata); } + +/// Obtains entropy from a cryptographically secure pseudo-random number +/// generator. +/// +/// The implementation *may* store RNG state in process memory and use it to +/// fill `buffer`. +/// +/// The randomness is seeded by `randomSecure`, or a less secure mechanism upon +/// failure. +/// +/// Threadsafe. +/// +/// See also `randomSecure`. +pub fn random(io: Io, buffer: []u8) void { + return io.vtable.random(io.userdata, buffer); +} + +pub const RandomSecureError = error{EntropyUnavailable} || Cancelable; + +/// Obtains cryptographically secure entropy from outside the process. +/// +/// Always makes a syscall, or otherwise avoids dependency on process memory, +/// in order to obtain fresh randomness. Does not rely on stored RNG state. +/// +/// Does not have any fallback mechanisms; returns `error.EntropyUnavailable` +/// if any problems occur. +/// +/// Threadsafe. +/// +/// See also `random`. +pub fn randomSecure(io: Io, buffer: []u8) RandomSecureError!void { + return io.vtable.randomSecure(io.userdata, buffer); +} diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 9ec1abe32e..67d1ec849c 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -936,10 +936,9 @@ pub fn deleteDirAbsolute(io: Io, absolute_path: []const u8) DeleteDirError!void pub const RenameError = error{ /// In WASI, this error may occur when the file descriptor does /// not hold the required rights to rename a resource by path relative to it. - /// - /// On Windows, this error may be returned instead of PathAlreadyExists when - /// renaming a directory over an existing directory. AccessDenied, + /// Attempted to replace a nonempty directory. + DirNotEmpty, PermissionDenied, FileBusy, DiskQuota, @@ -950,9 +949,8 @@ pub const RenameError = error{ NotDir, SystemResources, NoSpaceLeft, - PathAlreadyExists, ReadOnlyFileSystem, - RenameAcrossMountPoints, + CrossDevice, NoDevice, SharingViolation, PipeBusy, @@ -964,6 +962,7 @@ pub const RenameError = error{ /// intercepts file system operations and makes them significantly slower /// in addition to possibly failing with this error code. AntivirusInterference, + HardwareFailure, } || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Change the name or location of a file or directory. @@ -973,9 +972,9 @@ pub const RenameError = error{ /// Renaming a file over an existing directory or a directory over an existing /// file will fail with `error.IsDir` or `error.NotDir` /// -/// On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On WASI, both paths should be encoded as valid UTF-8. -/// On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// * On WASI, both paths should be encoded as valid UTF-8. +/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding. pub fn rename( old_dir: Dir, old_sub_path: []const u8, @@ -993,6 +992,39 @@ pub fn renameAbsolute(old_path: []const u8, new_path: []const u8, io: Io) Rename return io.vtable.dirRename(io.userdata, my_cwd, old_path, my_cwd, new_path); } +pub const RenamePreserveError = error{ + /// In WASI, this error may occur when the file descriptor does + /// not hold the required rights to rename a resource by path relative to it. + /// + /// On Windows, this error may be returned instead of PathAlreadyExists when + /// renaming a directory over an existing directory. + AccessDenied, + PathAlreadyExists, + /// Operating system or file system does not support atomic nonreplacing + /// rename. + OperationUnsupported, +} || RenameError; + +/// Change the name or location of a file or directory. +/// +/// If `new_sub_path` already exists, `error.PathAlreadyExists` will be returned. +/// +/// Renaming a file over an existing directory or a directory over an existing +/// file will fail with `error.IsDir` or `error.NotDir` +/// +/// * On Windows, both paths should be encoded as [WTF-8](https://wtf-8.codeberg.page/). +/// * On WASI, both paths should be encoded as valid UTF-8. +/// * On other platforms, both paths are an opaque sequence of bytes with no particular encoding. +pub fn renamePreserve( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, + io: Io, +) RenamePreserveError!void { + return io.vtable.dirRenamePreserve(io.userdata, old_dir, old_sub_path, new_dir, new_sub_path); +} + pub const HardLinkOptions = File.HardLinkOptions; pub const HardLinkError = File.HardLinkError; @@ -1098,8 +1130,10 @@ pub fn symLinkAtomic( const temp_path = temp_path_buf[0..temp_path_len]; + var random_integer: u64 = undefined; + while (true) { - const random_integer = std.crypto.random.int(u64); + io.random(@ptrCast(&random_integer)); temp_path[dirname.len + 1 ..][0..rand_len].* = std.fmt.hex(random_integer); if (dir.symLink(io, target_path, temp_path, flags)) { diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 389c8fb8b1..27833d4d8a 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -709,7 +709,7 @@ pub fn realPath(file: File, io: Io, out_buffer: []u8) RealPathError!usize { } pub const HardLinkOptions = struct { - follow_symlinks: bool = true, + follow_symlinks: bool = false, }; pub const HardLinkError = error{ @@ -726,7 +726,7 @@ pub const HardLinkError = error{ SystemResources, NoSpaceLeft, ReadOnlyFileSystem, - NotSameFileSystem, + CrossDevice, NotDir, } || Io.Cancelable || Dir.PathNameError || Io.UnexpectedError; diff --git a/lib/std/Io/File/Atomic.zig b/lib/std/Io/File/Atomic.zig index 440430e04c..1efacf9406 100644 --- a/lib/std/Io/File/Atomic.zig +++ b/lib/std/Io/File/Atomic.zig @@ -37,10 +37,14 @@ pub fn deinit(af: *Atomic, io: Io) void { af.* = undefined; } -pub const LinkError = Dir.HardLinkError; +pub const LinkError = File.HardLinkError || Dir.RenamePreserveError; /// Atomically materializes the file into place, failing with /// `error.PathAlreadyExists` if something already exists there. +/// +/// If this operation could not be done with an unnamed temporary file, the +/// named temporary file will be deleted in a following operation, which may +/// independently fail. The result of that operation is stored in `delete_err`. pub fn link(af: *Atomic, io: Io) LinkError!void { if (af.file_exists) { if (af.file_open) { @@ -48,8 +52,7 @@ pub fn link(af: *Atomic, io: Io) LinkError!void { af.file_open = false; } const tmp_sub_path = std.fmt.hex(af.file_basename_hex); - try af.dir.hardLink(&tmp_sub_path, af.dir, af.dest_sub_path, io, .{}); - af.dir.deleteFile(io, &tmp_sub_path) catch {}; + try af.dir.renamePreserve(&tmp_sub_path, af.dir, af.dest_sub_path, io); af.file_exists = false; } else { assert(af.file_open); diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index eb9b048cbd..065665532c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -65,6 +65,22 @@ argv0: Argv0, environ: Environ, null_file: NullFile = .{}, +random_file: RandomFile = .{}, + +csprng: Csprng = .{}, + +pub const Csprng = struct { + rng: std.Random.DefaultCsprng = .{ + .state = undefined, + .offset = std.math.maxInt(usize), + }, + + pub const seed_len = std.Random.DefaultCsprng.secret_seed_length; + + pub fn isInitialized(c: *const Csprng) bool { + return c.rng.offset != std.math.maxInt(usize); + } +}; pub const Argv0 = switch (native_os) { .openbsd, .haiku => struct { @@ -151,6 +167,15 @@ pub const NullFile = switch (native_os) { }, }; +pub const RandomFile = switch (native_os) { + .windows => NullFile, + else => if (use_dev_urandom) NullFile else struct { + fn deinit(this: @This()) void { + _ = this; + } + }, +}; + pub const Pid = if (native_os == .linux) enum(posix.pid_t) { unknown = 0, _, @@ -585,6 +610,8 @@ const Thread = struct { /// Always released when `Status.cancelation` is set to `.parked`. futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, + csprng: Csprng, + const Handle = Handle: { if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE; @@ -1285,6 +1312,7 @@ pub fn deinit(t: *Threaded) void { if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); } t.null_file.deinit(); + t.random_file.deinit(); t.* = undefined; } @@ -1313,6 +1341,7 @@ fn worker(t: *Threaded) void { }), .cancel_protection = .unblocked, .futex_waiter = undefined, + .csprng = .{}, }; Thread.current = &thread; @@ -1413,6 +1442,7 @@ pub fn io(t: *Threaded) Io { .dirDeleteFile = dirDeleteFile, .dirDeleteDir = dirDeleteDir, .dirRename = dirRename, + .dirRenamePreserve = dirRenamePreserve, .dirSymLink = dirSymLink, .dirReadLink = dirReadLink, .dirSetOwner = dirSetOwner, @@ -1466,6 +1496,9 @@ pub fn io(t: *Threaded) Io { .now = now, .sleep = sleep, + .random = random, + .randomSecure = randomSecure, + .netListenIp = switch (native_os) { .windows => netListenIpWindows, else => netListenIpPosix, @@ -1561,6 +1594,7 @@ pub fn ioBasic(t: *Threaded) Io { .dirDeleteFile = dirDeleteFile, .dirDeleteDir = dirDeleteDir, .dirRename = dirRename, + .dirRenamePreserve = dirRenamePreserve, .dirSymLink = dirSymLink, .dirReadLink = dirReadLink, .dirSetOwner = dirSetOwner, @@ -1614,6 +1648,9 @@ pub fn ioBasic(t: *Threaded) Io { .now = now, .sleep = sleep, + .random = random, + .randomSecure = randomSecure, + .netListenIp = netListenIpUnavailable, .netListenUnix = netListenUnixUnavailable, .netAccept = netAcceptUnavailable, @@ -1704,6 +1741,23 @@ const linux_copy_file_range_use_c = std.c.versionCheck(if (builtin.abi.isAndroid }); const linux_copy_file_range_sys = if (linux_copy_file_range_use_c) std.c else std.os.linux; +const statx_use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) + .{ .major = 30, .minor = 0, .patch = 0 } +else + .{ .major = 2, .minor = 28, .patch = 0 }); + +const use_libc_getrandom = std.c.versionCheck(if (builtin.abi.isAndroid()) .{ + .major = 28, + .minor = 0, + .patch = 0, +} else .{ + .major = 2, + .minor = 25, + .patch = 0, +}); + +const use_dev_urandom = @TypeOf(posix.system.getrandom) == void and native_os == .linux; + fn async( userdata: ?*anyopaque, result: []u8, @@ -2342,11 +2396,11 @@ fn dirCreateDirPath( status = .created; } else |err| switch (err) { error.PathAlreadyExists => { - // stat the file and return an error if it's not a directory - // this is important because otherwise a dangling symlink - // could cause an infinite loop - const fstat = try dirStatFile(t, dir, component.path, .{}); - if (fstat.kind != .directory) return error.NotDir; + // It is important to return an error if it's not a directory + // because otherwise a dangling symlink could cause an infinite + // loop. + const kind = try filePathKind(t, dir, component.path); + if (kind != .directory) return error.NotDir; }, error.FileNotFound => |e| { component = it.previous() orelse return e; @@ -2538,11 +2592,7 @@ fn dirStatFileLinux( const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; const linux = std.os.linux; - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) - .{ .major = 30, .minor = 0, .patch = 0 } - else - .{ .major = 2, .minor = 28, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; + const sys = if (statx_use_c) std.c else std.os.linux; var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -2691,6 +2741,35 @@ fn dirStatFileWasi( } } +fn filePathKind(t: *Threaded, dir: Dir, sub_path: []const u8) !File.Kind { + if (native_os == .linux) { + var path_buffer: [posix.PATH_MAX]u8 = undefined; + const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + + const linux = std.os.linux; + const syscall: Syscall = try .start(); + while (true) { + var statx = std.mem.zeroes(linux.Statx); + switch (linux.errno(linux.statx(dir.handle, sub_path_posix, 0, .{ .TYPE = true }, &statx))) { + .SUCCESS => { + syscall.finish(); + if (!statx.mask.TYPE) return error.Unexpected; + return statxKind(statx.mode); + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .NOMEM => return syscall.fail(error.SystemResources), + else => |err| return syscall.unexpectedErrno(err), + } + } + } + + const stat = try dirStatFile(t, dir, sub_path, .{}); + return stat.kind; +} + fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 { const t: *Threaded = @ptrCast(@alignCast(userdata)); @@ -2778,11 +2857,7 @@ fn fileStatLinux(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; const linux = std.os.linux; - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) - .{ .major = 30, .minor = 0, .patch = 0 } - else - .{ .major = 2, .minor = 28, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; + const sys = if (statx_use_c) std.c else std.os.linux; const syscall: Syscall = try .start(); while (true) { @@ -3450,7 +3525,7 @@ fn dirCreateFileAtomic( if (dest_dirname) |dirname| { // This has a nice side effect of preemptively triggering EISDIR or // ENOENT, avoiding the ambiguity below. - dir.createDirPath(t_io, dirname) catch |err| switch (err) { + if (options.make_path) dir.createDirPath(t_io, dirname) catch |err| switch (err) { // None of these make sense in this context. error.IsDir, error.Streaming, @@ -3553,8 +3628,9 @@ fn atomicFileInit( dir: Dir, close_dir_on_deinit: bool, ) Dir.CreateFileAtomicError!File.Atomic { + var random_integer: u64 = undefined; while (true) { - const random_integer = std.crypto.random.int(u64); + t_io.random(@ptrCast(&random_integer)); const tmp_sub_path = std.fmt.hex(random_integer); const file = dir.createFile(t_io, &tmp_sub_path, .{ .permissions = permissions, @@ -3636,10 +3712,12 @@ fn dirOpenFilePosix( }, }; + const mode: posix.mode_t = 0; + const fd: posix.fd_t = fd: { const syscall: Syscall = try .start(); while (true) { - const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0)); + const rc = openat_sym(dir.handle, sub_path_posix, os_flags, mode); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -4068,9 +4146,11 @@ fn dirOpenDirPosix( if (@hasField(posix.O, "PATH") and !options.iterate) flags.PATH = true; + const mode: posix.mode_t = 0; + const syscall: Syscall = try .start(); while (true) { - const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0)); + const rc = openat_sym(dir.handle, sub_path_posix, flags, mode); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -5169,12 +5249,21 @@ fn fileHardLink( var new_path_buffer: [posix.PATH_MAX]u8 = undefined; const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); - const flags: u32 = if (!options.follow_symlinks) - posix.AT.SYMLINK_NOFOLLOW | posix.AT.EMPTY_PATH + const flags: u32 = if (options.follow_symlinks) + posix.AT.SYMLINK_FOLLOW | posix.AT.EMPTY_PATH else posix.AT.EMPTY_PATH; - return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags); + return linkat(file.handle, "", new_dir.handle, new_sub_path_posix, flags) catch |err| switch (err) { + error.FileNotFound => { + if (options.follow_symlinks) return error.FileNotFound; + var proc_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; + const proc_path = std.fmt.bufPrintSentinel(&proc_buf, "/proc/self/fd/{d}", .{file.handle}, 0) catch + unreachable; + return linkat(posix.AT.FDCWD, proc_path, new_dir.handle, new_sub_path_posix, posix.AT.SYMLINK_FOLLOW); + }, + else => |e| return e, + }; } fn linkat( @@ -5205,7 +5294,7 @@ fn linkat( .NOTDIR => return syscall.fail(error.NotDir), .PERM => return syscall.fail(error.PermissionDenied), .ROFS => return syscall.fail(error.ReadOnlyFileSystem), - .XDEV => return syscall.fail(error.NotSameFileSystem), + .XDEV => return syscall.fail(error.CrossDevice), .ILSEQ => return syscall.fail(error.BadPathName), .FAULT => |err| return syscall.errnoBug(err), .INVAL => |err| return syscall.errnoBug(err), @@ -5614,15 +5703,44 @@ fn dirRenameWindows( new_dir: Dir, new_sub_path: []const u8, ) Dir.RenameError!void { - const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, true) catch |err| switch (err) { + error.PathAlreadyExists => return error.Unexpected, + error.OperationUnsupported => return error.Unexpected, + else => |e| return e, + }; +} +fn dirRenamePreserve( + userdata: ?*anyopaque, + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, +) Dir.RenamePreserveError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + if (is_windows) return dirRenameWindowsInner(old_dir, old_sub_path, new_dir, new_sub_path, false); + if (native_os == .linux) return dirRenamePreserveLinux(old_dir, old_sub_path, new_dir, new_sub_path); + // Make a hard link then delete the original. + try dirHardLink(t, old_dir, old_sub_path, new_dir, new_sub_path, .{ .follow_symlinks = false }); + const prev = swapCancelProtection(t, .blocked); + defer _ = swapCancelProtection(t, prev); + dirDeleteFile(t, old_dir, old_sub_path) catch {}; +} + +fn dirRenameWindowsInner( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, + replace_if_exists: bool, +) Dir.RenamePreserveError!void { + const w = windows; const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path); const old_path_w = old_path_w_buf.span(); const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path); const new_path_w = new_path_w_buf.span(); - const replace_if_exists = true; const src_fd = src_fd: { const syscall: Syscall = try .start(); @@ -5724,9 +5842,9 @@ fn dirRenameWindows( .ACCESS_DENIED => return error.AccessDenied, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, + .NOT_SAME_DEVICE => return error.CrossDevice, .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, + .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, .FILE_IS_A_DIRECTORY => return error.IsDir, .NOT_A_DIRECTORY => return error.NotDir, else => return w.unexpectedStatus(rc), @@ -5770,10 +5888,10 @@ fn dirRenameWasi( .NOTDIR => return error.NotDir, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, - .EXIST => return error.PathAlreadyExists, - .NOTEMPTY => return error.PathAlreadyExists, + .EXIST => return error.DirNotEmpty, + .NOTEMPTY => return error.DirNotEmpty, .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, + .XDEV => return error.CrossDevice, .NOTCAPABLE => return error.AccessDenied, .ILSEQ => return error.BadPathName, else => |err| return posix.unexpectedErrno(err), @@ -5799,9 +5917,105 @@ fn dirRenamePosix( const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); + return renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix); +} + +fn dirRenamePreserveLinux( + old_dir: Dir, + old_sub_path: []const u8, + new_dir: Dir, + new_sub_path: []const u8, +) Dir.RenamePreserveError!void { + const linux = std.os.linux; + + var old_path_buffer: [linux.PATH_MAX]u8 = undefined; + var new_path_buffer: [linux.PATH_MAX]u8 = undefined; + + const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); + const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); + + const syscall: Syscall = try .start(); + while (true) switch (linux.errno(linux.renameat2( + old_dir.handle, + old_sub_path_posix, + new_dir.handle, + new_sub_path_posix, + .{ .NOREPLACE = true }, + ))) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .PERM => return syscall.fail(error.PermissionDenied), + .BUSY => return syscall.fail(error.FileBusy), + .DQUOT => return syscall.fail(error.DiskQuota), + .ISDIR => return syscall.fail(error.IsDir), + .LOOP => return syscall.fail(error.SymLinkLoop), + .MLINK => return syscall.fail(error.LinkQuotaExceeded), + .NAMETOOLONG => return syscall.fail(error.NameTooLong), + .NOENT => return syscall.fail(error.FileNotFound), + .NOTDIR => return syscall.fail(error.NotDir), + .NOMEM => return syscall.fail(error.SystemResources), + .NOSPC => return syscall.fail(error.NoSpaceLeft), + .EXIST => return syscall.fail(error.PathAlreadyExists), + .NOTEMPTY => return syscall.fail(error.DirNotEmpty), + .ROFS => return syscall.fail(error.ReadOnlyFileSystem), + .XDEV => return syscall.fail(error.CrossDevice), + .ILSEQ => return syscall.fail(error.BadPathName), + .FAULT => |err| return syscall.errnoBug(err), + .INVAL => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; +} + +fn renameat( + old_dir: posix.fd_t, + old_sub_path: [*:0]const u8, + new_dir: posix.fd_t, + new_sub_path: [*:0]const u8, +) Dir.RenameError!void { + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .PERM => return syscall.fail(error.PermissionDenied), + .BUSY => return syscall.fail(error.FileBusy), + .DQUOT => return syscall.fail(error.DiskQuota), + .ISDIR => return syscall.fail(error.IsDir), + .IO => return syscall.fail(error.HardwareFailure), + .LOOP => return syscall.fail(error.SymLinkLoop), + .MLINK => return syscall.fail(error.LinkQuotaExceeded), + .NAMETOOLONG => return syscall.fail(error.NameTooLong), + .NOENT => return syscall.fail(error.FileNotFound), + .NOTDIR => return syscall.fail(error.NotDir), + .NOMEM => return syscall.fail(error.SystemResources), + .NOSPC => return syscall.fail(error.NoSpaceLeft), + .EXIST => return syscall.fail(error.DirNotEmpty), + .NOTEMPTY => return syscall.fail(error.DirNotEmpty), + .ROFS => return syscall.fail(error.ReadOnlyFileSystem), + .XDEV => return syscall.fail(error.CrossDevice), + .ILSEQ => return syscall.fail(error.BadPathName), + .FAULT => |err| return syscall.errnoBug(err), + .INVAL => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; +} + +fn renameatPreserve( + old_dir: posix.fd_t, + old_sub_path: [*:0]const u8, + new_dir: posix.fd_t, + new_sub_path: [*:0]const u8, +) Dir.RenameError!void { const syscall: Syscall = try .start(); while (true) { - switch (posix.errno(posix.system.renameat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix))) { + switch (posix.errno(posix.system.renameat(old_dir, old_sub_path, new_dir, new_sub_path))) { .SUCCESS => return syscall.finish(), .INTR => { try syscall.checkCancel(); @@ -5827,7 +6041,7 @@ fn dirRenamePosix( .EXIST => return error.PathAlreadyExists, .NOTEMPTY => return error.PathAlreadyExists, .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.RenameAcrossMountPoints, + .XDEV => return error.CrossDevice, .ILSEQ => return error.BadPathName, else => |err| return posix.unexpectedErrno(err), } @@ -6318,11 +6532,6 @@ fn fchmodatFallback( mode: posix.mode_t, ) Dir.SetFilePermissionsError!void { comptime assert(native_os == .linux); - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) - .{ .major = 30, .minor = 0, .patch = 0 } - else - .{ .major = 2, .minor = 28, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; // Fallback to changing permissions using procfs: // @@ -6369,6 +6578,7 @@ fn fchmodatFallback( defer posix.close(path_fd); const path_mode = mode: { + const sys = if (statx_use_c) std.c else std.os.linux; const syscall: Syscall = try .start(); while (true) { var statx = std.mem.zeroes(std.os.linux.Statx); @@ -7612,7 +7822,7 @@ fn dirHardLink( .NOTDIR => return error.NotDir, .PERM => return error.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, - .XDEV => return error.NotSameFileSystem, + .XDEV => return error.CrossDevice, .INVAL => |err| return errnoBug(err), .ILSEQ => return error.BadPathName, else => |err| return posix.unexpectedErrno(err), @@ -7628,7 +7838,7 @@ fn dirHardLink( const old_sub_path_posix = try pathToPosix(old_sub_path, &old_path_buffer); const new_sub_path_posix = try pathToPosix(new_sub_path, &new_path_buffer); - const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + const flags: u32 = if (options.follow_symlinks) posix.AT.SYMLINK_FOLLOW else 0; return linkat(old_dir.handle, old_sub_path_posix, new_dir.handle, new_sub_path_posix, flags); } @@ -12268,16 +12478,7 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { .nlink = stx.nlink, .size = stx.size, .permissions = .fromMode(stx.mode), - .kind = switch (stx.mode & std.os.linux.S.IFMT) { - std.os.linux.S.IFDIR => .directory, - std.os.linux.S.IFCHR => .character_device, - std.os.linux.S.IFBLK => .block_device, - std.os.linux.S.IFREG => .file, - std.os.linux.S.IFIFO => .named_pipe, - std.os.linux.S.IFLNK => .sym_link, - std.os.linux.S.IFSOCK => .unix_domain_socket, - else => .unknown, - }, + .kind = statxKind(stx.mode), .atime = if (!stx.mask.ATIME) null else .{ .nanoseconds = @intCast(@as(i128, stx.atime.sec) * std.time.ns_per_s + stx.atime.nsec), }, @@ -12286,6 +12487,19 @@ fn statFromLinux(stx: *const std.os.linux.Statx) Io.UnexpectedError!File.Stat { }; } +fn statxKind(stx_mode: u16) File.Kind { + return switch (stx_mode & std.os.linux.S.IFMT) { + std.os.linux.S.IFDIR => .directory, + std.os.linux.S.IFCHR => .character_device, + std.os.linux.S.IFBLK => .block_device, + std.os.linux.S.IFREG => .file, + std.os.linux.S.IFIFO => .named_pipe, + std.os.linux.S.IFLNK => .sym_link, + std.os.linux.S.IFSOCK => .unix_domain_socket, + else => .unknown, + }; +} + fn statFromPosix(st: *const posix.Stat) File.Stat { const atime = st.atime(); const mtime = st.mtime(); @@ -12441,7 +12655,8 @@ fn lookupDns( for (family_records) |fr| { if (options.family != fr.af) { - const entropy = std.crypto.random.array(u8, 2); + var entropy: [2]u8 = undefined; + random(t, &entropy); const len = writeResolutionQuery(&query_buffers[nq], 0, lookup_canon_name, 1, fr.rr, entropy); queries_buffer[nq] = query_buffers[nq][0..len]; nq += 1; @@ -13853,6 +14068,62 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }; } +fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { + { + t.mutex.lock(); + defer t.mutex.unlock(); + if (t.random_file.handle) |handle| return handle; + } + + const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }; + + var nt_name: windows.UNICODE_STRING = .{ + .Length = device_path.len * 2, + .MaximumLength = 0, + .Buffer = @constCast(&device_path), + }; + var fresh_handle: windows.HANDLE = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtOpenFile( + &fresh_handle, + .{ + .STANDARD = .{ .SYNCHRONIZE = true }, + .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, + }, + &.{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = null, + .ObjectName = &nt_name, + .Attributes = .{}, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }, + &io_status_block, + .VALID_FLAGS, + .{ .IO = .SYNCHRONOUS_NONALERT }, + )) { + .SUCCESS => { + syscall.finish(); + t.mutex.lock(); // Another thread might have won the race. + defer t.mutex.unlock(); + if (t.random_file.handle) |prev_handle| { + _ = windows.ntdll.NtClose(fresh_handle); + return prev_handle; + } else { + t.random_file.handle = fresh_handle; + return fresh_handle; + } + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.EntropyUnavailable), // Observed on wine 10.0 + else => return syscall.fail(error.EntropyUnavailable), + }; +} + fn getNulHandle(t: *Threaded) !windows.HANDLE { { t.mutex.lock(); @@ -14935,6 +15206,305 @@ pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { return @field(t.environ.string, name); } +fn random(userdata: ?*anyopaque, buffer: []u8) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const thread = Thread.current orelse return randomMainThread(t, buffer); + if (!thread.csprng.isInitialized()) { + @branchHint(.unlikely); + var seed: [Csprng.seed_len]u8 = undefined; + randomMainThread(t, &seed); + thread.csprng.rng = .init(seed); + } + thread.csprng.rng.fill(buffer); +} + +fn randomMainThread(t: *Threaded, buffer: []u8) void { + t.mutex.lock(); + defer t.mutex.unlock(); + + if (!t.csprng.isInitialized()) { + @branchHint(.unlikely); + var seed: [Csprng.seed_len]u8 = undefined; + { + t.mutex.unlock(); + defer t.mutex.lock(); + + const prev = swapCancelProtection(t, .blocked); + defer _ = swapCancelProtection(t, prev); + + randomSecure(t, &seed) catch |err| switch (err) { + error.Canceled => unreachable, + error.EntropyUnavailable => { + @memset(&seed, 0); + const aslr_addr = @intFromPtr(t); + std.mem.writeInt(usize, seed[seed.len - @sizeOf(usize) ..][0..@sizeOf(usize)], aslr_addr, .native); + switch (native_os) { + .windows => fallbackSeedWindows(&seed), + .wasi => if (builtin.link_libc) fallbackSeedPosix(&seed) else fallbackSeedWasi(&seed), + else => fallbackSeedPosix(&seed), + } + }, + }; + } + t.csprng.rng = .init(seed); + } + + t.csprng.rng.fill(buffer); +} + +fn fallbackSeedPosix(seed: *[Csprng.seed_len]u8) void { + std.mem.writeInt(posix.pid_t, seed[0..@sizeOf(posix.pid_t)], posix.system.getpid(), .native); + const i_1 = @sizeOf(posix.pid_t); + + var ts: posix.timespec = undefined; + const Sec = @TypeOf(ts.sec); + const Nsec = @TypeOf(ts.nsec); + const i_2 = i_1 + @sizeOf(Sec); + switch (posix.errno(posix.system.clock_gettime(.REALTIME, &ts))) { + .SUCCESS => { + std.mem.writeInt(Sec, seed[i_1..][0..@sizeOf(Sec)], ts.sec, .native); + std.mem.writeInt(Nsec, seed[i_2..][0..@sizeOf(Nsec)], ts.nsec, .native); + }, + else => {}, + } +} + +fn fallbackSeedWindows(seed: *[Csprng.seed_len]u8) void { + var pc: windows.LARGE_INTEGER = undefined; + _ = windows.ntdll.RtlQueryPerformanceCounter(&pc); + std.mem.writeInt(windows.LARGE_INTEGER, seed[0..@sizeOf(windows.LARGE_INTEGER)], pc, .native); +} + +fn fallbackSeedWasi(seed: *[Csprng.seed_len]u8) void { + var ts: std.os.wasi.timestamp_t = undefined; + if (std.os.wasi.clock_time_get(.REALTIME, 1, &ts) == .SUCCESS) { + std.mem.writeInt(std.os.wasi.timestamp_t, seed[0..@sizeOf(std.os.wasi.timestamp_t)], ts, .native); + } +} + +fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + + if (is_windows) { + if (buffer.len == 0) return; + // ProcessPrng from bcryptprimitives.dll has the following properties: + // * introduces a dependency on bcryptprimitives.dll, which apparently + // runs a test suite every time it is loaded + // * heap allocates a 48-byte buffer, handling failure by returning NO_MEMORY in a BOOL + // despite the function being documented to always return TRUE + // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG + // Therefore, that function is avoided in favor of using the device directly. + const cng_device = try getCngHandle(t); + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var i: usize = 0; + const syscall: Syscall = try .start(); + while (true) { + const remaining_len = std.math.lossyCast(u32, buffer.len - i); + switch (windows.ntdll.NtDeviceIoControlFile( + cng_device, + null, + null, + null, + &io_status_block, + windows.IOCTL.KSEC.GEN_RANDOM, + null, + 0, + buffer[i..].ptr, + remaining_len, + )) { + .SUCCESS => { + i += remaining_len; + if (buffer.len - i == 0) { + return syscall.finish(); + } else { + try syscall.checkCancel(); + continue; + } + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + } + } + } + + if (builtin.link_libc and @TypeOf(posix.system.arc4random_buf) != void) { + if (buffer.len == 0) return; + posix.system.arc4random_buf(buffer.ptr, buffer.len); + return; + } + + if (native_os == .wasi) { + if (buffer.len == 0) return; + const syscall: Syscall = try .start(); + while (true) switch (std.os.wasi.random_get(buffer.ptr, buffer.len)) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + }; + } + + if (@TypeOf(posix.system.getrandom) != void) { + const getrandom = if (use_libc_getrandom) std.c.getrandom else std.os.linux.getrandom; + var i: usize = 0; + const syscall: Syscall = try .start(); + while (buffer.len - i != 0) { + const buf = buffer[i..]; + const rc = getrandom(buf.ptr, buf.len, 0); + switch (posix.errno(rc)) { + .SUCCESS => { + syscall.finish(); + const n: usize = @intCast(rc); + i += n; + continue; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + } + } + return; + } + + if (native_os == .emscripten) { + if (buffer.len == 0) return; + const err = posix.errno(std.c.getentropy(buffer.ptr, buffer.len)); + switch (err) { + .SUCCESS => return, + else => return error.EntropyUnavailable, + } + } + + if (native_os == .linux) { + comptime assert(use_dev_urandom); + const urandom_fd = try getRandomFd(t); + + var i: usize = 0; + while (buffer.len - i != 0) { + const syscall: Syscall = try .start(); + const rc = posix.system.read(urandom_fd, buffer[i..].ptr, buffer.len - i); + switch (posix.errno(rc)) { + .SUCCESS => { + syscall.finish(); + const n: usize = @intCast(rc); + if (n == 0) return error.EntropyUnavailable; + i += n; + continue; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + } + } + } + + return error.EntropyUnavailable; +} + +fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { + { + t.mutex.lock(); + defer t.mutex.unlock(); + + if (t.random_file.fd == -2) return error.EntropyUnavailable; + if (t.random_file.fd != -1) return t.random_file.fd; + } + + const mode: posix.mode_t = 0; + + const fd: posix.fd_t = fd: { + const syscall: Syscall = try .start(); + while (true) { + const rc = openat_sym(posix.AT.FDCWD, "/dev/urandom", .{ + .ACCMODE = .RDONLY, + .CLOEXEC = true, + }, mode); + switch (posix.errno(rc)) { + .SUCCESS => { + syscall.finish(); + break :fd @intCast(rc); + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + } + } + }; + errdefer posix.close(fd); + + switch (native_os) { + .linux => { + const sys = if (statx_use_c) std.c else std.os.linux; + const syscall: Syscall = try .start(); + while (true) { + var statx = std.mem.zeroes(std.os.linux.Statx); + switch (sys.errno(sys.statx(fd, "", std.os.linux.AT.EMPTY_PATH, .{ .TYPE = true }, &statx))) { + .SUCCESS => { + syscall.finish(); + if (!statx.mask.TYPE) return error.EntropyUnavailable; + t.mutex.lock(); // Another thread might have won the race. + defer t.mutex.unlock(); + if (t.random_file.fd >= 0) { + posix.close(fd); + return t.random_file.fd; + } else if (!posix.S.ISCHR(statx.mode)) { + t.random_file.fd = -2; + return error.EntropyUnavailable; + } else { + t.random_file.fd = fd; + return fd; + } + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + } + } + }, + else => { + const syscall: Syscall = try .start(); + while (true) { + var stat = std.mem.zeroes(posix.Stat); + switch (posix.errno(fstat_sym(fd, &stat))) { + .SUCCESS => { + syscall.finish(); + t.mutex.lock(); // Another thread might have won the race. + defer t.mutex.unlock(); + if (t.random_file.fd >= 0) { + posix.close(fd); + return t.random_file.fd; + } else if (!posix.S.ISCHR(stat.mode)) { + t.random_file.fd = -2; + return error.EntropyUnavailable; + } else { + t.random_file.fd = fd; + return fd; + } + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => return syscall.fail(error.EntropyUnavailable), + } + } + }, + } +} + test { _ = @import("Threaded/test.zig"); } diff --git a/lib/std/Io/net/test.zig b/lib/std/Io/net/test.zig index 45c26f9540..75f72d3aff 100644 --- a/lib/std/Io/net/test.zig +++ b/lib/std/Io/net/test.zig @@ -275,7 +275,7 @@ test "listen on a unix socket, send bytes, receive bytes" { const io = testing.io; - const socket_path = try generateFileName("socket.unix"); + const socket_path = try generateFileName(io, "socket.unix"); defer testing.allocator.free(socket_path); const socket_addr = try net.UnixAddress.init(socket_path); @@ -308,11 +308,11 @@ test "listen on a unix socket, send bytes, receive bytes" { try testing.expectEqualSlices(u8, "Hello world!", buf[0..n]); } -fn generateFileName(base_name: []const u8) ![]const u8 { +fn generateFileName(io: Io, base_name: []const u8) ![]const u8 { const random_bytes_count = 12; const sub_path_len = comptime std.fs.base64_encoder.calcSize(random_bytes_count); var random_bytes: [12]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var sub_path: [sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); return std.fmt.allocPrint(testing.allocator, "{s}-{s}", .{ sub_path[0..], base_name }); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index a467ee1a93..17b86af234 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -564,3 +564,29 @@ test "tasks spawned in group after Group.cancel are canceled" { try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up try group.concurrent(io, global.waitThenSpawn, .{ io, &group }); } + +test "random" { + const io = testing.io; + + var a: u64 = undefined; + var b: u64 = undefined; + var c: u64 = undefined; + + io.random(@ptrCast(&a)); + io.random(@ptrCast(&b)); + io.random(@ptrCast(&c)); + + try std.testing.expect(a ^ b ^ c != 0); +} + +test "randomSecure" { + const io = testing.io; + + var buf_a: [50]u8 = undefined; + var buf_b: [50]u8 = undefined; + try io.randomSecure(&buf_a); + try io.randomSecure(&buf_b); + // If this test fails the chance is significantly higher that there is a bug than + // that two sets of 50 bytes were equal. + try expect(!mem.eql(u8, &buf_a, &buf_b)); +} diff --git a/lib/std/Random.zig b/lib/std/Random.zig index b90c8e3958..218ddac1d6 100644 --- a/lib/std/Random.zig +++ b/lib/std/Random.zig @@ -1,15 +1,13 @@ //! The engines provided here should be initialized from an external source. -//! For a thread-local cryptographically secure pseudo random number generator, -//! use `std.crypto.random`. //! Be sure to use a CSPRNG when required, otherwise using a normal PRNG will //! be faster and use substantially less stack space. +const Random = @This(); const std = @import("std.zig"); const math = std.math; const mem = std.mem; const assert = std.debug.assert; const maxInt = std.math.maxInt; -const Random = @This(); /// Fast unbiased random numbers. pub const DefaultPrng = Xoshiro256; @@ -35,6 +33,22 @@ pub const ziggurat = @import("Random/ziggurat.zig"); ptr: *anyopaque, fillFn: *const fn (ptr: *anyopaque, buf: []u8) void, +pub const IoSource = struct { + io: std.Io, + + pub fn interface(this: *const @This()) std.Random { + return .{ + .ptr = @constCast(this), + .fillFn = fill, + }; + } + + fn fill(ptr: *anyopaque, buffer: []u8) void { + const this: *const @This() = @ptrCast(@alignCast(ptr)); + this.io.random(buffer); + } +}; + pub fn init(pointer: anytype, comptime fillFn: fn (ptr: @TypeOf(pointer), buf: []u8) void) Random { const Ptr = @TypeOf(pointer); assert(@typeInfo(Ptr) == .pointer); // Must be a pointer diff --git a/lib/std/Random/ChaCha.zig b/lib/std/Random/ChaCha.zig index 5783ee4152..9c8f548ecf 100644 --- a/lib/std/Random/ChaCha.zig +++ b/lib/std/Random/ChaCha.zig @@ -20,7 +20,7 @@ pub const secret_seed_length = Cipher.key_length; /// The seed must be uniform, secret and `secret_seed_length` bytes long. pub fn init(secret_seed: [secret_seed_length]u8) Self { - var self = Self{ .state = undefined, .offset = 0 }; + var self: Self = .{ .state = undefined, .offset = 0 }; Cipher.stream(&self.state, 0, secret_seed, nonce); return self; } diff --git a/lib/std/Random/test.zig b/lib/std/Random/test.zig index 8ceacbc934..3db56df655 100644 --- a/lib/std/Random/test.zig +++ b/lib/std/Random/test.zig @@ -436,8 +436,9 @@ fn testRangeBias(r: Random, start: i8, end: i8, biased: bool) !void { } test "CSPRNG" { + const io = std.testing.io; var secret_seed: [DefaultCsprng.secret_seed_length]u8 = undefined; - std.crypto.random.bytes(&secret_seed); + io.random(&secret_seed); var csprng = DefaultCsprng.init(secret_seed); const random = csprng.random(); const a = random.int(u64); diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index bbcba6a02b..7833a31237 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -235,9 +235,6 @@ pub const nacl = struct { /// Finite-field arithmetic. pub const ff = @import("crypto/ff.zig"); -/// This is a thread-local, cryptographically secure pseudo random number generator. -pub const random = @import("crypto/tlcsprng.zig").interface; - /// Encoding and decoding pub const codecs = @import("crypto/codecs.zig"); @@ -306,6 +303,9 @@ test { _ = dh.X25519; _ = kem.kyber_d00; + _ = kem.hybrid; + _ = kem.kyber_d00; + _ = kem.ml_kem; _ = ecc.Curve25519; _ = ecc.Edwards25519; @@ -343,6 +343,7 @@ test { _ = sign.Ed25519; _ = sign.ecdsa; + _ = sign.mldsa; _ = stream.chacha.ChaCha20IETF; _ = stream.chacha.ChaCha12IETF; @@ -364,20 +365,12 @@ test { _ = secureZero; _ = timing_safe; _ = ff; - _ = random; _ = errors; _ = tls; _ = Certificate; _ = codecs; } -test "CSPRNG" { - const a = random.int(u64); - const b = random.int(u64); - const c = random.int(u64); - try std.testing.expect(a ^ b ^ c != 0); -} - test "issue #4532: no index out of bounds" { const types = [_]type{ hash.Md5, diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index a78e1aa3ab..b6f582839e 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -333,12 +333,10 @@ pub const Ed25519 = struct { } /// Generate a new, random key pair. - /// - /// `crypto.random.bytes` must be supported by the target. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; @@ -389,18 +387,21 @@ pub const Ed25519 = struct { /// Create a Signer, that can be used for incremental signing. /// Note that the signature is not deterministic. - /// The noise parameter, if set, should be something unique for each message, - /// such as a random nonce, or a counter. - pub fn signer(key_pair: KeyPair, noise: ?[noise_length]u8) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer { + pub fn signer( + key_pair: KeyPair, + /// If set, should be something unique for each message, such as a + /// random nonce, or a counter. + noise: ?[noise_length]u8, + /// Filled with cryptographically secure randomness. + entropy: *const [noise_length]u8, + ) (IdentityElementError || KeyMismatchError || NonCanonicalError || WeakPublicKeyError)!Signer { if (!mem.eql(u8, &key_pair.secret_key.publicKeyBytes(), &key_pair.public_key.toBytes())) { return error.KeyMismatch; } const scalar_and_prefix = key_pair.secret_key.scalarAndPrefix(); var h = Sha512.init(.{}); h.update(&scalar_and_prefix.prefix); - var noise2: [noise_length]u8 = undefined; - crypto.random.bytes(&noise2); - h.update(&noise2); + h.update(entropy); if (noise) |*z| { h.update(z); } @@ -420,7 +421,7 @@ pub const Ed25519 = struct { }; /// Verify several signatures in a single operation, much faster than verifying signatures one-by-one - pub fn verifyBatch(comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void { + pub fn verifyBatch(io: std.Io, comptime count: usize, signature_batch: [count]BatchElement) (SignatureVerificationError || IdentityElementError || WeakPublicKeyError || EncodingError || NonCanonicalError)!void { var r_batch: [count]CompressedScalar = undefined; var s_batch: [count]CompressedScalar = undefined; var a_batch: [count]Curve = undefined; @@ -454,7 +455,7 @@ pub const Ed25519 = struct { var z_batch: [count]Curve.scalar.CompressedScalar = undefined; for (&z_batch) |*z| { - crypto.random.bytes(z[0..16]); + io.random(z[0..16]); @memset(z[16..], 0); } @@ -587,12 +588,14 @@ test "signature" { } test "batch verification" { + const io = std.testing.io; + for (0..16) |_| { - const key_pair = Ed25519.KeyPair.generate(); + const key_pair = Ed25519.KeyPair.generate(io); var msg1: [32]u8 = undefined; var msg2: [32]u8 = undefined; - crypto.random.bytes(&msg1); - crypto.random.bytes(&msg2); + io.random(&msg1); + io.random(&msg2); const sig1 = try key_pair.sign(&msg1, null); const sig2 = try key_pair.sign(&msg2, null); var signature_batch = [_]Ed25519.BatchElement{ @@ -607,10 +610,10 @@ test "batch verification" { .public_key = key_pair.public_key, }, }; - try Ed25519.verifyBatch(2, signature_batch); + try Ed25519.verifyBatch(io, 2, signature_batch); signature_batch[1].sig = sig1; - try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(signature_batch.len, signature_batch)); + try std.testing.expectError(error.SignatureVerificationFailed, Ed25519.verifyBatch(io, signature_batch.len, signature_batch)); } } @@ -718,14 +721,15 @@ test "test vectors" { } test "with blind keys" { + const io = std.testing.io; const BlindKeyPair = Ed25519.key_blinding.BlindKeyPair; // Create a standard Ed25519 key pair - const kp = Ed25519.KeyPair.generate(); + const kp = Ed25519.KeyPair.generate(io); // Create a random blinding seed var blind: [32]u8 = undefined; - crypto.random.bytes(&blind); + io.random(&blind); // Blind the key pair const blind_kp = try BlindKeyPair.init(kp, blind, "ctx"); @@ -741,9 +745,12 @@ test "with blind keys" { } test "signatures with streaming" { - const kp = Ed25519.KeyPair.generate(); + const io = std.testing.io; + const kp = Ed25519.KeyPair.generate(io); - var signer = try kp.signer(null); + var entropy: [Ed25519.noise_length]u8 = undefined; + io.random(&entropy); + var signer = try kp.signer(null, &entropy); signer.update("mes"); signer.update("sage"); const sig = signer.finalize(); @@ -757,7 +764,8 @@ test "signatures with streaming" { } test "key pair from secret key" { - const kp = Ed25519.KeyPair.generate(); + const io = std.testing.io; + const kp = Ed25519.KeyPair.generate(io); const kp2 = try Ed25519.KeyPair.fromSecretKey(kp.secret_key); try std.testing.expectEqualSlices(u8, &kp.secret_key.toBytes(), &kp2.secret_key.toBytes()); try std.testing.expectEqualSlices(u8, &kp.public_key.toBytes(), &kp2.public_key.toBytes()); @@ -788,7 +796,8 @@ test "cofactored vs cofactorless verification" { } test "regular signature verifies with both verify and verifyStrict" { - const kp = Ed25519.KeyPair.generate(); + const io = std.testing.io; + const kp = Ed25519.KeyPair.generate(io); const msg = "test message"; const sig = try kp.sign(msg, null); try sig.verify(msg, kp.public_key); diff --git a/lib/std/crypto/25519/edwards25519.zig b/lib/std/crypto/25519/edwards25519.zig index 0bce0139f6..0672474eab 100644 --- a/lib/std/crypto/25519/edwards25519.zig +++ b/lib/std/crypto/25519/edwards25519.zig @@ -575,10 +575,11 @@ test "packing/unpacking" { } test "point addition/subtraction" { + const io = std.testing.io; var s1: [32]u8 = undefined; var s2: [32]u8 = undefined; - crypto.random.bytes(&s1); - crypto.random.bytes(&s2); + io.random(&s1); + io.random(&s2); const p = try Edwards25519.basePoint.clampedMul(s1); const q = try Edwards25519.basePoint.clampedMul(s2); const r = p.add(q).add(q).sub(q).sub(q); @@ -622,9 +623,10 @@ test "implicit reduction of invalid scalars" { } test "subgroup check" { + const io = std.testing.io; for (0..100) |_| { var p = Edwards25519.basePoint; - const s = Edwards25519.scalar.random(); + const s = Edwards25519.scalar.random(io); p = try p.mulPublic(s); try p.rejectUnexpectedSubgroup(); } diff --git a/lib/std/crypto/25519/scalar.zig b/lib/std/crypto/25519/scalar.zig index 59a4b41f7a..97887ee004 100644 --- a/lib/std/crypto/25519/scalar.zig +++ b/lib/std/crypto/25519/scalar.zig @@ -101,8 +101,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar) CompressedScalar { } /// Return a random scalar < L -pub fn random() CompressedScalar { - return Scalar.random().toBytes(); +pub fn random(io: std.Io) CompressedScalar { + return Scalar.random(io).toBytes(); } /// A scalar in unpacked representation @@ -560,10 +560,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [64]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes64(s); if (!n.isZero()) { return n; @@ -879,8 +879,9 @@ test "scalar field inversion" { } test "random scalar" { - const s1 = random(); - const s2 = random(); + const io = std.testing.io; + const s1 = random(io); + const s2 = random(io); try std.testing.expect(!mem.eql(u8, &s1, &s2)); } diff --git a/lib/std/crypto/25519/x25519.zig b/lib/std/crypto/25519/x25519.zig index ee40f34940..dc49d3e9a2 100644 --- a/lib/std/crypto/25519/x25519.zig +++ b/lib/std/crypto/25519/x25519.zig @@ -41,10 +41,10 @@ pub const X25519 = struct { } /// Generate a new, random key pair. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; diff --git a/lib/std/crypto/argon2.zig b/lib/std/crypto/argon2.zig index 0fd76e1282..f6bf85782d 100644 --- a/lib/std/crypto/argon2.zig +++ b/lib/std/crypto/argon2.zig @@ -533,7 +533,7 @@ const PhcFormatHasher = struct { if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding; var salt: [default_salt_len]u8 = undefined; - crypto.random.bytes(&salt); + io.random(&salt); var hash: [default_hash_len]u8 = undefined; try kdf(allocator, &hash, password, &salt, params, mode, io); diff --git a/lib/std/crypto/bcrypt.zig b/lib/std/crypto/bcrypt.zig index 62fc4fd4cc..738d4060df 100644 --- a/lib/std/crypto/bcrypt.zig +++ b/lib/std/crypto/bcrypt.zig @@ -17,7 +17,7 @@ const HasherError = pwhash.HasherError; const EncodingError = phc_format.Error; const Error = pwhash.Error; -const salt_length: usize = 16; +pub const salt_length: usize = 16; const salt_str_length: usize = 22; const ct_str_length: usize = 31; const ct_length: usize = 24; @@ -426,7 +426,7 @@ pub const Params = struct { fn bcryptWithTruncation( password: []const u8, - salt: [salt_length]u8, + salt: *const [salt_length]u8, params: Params, ) [dk_length]u8 { var state = State{}; @@ -435,13 +435,13 @@ fn bcryptWithTruncation( @memcpy(password_buf[0..trimmed_len], password[0..trimmed_len]); password_buf[trimmed_len] = 0; const passwordZ = password_buf[0 .. trimmed_len + 1]; - state.expand(salt[0..], passwordZ); + state.expand(salt, passwordZ); const rounds: u64 = @as(u64, 1) << params.rounds_log; var k: u64 = 0; while (k < rounds) : (k += 1) { state.expand0(passwordZ); - state.expand0(salt[0..]); + state.expand0(salt); } crypto.secureZero(u8, &password_buf); @@ -467,7 +467,7 @@ fn bcryptWithTruncation( /// For key derivation, use `bcrypt.pbkdf()` or `bcrypt.opensshKdf()` instead. pub fn bcrypt( password: []const u8, - salt: [salt_length]u8, + salt: *const [salt_length]u8, params: Params, ) [dk_length]u8 { if (password.len <= 72 or params.silently_truncate_password) { @@ -475,7 +475,7 @@ pub fn bcrypt( } var pre_hash: [HmacSha512.mac_length]u8 = undefined; - HmacSha512.create(&pre_hash, password, &salt); + HmacSha512.create(&pre_hash, password, salt); const Encoder = crypt_format.Codec.Encoder; var pre_hash_b64: [Encoder.calcSize(pre_hash.len)]u8 = undefined; @@ -623,16 +623,16 @@ const crypt_format = struct { fn strHashInternal( password: []const u8, - salt: [salt_length]u8, + salt: *const [salt_length]u8, params: Params, ) [hash_length]u8 { var dk = bcrypt(password, salt, params); var salt_str: [salt_str_length]u8 = undefined; - _ = Codec.Encoder.encode(salt_str[0..], salt[0..]); + _ = Codec.Encoder.encode(&salt_str, salt); var ct_str: [ct_str_length]u8 = undefined; - _ = Codec.Encoder.encode(ct_str[0..], dk[0..]); + _ = Codec.Encoder.encode(&ct_str, dk[0..]); var s_buf: [hash_length]u8 = undefined; const s = fmt.bufPrint( @@ -657,21 +657,20 @@ const PhcFormatHasher = struct { hash: BinValue(dk_length), }; - /// Return a non-deterministic hash of the password encoded as a PHC-format string + /// Return a non-deterministic hash of the password encoded as a PHC-format string. fn create( password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt: *const [salt_length]u8, ) HasherError![]const u8 { - var salt: [salt_length]u8 = undefined; - crypto.random.bytes(&salt); - const hash = bcrypt(password, salt, params); return phc_format.serialize(HashResult{ .alg_id = alg_id, .r = params.rounds_log, - .salt = try BinValue(salt_length).fromSlice(&salt), + .salt = try BinValue(salt_length).fromSlice(salt), .hash = try BinValue(dk_length).fromSlice(&hash), }, buf); } @@ -688,11 +687,11 @@ const PhcFormatHasher = struct { if (hash_result.salt.len != salt_length or hash_result.hash.len != dk_length) return HasherError.InvalidEncoding; - const params = Params{ + const params: Params = .{ .rounds_log = hash_result.r, .silently_truncate_password = silently_truncate_password, }; - const hash = bcrypt(password, hash_result.salt.buf, params); + const hash = bcrypt(password, &hash_result.salt.buf, params); const expected_hash = hash_result.hash.constSlice(); if (!mem.eql(u8, &hash, expected_hash)) return HasherError.PasswordVerificationFailed; @@ -709,12 +708,11 @@ const CryptFormatHasher = struct { password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt: *const [salt_length]u8, ) HasherError![]const u8 { if (buf.len < pwhash_str_length) return HasherError.NoSpaceLeft; - var salt: [salt_length]u8 = undefined; - crypto.random.bytes(&salt); - const hash = crypt_format.strHashInternal(password, salt, params); @memcpy(buf[0..hash.len], &hash); @@ -736,9 +734,9 @@ const CryptFormatHasher = struct { const salt_str = str[7..][0..salt_str_length]; var salt: [salt_length]u8 = undefined; - crypt_format.Codec.Decoder.decode(salt[0..], salt_str[0..]) catch return HasherError.InvalidEncoding; + crypt_format.Codec.Decoder.decode(&salt, salt_str) catch return HasherError.InvalidEncoding; - const wanted_s = crypt_format.strHashInternal(password, salt, .{ + const wanted_s = crypt_format.strHashInternal(password, &salt, .{ .rounds_log = rounds_log, .silently_truncate_password = silently_truncate_password, }); @@ -756,21 +754,28 @@ pub const HashOptions = struct { encoding: pwhash.Encoding, }; -/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key stretching function. -/// bcrypt is a computationally expensive and cache-hard function, explicitly designed to slow down exhaustive searches. +/// Compute a hash of a password using 2^rounds_log rounds of the bcrypt key +/// stretching function. /// -/// The function returns a string that includes all the parameters required for verification. +/// bcrypt is a computationally expensive and cache-hard function, explicitly +/// designed to slow down exhaustive searches. /// -/// IMPORTANT: by design, bcrypt silently truncates passwords to 72 bytes. -/// If this is an issue for your application, set the `silently_truncate_password` option to `false`. +/// The function returns a string that includes all the parameters required for +/// verification. +/// +/// By design, bcrypt silently truncates passwords to 72 bytes. If this is an +/// issue for your application, set the `silently_truncate_password` option to +/// `false`. pub fn strHash( password: []const u8, options: HashOptions, out: []u8, + /// Filled with cryptographically secure entropy. + salt: *const [salt_length]u8, ) Error![]const u8 { switch (options.encoding) { - .phc => return PhcFormatHasher.create(password, options.params, out), - .crypt => return CryptFormatHasher.create(password, options.params, out), + .phc => return PhcFormatHasher.create(password, options.params, out, salt), + .crypt => return CryptFormatHasher.create(password, options.params, out, salt), } } @@ -796,8 +801,9 @@ pub fn strVerify( } test "bcrypt codec" { + const io = testing.io; var salt: [salt_length]u8 = undefined; - crypto.random.bytes(&salt); + io.random(&salt); var salt_str: [salt_str_length]u8 = undefined; _ = crypt_format.Codec.Encoder.encode(salt_str[0..], salt[0..]); var salt2: [salt_length]u8 = undefined; @@ -806,14 +812,20 @@ test "bcrypt codec" { } test "bcrypt crypt format" { - var hash_options = HashOptions{ + const io = testing.io; + + var hash_options: HashOptions = .{ .params = .{ .rounds_log = 5, .silently_truncate_password = false }, .encoding = .crypt, }; - var verify_options = VerifyOptions{ .silently_truncate_password = false }; + var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; var buf: [hash_length]u8 = undefined; - const s = try strHash("password", hash_options, &buf); + const s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password", hash_options, &buf, &salt); + }; try testing.expect(mem.startsWith(u8, s, crypt_format.prefix)); try strVerify(s, "password", verify_options); @@ -823,7 +835,11 @@ test "bcrypt crypt format" { ); var long_buf: [hash_length]u8 = undefined; - var long_s = try strHash("password" ** 100, hash_options, &long_buf); + var long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try testing.expect(mem.startsWith(u8, long_s, crypt_format.prefix)); try strVerify(long_s, "password" ** 100, verify_options); @@ -834,7 +850,11 @@ test "bcrypt crypt format" { hash_options.params.silently_truncate_password = true; verify_options.silently_truncate_password = true; - long_s = try strHash("password" ** 100, hash_options, &long_buf); + long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try strVerify(long_s, "password" ** 101, verify_options); try strVerify( @@ -845,15 +865,20 @@ test "bcrypt crypt format" { } test "bcrypt phc format" { - var hash_options = HashOptions{ + const io = testing.io; + var hash_options: HashOptions = .{ .params = .{ .rounds_log = 5, .silently_truncate_password = false }, .encoding = .phc, }; - var verify_options = VerifyOptions{ .silently_truncate_password = false }; + var verify_options: VerifyOptions = .{ .silently_truncate_password = false }; const prefix = "$bcrypt$"; var buf: [hash_length * 2]u8 = undefined; - const s = try strHash("password", hash_options, &buf); + const s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password", hash_options, &buf, &salt); + }; try testing.expect(mem.startsWith(u8, s, prefix)); try strVerify(s, "password", verify_options); @@ -863,7 +888,11 @@ test "bcrypt phc format" { ); var long_buf: [hash_length * 2]u8 = undefined; - var long_s = try strHash("password" ** 100, hash_options, &long_buf); + var long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try testing.expect(mem.startsWith(u8, long_s, prefix)); try strVerify(long_s, "password" ** 100, verify_options); @@ -874,7 +903,11 @@ test "bcrypt phc format" { hash_options.params.silently_truncate_password = true; verify_options.silently_truncate_password = true; - long_s = try strHash("password" ** 100, hash_options, &long_buf); + long_s = s: { + var salt: [salt_length]u8 = undefined; + io.random(&salt); + break :s try strHash("password" ** 100, hash_options, &long_buf, &salt); + }; try strVerify(long_s, "password" ** 101, verify_options); try strVerify( diff --git a/lib/std/crypto/ecdsa.zig b/lib/std/crypto/ecdsa.zig index e649cc8c7f..ff583896b9 100644 --- a/lib/std/crypto/ecdsa.zig +++ b/lib/std/crypto/ecdsa.zig @@ -323,10 +323,10 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { } /// Generate a new, random key pair. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; @@ -417,12 +417,13 @@ pub fn Ecdsa(comptime Curve: type, comptime Hash: type) type { test "Basic operations over EcdsaP384Sha384" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = EcdsaP384Sha384; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig = try kp.sign(msg, noise); try sig.verify(msg, kp.public_key); @@ -433,12 +434,13 @@ test "Basic operations over EcdsaP384Sha384" { test "Basic operations over Secp256k1" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = EcdsaSecp256k1Sha256oSha256; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig = try kp.sign(msg, noise); try sig.verify(msg, kp.public_key); @@ -449,12 +451,13 @@ test "Basic operations over Secp256k1" { test "Basic operations over EcdsaP384Sha256" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = Ecdsa(crypto.ecc.P384, crypto.hash.sha2.Sha256); - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test"; var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig = try kp.sign(msg, noise); try sig.verify(msg, kp.public_key); @@ -502,8 +505,10 @@ test "Verifying a existing signature with EcdsaP384Sha256" { test "Prehashed message operations" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; + const Scheme = EcdsaP256Sha256; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const msg = "test message for prehashed signing"; const Hash = crypto.hash.sha2.Sha256; @@ -518,7 +523,7 @@ test "Prehashed message operations" { try testing.expectError(error.SignatureVerificationFailed, sig.verifyPrehashed(bad_hash, kp.public_key)); var noise: [Scheme.noise_length]u8 = undefined; - crypto.random.bytes(&noise); + io.random(&noise); const sig_with_noise = try kp.signPrehashed(msg_hash, noise); try sig_with_noise.verifyPrehashed(msg_hash, kp.public_key); @@ -1628,8 +1633,9 @@ fn tvTry(comptime Scheme: type, vector: TestVector) !void { test "Sec1 encoding/decoding" { if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; + const io = testing.io; const Scheme = EcdsaP384Sha384; - const kp = Scheme.KeyPair.generate(); + const kp = Scheme.KeyPair.generate(io); const pk = kp.public_key; const pk_compressed_sec1 = pk.toCompressedSec1(); const pk_recovered1 = try Scheme.PublicKey.fromSec1(&pk_compressed_sec1); diff --git a/lib/std/crypto/hybrid_kem.zig b/lib/std/crypto/hybrid_kem.zig index ee38a8022d..1ce034882a 100644 --- a/lib/std/crypto/hybrid_kem.zig +++ b/lib/std/crypto/hybrid_kem.zig @@ -174,43 +174,56 @@ pub fn HybridKem(comptime params: Params) type { return .{ .bytes = buf.* }; } - /// Generates a shared secret and encapsulates it for the public key. - /// If `seed` is `null`, uses random bytes from `std.crypto.random`. - /// If `seed` is set, encapsulation is deterministic (for testing only). - pub fn encaps(self: PublicKey, seed: ?[]const u8) !EncapsulatedSecret { - const pq_nek = params.PqKem.PublicKey.encoded_length; - const ek_pq = try params.PqKem.PublicKey.fromBytes(self.bytes[0..pq_nek]); - const ek_t = self.bytes[pq_nek..][0..params.Group.element_length]; - + /// Generates a shared secret, encapsulated for the public key, + /// using random bytes. + /// + /// This is recommended over `encapsDeterministic`. + pub fn encaps(pk: PublicKey, io: std.Io) !EncapsulatedSecret { var seed_pq: [32]u8 = undefined; + io.random(&seed_pq); + var seed_t: [32]u8 = undefined; + io.random(&seed_t); + var seed_t_expanded: [params.Group.seed_length]u8 = try expandRandomnessSeed(seed_t); + return encapsInner(pk, &seed_pq, &seed_t_expanded); + } + + /// Generates a shared secret, encapsulated for the public key, + /// using the provided seed. + /// + /// Calling `encaps` instead is recommended. + pub fn encapsDeterministic(pk: PublicKey, seed: []const u8) !EncapsulatedSecret { + if (seed.len < 32) return error.InsufficientRandomness; + var seed_pq: [32]u8 = seed[0..32].*; var seed_t_expanded: [params.Group.seed_length]u8 = undefined; - if (seed) |r| { - if (r.len < 32) return error.InsufficientRandomness; - seed_pq = r[0..32].*; - - const t_randomness = r[32..]; + const t_randomness = seed[32..]; + if (t_randomness.len < params.Group.seed_length) { + // Provided randomness is shorter than seed_length, use it directly + // (test vectors provide just enough for randomScalar) + @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness); + // Pad the rest with zeros if needed (shouldn't be used by randomScalar) if (t_randomness.len < params.Group.seed_length) { - // Provided randomness is shorter than seed_length, use it directly - // (test vectors provide just enough for randomScalar) - @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness); - // Pad the rest with zeros if needed (shouldn't be used by randomScalar) - if (t_randomness.len < params.Group.seed_length) { - @memset(seed_t_expanded[t_randomness.len..], 0); - } - } else { - // Full randomness provided - @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]); + @memset(seed_t_expanded[t_randomness.len..], 0); } } else { - crypto.random.bytes(&seed_pq); - var seed_t: [32]u8 = undefined; - crypto.random.bytes(&seed_t); - seed_t_expanded = try expandRandomnessSeed(seed_t); + // Full randomness provided + @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]); } - const pq_encap = ek_pq.encaps(seed_pq); - const sk_e = try params.Group.randomScalar(&seed_t_expanded); + return encapsInner(pk, &seed_pq, &seed_t_expanded); + } + + fn encapsInner( + pk: PublicKey, + seed_pq: *[32]u8, + seed_t_expanded: *[params.Group.seed_length]u8, + ) !EncapsulatedSecret { + const pq_nek = params.PqKem.PublicKey.encoded_length; + const ek_pq = try params.PqKem.PublicKey.fromBytes(pk.bytes[0..pq_nek]); + const ek_t = pk.bytes[pq_nek..][0..params.Group.element_length]; + + const pq_encap = ek_pq.encapsDeterministic(seed_pq); + const sk_e = try params.Group.randomScalar(seed_t_expanded); const ct_t_point = try params.Group.mulBase(sk_e); const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point; @@ -280,9 +293,9 @@ pub fn HybridKem(comptime params: Params) type { } /// Generates a new random key pair. - pub fn generate() !KeyPair { + pub fn generate(io: std.Io) !KeyPair { var seed: [params.Nseed]u8 = undefined; - crypto.random.bytes(&seed); + io.random(&seed); return generateDeterministic(seed); } }; @@ -386,7 +399,7 @@ test "MLKEM768-X25519 basic round trip" { var enc_seed: [64]u8 = undefined; @memset(&enc_seed, 0x43); - const encap_result = try kp.public_key.encaps(&enc_seed); + const encap_result = try kp.public_key.encapsDeterministic(&enc_seed); const ss_decap = try kp.secret_key.decaps(&encap_result.ciphertext); try testing.expectEqualSlices(u8, &encap_result.shared_secret, &ss_decap); @@ -408,7 +421,7 @@ test "MLKEM768-X25519 test vector 0" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -432,7 +445,7 @@ test "MLKEM768-X25519 test vector 1" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -456,7 +469,7 @@ test "MLKEM768-X25519 test vector 2" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -480,7 +493,7 @@ test "MLKEM768-X25519 test vector 3" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -504,7 +517,7 @@ test "MLKEM768-X25519 test vector 4" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -528,7 +541,7 @@ test "MLKEM768-X25519 test vector 5" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -552,7 +565,7 @@ test "MLKEM768-X25519 test vector 6" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -576,7 +589,7 @@ test "MLKEM768-X25519 test vector 7" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -600,7 +613,7 @@ test "MLKEM768-X25519 test vector 8" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -624,7 +637,7 @@ test "MLKEM768-X25519 test vector 9" { const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -648,7 +661,7 @@ test "MLKEM768-P256 test vector 0" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -672,7 +685,7 @@ test "MLKEM768-P256 test vector 1" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -696,7 +709,7 @@ test "MLKEM768-P256 test vector 2" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -720,7 +733,7 @@ test "MLKEM768-P256 test vector 3" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -744,7 +757,7 @@ test "MLKEM768-P256 test vector 4" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -768,7 +781,7 @@ test "MLKEM768-P256 test vector 5" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -792,7 +805,7 @@ test "MLKEM768-P256 test vector 6" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -816,7 +829,7 @@ test "MLKEM768-P256 test vector 7" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -840,7 +853,7 @@ test "MLKEM768-P256 test vector 8" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -864,7 +877,7 @@ test "MLKEM768-P256 test vector 9" { const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -888,7 +901,7 @@ test "MLKEM1024-P384 test vector 0" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -912,7 +925,7 @@ test "MLKEM1024-P384 test vector 1" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -936,7 +949,7 @@ test "MLKEM1024-P384 test vector 2" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -960,7 +973,7 @@ test "MLKEM1024-P384 test vector 3" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -984,7 +997,7 @@ test "MLKEM1024-P384 test vector 4" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1008,7 +1021,7 @@ test "MLKEM1024-P384 test vector 5" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1032,7 +1045,7 @@ test "MLKEM1024-P384 test vector 6" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1056,7 +1069,7 @@ test "MLKEM1024-P384 test vector 7" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); @@ -1080,7 +1093,7 @@ test "MLKEM1024-P384 test vector 8" { const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); - const enc_result = try kp.public_key.encaps(&randomness); + const enc_result = try kp.public_key.encapsDeterministic(&randomness); try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); diff --git a/lib/std/crypto/ml_dsa.zig b/lib/std/crypto/ml_dsa.zig index 9a699e5439..5132899c48 100644 --- a/lib/std/crypto/ml_dsa.zig +++ b/lib/std/crypto/ml_dsa.zig @@ -2019,12 +2019,9 @@ fn MLDSAImpl(comptime p: Params) type { secret_key: SecretKey, /// Generate a new random key pair. - /// This uses the system's cryptographically secure random number generator. - /// - /// `crypto.random.bytes` must be supported by the target. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var seed: [Self.seed_length]u8 = undefined; - crypto.random.bytes(&seed); + io.random(&seed); return generateDeterministic(seed) catch unreachable; } @@ -3198,8 +3195,9 @@ test "ML-DSA-87 KAT test vector 0" { } test "KeyPair API - generate and sign" { + const io = std.testing.io; // Test the new KeyPair API with random generation - const kp = MLDSA44.KeyPair.generate(); + const kp = MLDSA44.KeyPair.generate(io); const msg = "Test message for KeyPair API"; // Sign with deterministic mode (no noise) @@ -3222,8 +3220,9 @@ test "KeyPair API - generateDeterministic" { } test "KeyPair API - fromSecretKey" { + const io = std.testing.io; // Generate a key pair - const kp1 = MLDSA44.KeyPair.generate(); + const kp1 = MLDSA44.KeyPair.generate(io); // Derive public key from secret key const kp2 = try MLDSA44.KeyPair.fromSecretKey(kp1.secret_key); @@ -3235,8 +3234,9 @@ test "KeyPair API - fromSecretKey" { } test "Signature verification with noise" { + const io = std.testing.io; // Test signing with randomness (hedged signatures) - const kp = MLDSA65.KeyPair.generate(); + const kp = MLDSA65.KeyPair.generate(io); const msg = "Message to be signed with randomness"; // Create some noise @@ -3250,8 +3250,9 @@ test "Signature verification with noise" { } test "Signature verification failure" { + const io = std.testing.io; // Test that invalid signatures are rejected - const kp = MLDSA44.KeyPair.generate(); + const kp = MLDSA44.KeyPair.generate(io); const msg = "Original message"; const sig = try kp.sign(msg, null); diff --git a/lib/std/crypto/ml_kem.zig b/lib/std/crypto/ml_kem.zig index 62e21f60a9..468be166d0 100644 --- a/lib/std/crypto/ml_kem.zig +++ b/lib/std/crypto/ml_kem.zig @@ -244,32 +244,41 @@ fn Kyber(comptime p: Params) type { /// Size of a serialized representation of the key, in bytes. pub const encoded_length = InnerPk.encoded_length; - /// Generates a shared secret, and encapsulates it for the public key. - /// If `seed` is `null`, a random seed is used. This is recommended. - /// If `seed` is set, encapsulation is deterministic. - pub fn encaps(pk: PublicKey, seed_: ?[encaps_seed_length]u8) EncapsulatedSecret { + /// Generates a shared secret, encapsulated for the public key, + /// using random bytes. + /// + /// This is recommended over `encapsDeterministic`. + pub fn encaps(pk: PublicKey, io: std.Io) EncapsulatedSecret { var m: [inner_plaintext_length]u8 = undefined; + io.random(&m); + return encapsInner(pk, &m); + } - if (seed_) |seed| { - if (p.ml_kem) { - @memcpy(&m, &seed); - } else { - // m = H(seed) - sha3.Sha3_256.hash(&seed, &m, .{}); - } + /// Generates a shared secret, encapsulated for the public key, + /// using the provided seed. + /// + /// Calling `encaps` instead is recommended. + pub fn encapsDeterministic(pk: PublicKey, seed: *const [encaps_seed_length]u8) EncapsulatedSecret { + var m: [inner_plaintext_length]u8 = undefined; + if (p.ml_kem) { + @memcpy(&m, seed); } else { - crypto.random.bytes(&m); + // m = H(seed) + sha3.Sha3_256.hash(seed, &m, .{}); } + return encapsInner(pk, &m); + } + fn encapsInner(pk: PublicKey, m: *[inner_plaintext_length]u8) EncapsulatedSecret { // (K', r) = G(m ‖ H(pk)) var kr: [inner_plaintext_length + h_length]u8 = undefined; var g = sha3.Sha3_512.init(.{}); - g.update(&m); + g.update(m); g.update(&pk.hpk); g.final(&kr); // c = innerEncrypt(pk, m, r) - const ct = pk.pk.encrypt(&m, kr[32..64]); + const ct = pk.pk.encrypt(m, kr[32..64]); if (p.ml_kem) { return EncapsulatedSecret{ @@ -398,10 +407,10 @@ fn Kyber(comptime p: Params) type { } /// Generate a new, random key pair. - pub fn generate() KeyPair { + pub fn generate(io: std.Io) KeyPair { var random_seed: [seed_length]u8 = undefined; while (true) { - crypto.random.bytes(&random_seed); + io.random(&random_seed); return generateDeterministic(random_seed) catch { @branchHint(.unlikely); continue; @@ -1634,15 +1643,15 @@ test "Test happy flow" { } inline for (modes) |mode| { for (0..10) |i| { - seed[0] = @as(u8, @intCast(i)); + seed[0] = @intCast(i); const kp = try mode.KeyPair.generateDeterministic(seed); const sk = try mode.SecretKey.fromBytes(&kp.secret_key.toBytes()); try testing.expectEqual(sk, kp.secret_key); const pk = try mode.PublicKey.fromBytes(&kp.public_key.toBytes()); try testing.expectEqual(pk, kp.public_key); for (0..10) |j| { - seed[1] = @as(u8, @intCast(j)); - const e = pk.encaps(seed[0..32].*); + seed[1] = @intCast(j); + const e = pk.encapsDeterministic(seed[0..32]); try testing.expectEqual(e.shared_secret, try sk.decaps(&e.ciphertext)); } } @@ -1695,7 +1704,7 @@ fn testNistKat(mode: type, hash: []const u8) !void { g2.fill(kseed[32..64]); g2.fill(&eseed); const kp = try mode.KeyPair.generateDeterministic(kseed); - const e = kp.public_key.encaps(eseed); + const e = kp.public_key.encapsDeterministic(&eseed); const ss2 = try kp.secret_key.decaps(&e.ciphertext); try testing.expectEqual(ss2, e.shared_secret); try fw.writer.print("pk = {X}\n", .{&kp.public_key.toBytes()}); diff --git a/lib/std/crypto/pcurves/p256.zig b/lib/std/crypto/pcurves/p256.zig index aa9226ae0d..4746061e02 100644 --- a/lib/std/crypto/pcurves/p256.zig +++ b/lib/std/crypto/pcurves/p256.zig @@ -122,8 +122,8 @@ pub const P256 = struct { } /// Return a random point. - pub fn random() P256 { - const n = scalar.random(.little); + pub fn random(io: std.Io) P256 { + const n = scalar.random(io, .little); return basePoint.mul(n, .little) catch unreachable; } diff --git a/lib/std/crypto/pcurves/p256/scalar.zig b/lib/std/crypto/pcurves/p256/scalar.zig index bd071f4b8b..cd31d5e10f 100644 --- a/lib/std/crypto/pcurves/p256/scalar.zig +++ b/lib/std/crypto/pcurves/p256/scalar.zig @@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) } /// Return a random scalar -pub fn random(endian: std.builtin.Endian) CompressedScalar { - return Scalar.random().toBytes(endian); +pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar { + return Scalar.random(io).toBytes(endian); } /// A scalar in unpacked representation. @@ -170,10 +170,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [48]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes48(s, .little); if (!n.isZero()) { return n; diff --git a/lib/std/crypto/pcurves/p384.zig b/lib/std/crypto/pcurves/p384.zig index 61632f61ed..0dbfdc67f1 100644 --- a/lib/std/crypto/pcurves/p384.zig +++ b/lib/std/crypto/pcurves/p384.zig @@ -122,8 +122,8 @@ pub const P384 = struct { } /// Return a random point. - pub fn random() P384 { - const n = scalar.random(.little); + pub fn random(io: std.Io) P384 { + const n = scalar.random(io, .little); return basePoint.mul(n, .little) catch unreachable; } diff --git a/lib/std/crypto/pcurves/p384/scalar.zig b/lib/std/crypto/pcurves/p384/scalar.zig index 6ea25f214a..19e94c4b42 100644 --- a/lib/std/crypto/pcurves/p384/scalar.zig +++ b/lib/std/crypto/pcurves/p384/scalar.zig @@ -63,8 +63,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) } /// Return a random scalar -pub fn random(endian: std.builtin.Endian) CompressedScalar { - return Scalar.random().toBytes(endian); +pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar { + return Scalar.random(io).toBytes(endian); } /// A scalar in unpacked representation. @@ -159,10 +159,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [64]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes64(s, .little); if (!n.isZero()) { return n; diff --git a/lib/std/crypto/pcurves/secp256k1.zig b/lib/std/crypto/pcurves/secp256k1.zig index c891f414f5..1c1caae19a 100644 --- a/lib/std/crypto/pcurves/secp256k1.zig +++ b/lib/std/crypto/pcurves/secp256k1.zig @@ -175,8 +175,8 @@ pub const Secp256k1 = struct { } /// Return a random point. - pub fn random() Secp256k1 { - const n = scalar.random(.little); + pub fn random(io: std.Io) Secp256k1 { + const n = scalar.random(io, .little); return basePoint.mul(n, .little) catch unreachable; } diff --git a/lib/std/crypto/pcurves/secp256k1/scalar.zig b/lib/std/crypto/pcurves/secp256k1/scalar.zig index 132325026f..d79d2a2305 100644 --- a/lib/std/crypto/pcurves/secp256k1/scalar.zig +++ b/lib/std/crypto/pcurves/secp256k1/scalar.zig @@ -68,8 +68,8 @@ pub fn sub(a: CompressedScalar, b: CompressedScalar, endian: std.builtin.Endian) } /// Return a random scalar -pub fn random(endian: std.builtin.Endian) CompressedScalar { - return Scalar.random().toBytes(endian); +pub fn random(io: std.Io, endian: std.builtin.Endian) CompressedScalar { + return Scalar.random(io).toBytes(endian); } /// A scalar in unpacked representation. @@ -170,10 +170,10 @@ pub const Scalar = struct { } /// Return a random scalar < L. - pub fn random() Scalar { + pub fn random(io: std.Io) Scalar { var s: [48]u8 = undefined; while (true) { - crypto.random.bytes(&s); + io.random(&s); const n = Scalar.fromBytes48(s, .little); if (!n.isZero()) { return n; diff --git a/lib/std/crypto/pcurves/tests/p256.zig b/lib/std/crypto/pcurves/tests/p256.zig index 7da68044f8..3ca12fa241 100644 --- a/lib/std/crypto/pcurves/tests/p256.zig +++ b/lib/std/crypto/pcurves/tests/p256.zig @@ -5,8 +5,9 @@ const testing = std.testing; const P256 = @import("../p256.zig").P256; test "p256 ECDH key exchange" { - const dha = P256.scalar.random(.little); - const dhb = P256.scalar.random(.little); + const io = testing.io; + const dha = P256.scalar.random(io, .little); + const dhb = P256.scalar.random(io, .little); const dhA = try P256.basePoint.mul(dha, .little); const dhB = try P256.basePoint.mul(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -66,28 +67,32 @@ test "p256 test vectors - doubling" { } test "p256 compressed sec1 encoding/decoding" { - const p = P256.random(); + const io = testing.io; + const p = P256.random(io); const s = p.toCompressedSec1(); const q = try P256.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "p256 uncompressed sec1 encoding/decoding" { - const p = P256.random(); + const io = testing.io; + const p = P256.random(io); const s = p.toUncompressedSec1(); const q = try P256.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "p256 public key is the neutral element" { + const io = testing.io; const n = P256.scalar.Scalar.zero.toBytes(.little); - const p = P256.random(); + const p = P256.random(io); try testing.expectError(error.IdentityElement, p.mul(n, .little)); } test "p256 public key is the neutral element (public verification)" { + const io = testing.io; const n = P256.scalar.Scalar.zero.toBytes(.little); - const p = P256.random(); + const p = P256.random(io); try testing.expectError(error.IdentityElement, p.mulPublic(n, .little)); } diff --git a/lib/std/crypto/pcurves/tests/p384.zig b/lib/std/crypto/pcurves/tests/p384.zig index e51a683136..316e8679ad 100644 --- a/lib/std/crypto/pcurves/tests/p384.zig +++ b/lib/std/crypto/pcurves/tests/p384.zig @@ -5,8 +5,9 @@ const testing = std.testing; const P384 = @import("../p384.zig").P384; test "p384 ECDH key exchange" { - const dha = P384.scalar.random(.little); - const dhb = P384.scalar.random(.little); + const io = testing.io; + const dha = P384.scalar.random(io, .little); + const dhb = P384.scalar.random(io, .little); const dhA = try P384.basePoint.mul(dha, .little); const dhB = try P384.basePoint.mul(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -67,7 +68,8 @@ test "p384 test vectors - doubling" { } test "p384 compressed sec1 encoding/decoding" { - const p = P384.random(); + const io = testing.io; + const p = P384.random(io); const s0 = p.toUncompressedSec1(); const s = p.toCompressedSec1(); try testing.expectEqualSlices(u8, s0[1..49], s[1..49]); @@ -76,21 +78,24 @@ test "p384 compressed sec1 encoding/decoding" { } test "p384 uncompressed sec1 encoding/decoding" { - const p = P384.random(); + const io = testing.io; + const p = P384.random(io); const s = p.toUncompressedSec1(); const q = try P384.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "p384 public key is the neutral element" { + const io = testing.io; const n = P384.scalar.Scalar.zero.toBytes(.little); - const p = P384.random(); + const p = P384.random(io); try testing.expectError(error.IdentityElement, p.mul(n, .little)); } test "p384 public key is the neutral element (public verification)" { + const io = testing.io; const n = P384.scalar.Scalar.zero.toBytes(.little); - const p = P384.random(); + const p = P384.random(io); try testing.expectError(error.IdentityElement, p.mulPublic(n, .little)); } diff --git a/lib/std/crypto/pcurves/tests/secp256k1.zig b/lib/std/crypto/pcurves/tests/secp256k1.zig index 0fb2a13cf8..ff400c0322 100644 --- a/lib/std/crypto/pcurves/tests/secp256k1.zig +++ b/lib/std/crypto/pcurves/tests/secp256k1.zig @@ -5,8 +5,9 @@ const testing = std.testing; const Secp256k1 = @import("../secp256k1.zig").Secp256k1; test "secp256k1 ECDH key exchange" { - const dha = Secp256k1.scalar.random(.little); - const dhb = Secp256k1.scalar.random(.little); + const io = testing.io; + const dha = Secp256k1.scalar.random(io, .little); + const dhb = Secp256k1.scalar.random(io, .little); const dhA = try Secp256k1.basePoint.mul(dha, .little); const dhB = try Secp256k1.basePoint.mul(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -15,8 +16,9 @@ test "secp256k1 ECDH key exchange" { } test "secp256k1 ECDH key exchange including public multiplication" { - const dha = Secp256k1.scalar.random(.little); - const dhb = Secp256k1.scalar.random(.little); + const io = testing.io; + const dha = Secp256k1.scalar.random(io, .little); + const dhb = Secp256k1.scalar.random(io, .little); const dhA = try Secp256k1.basePoint.mul(dha, .little); const dhB = try Secp256k1.basePoint.mulPublic(dhb, .little); const shareda = try dhA.mul(dhb, .little); @@ -77,28 +79,32 @@ test "secp256k1 test vectors - doubling" { } test "secp256k1 compressed sec1 encoding/decoding" { - const p = Secp256k1.random(); + const io = testing.io; + const p = Secp256k1.random(io); const s = p.toCompressedSec1(); const q = try Secp256k1.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "secp256k1 uncompressed sec1 encoding/decoding" { - const p = Secp256k1.random(); + const io = testing.io; + const p = Secp256k1.random(io); const s = p.toUncompressedSec1(); const q = try Secp256k1.fromSec1(&s); try testing.expect(p.equivalent(q)); } test "secp256k1 public key is the neutral element" { + const io = testing.io; const n = Secp256k1.scalar.Scalar.zero.toBytes(.little); - const p = Secp256k1.random(); + const p = Secp256k1.random(io); try testing.expectError(error.IdentityElement, p.mul(n, .little)); } test "secp256k1 public key is the neutral element (public verification)" { + const io = testing.io; const n = Secp256k1.scalar.Scalar.zero.toBytes(.little); - const p = Secp256k1.random(); + const p = Secp256k1.random(io); try testing.expectError(error.IdentityElement, p.mulPublic(n, .little)); } diff --git a/lib/std/crypto/salsa20.zig b/lib/std/crypto/salsa20.zig index eb434ed15a..d47498704d 100644 --- a/lib/std/crypto/salsa20.zig +++ b/lib/std/crypto/salsa20.zig @@ -533,9 +533,9 @@ pub const SealedBox = struct { /// Encrypt a message `m` for a recipient whose public key is `public_key`. /// `c` must be `seal_length` bytes larger than `m`, so that the required metadata can be added. - pub fn seal(c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void { + pub fn seal(io: std.Io, c: []u8, m: []const u8, public_key: [public_length]u8) (WeakPublicKeyError || IdentityElementError)!void { debug.assert(c.len == m.len + seal_length); - var ekp = KeyPair.generate(); + var ekp = KeyPair.generate(io); const nonce = createNonce(ekp.public_key, public_key); c[0..public_length].* = ekp.public_key; try Box.seal(c[Box.public_length..], m, nonce, public_key, ekp.secret_key); @@ -573,29 +573,31 @@ test "(x)salsa20" { } test "xsalsa20poly1305" { + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var c: [msg.len]u8 = undefined; var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [XSalsa20Poly1305.nonce_length]u8 = undefined; var tag: [XSalsa20Poly1305.tag_length]u8 = undefined; - crypto.random.bytes(&msg); - crypto.random.bytes(&key); - crypto.random.bytes(&nonce); + io.random(&msg); + io.random(&key); + io.random(&nonce); XSalsa20Poly1305.encrypt(c[0..], &tag, msg[0..], "ad", nonce, key); try XSalsa20Poly1305.decrypt(msg2[0..], c[0..], tag, "ad", nonce, key); } test "xsalsa20poly1305 secretbox" { + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var key: [XSalsa20Poly1305.key_length]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - crypto.random.bytes(&msg); - crypto.random.bytes(&key); - crypto.random.bytes(&nonce); + io.random(&msg); + io.random(&key); + io.random(&nonce); SecretBox.seal(boxed[0..], msg[0..], nonce, key); try SecretBox.open(msg2[0..], boxed[0..], nonce, key); @@ -604,15 +606,16 @@ test "xsalsa20poly1305 secretbox" { test "xsalsa20poly1305 box" { if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299 + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var nonce: [Box.nonce_length]u8 = undefined; var boxed: [msg.len + Box.tag_length]u8 = undefined; - crypto.random.bytes(&msg); - crypto.random.bytes(&nonce); + io.random(&msg); + io.random(&nonce); - const kp1 = Box.KeyPair.generate(); - const kp2 = Box.KeyPair.generate(); + const kp1 = Box.KeyPair.generate(io); + const kp2 = Box.KeyPair.generate(io); try Box.seal(boxed[0..], msg[0..], nonce, kp1.public_key, kp2.secret_key); try Box.open(msg2[0..], boxed[0..], nonce, kp2.public_key, kp1.secret_key); } @@ -620,13 +623,14 @@ test "xsalsa20poly1305 box" { test "xsalsa20poly1305 sealedbox" { if (builtin.cpu.has(.riscv, .v) and builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/24299 + const io = std.testing.io; var msg: [100]u8 = undefined; var msg2: [msg.len]u8 = undefined; var boxed: [msg.len + SealedBox.seal_length]u8 = undefined; - crypto.random.bytes(&msg); + io.random(&msg); - const kp = Box.KeyPair.generate(); - try SealedBox.seal(boxed[0..], msg[0..], kp.public_key); + const kp = Box.KeyPair.generate(io); + try SealedBox.seal(io, boxed[0..], msg[0..], kp.public_key); try SealedBox.open(msg2[0..], boxed[0..], kp); } diff --git a/lib/std/crypto/scrypt.zig b/lib/std/crypto/scrypt.zig index 00440642e3..43bf5ff054 100644 --- a/lib/std/crypto/scrypt.zig +++ b/lib/std/crypto/scrypt.zig @@ -20,7 +20,7 @@ const Error = pwhash.Error; const max_size = math.maxInt(usize); const max_int = max_size >> 1; -const default_salt_len = 32; +pub const default_salt_len = 32; const default_hash_len = 32; const max_salt_len = 64; const max_hash_len = 64; @@ -417,10 +417,9 @@ const PhcFormatHasher = struct { password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt: []const u8, ) HasherError![]const u8 { - var salt: [default_salt_len]u8 = undefined; - crypto.random.bytes(&salt); - var hash: [default_hash_len]u8 = undefined; try kdf(allocator, &hash, password, &salt, params); @@ -466,9 +465,9 @@ const CryptFormatHasher = struct { password: []const u8, params: Params, buf: []u8, + /// Filled with cryptographically secure entropy. + salt_bin: []const u8, ) HasherError![]const u8 { - var salt_bin: [default_salt_len]u8 = undefined; - crypto.random.bytes(&salt_bin); const salt = crypt_format.saltFromBin(salt_bin.len, salt_bin); var hash: [default_hash_len]u8 = undefined; diff --git a/lib/std/crypto/timing_safe.zig b/lib/std/crypto/timing_safe.zig index 85767eb006..7d9b53c0aa 100644 --- a/lib/std/crypto/timing_safe.zig +++ b/lib/std/crypto/timing_safe.zig @@ -180,24 +180,24 @@ pub fn declassify(ptr: anytype) void { } test eql { - const random = std.crypto.random; + const io = std.testing.io; const expect = std.testing.expect; var a: [100]u8 = undefined; var b: [100]u8 = undefined; - random.bytes(a[0..]); - random.bytes(b[0..]); + io.random(&a); + io.random(&b); try expect(!eql([100]u8, a, b)); a = b; try expect(eql([100]u8, a, b)); } test "eql (vectors)" { - const random = std.crypto.random; + const io = std.testing.io; const expect = std.testing.expect; var a: [100]u8 = undefined; var b: [100]u8 = undefined; - random.bytes(a[0..]); - random.bytes(b[0..]); + io.random(&a); + io.random(&b); const v1: @Vector(100, u8) = a; const v2: @Vector(100, u8) = b; try expect(!eql(@Vector(100, u8), v1, v2)); @@ -220,9 +220,10 @@ test compare { } test "add and sub" { + const io = std.testing.io; + const expectEqual = std.testing.expectEqual; const expectEqualSlices = std.testing.expectEqualSlices; - const random = std.crypto.random; const len = 32; var a: [len]u8 = undefined; var b: [len]u8 = undefined; @@ -230,8 +231,8 @@ test "add and sub" { const zero = [_]u8{0} ** len; var iterations: usize = 100; while (iterations != 0) : (iterations -= 1) { - random.bytes(&a); - random.bytes(&b); + io.random(&a); + io.random(&b); const endian = if (iterations % 2 == 0) Endian.big else Endian.little; _ = sub(u8, &a, &b, &c, endian); // a-b _ = add(u8, &c, &b, &c, endian); // (a-b)+b @@ -243,11 +244,11 @@ test "add and sub" { } test classify { - const random = std.crypto.random; + const io = std.testing.io; const expect = std.testing.expect; var secret: [32]u8 = undefined; - random.bytes(&secret); + io.random(&secret); // Input of the hash function is marked as secret classify(&secret); diff --git a/lib/std/crypto/tlcsprng.zig b/lib/std/crypto/tlcsprng.zig deleted file mode 100644 index 9e20de1281..0000000000 --- a/lib/std/crypto/tlcsprng.zig +++ /dev/null @@ -1,169 +0,0 @@ -//! Thread-local cryptographically secure pseudo-random number generator. -//! This file has public declarations that are intended to be used internally -//! by the standard library; this namespace is not intended to be exposed -//! directly to standard library users. - -const std = @import("std"); -const builtin = @import("builtin"); -const mem = std.mem; -const native_os = builtin.os.tag; -const posix = std.posix; - -/// We use this as a layer of indirection because global const pointers cannot -/// point to thread-local variables. -pub const interface: std.Random = .{ - .ptr = undefined, - .fillFn = tlsCsprngFill, -}; - -const os_has_fork = @TypeOf(posix.fork) != void; -const os_has_arc4random = builtin.link_libc and (@TypeOf(std.c.arc4random_buf) != void); -const want_fork_safety = os_has_fork and !os_has_arc4random and std.options.crypto_fork_safety; -const maybe_have_wipe_on_fork = builtin.os.isAtLeast(.linux, .{ - .major = 4, - .minor = 14, - .patch = 0, -}) orelse true; - -const Rng = std.Random.DefaultCsprng; - -const Context = struct { - init_state: enum(u8) { uninitialized = 0, initialized, failed }, - rng: Rng, -}; - -var install_atfork_handler = std.once(struct { - // Install the global handler only once. - // The same handler is shared among threads and is inherinted by fork()-ed - // processes. - fn do() void { - const r = std.c.pthread_atfork(null, null, childAtForkHandler); - std.debug.assert(r == 0); - } -}.do); - -threadlocal var wipe_mem: []align(std.heap.page_size_min) u8 = &[_]u8{}; - -fn tlsCsprngFill(_: *anyopaque, buffer: []u8) void { - if (os_has_arc4random) { - // arc4random is already a thread-local CSPRNG. - return std.c.arc4random_buf(buffer.ptr, buffer.len); - } - // Allow applications to decide they would prefer to have every call to - // std.crypto.random always make an OS syscall, rather than rely on an - // application implementation of a CSPRNG. - if (std.options.crypto_always_getrandom) { - return std.options.cryptoRandomSeed(buffer); - } - - 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 = posix.mmap( - null, - @sizeOf(Context), - posix.PROT.READ | posix.PROT.WRITE, - .{ .TYPE = .PRIVATE, .ANONYMOUS = true }, - -1, - 0, - ) catch { - // Could not allocate memory for the local state, fall back to - // the OS syscall. - return std.options.cryptoRandomSeed(buffer); - }; - // The memory is already zero-initialized. - } else { - // Use a static thread-local buffer. - const S = struct { - threadlocal var buf: Context align(std.heap.page_size_min) = .{ - .init_state = .uninitialized, - .rng = undefined, - }; - }; - wipe_mem = mem.asBytes(&S.buf); - } - } - const ctx: *Context = @ptrCast(wipe_mem.ptr); - - switch (ctx.init_state) { - .uninitialized => { - 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 (posix.madvise(wipe_mem.ptr, 0, 0xffffffff)) |_| { - break :wof; - } else |_| {} - - if (posix.madvise(wipe_mem.ptr, wipe_mem.len, posix.MADV.WIPEONFORK)) |_| { - return initAndFill(buffer); - } else |_| {} - } - - 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 std.options.cryptoRandomSeed(buffer); - }, - .initialized => { - return fillWithCsprng(buffer); - }, - .failed => { - if (want_fork_safety) { - return std.options.cryptoRandomSeed(buffer); - } else { - unreachable; - } - }, - } -} - -fn setupPthreadAtforkAndFill(buffer: []u8) void { - install_atfork_handler.call(); - return initAndFill(buffer); -} - -fn childAtForkHandler() callconv(.c) void { - // The atfork handler is global, this function may be called after - // fork()-ing threads that never initialized the CSPRNG context. - if (wipe_mem.len == 0) return; - std.crypto.secureZero(u8, wipe_mem); -} - -fn fillWithCsprng(buffer: []u8) void { - const ctx: *Context = @ptrCast(wipe_mem.ptr); - return ctx.rng.fill(buffer); -} - -pub fn defaultRandomSeed(buffer: []u8) void { - posix.getrandom(buffer) catch @panic("getrandom() failed to provide entropy"); -} - -fn initAndFill(buffer: []u8) void { - var seed: [Rng.secret_seed_length]u8 = undefined; - // Because we panic on getrandom() failing, we provide the opportunity - // to override the default seed function. This also makes - // `std.crypto.random` available on freestanding targets, provided that - // the `std.options.cryptoRandomSeed` function is provided. - std.options.cryptoRandomSeed(&seed); - - const ctx: *Context = @ptrCast(wipe_mem.ptr); - ctx.rng = Rng.init(seed); - std.crypto.secureZero(u8, &seed); - - // This is at the end so that accidental recursive dependencies result - // in stack overflows instead of invalid random data. - ctx.init_state = .initialized; - - return fillWithCsprng(buffer); -} diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index d3a06819bf..44a73c344a 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -109,7 +109,7 @@ pub const Options = struct { read_buffer: []u8, /// Cryptographically secure random bytes. The pointer is not captured; data is only /// read during `init`. - entropy: *const [176]u8, + entropy: *const [entropy_len]u8, /// Current time according to the wall clock / calendar, in seconds. realtime_now_seconds: i64, @@ -130,6 +130,8 @@ pub const Options = struct { allow_truncation_attacks: bool = false, /// Populated when `error.TlsAlert` is returned from `init`. alert: ?*tls.Alert = null, + + pub const entropy_len = 240; }; const InitError = error{ @@ -200,7 +202,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client var server_hello_rand: [32]u8 = undefined; const legacy_session_id = options.entropy[32..64].*; - var key_share = KeyShare.init(options.entropy[64..176].*) catch |err| switch (err) { + var key_share = KeyShare.init(options.entropy[64..240]) catch |err| switch (err) { // Only possible to happen if the seed is all zeroes. error.IdentityElement => return error.InsufficientEntropy, }; @@ -1330,12 +1332,12 @@ const KeyShare = struct { crypto.dh.X25519.shared_length, ); - fn init(seed: [112]u8) error{IdentityElement}!KeyShare { + fn init(seed: *const [176]u8) error{IdentityElement}!KeyShare { return .{ - .ml_kem768_kp = .generate(), - .secp256r1_kp = try .generateDeterministic(seed[0..32].*), - .secp384r1_kp = try .generateDeterministic(seed[32..80].*), - .x25519_kp = try .generateDeterministic(seed[80..112].*), + .ml_kem768_kp = try .generateDeterministic(seed[0..64].*), + .secp256r1_kp = try .generateDeterministic(seed[64..96].*), + .secp384r1_kp = try .generateDeterministic(seed[96..144].*), + .x25519_kp = try .generateDeterministic(seed[144..176].*), .sk_buf = undefined, .sk_len = 0, }; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index c53ce18996..a584fa3477 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1022,8 +1022,7 @@ test "Dir.rename directory onto non-empty dir" { file.close(io); target_dir.close(io); - // Rename should fail with PathAlreadyExists if target_dir is non-empty - try expectError(error.PathAlreadyExists, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io)); + try expectError(error.DirNotEmpty, ctx.dir.rename(test_dir_path, ctx.dir, target_dir_path, io)); // Ensure the directory was not renamed var dir = try ctx.dir.openDir(io, test_dir_path, .{}); @@ -1651,6 +1650,21 @@ test "AtomicFile" { \\ this is a test file ; + // link() succeeds with no file already present + { + var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false }); + defer af.deinit(io); + try af.file.writeStreamingAll(io, test_content); + try af.link(io); + } + // link() returns error.PathAlreadyExists if file already present + { + var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = false }); + defer af.deinit(io); + try af.file.writeStreamingAll(io, test_content); + try expectError(error.PathAlreadyExists, af.link(io)); + } + // replace() succeeds if file already present { var af = try ctx.dir.createFileAtomic(io, test_out_file, .{ .replace = true }); defer af.deinit(io); @@ -1761,7 +1775,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { const io = testing.io; var random_bytes: [12]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var random_b64: [std.fs.base64_encoder.calcSize(random_bytes.len)]u8 = undefined; _ = std.fs.base64_encoder.encode(&random_b64, &random_bytes); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index 3979a18029..ddfcd96b6a 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -321,8 +321,8 @@ pub const Connection = struct { assert(base.ptr + alloc_len == socket_read_buffer.ptr + socket_read_buffer.len); @memcpy(host_buffer, remote_host.bytes); const tls: *Tls = @ptrCast(base); - var random_buffer: [176]u8 = undefined; - std.crypto.random.bytes(&random_buffer); + var random_buffer: [std.crypto.tls.Client.Options.entropy_len]u8 = undefined; + io.random(&random_buffer); tls.* = .{ .connection = .{ .client = client, diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 812e07f7e8..92ea207fd0 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -501,6 +501,15 @@ pub const O = switch (native_arch) { else => @compileError("missing std.os.linux.O constants for this architecture"), }; +pub const RENAME = packed struct(u32) { + /// Cannot be set together with `EXCHANGE`. + NOREPLACE: bool = false, + /// Cannot be set together with `NOREPLACE`. + EXCHANGE: bool = false, + WHITEOUT: bool = false, + _: u29 = 0, +}; + /// Set by startup code, used by `getauxval`. pub var elf_aux_maybe: ?[*]std.elf.Auxv = null; @@ -1346,9 +1355,22 @@ pub fn rename(old: [*:0]const u8, new: [*:0]const u8) usize { if (@hasField(SYS, "rename")) { return syscall2(.rename, @intFromPtr(old), @intFromPtr(new)); } else if (@hasField(SYS, "renameat")) { - return syscall4(.renameat, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new)); + return syscall4( + .renameat, + @as(usize, @bitCast(@as(isize, AT.FDCWD))), + @intFromPtr(old), + @as(usize, @bitCast(@as(isize, AT.FDCWD))), + @intFromPtr(new), + ); } else { - return syscall5(.renameat2, @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(old), @as(usize, @bitCast(@as(isize, AT.FDCWD))), @intFromPtr(new), 0); + return syscall5( + .renameat2, + @as(usize, @bitCast(@as(isize, AT.FDCWD))), + @intFromPtr(old), + @as(usize, @bitCast(@as(isize, AT.FDCWD))), + @intFromPtr(new), + 0, + ); } } @@ -1373,14 +1395,14 @@ pub fn renameat(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]co } } -pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: u32) usize { +pub fn renameat2(oldfd: i32, oldpath: [*:0]const u8, newfd: i32, newpath: [*:0]const u8, flags: RENAME) usize { return syscall5( .renameat2, @as(usize, @bitCast(@as(isize, oldfd))), @intFromPtr(oldpath), @as(usize, @bitCast(@as(isize, newfd))), @intFromPtr(newpath), - flags, + @as(u32, @bitCast(flags)), ); } diff --git a/lib/std/os/linux/tls.zig b/lib/std/os/linux/tls.zig index cbec3cc00a..e8598eb111 100644 --- a/lib/std/os/linux/tls.zig +++ b/lib/std/os/linux/tls.zig @@ -531,13 +531,11 @@ pub fn prepareArea(area: []u8) usize { }; } -/// The main motivation for the size chosen here is that this is how much ends up being requested for -/// the thread-local variables of the `std.crypto.random` implementation. I'm not sure why it ends up -/// being so much; the struct itself is only 64 bytes. I think it has to do with being page-aligned -/// and LLVM or LLD is not smart enough to lay out the TLS data in a space-conserving way. Anyway, I -/// think it's fine because it's less than 3 pages of memory, and putting it in the ELF like this is -/// equivalent to moving the `mmap` call below into the kernel, avoiding syscall overhead. -var main_thread_area_buffer: [0x2100]u8 align(page_size_min) = undefined; +/// The main motivation for the size chosen here is to be larger than total +/// amount of thread-local variables for most programs. Putting this allocation +/// in the ELF like this is equivalent to moving the `mmap` call below into the +/// kernel, avoiding syscall overhead. +var main_thread_area_buffer: [0x1000]u8 align(page_size_min) = undefined; /// Computes the layout of the static TLS area, allocates the area, initializes all of its fields, /// and assigns the architecture-specific value to the TP register. diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 8155634675..52e4bd387d 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2647,62 +2647,6 @@ pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInfor } } -/// An alternate implementation of ProcessPrng from bcryptprimitives.dll -/// This one has the following differences: -/// * does not heap allocate `buffer` -/// * does not introduce a dependency on bcryptprimitives.dll, which apparently -/// runs a test suite every time it is loaded -/// * reads buffer.len bytes from "\\Device\\CNG" rather than seeding a per-CPU -/// AES csprng with 48 bytes. -pub fn ProcessPrng(buffer: []u8) error{Unexpected}!void { - const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }; - var nt_name: UNICODE_STRING = .{ - .Length = device_path.len * 2, - .MaximumLength = 0, - .Buffer = @constCast(&device_path), - }; - var cng_device: HANDLE = undefined; - var io_status_block: IO_STATUS_BLOCK = undefined; - switch (ntdll.NtOpenFile( - &cng_device, - .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, - }, - &.{ - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = null, - .ObjectName = &nt_name, - .Attributes = .{}, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }, - &io_status_block, - .VALID_FLAGS, - .{ .IO = .SYNCHRONOUS_NONALERT }, - )) { - .SUCCESS => {}, - .OBJECT_NAME_NOT_FOUND => return error.Unexpected, // Observed on wine 10.0 - else => |status| return unexpectedStatus(status), - } - defer _ = ntdll.NtClose(cng_device); - switch (ntdll.NtDeviceIoControlFile( - cng_device, - null, - null, - null, - &io_status_block, - IOCTL.KSEC.GEN_RANDOM, - null, - 0, - buffer.ptr, - @intCast(buffer.len), - )) { - .SUCCESS => {}, - else => |status| return unexpectedStatus(status), - } -} - pub const WaitForSingleObjectError = error{ WaitAbandoned, WaitTimeOut, @@ -3250,7 +3194,7 @@ pub const RenameError = error{ NetworkNotFound, AntivirusInterference, BadPathName, - RenameAcrossMountPoints, + CrossDevice, } || UnexpectedError; pub fn RenameFile( @@ -3351,7 +3295,7 @@ pub fn RenameFile( .ACCESS_DENIED => return error.AccessDenied, .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NOT_SAME_DEVICE => return error.RenameAcrossMountPoints, + .NOT_SAME_DEVICE => return error.CrossDevice, .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, .FILE_IS_A_DIRECTORY => return error.IsDir, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 5d5d51805d..60938818df 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -361,107 +361,6 @@ pub fn reboot(cmd: RebootCommand) RebootError!void { } } -pub const GetRandomError = OpenError; - -/// Obtain a series of random bytes. These bytes can be used to seed user-space -/// random number generators or for cryptographic purposes. -/// When linking against libc, this calls the -/// appropriate OS-specific library call. Otherwise it uses the zig standard -/// library implementation. -pub fn getrandom(buffer: []u8) GetRandomError!void { - if (native_os == .windows) { - return windows.ProcessPrng(buffer); - } - if (builtin.link_libc and @TypeOf(system.arc4random_buf) != void) { - system.arc4random_buf(buffer.ptr, buffer.len); - return; - } - if (native_os == .wasi) switch (wasi.random_get(buffer.ptr, buffer.len)) { - .SUCCESS => return, - else => |err| return unexpectedErrno(err), - }; - if (@TypeOf(system.getrandom) != void) { - var buf = buffer; - const use_c = native_os != .linux or - std.c.versionCheck(if (builtin.abi.isAndroid()) .{ .major = 28, .minor = 0, .patch = 0 } else .{ .major = 2, .minor = 25, .patch = 0 }); - - while (buf.len != 0) { - const num_read: usize, const err = if (use_c) res: { - const rc = std.c.getrandom(buf.ptr, buf.len, 0); - break :res .{ @bitCast(rc), errno(rc) }; - } else res: { - const rc = linux.getrandom(buf.ptr, buf.len, 0); - break :res .{ rc, linux.errno(rc) }; - }; - - switch (err) { - .SUCCESS => buf = buf[num_read..], - .INVAL => unreachable, - .FAULT => unreachable, - .INTR => continue, - else => return unexpectedErrno(err), - } - } - return; - } - if (native_os == .emscripten) { - const err = errno(std.c.getentropy(buffer.ptr, buffer.len)); - switch (err) { - .SUCCESS => return, - else => return unexpectedErrno(err), - } - } - return getRandomBytesDevURandom(buffer); -} - -fn getRandomBytesDevURandom(buf: []u8) GetRandomError!void { - const fd = try openZ("/dev/urandom", .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0); - defer close(fd); - - switch (native_os) { - .linux => { - var stx = std.mem.zeroes(linux.Statx); - const rc = linux.statx( - fd, - "", - linux.AT.EMPTY_PATH, - .{ .TYPE = true }, - &stx, - ); - switch (errno(rc)) { - .SUCCESS => {}, - .ACCES => unreachable, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .LOOP => unreachable, - .NAMETOOLONG => unreachable, - .NOENT => unreachable, - .NOMEM => return error.SystemResources, - .NOTDIR => unreachable, - else => |err| return unexpectedErrno(err), - } - if (!S.ISCHR(stx.mode)) { - return error.NoDevice; - } - }, - else => { - const st = fstat(fd) catch |err| switch (err) { - error.Streaming => return error.NoDevice, - else => |e| return e, - }; - if (!S.ISCHR(st.mode)) { - return error.NoDevice; - } - }, - } - - var i: usize = 0; - while (i < buf.len) { - i += read(fd, buf[i..]) catch return error.Unexpected; - } -} - pub const RaiseError = UnexpectedError; pub fn raise(sig: SIG) RaiseError!void { @@ -1695,7 +1594,7 @@ pub const FanotifyMarkError = error{ NotDir, OperationUnsupported, PermissionDenied, - NotSameFileSystem, + CrossDevice, NameTooLong, } || UnexpectedError; @@ -1735,7 +1634,7 @@ pub fn fanotify_markZ( .NOTDIR => return error.NotDir, .OPNOTSUPP => return error.OperationUnsupported, .PERM => return error.PermissionDenied, - .XDEV => return error.NotSameFileSystem, + .XDEV => return error.CrossDevice, else => |err| return unexpectedErrno(err), } } diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 905b3438e4..47341ea525 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -33,16 +33,6 @@ test "check WASI CWD" { } } -test "getrandom" { - var buf_a: [50]u8 = undefined; - var buf_b: [50]u8 = undefined; - try posix.getrandom(&buf_a); - try posix.getrandom(&buf_b); - // If this test fails the chance is significantly higher that there is a bug than - // that two sets of 50 bytes were equal. - try expect(!mem.eql(u8, &buf_a, &buf_b)); -} - test "getuid" { if (native_os == .windows or native_os == .wasi) return error.SkipZigTest; _ = posix.getuid(); diff --git a/lib/std/std.zig b/lib/std/std.zig index c5d71dcb60..a22cc6d312 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -137,12 +137,6 @@ pub const Options = struct { fmt_max_depth: usize = fmt.default_max_depth, - cryptoRandomSeed: fn (buffer: []u8) void = @import("crypto/tlcsprng.zig").defaultRandomSeed, - - crypto_always_getrandom: bool = false, - - crypto_fork_safety: bool = true, - /// By default, std.http.Client will support HTTPS connections. Set this option to `true` to /// disable TLS support. /// diff --git a/lib/std/testing.zig b/lib/std/testing.zig index 9bb2622c3d..1138869663 100644 --- a/lib/std/testing.zig +++ b/lib/std/testing.zig @@ -631,7 +631,7 @@ pub const TmpDir = struct { pub fn tmpDir(opts: Io.Dir.OpenOptions) TmpDir { comptime assert(builtin.is_test); var random_bytes: [TmpDir.random_bytes_count]u8 = undefined; - std.crypto.random.bytes(&random_bytes); + io.random(&random_bytes); var sub_path: [TmpDir.sub_path_len]u8 = undefined; _ = std.fs.base64_encoder.encode(&sub_path, &random_bytes); diff --git a/src/Compilation.zig b/src/Compilation.zig index f308a60bd3..5d369594c8 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2942,7 +2942,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE .none => |none| { assert(none.tmp_artifact_directory == null); none.tmp_artifact_directory = d: { - tmp_dir_rand_int = std.crypto.random.int(u64); + io.random(@ptrCast(&tmp_dir_rand_int)); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { @@ -3023,7 +3023,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE // Compile the artifacts to a temporary directory. whole.tmp_artifact_directory = d: { - tmp_dir_rand_int = std.crypto.random.int(u64); + io.random(@ptrCast(&tmp_dir_rand_int)); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(tmp_dir_rand_int); const path = try comp.dirs.local_cache.join(arena, &.{tmp_dir_sub_path}); const handle = comp.dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}) catch |err| { @@ -3460,7 +3460,7 @@ fn renameTmpIntoCache( }, else => return error.AccessDenied, }, - error.PathAlreadyExists => { + error.DirNotEmpty => { try cache_directory.handle.deleteTree(io, o_sub_path); continue; }, @@ -5759,7 +5759,11 @@ pub fn translateC( const gpa = comp.gpa; const io = comp.io; - const tmp_basename = std.fmt.hex(std.crypto.random.int(u64)); + const tmp_basename = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r std.fmt.hex(x); + }; const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename; const cache_dir = comp.dirs.local_cache.handle; var cache_tmp_dir = try cache_dir.createDirPathOpen(io, tmp_sub_path, .{}); @@ -6889,8 +6893,13 @@ fn spawnZigRc( } pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { + const io = comp.io; + const rand_int = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const s = fs.path.sep_str; - const rand_int = std.crypto.random.int(u64); if (comp.dirs.local_cache.path) |p| { return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); } else { diff --git a/src/Package.zig b/src/Package.zig index 9f05f33a46..fda4c1c178 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -14,9 +14,9 @@ pub const Fingerprint = packed struct(u64) { id: u32, checksum: u32, - pub fn generate(name: []const u8) Fingerprint { + pub fn generate(rng: std.Random, name: []const u8) Fingerprint { return .{ - .id = std.crypto.random.intRangeLessThan(u32, 1, 0xffffffff), + .id = rng.intRangeLessThan(u32, 1, 0xffffffff), .checksum = std.hash.Crc32.hash(name), }; } diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d595465db5..d42e441d77 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -494,7 +494,11 @@ fn runResource( const eb = &f.error_bundle; const s = fs.path.sep_str; const cache_root = f.job_queue.global_cache; - const rand_int = std.crypto.random.int(u64); + const rand_int = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int); const package_sub_path = blk: { @@ -690,7 +694,9 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { return error.FetchFailed; } - f.manifest = try Manifest.parse(arena, ast.*, .{ + const rng: std.Random.IoSource = .{ .io = io }; + + f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{ .allow_missing_paths_field = f.allow_missing_paths_field, .allow_missing_fingerprint = f.allow_missing_fingerprint, .allow_name_string = f.allow_name_string, @@ -1305,7 +1311,11 @@ fn unzip( zip_path[prefix.len + random_len ..].* = suffix.*; var zip_file = while (true) { - const random_integer = std.crypto.random.int(u64); + const random_integer = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; zip_path[prefix.len..][0..random_len].* = std.fmt.hex(random_integer); break cache_root.handle.createFile(io, &zip_path, .{ @@ -1466,7 +1476,7 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u }; continue; }, - error.PathAlreadyExists, error.AccessDenied => { + error.DirNotEmpty, error.AccessDenied => { // Package has been already downloaded and may already be in use on the system. cache_dir.deleteTree(io, tmp_dir_sub_path) catch { // Garbage files leftover in zig-cache/tmp/ is, as they say diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index 90243a23e0..a8bc8b5013 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -57,7 +57,7 @@ pub const ParseOptions = struct { pub const Error = Allocator.Error; -pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest { +pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest { const main_node_index = ast.nodeData(.root).node; var arena_instance = std.heap.ArenaAllocator.init(gpa); @@ -87,7 +87,7 @@ pub fn parse(gpa: Allocator, ast: Ast, options: ParseOptions) Error!Manifest { defer p.dependencies.deinit(gpa); defer p.paths.deinit(gpa); - p.parseRoot(main_node_index) catch |err| switch (err) { + p.parseRoot(main_node_index, rng) catch |err| switch (err) { error.ParseFailure => assert(p.errors.items.len > 0), else => |e| return e, }; @@ -157,7 +157,7 @@ const Parse = struct { const InnerError = error{ ParseFailure, OutOfMemory }; - fn parseRoot(p: *Parse, node: Ast.Node.Index) !void { + fn parseRoot(p: *Parse, node: Ast.Node.Index, rng: std.Random) !void { const ast = p.ast; const main_token = ast.nodeMainToken(node); @@ -217,13 +217,13 @@ const Parse = struct { if (fingerprint) |n| { if (!n.validate(p.name)) { return fail(p, main_token, "invalid fingerprint: 0x{x}; if this is a new or forked package, use this value: 0x{x}", .{ - n.int(), Package.Fingerprint.generate(p.name).int(), + n.int(), Package.Fingerprint.generate(rng, p.name).int(), }); } p.id = n.id; } else if (!p.allow_missing_fingerprint) { try appendError(p, main_token, "missing top-level 'fingerprint' field; suggested value: 0x{x}", .{ - Package.Fingerprint.generate(p.name).int(), + Package.Fingerprint.generate(rng, p.name).int(), }); } else { p.id = 0; @@ -623,7 +623,9 @@ test "basic" { try testing.expect(ast.errors.len == 0); - var manifest = try Manifest.parse(gpa, ast, .{}); + var rng = std.Random.DefaultPrng.init(0); + + var manifest = try Manifest.parse(gpa, ast, rng.random(), .{}); defer manifest.deinit(gpa); try testing.expect(manifest.errors.len == 0); @@ -666,7 +668,9 @@ test "minimum_zig_version" { try testing.expect(ast.errors.len == 0); - var manifest = try Manifest.parse(gpa, ast, .{}); + var rng = std.Random.DefaultPrng.init(0); + + var manifest = try Manifest.parse(gpa, ast, rng.random(), .{}); defer manifest.deinit(gpa); try testing.expect(manifest.errors.len == 0); @@ -698,7 +702,9 @@ test "minimum_zig_version - invalid version" { try testing.expect(ast.errors.len == 0); - var manifest = try Manifest.parse(gpa, ast, .{}); + var rng = std.Random.DefaultPrng.init(0); + + var manifest = try Manifest.parse(gpa, ast, rng.random(), .{}); defer manifest.deinit(gpa); try testing.expect(manifest.errors.len == 1); diff --git a/src/link.zig b/src/link.zig index 13306b90a4..0e89723296 100644 --- a/src/link.zig +++ b/src/link.zig @@ -616,8 +616,13 @@ pub const File = struct { // it will return ETXTBSY. So instead, we copy the file, atomically rename it // over top of the exe path, and then proceed normally. This changes the inode, // avoiding the error. + const random_integer = r: { + var x: u32 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const tmp_sub_path = try std.fmt.allocPrint(gpa, "{s}-{x}", .{ - emit.sub_path, std.crypto.random.int(u32), + emit.sub_path, random_integer, }); defer gpa.free(tmp_sub_path); try emit.root_dir.handle.copyFile(emit.sub_path, emit.root_dir.handle, tmp_sub_path, io, .{}); diff --git a/src/link/Lld.zig b/src/link/Lld.zig index 0110e21770..dd84da15d4 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1636,7 +1636,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi const err = switch (first_err) { error.NameTooLong => err: { const s = fs.path.sep_str; - const rand_int = std.crypto.random.int(u64); + const rand_int = r: { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + break :r x; + }; const rsp_path = "tmp" ++ s ++ std.fmt.hex(rand_int) ++ ".rsp"; const rsp_file = try comp.dirs.local_cache.handle.createFile(io, rsp_path, .{}); diff --git a/src/main.zig b/src/main.zig index 1f373b2340..97a4e53c2f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -3395,7 +3395,7 @@ fn buildOutputType( // "-" is stdin. Dump it to a real file. const sep = fs.path.sep_str; const dump_path = try std.fmt.allocPrint(arena, "tmp" ++ sep ++ "{x}-dump-stdin{s}", .{ - std.crypto.random.int(u64), ext.canonicalName(target), + randInt(io, u64), ext.canonicalName(target), }); try dirs.local_cache.handle.createDirPath(io, "tmp"); @@ -4433,7 +4433,7 @@ fn runOrTest( try argv.append(exe_path); if (arg_mode == .zig_test) { try argv.append( - try std.fmt.allocPrint(arena, "--seed=0x{x}", .{std.crypto.random.int(u32)}), + try std.fmt.allocPrint(arena, "--seed=0x{x}", .{randInt(io, u32)}), ); } } else { @@ -4763,7 +4763,8 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! const cwd_basename = fs.path.basename(cwd_path); const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename); - const fingerprint: Package.Fingerprint = .generate(sanitized_root_name); + const rng: std.Random.IoSource = .{ .io = io }; + const fingerprint: Package.Fingerprint = .generate(rng.interface(), sanitized_root_name); switch (template) { .example => { @@ -4919,7 +4920,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, try child_argv.appendSlice(&.{ "--seed", - try std.fmt.allocPrint(arena, "0x{x}", .{std.crypto.random.int(u32)}), + try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}), }); const argv_index_seed = child_argv.items.len - 1; @@ -4937,7 +4938,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, // the strategy is to choose a temporary file name ahead of time, and then // read this file in the parent to obtain the results, in the case the child // exits with code 3. - const results_tmp_file_nonce = std.fmt.hex(std.crypto.random.int(u64)); + const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64)); try child_argv.append("-Z" ++ results_tmp_file_nonce); var color: Color = .auto; @@ -7223,7 +7224,7 @@ fn createDependenciesModule( ) !*Package.Module { // Atomically create the file in a directory named after the hash of its contents. const basename = "dependencies.zig"; - const rand_int = std.crypto.random.int(u64); + const rand_int = randInt(io, u64); const tmp_dir_sub_path = "tmp" ++ fs.path.sep_str ++ std.fmt.hex(rand_int); { var tmp_dir = try dirs.local_cache.handle.createDirPathOpen(io, tmp_dir_sub_path, .{}); @@ -7339,6 +7340,8 @@ fn loadManifest( io: Io, options: LoadManifestOptions, ) !struct { Package.Manifest, Ast } { + const rng: std.Random.IoSource = .{ .io = io }; + const manifest_bytes = while (true) { break options.dir.readFileAllocOptions( io, @@ -7360,15 +7363,13 @@ fn loadManifest( , .{ options.root_name, build_options.version, - Package.Fingerprint.generate(options.root_name).int(), + Package.Fingerprint.generate(rng.interface(), options.root_name).int(), }) catch |e| { - fatal("unable to write {s}: {s}", .{ Package.Manifest.basename, @errorName(e) }); + fatal("unable to write {s}: {t}", .{ Package.Manifest.basename, e }); }; continue; }, - else => |e| fatal("unable to load {s}: {s}", .{ - Package.Manifest.basename, @errorName(e), - }), + else => |e| fatal("unable to load {s}: {t}", .{ Package.Manifest.basename, e }), }; }; var ast = try Ast.parse(gpa, manifest_bytes, .zon); @@ -7379,7 +7380,7 @@ fn loadManifest( process.exit(2); } - var manifest = try Package.Manifest.parse(gpa, ast, .{}); + var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{}); errdefer manifest.deinit(gpa); if (manifest.errors.len > 0) { @@ -7632,3 +7633,9 @@ fn setThreadLimit(n: usize) void { threaded_impl_ptr.setAsyncLimit(limit); threaded_impl_ptr.concurrent_limit = limit; } + +fn randInt(io: Io, comptime T: type) T { + var x: T = undefined; + io.random(@ptrCast(&x)); + return x; +} diff --git a/test/standalone/simple/guess_number/main.zig b/test/standalone/simple/guess_number/main.zig index e7b30867ec..9af4787488 100644 --- a/test/standalone/simple/guess_number/main.zig +++ b/test/standalone/simple/guess_number/main.zig @@ -10,7 +10,8 @@ pub fn main(init: std.process.Init) !void { try out.writeAll("Welcome to the Guess Number Game in Zig.\n"); - const answer = std.crypto.random.intRangeLessThan(u8, 0, 100) + 1; + var rng: std.Random.IoSource = .{ .io = init.io }; + const answer = rng.interface().intRangeLessThan(u8, 0, 100) + 1; while (true) { try out.writeAll("\nGuess a number between 1 and 100: "); diff --git a/test/standalone/windows_argv/build.zig b/test/standalone/windows_argv/build.zig index 9b507e978a..019cd34fc8 100644 --- a/test/standalone/windows_argv/build.zig +++ b/test/standalone/windows_argv/build.zig @@ -52,7 +52,7 @@ pub fn build(b: *std.Build) !void { const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: { var buf: [8]u8 = undefined; - try std.posix.getrandom(&buf); + b.graph.io.random(&buf); break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian()); }; const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom"); diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig index 7e7f43a6af..fde26ee5f4 100644 --- a/test/standalone/windows_argv/fuzz.zig +++ b/test/standalone/windows_argv/fuzz.zig @@ -5,6 +5,7 @@ const Allocator = std.mem.Allocator; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; + const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); if (args.len < 2) return error.MissingArgs; @@ -23,7 +24,7 @@ pub fn main(init: std.process.Init) !void { if (args.len < 4) { rand_seed = true; var buf: [8]u8 = undefined; - try std.posix.getrandom(&buf); + io.random(&buf); break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian()); } break :seed try std.fmt.parseUnsigned(u64, args[3], 10); diff --git a/test/standalone/windows_bat_args/build.zig b/test/standalone/windows_bat_args/build.zig index d54b6cd48c..d5053c5aad 100644 --- a/test/standalone/windows_bat_args/build.zig +++ b/test/standalone/windows_bat_args/build.zig @@ -65,7 +65,7 @@ pub fn build(b: *std.Build) !void { const fuzz_seed = b.option(u64, "seed", "Seed to use for the PRNG (default: random)") orelse seed: { var buf: [8]u8 = undefined; - try std.posix.getrandom(&buf); + b.graph.io.random(&buf); break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian()); }; const fuzz_seed_arg = std.fmt.allocPrint(b.allocator, "{}", .{fuzz_seed}) catch @panic("oom"); diff --git a/test/standalone/windows_bat_args/fuzz.zig b/test/standalone/windows_bat_args/fuzz.zig index f3f72ab897..b9f0cc930d 100644 --- a/test/standalone/windows_bat_args/fuzz.zig +++ b/test/standalone/windows_bat_args/fuzz.zig @@ -22,7 +22,7 @@ pub fn main(init: std.process.Init) !void { const seed_arg = it.next() orelse { rand_seed = true; var buf: [8]u8 = undefined; - try std.posix.getrandom(&buf); + io.random(&buf); break :seed std.mem.readInt(u64, &buf, builtin.cpu.arch.endian()); }; break :seed try std.fmt.parseUnsigned(u64, seed_arg, 10); diff --git a/tools/doctest.zig b/tools/doctest.zig index 7f4b8fbda8..5037bac2c2 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -78,9 +78,10 @@ pub fn main(init: std.process.Init) !void { const code = try parseManifest(arena, source_bytes); const source = stripManifest(source_bytes); - const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ - cache_root, std.crypto.random.int(u64), - }); + var random_integer: u64 = undefined; + io.random(@ptrCast(&random_integer)); + + const tmp_dir_path = try std.fmt.allocPrint(arena, "{s}/tmp/{x}", .{ cache_root, random_integer }); Dir.cwd().createDirPath(io, tmp_dir_path) catch |err| fatal("unable to create tmp dir '{s}': {t}", .{ tmp_dir_path, err }); defer Dir.cwd().deleteTree(io, tmp_dir_path) catch |err| std.log.err("unable to delete '{s}': {t}", .{ diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 2dfb23447a..daf93be73f 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -100,7 +100,7 @@ pub fn main(init: std.process.Init) !void { const prog_node = std.Progress.start(io, .{}); defer prog_node.end(); - const rand_int = std.crypto.random.int(u64); + const rand_int = rand64(io); const tmp_dir_path = "tmp_" ++ std.fmt.hex(rand_int); var tmp_dir = try Dir.cwd().createDirPathOpen(io, tmp_dir_path, .{}); defer { @@ -452,20 +452,19 @@ const Eval = struct { std.debug.assert(eval.target.backend == .sema); return; }; + const io = eval.io; const binary_path = switch (eval.target.backend) { .sema => unreachable, .selfhosted, .llvm => emitted_path, .cbe => bin: { - const rand_int = std.crypto.random.int(u64); + const rand_int = rand64(io); const out_bin_name = "./out_" ++ std.fmt.hex(rand_int); try eval.buildCOutput(emitted_path, out_bin_name, prog_node); break :bin out_bin_name; }, }; - const io = eval.io; - var argv_buf: [2][]const u8 = undefined; const argv: []const []const u8, const is_foreign: bool = sw: switch (std.zig.system.getExternalExecutor( io, @@ -957,3 +956,9 @@ fn parseExpectedError(str: []const u8, l: usize) Case.ExpectedError { .msg = message, }; } + +fn rand64(io: Io) u64 { + var x: u64 = undefined; + io.random(@ptrCast(&x)); + return x; +}