From e1ba69ea5192a245263b6a5ea5b4359cae7c0220 Mon Sep 17 00:00:00 2001 From: Yawning Angel Date: Sat, 29 Nov 2025 09:18:11 +0900 Subject: base/runtime: Add `rand_bytes` and `HAS_RAND_BYTES` Having the OS/runtime provide a cryptographic entropy source is the right thing to do, and we need it to initialize the default random number generator. --- base/runtime/os_specific.odin | 10 ++++++ base/runtime/os_specific_bsd.odin | 8 +++++ base/runtime/os_specific_darwin.odin | 14 +++++++++ base/runtime/os_specific_freestanding.odin | 2 ++ base/runtime/os_specific_haiku.odin | 8 ++++- base/runtime/os_specific_js.odin | 18 +++++++++++ base/runtime/os_specific_linux.odin | 49 ++++++++++++++++++++++++++++++ base/runtime/os_specific_orca.odin | 2 ++ base/runtime/os_specific_wasi.odin | 11 +++++++ base/runtime/os_specific_windows.odin | 33 ++++++++++++++++++++ 10 files changed, 154 insertions(+), 1 deletion(-) (limited to 'base') diff --git a/base/runtime/os_specific.odin b/base/runtime/os_specific.odin index b6c1288d0..16e7e4751 100644 --- a/base/runtime/os_specific.odin +++ b/base/runtime/os_specific.odin @@ -2,10 +2,20 @@ package runtime _OS_Errno :: distinct int +HAS_RAND_BYTES :: _HAS_RAND_BYTES + stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return _stderr_write(data) } +rand_bytes :: proc "contextless" (dst: []byte) { + when HAS_RAND_BYTES { + _rand_bytes(dst) + } else { + panic_contextless("base/runtime: no runtime entropy source") + } +} + exit :: proc "contextless" (code: int) -> ! { _exit(code) } \ No newline at end of file diff --git a/base/runtime/os_specific_bsd.odin b/base/runtime/os_specific_bsd.odin index de300f1e0..ab8eabb6c 100644 --- a/base/runtime/os_specific_bsd.odin +++ b/base/runtime/os_specific_bsd.odin @@ -4,6 +4,8 @@ package runtime foreign import libc "system:c" +_HAS_RAND_BYTES :: true + @(default_calling_convention="c") foreign libc { @(link_name="write") @@ -14,6 +16,8 @@ foreign libc { } else { __error :: proc() -> ^i32 --- } + + arc4random_buf :: proc(buf: [^]byte, nbytes: uint) --- } _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { @@ -25,6 +29,10 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return int(ret), 0 } +_rand_bytes :: proc "contextless" (dst: []byte) { + arc4random_buf(raw_data(dst), len(dst)) +} + _exit :: proc "contextless" (code: int) -> ! { @(default_calling_convention="c") foreign libc { diff --git a/base/runtime/os_specific_darwin.odin b/base/runtime/os_specific_darwin.odin index 37315240f..576725a1c 100644 --- a/base/runtime/os_specific_darwin.odin +++ b/base/runtime/os_specific_darwin.odin @@ -4,6 +4,8 @@ package runtime import "base:intrinsics" +_HAS_RAND_BYTES :: true + _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { STDERR :: 2 when ODIN_NO_CRT { @@ -29,6 +31,18 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { foreign import libc "system:System" +_rand_bytes :: proc "contextless" (dst: []byte) { + // This process used to use Security/RandomCopyBytes, however + // on every version of MacOS (>= 10.12) that we care about, + // arc4random is implemented securely. + + @(default_calling_convention="c") + foreign libc { + arc4random_buf :: proc(buf: [^]byte, nbytes: uint) --- + } + arc4random_buf(raw_data(dst), len(dst)) +} + _exit :: proc "contextless" (code: int) -> ! { @(default_calling_convention="c") foreign libc { diff --git a/base/runtime/os_specific_freestanding.odin b/base/runtime/os_specific_freestanding.odin index b5a5fb146..3b2b5a714 100644 --- a/base/runtime/os_specific_freestanding.odin +++ b/base/runtime/os_specific_freestanding.odin @@ -2,6 +2,8 @@ #+private package runtime +_HAS_RAND_BYTES :: false + // TODO(bill): reimplement `os.write` _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return 0, -1 diff --git a/base/runtime/os_specific_haiku.odin b/base/runtime/os_specific_haiku.odin index 74ff58cde..7e53539a1 100644 --- a/base/runtime/os_specific_haiku.odin +++ b/base/runtime/os_specific_haiku.odin @@ -4,11 +4,15 @@ package runtime foreign import libc "system:c" +_HAS_RAND_BYTES :: true + foreign libc { @(link_name="write") _unix_write :: proc(fd: i32, buf: rawptr, size: int) -> int --- _errnop :: proc() -> ^i32 --- + + arc4random_buf :: proc(buf: [^]byte, nbytes: uint) --- } _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { @@ -20,7 +24,9 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return int(ret), 0 } - +_rand_bytes :: proc "contextless" (dst: []byte) { + arc4random_buf(raw_data(dst), len(dst)) +} _exit :: proc "contextless" (code: int) -> ! { trap() diff --git a/base/runtime/os_specific_js.odin b/base/runtime/os_specific_js.odin index bd88b1871..8676f3a6e 100644 --- a/base/runtime/os_specific_js.odin +++ b/base/runtime/os_specific_js.odin @@ -4,6 +4,8 @@ package runtime foreign import "odin_env" +_HAS_RAND_BYTES :: true + _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { foreign odin_env { write :: proc "contextless" (fd: u32, p: []byte) --- @@ -12,6 +14,22 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return len(data), 0 } +_rand_bytes :: proc "contextless" (dst: []byte) { + foreign odin_env { + @(link_name = "rand_bytes") + env_rand_bytes :: proc "contextless" (buf: []byte) --- + } + + MAX_PER_CALL_BYTES :: 65536 // 64kiB + + dst := dst + for len(dst) > 0 { + to_read := min(len(dst), MAX_PER_CALL_BYTES) + env_rand_bytes(dst[:to_read]) + + dst = dst[to_read:] + } +} _exit :: proc "contextless" (code: int) -> ! { trap() diff --git a/base/runtime/os_specific_linux.odin b/base/runtime/os_specific_linux.odin index dfe3c8841..1abcc03e5 100644 --- a/base/runtime/os_specific_linux.odin +++ b/base/runtime/os_specific_linux.odin @@ -3,6 +3,8 @@ package runtime import "base:intrinsics" +_HAS_RAND_BYTES :: true + _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { when ODIN_ARCH == .amd64 { SYS_write :: uintptr(1) @@ -25,6 +27,53 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return ret, 0 } +_rand_bytes :: proc "contextless" (dst: []byte) { + when ODIN_ARCH == .amd64 { + SYS_getrandom :: uintptr(318) + } else when ODIN_ARCH == .arm64 { + SYS_getrandom :: uintptr(278) + } else when ODIN_ARCH == .i386 { + SYS_getrandom :: uintptr(355) + } else when ODIN_ARCH == .arm32 { + SYS_getrandom :: uintptr(384) + } else when ODIN_ARCH == .riscv64 { + SYS_getrandom :: uintptr(278) + } else { + #panic("base/runtime: no SYS_getrandom definition for target") + } + + ERR_EINTR :: 4 + ERR_ENOSYS :: 38 + + MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1 + + dst := dst + l := len(dst) + + for l > 0 { + to_read := min(l, MAX_PER_CALL_BYTES) + ret := int(intrinsics.syscall(SYS_getrandom, uintptr(raw_data(dst[:to_read])), uintptr(to_read), uintptr(0))) + switch ret { + case -ERR_EINTR: + // Call interupted by a signal handler, just retry the + // request. + continue + case -ERR_ENOSYS: + // The kernel is apparently prehistoric (< 3.17 circa 2014) + // and does not support getrandom. + panic_contextless("base/runtime: getrandom not available in kernel") + case: + if ret < 0 { + // All other failures are things that should NEVER happen + // unless the kernel interface changes (ie: the Linux + // developers break userland). + panic_contextless("base/runtime: getrandom failed") + } + } + l -= ret + dst = dst[ret:] + } +} _exit :: proc "contextless" (code: int) -> ! { SYS_exit_group :: diff --git a/base/runtime/os_specific_orca.odin b/base/runtime/os_specific_orca.odin index 491edcfa4..f5ce50411 100644 --- a/base/runtime/os_specific_orca.odin +++ b/base/runtime/os_specific_orca.odin @@ -4,6 +4,8 @@ package runtime import "base:intrinsics" +_HAS_RAND_BYTES :: false + // Constants allowing to specify the level of logging verbosity. log_level :: enum u32 { // Only errors are logged. diff --git a/base/runtime/os_specific_wasi.odin b/base/runtime/os_specific_wasi.odin index 194034865..c5e94653a 100644 --- a/base/runtime/os_specific_wasi.odin +++ b/base/runtime/os_specific_wasi.odin @@ -4,6 +4,8 @@ package runtime foreign import wasi "wasi_snapshot_preview1" +_HAS_RAND_BYTES :: true + @(default_calling_convention="contextless") foreign wasi { fd_write :: proc( @@ -26,6 +28,9 @@ foreign wasi { @(private="file") proc_exit :: proc(rval: u32) -> ! --- + + @(private ="file") + random_get :: proc(buf: []u8) -> u16 --- } _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { @@ -34,6 +39,12 @@ _stderr_write :: proc "contextless" (data: []byte) -> (int, _OS_Errno) { return int(n), _OS_Errno(err) } +_rand_bytes :: proc "contextless" (dst: []byte) { + if errno := random_get(dst); errno != 0 { + panic_contextless("base/runtime: wasi.random_get failed") + } +} + _wasi_setup_args :: proc() { num_of_args, size_of_args: uint if errno := args_sizes_get(&num_of_args, &size_of_args); errno != 0 { diff --git a/base/runtime/os_specific_windows.odin b/base/runtime/os_specific_windows.odin index c5ca1e4c5..d938e87ea 100644 --- a/base/runtime/os_specific_windows.odin +++ b/base/runtime/os_specific_windows.odin @@ -2,8 +2,11 @@ #+private package runtime +foreign import bcrypt "system:Bcrypt.lib" foreign import kernel32 "system:Kernel32.lib" +_HAS_RAND_BYTES :: true + @(private="file") @(default_calling_convention="system") foreign kernel32 { @@ -18,6 +21,12 @@ foreign kernel32 { ExitProcess :: proc(code: u32) -> ! --- } +@(private="file") +@(default_calling_convention="system") +foreign bcrypt { + BCryptGenRandom :: proc(hAlgorithm: rawptr, pBuffer: [^]u8, cbBuffer: u32, dwFlags: u32) -> i32 --- +} + _stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) #no_bounds_check { if len(data) == 0 { return 0, 0 @@ -52,6 +61,30 @@ _stderr_write :: proc "contextless" (data: []byte) -> (n: int, err: _OS_Errno) # return } +_rand_bytes :: proc "contextless" (dst: []byte) { + ensure_contextless(u64(len(dst)) <= u64(max(u32)), "base/runtime: oversized rand_bytes request") + + BCRYPT_USE_SYSTEM_PREFERRED_RNG :: 0x00000002 + + ERROR_INVALID_HANDLE :: 6 + ERROR_INVALID_PARAMETER :: 87 + + ret := BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), BCRYPT_USE_SYSTEM_PREFERRED_RNG) + switch ret { + case 0: + case ERROR_INVALID_HANDLE: + // The handle to the first parameter is invalid. + // This should not happen here, since we explicitly pass nil to it + panic_contextless("base/runtime: BCryptGenRandom Invalid handle for hAlgorithm") + case ERROR_INVALID_PARAMETER: + // One of the parameters was invalid + panic_contextless("base/runtime: BCryptGenRandom Invalid parameter") + case: + // Unknown error + panic_contextless("base/runtime: BCryptGenRandom failed") + } +} + _exit :: proc "contextless" (code: int) -> ! { ExitProcess(u32(code)) } \ No newline at end of file -- cgit v1.2.3