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 diff --git a/core/crypto/crypto.odin b/core/crypto/crypto.odin index 89e92e35f..7ccf126e6 100644 --- a/core/crypto/crypto.odin +++ b/core/crypto/crypto.odin @@ -4,6 +4,10 @@ package crypto import "base:runtime" import "core:mem" +// HAS_RAND_BYTES is true iff the runtime provides a cryptographic +// entropy source. +HAS_RAND_BYTES :: runtime.HAS_RAND_BYTES + // compare_constant_time returns 1 iff a and b are equal, 0 otherwise. // // The execution time of this routine is constant regardless of the contents @@ -54,7 +58,7 @@ rand_bytes :: proc (dst: []byte) { // zero-fill the buffer first mem.zero_explicit(raw_data(dst), len(dst)) - _rand_bytes(dst) + runtime.rand_bytes(dst) } // random_generator returns a `runtime.Random_Generator` backed by the diff --git a/core/crypto/rand_bsd.odin b/core/crypto/rand_bsd.odin deleted file mode 100644 index 78a6fcaaf..000000000 --- a/core/crypto/rand_bsd.odin +++ /dev/null @@ -1,15 +0,0 @@ -#+build freebsd, openbsd, netbsd -package crypto - -foreign import libc "system:c" - -HAS_RAND_BYTES :: true - -foreign libc { - arc4random_buf :: proc(buf: [^]byte, nbytes: uint) --- -} - -@(private) -_rand_bytes :: proc(dst: []byte) { - arc4random_buf(raw_data(dst), len(dst)) -} diff --git a/core/crypto/rand_darwin.odin b/core/crypto/rand_darwin.odin deleted file mode 100644 index df474bc4c..000000000 --- a/core/crypto/rand_darwin.odin +++ /dev/null @@ -1,17 +0,0 @@ -package crypto - -import "core:fmt" - -import CF "core:sys/darwin/CoreFoundation" -import Sec "core:sys/darwin/Security" - -HAS_RAND_BYTES :: true - -@(private) -_rand_bytes :: proc(dst: []byte) { - err := Sec.RandomCopyBytes(count=len(dst), bytes=raw_data(dst)) - if err != .Success { - msg := CF.StringCopyToOdinString(Sec.CopyErrorMessageString(err)) - fmt.panicf("crypto/rand_bytes: SecRandomCopyBytes returned non-zero result: %v %s", err, msg) - } -} diff --git a/core/crypto/rand_generic.odin b/core/crypto/rand_generic.odin deleted file mode 100644 index 8266f8ffc..000000000 --- a/core/crypto/rand_generic.odin +++ /dev/null @@ -1,16 +0,0 @@ -#+build !linux -#+build !windows -#+build !openbsd -#+build !freebsd -#+build !netbsd -#+build !darwin -#+build !js -#+build !wasi -package crypto - -HAS_RAND_BYTES :: false - -@(private) -_rand_bytes :: proc(dst: []byte) { - unimplemented("crypto: rand_bytes not supported on this OS") -} diff --git a/core/crypto/rand_js.odin b/core/crypto/rand_js.odin deleted file mode 100644 index 72093810e..000000000 --- a/core/crypto/rand_js.odin +++ /dev/null @@ -1,24 +0,0 @@ -package crypto - -foreign import "odin_env" -foreign odin_env { - @(link_name = "rand_bytes") - env_rand_bytes :: proc "contextless" (buf: []byte) --- -} - -HAS_RAND_BYTES :: true - -@(private) -_MAX_PER_CALL_BYTES :: 65536 // 64kiB - -@(private) -_rand_bytes :: proc(dst: []byte) { - 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:] - } -} diff --git a/core/crypto/rand_linux.odin b/core/crypto/rand_linux.odin deleted file mode 100644 index 7e0edbb7e..000000000 --- a/core/crypto/rand_linux.odin +++ /dev/null @@ -1,40 +0,0 @@ -package crypto - -import "core:fmt" - -import "core:sys/linux" - -HAS_RAND_BYTES :: true - -@(private) -_MAX_PER_CALL_BYTES :: 33554431 // 2^25 - 1 - -@(private) -_rand_bytes :: proc (dst: []byte) { - dst := dst - l := len(dst) - - for l > 0 { - to_read := min(l, _MAX_PER_CALL_BYTES) - n_read, errno := linux.getrandom(dst[:to_read], {}) - #partial switch errno { - case .NONE: - // Do nothing - case .EINTR: - // Call interupted by a signal handler, just retry the - // request. - continue - case .ENOSYS: - // The kernel is apparently prehistoric (< 3.17 circa 2014) - // and does not support getrandom. - panic("crypto: getrandom not available in kernel") - case: - // All other failures are things that should NEVER happen - // unless the kernel interface changes (ie: the Linux - // developers break userland). - fmt.panicf("crypto: getrandom failed: %v", errno) - } - l -= n_read - dst = dst[n_read:] - } -} diff --git a/core/crypto/rand_wasi.odin b/core/crypto/rand_wasi.odin deleted file mode 100644 index 9653fb985..000000000 --- a/core/crypto/rand_wasi.odin +++ /dev/null @@ -1,13 +0,0 @@ -package crypto - -import "core:fmt" -import "core:sys/wasm/wasi" - -HAS_RAND_BYTES :: true - -@(private) -_rand_bytes :: proc(dst: []byte) { - if err := wasi.random_get(dst); err != nil { - fmt.panicf("crypto: wasi.random_get failed: %v", err) - } -} diff --git a/core/crypto/rand_windows.odin b/core/crypto/rand_windows.odin deleted file mode 100644 index 83a976e38..000000000 --- a/core/crypto/rand_windows.odin +++ /dev/null @@ -1,26 +0,0 @@ -package crypto - -import win32 "core:sys/windows" -import "core:os" -import "core:fmt" - -HAS_RAND_BYTES :: true - -@(private) -_rand_bytes :: proc(dst: []byte) { - ret := os.Platform_Error(win32.BCryptGenRandom(nil, raw_data(dst), u32(len(dst)), win32.BCRYPT_USE_SYSTEM_PREFERRED_RNG)) - if ret != nil { - #partial switch ret { - case os.ERROR_INVALID_HANDLE: - // The handle to the first parameter is invalid. - // This should not happen here, since we explicitly pass nil to it - panic("crypto: BCryptGenRandom Invalid handle for hAlgorithm") - case os.ERROR_INVALID_PARAMETER: - // One of the parameters was invalid - panic("crypto: BCryptGenRandom Invalid parameter") - case: - // Unknown error - fmt.panicf("crypto: BCryptGenRandom failed: %d\n", ret) - } - } -}