From 92b80346a2ede6b1c832c28be6dad95d7c872935 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 11:17:14 +1000 Subject: [PATCH 1/7] Add `custom-fallback` feature --- Cargo.toml | 7 + src/backends.rs | 152 ++++++++++++++------ src/backends/apple_other.rs | 20 +-- src/backends/custom.rs | 14 +- src/backends/efi_rng.rs | 44 +++--- src/backends/esp_idf.rs | 24 ++-- src/backends/fallback.rs | 70 +++++++++ src/backends/fuchsia.rs | 14 +- src/backends/getentropy.rs | 20 +-- src/backends/getrandom.rs | 28 ++-- src/backends/hermit.rs | 68 ++++----- src/backends/linux_android_with_fallback.rs | 54 +++---- src/backends/linux_raw.rs | 41 +++--- src/backends/netbsd.rs | 36 ++--- src/backends/rdrand.rs | 44 +++--- src/backends/rndr.rs | 48 ++++--- src/backends/solaris.rs | 36 ++--- src/backends/solid.rs | 21 +-- src/backends/unsupported.rs | 19 ++- src/backends/use_file.rs | 20 +-- src/backends/vxworks.rs | 41 +++--- src/backends/wasi_p1.rs | 34 ++--- src/backends/wasi_p2.rs | 74 +++++----- src/backends/wasm_js.rs | 15 +- src/backends/windows.rs | 26 ++-- src/backends/windows7.rs | 27 ++-- src/lib.rs | 9 +- 27 files changed, 615 insertions(+), 391 deletions(-) create mode 100644 src/backends/fallback.rs diff --git a/Cargo.toml b/Cargo.toml index 4a982ee7..6864bdb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,12 @@ rustc-dep-of-std = ["dep:compiler_builtins", "dep:core"] # i.e. avoid unconditionally enabling it in library crates. wasm_js = ["dep:wasm-bindgen", "dep:js-sys"] +# Optional backend: custom-fallback +# This flag allows a custom fallback backend. +# This will be used as a last-resort instead of raising a compiler error when +# no other backend is available. +custom-fallback = [] + [dependencies] cfg-if = "1" @@ -90,6 +96,7 @@ check-cfg = [ 'cfg(getrandom_test_linux_without_fallback)', 'cfg(getrandom_test_netbsd_fallback)', 'cfg(target_os, values("cygwin"))', # TODO(MSRV 1.86): Remove this. + 'cfg(getrandom_no_external_fallback)', ] [package.metadata.docs.rs] diff --git a/src/backends.rs b/src/backends.rs index bf0381d2..c0b7a576 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -7,33 +7,59 @@ //! The function MUST NOT ever write uninitialized bytes into `dest`, //! regardless of what value it returns. +use core::mem::MaybeUninit; + +use crate::Error; + +/// If an external fallback _may_ be used, use it. +/// If the fallback may not be used, the provided token trees will be included instead. +/// +/// This is extracted into its own macro to allow using a fallback in multiple branches. +/// For example, on unsupported WASI targets. +#[allow(unused)] +// May be unused if a fallback is not required. +macro_rules! use_fallback_or { + ($($tt: tt)*) => { + cfg_if! { + if #[cfg(feature = "custom-fallback")] { + mod fallback; + pub use fallback::Implementation; + } else { + $($tt)* + } + } + } +} + cfg_if! { if #[cfg(getrandom_backend = "custom")] { mod custom; - pub use custom::*; + pub use custom::Implementation; } else if #[cfg(getrandom_backend = "linux_getrandom")] { mod getrandom; mod sanitizer; - pub use getrandom::*; + pub use getrandom::Implementation; } else if #[cfg(getrandom_backend = "linux_raw")] { mod linux_raw; mod sanitizer; - pub use linux_raw::*; + pub use linux_raw::Implementation; } else if #[cfg(getrandom_backend = "rdrand")] { mod rdrand; - pub use rdrand::*; + pub use rdrand::Implementation; } else if #[cfg(getrandom_backend = "rndr")] { mod rndr; - pub use rndr::*; + pub use rndr::Implementation; } else if #[cfg(getrandom_backend = "efi_rng")] { mod efi_rng; - pub use efi_rng::*; + pub use efi_rng::Implementation; } else if #[cfg(all(getrandom_backend = "wasm_js"))] { cfg_if! { if #[cfg(feature = "wasm_js")] { mod wasm_js; - pub use wasm_js::*; + pub use wasm_js::Implementation; } else { + // Fallback not used here as the user indicated they intended on using "wasm_js", + // but failed to activate the feature flag. compile_error!(concat!( "The \"wasm_js\" backend requires the `wasm_js` feature \ for `getrandom`. For more information see: \ @@ -43,14 +69,14 @@ cfg_if! { } } else if #[cfg(getrandom_backend = "unsupported")] { mod unsupported; - pub use unsupported::*; + pub use unsupported::Implementation; } else if #[cfg(all(target_os = "linux", target_env = ""))] { mod linux_raw; mod sanitizer; - pub use linux_raw::*; + pub use linux_raw::Implementation; } else if #[cfg(target_os = "espidf")] { mod esp_idf; - pub use esp_idf::*; + pub use esp_idf::Implementation; } else if #[cfg(any( target_os = "haiku", target_os = "redox", @@ -58,7 +84,7 @@ cfg_if! { target_os = "aix", ))] { mod use_file; - pub use use_file::*; + pub use use_file::Implementation; } else if #[cfg(any( target_os = "macos", target_os = "openbsd", @@ -66,7 +92,7 @@ cfg_if! { target_os = "emscripten", ))] { mod getentropy; - pub use getentropy::*; + pub use getentropy::Implementation; } else if #[cfg(any( // Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets // level 21 (Lollipop) [1], while `getrandom(2)` was added only in @@ -107,7 +133,7 @@ cfg_if! { mod use_file; mod linux_android_with_fallback; mod sanitizer; - pub use linux_android_with_fallback::*; + pub use linux_android_with_fallback::Implementation; } else if #[cfg(any( target_os = "android", target_os = "linux", @@ -123,16 +149,16 @@ cfg_if! { mod getrandom; #[cfg(any(target_os = "android", target_os = "linux"))] mod sanitizer; - pub use getrandom::*; + pub use getrandom::Implementation; } else if #[cfg(target_os = "solaris")] { mod solaris; - pub use solaris::*; + pub use solaris::Implementation; } else if #[cfg(target_os = "netbsd")] { mod netbsd; - pub use netbsd::*; + pub use netbsd::Implementation; } else if #[cfg(target_os = "fuchsia")] { mod fuchsia; - pub use fuchsia::*; + pub use fuchsia::Implementation; } else if #[cfg(any( target_os = "ios", target_os = "visionos", @@ -140,52 +166,94 @@ cfg_if! { target_os = "tvos", ))] { mod apple_other; - pub use apple_other::*; + pub use apple_other::Implementation; } else if #[cfg(all(target_arch = "wasm32", target_os = "wasi"))] { cfg_if! { if #[cfg(target_env = "p1")] { mod wasi_p1; - pub use wasi_p1::*; + pub use wasi_p1::Implementation; } else if #[cfg(target_env = "p2")] { mod wasi_p2; - pub use wasi_p2::*; + pub use wasi_p2::Implementation; } else { - compile_error!( - "Unknown version of WASI (only previews 1 and 2 are supported) \ - or Rust version older than 1.80 was used" - ); + use_fallback_or! { + compile_error!( + "Unknown version of WASI (only previews 1 and 2 are supported) \ + or Rust version older than 1.80 was used" + ); + } } } } else if #[cfg(target_os = "hermit")] { mod hermit; - pub use hermit::*; + pub use hermit::Implementation; } else if #[cfg(target_os = "vxworks")] { mod vxworks; - pub use vxworks::*; + pub use vxworks::Implementation; } else if #[cfg(target_os = "solid_asp3")] { mod solid; - pub use solid::*; + pub use solid::Implementation; } else if #[cfg(all(windows, any(target_vendor = "win7", getrandom_windows_legacy)))] { mod windows7; - pub use windows7::*; + pub use windows7::Implementation; } else if #[cfg(windows)] { mod windows; - pub use windows::*; + pub use windows::Implementation; } else if #[cfg(all(target_arch = "x86_64", target_env = "sgx"))] { mod rdrand; - pub use rdrand::*; - } else if #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] { - compile_error!(concat!( - "The wasm32-unknown-unknown targets are not supported by default; \ - you may need to enable the \"wasm_js\" configuration flag. Note \ - that enabling the `wasm_js` feature flag alone is insufficient. \ - For more information see: \ - https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#webassembly-support" - )); + pub use rdrand::Implementation; } else { - compile_error!(concat!( - "target is not supported. You may need to define a custom backend see: \ - https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#custom-backend" - )); + use_fallback_or! { + cfg_if! { + if #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))] { + compile_error!(concat!( + "The wasm32-unknown-unknown targets are not supported by default; \ + you may need to enable the \"wasm_js\" configuration flag. Note \ + that enabling the `wasm_js` feature flag alone is insufficient. \ + For more information see: \ + https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#webassembly-support" + )); + } else { + compile_error!(concat!( + "target is not supported. You may need to define a custom backend see: \ + https://docs.rs/getrandom/", env!("CARGO_PKG_VERSION"), "/#custom-backend" + )); + } + } + } + } +} + +/// Provides entropy suitable for CSPRNG. +/// +/// # Safety +/// +/// Implementors must uphold the contracts of all methods provided by this trait. +pub unsafe trait Backend { + /// Fill `dest` with pseudo-random values _or_ return an [`Error`]. + /// + /// # Implementors + /// + /// - An implementation of this method _must_ totally fill `dest`. + /// If this cannot be done, an error should be returned. + /// - The values filling `dest` _must_ be cryptographically secure. + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error>; + + /// Provides a pseudo-random [`u32`]. + /// + /// # Implementors + /// + /// - The [`u32`] returned _must_ be cryptographically secure. + #[inline] + fn u32() -> Result { + crate::util::inner_u32() + } + + /// Provides a pseudo-random [`u64`]. + /// + /// - The [`u64`] returned _must_ be cryptographically secure. + #[inline] + fn u64() -> Result { + crate::util::inner_u64() } } diff --git a/src/backends/apple_other.rs b/src/backends/apple_other.rs index c7b51c0e..88f4819e 100644 --- a/src/backends/apple_other.rs +++ b/src/backends/apple_other.rs @@ -2,16 +2,18 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; +pub struct Implementation; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let dst_ptr = dest.as_mut_ptr().cast::(); - let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) }; - if ret == libc::kCCSuccess { - Ok(()) - } else { - Err(Error::IOS_RANDOM_GEN) +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let dst_ptr = dest.as_mut_ptr().cast::(); + let ret = unsafe { libc::CCRandomGenerateBytes(dst_ptr, dest.len()) }; + if ret == libc::kCCSuccess { + Ok(()) + } else { + Err(Error::IOS_RANDOM_GEN) + } } } diff --git a/src/backends/custom.rs b/src/backends/custom.rs index c505481a..a2d1f5ad 100644 --- a/src/backends/custom.rs +++ b/src/backends/custom.rs @@ -2,12 +2,14 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; +pub struct Implementation; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - extern "Rust" { - fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>; +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v03_custom(dest: *mut u8, len: usize) -> Result<(), Error>; + } + unsafe { __getrandom_v03_custom(dest.as_mut_ptr().cast(), dest.len()) } } - unsafe { __getrandom_v03_custom(dest.as_mut_ptr().cast(), dest.len()) } } diff --git a/src/backends/efi_rng.rs b/src/backends/efi_rng.rs index 768c8cc8..196336bb 100644 --- a/src/backends/efi_rng.rs +++ b/src/backends/efi_rng.rs @@ -12,8 +12,6 @@ use r_efi::{ extern crate std; -pub use crate::util::{inner_u32, inner_u64}; - #[cfg(not(target_os = "uefi"))] compile_error!("`efi_rng` backend can be enabled only for UEFI targets!"); @@ -94,27 +92,31 @@ fn init() -> Result, Error> { Err(Error::NO_RNG_HANDLE) } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) { - Some(p) => p, - None => init()?, - }; +pub struct Implementation; - let mut alg_guid = rng::ALGORITHM_RAW; - let ret = unsafe { - ((*protocol.as_ptr()).get_rng)( - protocol.as_ptr(), - &mut alg_guid, - dest.len(), - dest.as_mut_ptr().cast::(), - ) - }; +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let protocol = match NonNull::new(RNG_PROTOCOL.load(Relaxed)) { + Some(p) => p, + None => init()?, + }; - if ret.is_error() { - Err(Error::from_uefi_code(ret.as_usize())) - } else { - Ok(()) + let mut alg_guid = rng::ALGORITHM_RAW; + let ret = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + &mut alg_guid, + dest.len(), + dest.as_mut_ptr().cast::(), + ) + }; + + if ret.is_error() { + Err(Error::from_uefi_code(ret.as_usize())) + } else { + Ok(()) + } } } diff --git a/src/backends/esp_idf.rs b/src/backends/esp_idf.rs index 4d1689dc..ad868db6 100644 --- a/src/backends/esp_idf.rs +++ b/src/backends/esp_idf.rs @@ -2,20 +2,22 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - extern "C" { fn esp_fill_random(buf: *mut c_void, len: usize) -> u32; } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) - // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: - // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html - // - // However tracking if some of these entropy sources is enabled is way too difficult to implement here - unsafe { esp_fill_random(dest.as_mut_ptr().cast(), dest.len()) }; +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Not that NOT enabling WiFi, BT, or the voltage noise entropy source (via `bootloader_random_enable`) + // will cause ESP-IDF to return pseudo-random numbers based on the voltage noise entropy, after the initial boot process: + // https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html + // + // However tracking if some of these entropy sources is enabled is way too difficult to implement here + unsafe { esp_fill_random(dest.as_mut_ptr().cast(), dest.len()) }; - Ok(()) + Ok(()) + } } diff --git a/src/backends/fallback.rs b/src/backends/fallback.rs new file mode 100644 index 00000000..7c04195a --- /dev/null +++ b/src/backends/fallback.rs @@ -0,0 +1,70 @@ +//! An implementation which calls out to an externally defined function if no other is provided. + +#[cfg(getrandom_no_external_fallback)] +compile_error! { + "getrandom does not have a suitable backend for this target, requiring the use of a fallback.\ + However, `getrandom_no_external_fallback` has been set, indicating the use of a fallback is prohibited.\ + Consider providing a `custom` backend." +} + +use crate::Error; +use core::mem::MaybeUninit; + +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + extern "Rust" { + fn __getrandom_v03_fallback_fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error>; + } + unsafe { __getrandom_v03_fallback_fill_uninit(dest) } + } + + #[inline] + fn u32() -> Result { + extern "Rust" { + fn __getrandom_v03_fallback_u32() -> Result; + } + unsafe { __getrandom_v03_fallback_u32() } + } + + #[inline] + fn u64() -> Result { + extern "Rust" { + fn __getrandom_v03_fallback_u64() -> Result; + } + unsafe { __getrandom_v03_fallback_u64() } + } +} + +/// Sets the fallback [`Backend`](crate::Backend). +/// +/// # Examples +/// +/// ```ignore +/// struct MyBackend; +/// +/// impl Backend for MyBackend { /* ... */ } +/// +/// set_backend!(MyBackend); +/// ``` +#[macro_export] +macro_rules! set_backend { + ($t: ty) => { + #[no_mangle] + extern "Rust" fn __getrandom_v03_fallback_fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + <$t as $crate::Backend>::fill_uninit(dest) + } + + #[no_mangle] + extern "Rust" fn __getrandom_v03_fallback_u32() -> Result { + <$t as $crate::Backend>::u32() + } + + #[no_mangle] + extern "Rust" fn __getrandom_v03_fallback_u64() -> Result { + <$t as $crate::Backend>::u64() + } + }; +} \ No newline at end of file diff --git a/src/backends/fuchsia.rs b/src/backends/fuchsia.rs index b5f1ade5..b9a47a31 100644 --- a/src/backends/fuchsia.rs +++ b/src/backends/fuchsia.rs @@ -2,15 +2,17 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - #[link(name = "zircon")] extern "C" { fn zx_cprng_draw(buffer: *mut u8, length: usize); } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - unsafe { zx_cprng_draw(dest.as_mut_ptr().cast::(), dest.len()) } - Ok(()) +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + unsafe { zx_cprng_draw(dest.as_mut_ptr().cast::(), dest.len()) } + Ok(()) + } } diff --git a/src/backends/getentropy.rs b/src/backends/getentropy.rs index ed181f01..d2fdc6a4 100644 --- a/src/backends/getentropy.rs +++ b/src/backends/getentropy.rs @@ -10,18 +10,20 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(256) { - let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) }; - if ret != 0 { - return Err(util_libc::last_os_error()); +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(256) { + let ret = unsafe { libc::getentropy(chunk.as_mut_ptr().cast::(), chunk.len()) }; + if ret != 0 { + return Err(util_libc::last_os_error()); + } } + Ok(()) } - Ok(()) } diff --git a/src/backends/getrandom.rs b/src/backends/getrandom.rs index f3c33c3c..5a16271c 100644 --- a/src/backends/getrandom.rs +++ b/src/backends/getrandom.rs @@ -18,22 +18,24 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::sys_fill_exact(dest, |buf| unsafe { - let ret = libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0); +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + util_libc::sys_fill_exact(dest, |buf| unsafe { + let ret = libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0); - #[cfg(any(target_os = "android", target_os = "linux"))] - #[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. - unsafe { - super::sanitizer::unpoison_linux_getrandom_result(buf, ret); - } + #[cfg(any(target_os = "android", target_os = "linux"))] + #[allow(unused_unsafe)] // TODO(MSRV 1.65): Remove this. + unsafe { + super::sanitizer::unpoison_linux_getrandom_result(buf, ret); + } - ret - }) + ret + }) + } } diff --git a/src/backends/hermit.rs b/src/backends/hermit.rs index 34d7cdbb..3e45f020 100644 --- a/src/backends/hermit.rs +++ b/src/backends/hermit.rs @@ -12,42 +12,46 @@ extern "C" { fn sys_secure_rand64(value: *mut u64) -> i32; } -#[inline] -pub fn inner_u32() -> Result { - let mut res = MaybeUninit::uninit(); - let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) }; - match ret { - 0 => Ok(unsafe { res.assume_init() }), - -1 => Err(Error::UNSUPPORTED), - _ => Err(Error::UNEXPECTED), +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !dest.is_empty() { + let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::(), dest.len(), 0) }; + match res { + res if res > 0 => { + let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; + dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + } + code => { + let code = i32::try_from(code).map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_neg_error_code(code)); + } + } + } + Ok(()) } -} -#[inline] -pub fn inner_u64() -> Result { - let mut res = MaybeUninit::uninit(); - let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) }; - match ret { - 0 => Ok(unsafe { res.assume_init() }), - -1 => Err(Error::UNSUPPORTED), - _ => Err(Error::UNEXPECTED), + #[inline] + fn u32() -> Result { + let mut res = MaybeUninit::uninit(); + let ret = unsafe { sys_secure_rand32(res.as_mut_ptr()) }; + match ret { + 0 => Ok(unsafe { res.assume_init() }), + -1 => Err(Error::UNSUPPORTED), + _ => Err(Error::UNEXPECTED), + } } -} -#[inline] -pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { - while !dest.is_empty() { - let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::(), dest.len(), 0) }; - match res { - res if res > 0 => { - let len = usize::try_from(res).map_err(|_| Error::UNEXPECTED)?; - dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; - } - code => { - let code = i32::try_from(code).map_err(|_| Error::UNEXPECTED)?; - return Err(Error::from_neg_error_code(code)); - } + #[inline] + fn u64() -> Result { + let mut res = MaybeUninit::uninit(); + let ret = unsafe { sys_secure_rand64(res.as_mut_ptr()) }; + match ret { + 0 => Ok(unsafe { res.assume_init() }), + -1 => Err(Error::UNSUPPORTED), + _ => Err(Error::UNEXPECTED), } } - Ok(()) } diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index 6c9dd065..67960af0 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -9,8 +9,6 @@ use core::{ }; use use_file::util_libc; -pub use crate::util::{inner_u32, inner_u64}; - type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; /// Sentinel value which indicates that `libc::getrandom` either not available, @@ -72,32 +70,36 @@ fn init() -> NonNull { // Prevent inlining of the fallback implementation #[inline(never)] fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { - use_file::fill_inner(dest) + use_file::Implementation::fill_uninit(dest) } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Despite being only a single atomic variable, we still cannot always use - // Ordering::Relaxed, as we need to make sure a successful call to `init` - // is "ordered before" any data read through the returned pointer (which - // occurs when the function is called). Our implementation mirrors that of - // the one in libstd, meaning that the use of non-Relaxed operations is - // probably unnecessary. - let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); - let fptr = match NonNull::new(raw_ptr) { - Some(p) => p, - None => init(), - }; +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); + let fptr = match NonNull::new(raw_ptr) { + Some(p) => p, + None => init(), + }; - if fptr == NOT_AVAILABLE { - use_file_fallback(dest) - } else { - // note: `transmute` is currently the only way to convert a pointer into a function reference - let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { - let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); - sanitizer::unpoison_linux_getrandom_result(buf, ret); - ret - }) + if fptr == NOT_AVAILABLE { + use_file_fallback(dest) + } else { + // note: `transmute` is currently the only way to convert a pointer into a function reference + let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); + sanitizer::unpoison_linux_getrandom_result(buf, ret); + ret + }) + } } } diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index f199e201..92fd9d2e 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -1,6 +1,5 @@ //! Implementation for Linux / Android using `asm!`-based syscalls. use super::sanitizer; -pub use crate::util::{inner_u32, inner_u64}; use crate::{Error, MaybeUninit}; #[cfg(not(any(target_os = "android", target_os = "linux")))] @@ -111,26 +110,30 @@ unsafe fn getrandom_syscall(buf: *mut u8, buflen: usize, flags: u32) -> isize { r0 } -#[inline] -pub fn fill_inner(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Value of this error code is stable across all target arches. - const EINTR: isize = -4; +pub struct Implementation; - loop { - let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; - unsafe { sanitizer::unpoison_linux_getrandom_result(dest, ret) }; - match usize::try_from(ret) { - Ok(0) => return Err(Error::UNEXPECTED), - Ok(len) => { - dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; - if dest.is_empty() { - return Ok(()); +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Value of this error code is stable across all target arches. + const EINTR: isize = -4; + + loop { + let ret = unsafe { getrandom_syscall(dest.as_mut_ptr().cast(), dest.len(), 0) }; + unsafe { sanitizer::unpoison_linux_getrandom_result(dest, ret) }; + match usize::try_from(ret) { + Ok(0) => return Err(Error::UNEXPECTED), + Ok(len) => { + dest = dest.get_mut(len..).ok_or(Error::UNEXPECTED)?; + if dest.is_empty() { + return Ok(()); + } + } + Err(_) if ret == EINTR => continue, + Err(_) => { + let code = i32::try_from(ret).map_err(|_| Error::UNEXPECTED)?; + return Err(Error::from_neg_error_code(code)); } - } - Err(_) if ret == EINTR => continue, - Err(_) => { - let code = i32::try_from(ret).map_err(|_| Error::UNEXPECTED)?; - return Err(Error::from_neg_error_code(code)); } } } diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs index f228a8b1..c0cd5fcf 100644 --- a/src/backends/netbsd.rs +++ b/src/backends/netbsd.rs @@ -12,8 +12,6 @@ use core::{ sync::atomic::{AtomicPtr, Ordering}, }; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; @@ -59,20 +57,24 @@ fn init() -> *mut c_void { ptr } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Despite being only a single atomic variable, we still cannot always use - // Ordering::Relaxed, as we need to make sure a successful call to `init` - // is "ordered before" any data read through the returned pointer (which - // occurs when the function is called). Our implementation mirrors that of - // the one in libstd, meaning that the use of non-Relaxed operations is - // probably unnecessary. - let mut fptr = GETRANDOM.load(Ordering::Acquire); - if fptr.is_null() { - fptr = init(); +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let mut fptr = GETRANDOM.load(Ordering::Acquire); + if fptr.is_null() { + fptr = init(); + } + let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) + }) } - let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { - fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) - }) } diff --git a/src/backends/rdrand.rs b/src/backends/rdrand.rs index 609fcc38..2d98fbaf 100644 --- a/src/backends/rdrand.rs +++ b/src/backends/rdrand.rs @@ -147,31 +147,35 @@ unsafe fn rdrand_u64() -> Option { Some((u64::from(a) << 32) | u64::from(b)) } -#[inline] -pub fn inner_u32() -> Result { - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND) -} -#[inline] -pub fn inner_u64() -> Result { - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); + #[inline] + fn u32() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u32() }.ok_or(Error::FAILED_RDRAND) } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND) -} -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - if !RDRAND_GOOD.unsync_init(is_rdrand_good) { - return Err(Error::NO_RDRAND); + #[inline] + fn u64() -> Result { + if !RDRAND_GOOD.unsync_init(is_rdrand_good) { + return Err(Error::NO_RDRAND); + } + // SAFETY: After this point, we know rdrand is supported. + unsafe { rdrand_u64() }.ok_or(Error::FAILED_RDRAND) } - // SAFETY: After this point, we know rdrand is supported. - unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND) } impl Error { diff --git a/src/backends/rndr.rs b/src/backends/rndr.rs index eea741a2..3dd3bb16 100644 --- a/src/backends/rndr.rs +++ b/src/backends/rndr.rs @@ -108,33 +108,37 @@ fn is_rndr_available() -> bool { } } -#[inline] -pub fn inner_u32() -> Result { - if !is_rndr_available() { - return Err(Error::RNDR_NOT_AVAILABLE); +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + if !is_rndr_available() { + return Err(Error::RNDR_NOT_AVAILABLE); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + unsafe { rndr_fill(dest).ok_or(Error::RNDR_FAILURE) } } - // SAFETY: after this point, we know the `rand` target feature is enabled - let res = unsafe { rndr() }; - res.map(truncate).ok_or(Error::RNDR_FAILURE) -} -#[inline] -pub fn inner_u64() -> Result { - if !is_rndr_available() { - return Err(Error::RNDR_NOT_AVAILABLE); + #[inline] + fn u32() -> Result { + if !is_rndr_available() { + return Err(Error::RNDR_NOT_AVAILABLE); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.map(truncate).ok_or(Error::RNDR_FAILURE) } - // SAFETY: after this point, we know the `rand` target feature is enabled - let res = unsafe { rndr() }; - res.ok_or(Error::RNDR_FAILURE) -} -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - if !is_rndr_available() { - return Err(Error::RNDR_NOT_AVAILABLE); + #[inline] + fn u64() -> Result { + if !is_rndr_available() { + return Err(Error::RNDR_NOT_AVAILABLE); + } + // SAFETY: after this point, we know the `rand` target feature is enabled + let res = unsafe { rndr() }; + res.ok_or(Error::RNDR_FAILURE) } - // SAFETY: after this point, we know the `rand` target feature is enabled - unsafe { rndr_fill(dest).ok_or(Error::RNDR_FAILURE) } } impl Error { diff --git a/src/backends/solaris.rs b/src/backends/solaris.rs index c27f91a5..8048dea6 100644 --- a/src/backends/solaris.rs +++ b/src/backends/solaris.rs @@ -15,28 +15,30 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] mod util_libc; const MAX_BYTES: usize = 1024; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - for chunk in dest.chunks_mut(MAX_BYTES) { - let ptr = chunk.as_mut_ptr().cast::(); - let ret = unsafe { libc::getrandom(ptr, chunk.len(), libc::GRND_RANDOM) }; - // In case the man page has a typo, we also check for negative ret. - // If getrandom(2) succeeds, it should have completely filled chunk. - match usize::try_from(ret) { - // Good. Keep going. - Ok(ret) if ret == chunk.len() => {} - // The syscall failed. - Ok(0) => return Err(util_libc::last_os_error()), - // All other cases should be impossible. - _ => return Err(Error::UNEXPECTED), +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + for chunk in dest.chunks_mut(MAX_BYTES) { + let ptr = chunk.as_mut_ptr().cast::(); + let ret = unsafe { libc::getrandom(ptr, chunk.len(), libc::GRND_RANDOM) }; + // In case the man page has a typo, we also check for negative ret. + // If getrandom(2) succeeds, it should have completely filled chunk. + match usize::try_from(ret) { + // Good. Keep going. + Ok(ret) if ret == chunk.len() => {} + // The syscall failed. + Ok(0) => return Err(util_libc::last_os_error()), + // All other cases should be impossible. + _ => return Err(Error::UNEXPECTED), + } } + Ok(()) } - Ok(()) } diff --git a/src/backends/solid.rs b/src/backends/solid.rs index caa773f8..5aad3524 100644 --- a/src/backends/solid.rs +++ b/src/backends/solid.rs @@ -2,18 +2,21 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - extern "C" { pub fn SOLID_RNG_SampleRandomBytes(buffer: *mut u8, length: usize) -> i32; } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let ret = unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr().cast::(), dest.len()) }; - if ret >= 0 { - Ok(()) - } else { - Err(Error::from_neg_error_code(ret)) +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let ret = + unsafe { SOLID_RNG_SampleRandomBytes(dest.as_mut_ptr().cast::(), dest.len()) }; + if ret >= 0 { + Ok(()) + } else { + Err(Error::from_neg_error_code(ret)) + } } } diff --git a/src/backends/unsupported.rs b/src/backends/unsupported.rs index 4ea381fc..2c6065d1 100644 --- a/src/backends/unsupported.rs +++ b/src/backends/unsupported.rs @@ -2,8 +2,21 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; +pub struct Implementation; -pub fn fill_inner(_dest: &mut [MaybeUninit]) -> Result<(), Error> { - Err(Error::UNSUPPORTED) +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + Err(Error::UNSUPPORTED) + } + + #[inline] + fn u32() -> Result { + Err(Error::UNSUPPORTED) + } + + #[inline] + fn u64() -> Result { + Err(Error::UNSUPPORTED) + } } diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs index 7b48d433..8a7142b9 100644 --- a/src/backends/use_file.rs +++ b/src/backends/use_file.rs @@ -40,15 +40,19 @@ const FD_ONGOING_INIT: libc::c_int = -2; // `Ordering::Acquire` to synchronize with it. static FD: AtomicI32 = AtomicI32::new(FD_UNINIT); -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let mut fd = FD.load(Ordering::Acquire); - if fd == FD_UNINIT || fd == FD_ONGOING_INIT { - fd = open_or_wait()?; +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let mut fd = FD.load(Ordering::Acquire); + if fd == FD_UNINIT || fd == FD_ONGOING_INIT { + fd = open_or_wait()?; + } + util_libc::sys_fill_exact(dest, |buf| unsafe { + libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) + }) } - util_libc::sys_fill_exact(dest, |buf| unsafe { - libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) - }) } /// Open a file in read-only mode. diff --git a/src/backends/vxworks.rs b/src/backends/vxworks.rs index 5f5e6773..28b73570 100644 --- a/src/backends/vxworks.rs +++ b/src/backends/vxworks.rs @@ -9,8 +9,6 @@ use core::{ #[path = "../util_libc.rs"] mod util_libc; -pub use crate::util::{inner_u32, inner_u64}; - static RNG_INIT: AtomicBool = AtomicBool::new(false); #[cold] @@ -26,26 +24,31 @@ fn init() -> Result<(), Error> { Ok(()) } -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - while !RNG_INIT.load(Relaxed) { - init()?; - } +pub struct Implementation; - // Prevent overflow of i32 - let chunk_size = usize::try_from(i32::MAX).expect("VxWorks does not support 16-bit targets"); - for chunk in dest.chunks_mut(chunk_size) { - let chunk_len: libc::c_int = chunk - .len() - .try_into() - .expect("chunk size is bounded by i32::MAX"); - let p: *mut libc::c_uchar = chunk.as_mut_ptr().cast(); - let ret = unsafe { libc::randABytes(p, chunk_len) }; - if ret != 0 { - return Err(util_libc::last_os_error()); +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + while !RNG_INIT.load(Relaxed) { + init()?; } + + // Prevent overflow of i32 + let chunk_size = + usize::try_from(i32::MAX).expect("VxWorks does not support 16-bit targets"); + for chunk in dest.chunks_mut(chunk_size) { + let chunk_len: libc::c_int = chunk + .len() + .try_into() + .expect("chunk size is bounded by i32::MAX"); + let p: *mut libc::c_uchar = chunk.as_mut_ptr().cast(); + let ret = unsafe { libc::randABytes(p, chunk_len) }; + if ret != 0 { + return Err(util_libc::last_os_error()); + } + } + Ok(()) } - Ok(()) } impl Error { diff --git a/src/backends/wasi_p1.rs b/src/backends/wasi_p1.rs index 25b5ca3b..a3565b55 100644 --- a/src/backends/wasi_p1.rs +++ b/src/backends/wasi_p1.rs @@ -2,8 +2,6 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - // This linking is vendored from the wasi crate: // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2344-2350 #[link(wasm_import_module = "wasi_snapshot_preview1")] @@ -15,18 +13,22 @@ extern "C" { /// https://github.com/WebAssembly/WASI/blob/38454e9e/legacy/preview1/witx/typenames.witx#L34-L39 const MAX_ERROR_CODE: i32 = u16::MAX as i32; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Based on the wasi code: - // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 - // Note that size of an allocated object can not be bigger than isize::MAX bytes. - // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. - #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] - let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) }; - match ret { - 0 => Ok(()), - // WASI functions should return positive error codes which are smaller than `MAX_ERROR_CODE` - code if code <= MAX_ERROR_CODE => Err(Error::from_neg_error_code(-code)), - _ => Err(Error::UNEXPECTED), +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Based on the wasi code: + // https://docs.rs/wasi/0.11.0+wasi-snapshot-preview1/src/wasi/lib_generated.rs.html#2046-2062 + // Note that size of an allocated object can not be bigger than isize::MAX bytes. + // WASI 0.1 supports only 32-bit WASM, so casting length to `i32` is safe. + #[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)] + let ret = unsafe { random_get(dest.as_mut_ptr() as i32, dest.len() as i32) }; + match ret { + 0 => Ok(()), + // WASI functions should return positive error codes which are smaller than `MAX_ERROR_CODE` + code if code <= MAX_ERROR_CODE => Err(Error::from_neg_error_code(-code)), + _ => Err(Error::UNEXPECTED), + } } -} +} \ No newline at end of file diff --git a/src/backends/wasi_p2.rs b/src/backends/wasi_p2.rs index 63bd2d7c..7f21e38b 100644 --- a/src/backends/wasi_p2.rs +++ b/src/backends/wasi_p2.rs @@ -3,48 +3,52 @@ use crate::Error; use core::mem::MaybeUninit; use wasi::random::random::get_random_u64; -#[inline] -pub fn inner_u32() -> Result { - let val = get_random_u64(); - Ok(crate::util::truncate(val)) -} - -#[inline] -pub fn inner_u64() -> Result { - Ok(get_random_u64()) -} - -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - use core::ptr::copy_nonoverlapping; - use wasi::random::random::get_random_u64; +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + use core::ptr::copy_nonoverlapping; + use wasi::random::random::get_random_u64; + + let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; + + // We use `get_random_u64` instead of `get_random_bytes` because the latter creates + // an allocation due to the Wit IDL [restrictions][0]. This should be fine since + // the main use case of `getrandom` is seed generation. + // + // [0]: https://github.com/WebAssembly/wasi-random/issues/27 + if !prefix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); + } + } - let (prefix, chunks, suffix) = unsafe { dest.align_to_mut::>() }; + for dst in chunks { + dst.write(get_random_u64()); + } - // We use `get_random_u64` instead of `get_random_bytes` because the latter creates - // an allocation due to the Wit IDL [restrictions][0]. This should be fine since - // the main use case of `getrandom` is seed generation. - // - // [0]: https://github.com/WebAssembly/wasi-random/issues/27 - if !prefix.is_empty() { - let val = get_random_u64(); - let src = (&val as *const u64).cast(); - unsafe { - copy_nonoverlapping(src, prefix.as_mut_ptr(), prefix.len()); + if !suffix.is_empty() { + let val = get_random_u64(); + let src = (&val as *const u64).cast(); + unsafe { + copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); + } } - } - for dst in chunks { - dst.write(get_random_u64()); + Ok(()) } - if !suffix.is_empty() { + #[inline] + fn u32() -> Result { let val = get_random_u64(); - let src = (&val as *const u64).cast(); - unsafe { - copy_nonoverlapping(src, suffix.as_mut_ptr(), suffix.len()); - } + Ok(crate::util::truncate(val)) } - Ok(()) + #[inline] + fn u64() -> Result { + Ok(get_random_u64()) + } } diff --git a/src/backends/wasm_js.rs b/src/backends/wasm_js.rs index 1320d9fc..c6cce463 100644 --- a/src/backends/wasm_js.rs +++ b/src/backends/wasm_js.rs @@ -2,8 +2,6 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] compile_error!("`wasm_js` backend can be enabled only for OS-less WASM targets!"); @@ -15,7 +13,7 @@ const MAX_BUFFER_SIZE: usize = 65536; #[cfg(not(target_feature = "atomics"))] #[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { +fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { for chunk in dest.chunks_mut(MAX_BUFFER_SIZE) { if get_random_values(chunk).is_err() { return Err(Error::WEB_CRYPTO); @@ -25,7 +23,7 @@ pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } #[cfg(target_feature = "atomics")] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { +fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getRandomValues does not work with all types of WASM memory, // so we initially write to browser memory to avoid exceptions. let buf_len = usize::min(dest.len(), MAX_BUFFER_SIZE); @@ -70,3 +68,12 @@ impl Error { /// The environment does not support the Web Crypto API. pub(crate) const WEB_CRYPTO: Error = Self::new_internal(10); } + +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + fill_inner(dest) + } +} diff --git a/src/backends/windows.rs b/src/backends/windows.rs index b5cd504f..1e700b79 100644 --- a/src/backends/windows.rs +++ b/src/backends/windows.rs @@ -23,8 +23,6 @@ use crate::Error; use core::mem::MaybeUninit; -pub use crate::util::{inner_u32, inner_u64}; - // Binding to the Windows.Win32.Security.Cryptography.ProcessPrng API. As // bcryptprimitives.dll lacks an import library, we use "raw-dylib". This // was added in Rust 1.65 for x86_64/aarch64 and in Rust 1.71 for x86. @@ -48,14 +46,18 @@ extern "system" { type BOOL = core::ffi::c_int; // MSRV 1.64, similarly OK for this backend. const TRUE: BOOL = 1; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - let result = unsafe { ProcessPrng(dest.as_mut_ptr().cast::(), dest.len()) }; - // Since Windows 10, calls to the user-mode RNG are guaranteed to never - // fail during runtime (rare windows W); `ProcessPrng` will only ever - // return 1 (which is how windows represents TRUE). - // See the bottom of page 6 of the aforementioned Windows RNG - // whitepaper for more information. - debug_assert!(result == TRUE); - Ok(()) +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + let result = unsafe { ProcessPrng(dest.as_mut_ptr().cast::(), dest.len()) }; + // Since Windows 10, calls to the user-mode RNG are guaranteed to never + // fail during runtime (rare windows W); `ProcessPrng` will only ever + // return 1 (which is how windows represents TRUE). + // See the bottom of page 6 of the aforementioned Windows RNG + // whitepaper for more information. + debug_assert!(result == TRUE); + Ok(()) + } } diff --git a/src/backends/windows7.rs b/src/backends/windows7.rs index 8a353a9f..02a253b6 100644 --- a/src/backends/windows7.rs +++ b/src/backends/windows7.rs @@ -12,8 +12,6 @@ use crate::Error; use core::{ffi::c_void, mem::MaybeUninit}; -pub use crate::util::{inner_u32, inner_u64}; - // Binding to the Windows.Win32.Security.Authentication.Identity.RtlGenRandom // API. Don't use windows-targets as it doesn't support Windows 7 targets. #[link(name = "advapi32")] @@ -25,18 +23,23 @@ extern "system" { type BOOLEAN = u8; const TRUE: BOOLEAN = 1u8; -#[inline] -pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // Prevent overflow of u32 - let chunk_size = usize::try_from(i32::MAX).expect("Windows does not support 16-bit targets"); - for chunk in dest.chunks_mut(chunk_size) { - let chunk_len = u32::try_from(chunk.len()).expect("chunk size is bounded by i32::MAX"); - let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::(), chunk_len) }; - if ret != TRUE { - return Err(Error::WINDOWS_RTL_GEN_RANDOM); +pub struct Implementation; + +unsafe impl crate::Backend for Implementation { + #[inline] + fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Prevent overflow of u32 + let chunk_size = + usize::try_from(i32::MAX).expect("Windows does not support 16-bit targets"); + for chunk in dest.chunks_mut(chunk_size) { + let chunk_len = u32::try_from(chunk.len()).expect("chunk size is bounded by i32::MAX"); + let ret = unsafe { RtlGenRandom(chunk.as_mut_ptr().cast::(), chunk_len) }; + if ret != TRUE { + return Err(Error::WINDOWS_RTL_GEN_RANDOM); + } } + Ok(()) } - Ok(()) } impl Error { diff --git a/src/lib.rs b/src/lib.rs index 8ffd4c98..af330a5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -39,8 +39,11 @@ mod util; #[cfg(feature = "std")] mod error_std_impls; +pub use crate::backends::Backend; pub use crate::error::Error; +use backends::Implementation; + /// Fill `dest` with random bytes from the system's preferred random number source. /// /// This function returns an error on any failure, including partial reads. We @@ -96,7 +99,7 @@ pub fn fill(dest: &mut [u8]) -> Result<(), Error> { #[inline] pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { if !dest.is_empty() { - backends::fill_inner(dest)?; + Implementation::fill_uninit(dest)?; } #[cfg(getrandom_msan)] @@ -120,7 +123,7 @@ pub fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<&mut [u8], Error> { /// ``` #[inline] pub fn u32() -> Result { - backends::inner_u32() + Implementation::u32() } /// Get random `u64` from the system's preferred random number source. @@ -134,5 +137,5 @@ pub fn u32() -> Result { /// ``` #[inline] pub fn u64() -> Result { - backends::inner_u64() + Implementation::u64() } From d30f5591536fc015a99862f34d64e2a032ea8d37 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 12:02:47 +1000 Subject: [PATCH 2/7] Address CI failures --- src/backends/linux_android_with_fallback.rs | 4 ++-- src/backends/linux_raw.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index 67960af0..157b01bd 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -1,5 +1,5 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback -use super::{sanitizer, use_file}; +use super::{sanitizer, use_file, Backend}; use crate::Error; use core::{ ffi::c_void, @@ -75,7 +75,7 @@ fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { pub struct Implementation; -unsafe impl crate::Backend for Implementation { +unsafe impl Backend for Implementation { #[inline] fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { // Despite being only a single atomic variable, we still cannot always use diff --git a/src/backends/linux_raw.rs b/src/backends/linux_raw.rs index 92fd9d2e..7879b148 100644 --- a/src/backends/linux_raw.rs +++ b/src/backends/linux_raw.rs @@ -114,7 +114,7 @@ pub struct Implementation; unsafe impl crate::Backend for Implementation { #[inline] - fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + fn fill_uninit(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { // Value of this error code is stable across all target arches. const EINTR: isize = -4; From 63b9e5f35efa01b806905fba92b9315dc489262f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 12:05:37 +1000 Subject: [PATCH 3/7] Address further CI failures --- src/backends/unsupported.rs | 2 +- src/backends/use_file.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/backends/unsupported.rs b/src/backends/unsupported.rs index 2c6065d1..d62b3578 100644 --- a/src/backends/unsupported.rs +++ b/src/backends/unsupported.rs @@ -6,7 +6,7 @@ pub struct Implementation; unsafe impl crate::Backend for Implementation { #[inline] - fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + fn fill_uninit(_dest: &mut [MaybeUninit]) -> Result<(), Error> { Err(Error::UNSUPPORTED) } diff --git a/src/backends/use_file.rs b/src/backends/use_file.rs index 8a7142b9..a71f2b2c 100644 --- a/src/backends/use_file.rs +++ b/src/backends/use_file.rs @@ -6,9 +6,6 @@ use core::{ sync::atomic::{AtomicI32, Ordering}, }; -#[cfg(not(any(target_os = "android", target_os = "linux")))] -pub use crate::util::{inner_u32, inner_u64}; - #[path = "../util_libc.rs"] pub(super) mod util_libc; From 17d7d585b9d8a3bf8064d0aa66581700af0f0b90 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 12:09:12 +1000 Subject: [PATCH 4/7] Another round of CI failures --- src/backends/hermit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/backends/hermit.rs b/src/backends/hermit.rs index 3e45f020..b22591bf 100644 --- a/src/backends/hermit.rs +++ b/src/backends/hermit.rs @@ -16,7 +16,7 @@ pub struct Implementation; unsafe impl crate::Backend for Implementation { #[inline] - fn fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { + fn fill_uninit(mut dest: &mut [MaybeUninit]) -> Result<(), Error> { while !dest.is_empty() { let res = unsafe { sys_read_entropy(dest.as_mut_ptr().cast::(), dest.len(), 0) }; match res { From 7b98ff734e892e2fa38c3f4e6676f1fe62ebc448 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 14:29:50 +1000 Subject: [PATCH 5/7] Sanitize `set_backend` macro and test in CI --- .github/workflows/tests.yml | 18 ++++++++++++ .gitignore | 2 ++ Cargo.toml | 2 +- fallback_test/Cargo.toml | 19 +++++++++++++ fallback_test/src/main.rs | 56 +++++++++++++++++++++++++++++++++++++ src/backends.rs | 38 +++++++++++++++++++++++++ src/backends/fallback.rs | 35 ++--------------------- 7 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 fallback_test/Cargo.toml create mode 100644 fallback_test/src/main.rs diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 94d60404..86fce95a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -358,3 +358,21 @@ jobs: run: cargo test --target wasm32-wasip1 - name: WASI 0.2 Test run: cargo test --target wasm32-wasip2 + + fallback: + name: Custom Fallback + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v4 + - uses: dtolnay/rust-toolchain@master + with: + toolchain: 1.82 + targets: thumbv6m-none-eabi # A target getrandom doesn't currently support + - uses: Swatinem/rust-cache@v2 + - run: cd fallback_test + - name: No Fallback (Should Fail) + run: $(cargo build --target thumbv6m-none-eabi; if [ $? -ne 0 ]; then exit 0; else exit 1; fi) + - name: Fallback + run: cargo build --target thumbv6m-none-eabi --features fallback + - name: Double Fallback (Should Fail) + run: $(cargo build --target thumbv6m-none-eabi --features fallback,fail-double-definition; if [ $? -ne 0 ]; then exit 0; else exit 1; fi) diff --git a/.gitignore b/.gitignore index 51778e4a..d74f20ea 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ **/*.rs.bk nopanic_check/Cargo.lock nopanic_check/target/ +fallback_test/Cargo.lock +fallback_test/target/ diff --git a/Cargo.toml b/Cargo.toml index 6864bdb5..c5ac0fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -96,7 +96,7 @@ check-cfg = [ 'cfg(getrandom_test_linux_without_fallback)', 'cfg(getrandom_test_netbsd_fallback)', 'cfg(target_os, values("cygwin"))', # TODO(MSRV 1.86): Remove this. - 'cfg(getrandom_no_external_fallback)', + 'cfg(getrandom_no_custom_fallback)', ] [package.metadata.docs.rs] diff --git a/fallback_test/Cargo.toml b/fallback_test/Cargo.toml new file mode 100644 index 00000000..dcbb95b4 --- /dev/null +++ b/fallback_test/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "fallback_test" +description = "Test crate for assessing the custom-fallback feature" +version = "0.1.0" +edition = "2021" +publish = false + +[dependencies] +getrandom = { path = "..", default-features = false } + +[features] +fallback = ["getrandom/custom-fallback"] +fail-double-definition = ["fallback"] + +[profile.release] +panic = "abort" + +[profile.dev] +panic = "abort" \ No newline at end of file diff --git a/fallback_test/src/main.rs b/fallback_test/src/main.rs new file mode 100644 index 00000000..9982375c --- /dev/null +++ b/fallback_test/src/main.rs @@ -0,0 +1,56 @@ +#![no_std] +#![no_main] + +#[panic_handler] +fn handle(_info: &core::panic::PanicInfo) -> ! { + loop {} +} + +use getrandom::{Backend, Error}; + +/// This implementation fills using a constant value of 4. +/// +/// WARNING: this custom implementation is for testing purposes ONLY! +struct ConstantBackend; + +unsafe impl Backend for ConstantBackend { + #[inline] + fn fill_uninit(dest: &mut [core::mem::MaybeUninit]) -> Result<(), Error> { + for out in dest { + // Chosen by fair dice roll + out.write(4); + } + + Ok(()) + } +} + +#[cfg(feature = "fallback")] +const _: () = { + getrandom::set_backend!(ConstantBackend); +}; + +// This second call will cause the following compile-time error: +/* +error: symbol `__getrandom_v03_fallback_fill_uninit` is already defined + --> src\main.rs:43:1 + | +43 | getrandom::set_backend!(ConstantBackend); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `getrandom::set_backend` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: could not compile `fallback_test` (bin "fallback_test") due to 1 previous error +*/ +#[cfg(feature = "fail-double-definition")] +const _: () = { + getrandom::set_backend!(ConstantBackend); +}; + +#[no_mangle] +pub extern "C" fn _start() { + let mut dest = [0u8; 13]; + let result = getrandom::fill(&mut dest); + assert!(result.is_ok()); + assert_eq!(dest, [4; 13]); +} \ No newline at end of file diff --git a/src/backends.rs b/src/backends.rs index c0b7a576..be6d0c33 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -257,3 +257,41 @@ pub unsafe trait Backend { crate::util::inner_u64() } } + +/// Sets the fallback [`Backend`](crate::Backend). +/// +/// # Examples +/// +/// ```ignore +/// struct MyBackend; +/// +/// impl Backend for MyBackend { /* ... */ } +/// +/// set_backend!(MyBackend); +/// ``` +#[cfg(feature = "custom-fallback")] +#[macro_export] +// The macro is defined outside of the `fallback` module to allow its use even when the fallback is +// not used. +// This ensures that a future backend making a particular fallback use redundant is _not_ a compiler +// error. +macro_rules! set_backend { + ($t: ty) => { + const _: () = { + #[no_mangle] + extern "Rust" fn __getrandom_v03_fallback_fill_uninit(dest: &mut [::core::mem::MaybeUninit]) -> Result<(), $crate::Error> { + <$t as $crate::Backend>::fill_uninit(dest) + } + + #[no_mangle] + extern "Rust" fn __getrandom_v03_fallback_u32() -> Result { + <$t as $crate::Backend>::u32() + } + + #[no_mangle] + extern "Rust" fn __getrandom_v03_fallback_u64() -> Result { + <$t as $crate::Backend>::u64() + } + }; + }; +} diff --git a/src/backends/fallback.rs b/src/backends/fallback.rs index 7c04195a..91e0dd61 100644 --- a/src/backends/fallback.rs +++ b/src/backends/fallback.rs @@ -1,9 +1,9 @@ //! An implementation which calls out to an externally defined function if no other is provided. -#[cfg(getrandom_no_external_fallback)] +#[cfg(getrandom_no_custom_fallback)] compile_error! { "getrandom does not have a suitable backend for this target, requiring the use of a fallback.\ - However, `getrandom_no_external_fallback` has been set, indicating the use of a fallback is prohibited.\ + However, `getrandom_no_custom_fallback` has been set, indicating the use of a fallback is prohibited.\ Consider providing a `custom` backend." } @@ -37,34 +37,3 @@ unsafe impl crate::Backend for Implementation { unsafe { __getrandom_v03_fallback_u64() } } } - -/// Sets the fallback [`Backend`](crate::Backend). -/// -/// # Examples -/// -/// ```ignore -/// struct MyBackend; -/// -/// impl Backend for MyBackend { /* ... */ } -/// -/// set_backend!(MyBackend); -/// ``` -#[macro_export] -macro_rules! set_backend { - ($t: ty) => { - #[no_mangle] - extern "Rust" fn __getrandom_v03_fallback_fill_uninit(dest: &mut [MaybeUninit]) -> Result<(), Error> { - <$t as $crate::Backend>::fill_uninit(dest) - } - - #[no_mangle] - extern "Rust" fn __getrandom_v03_fallback_u32() -> Result { - <$t as $crate::Backend>::u32() - } - - #[no_mangle] - extern "Rust" fn __getrandom_v03_fallback_u64() -> Result { - <$t as $crate::Backend>::u64() - } - }; -} \ No newline at end of file From ca81b6880bac0210a26cb209ff71884008bb8fa6 Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 14:30:31 +1000 Subject: [PATCH 6/7] `cargo fmt` --- fallback_test/src/main.rs | 2 +- src/backends.rs | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/fallback_test/src/main.rs b/fallback_test/src/main.rs index 9982375c..b4b180ad 100644 --- a/fallback_test/src/main.rs +++ b/fallback_test/src/main.rs @@ -53,4 +53,4 @@ pub extern "C" fn _start() { let result = getrandom::fill(&mut dest); assert!(result.is_ok()); assert_eq!(dest, [4; 13]); -} \ No newline at end of file +} diff --git a/src/backends.rs b/src/backends.rs index be6d0c33..9f7c1c38 100644 --- a/src/backends.rs +++ b/src/backends.rs @@ -259,14 +259,14 @@ pub unsafe trait Backend { } /// Sets the fallback [`Backend`](crate::Backend). -/// +/// /// # Examples -/// +/// /// ```ignore /// struct MyBackend; -/// +/// /// impl Backend for MyBackend { /* ... */ } -/// +/// /// set_backend!(MyBackend); /// ``` #[cfg(feature = "custom-fallback")] @@ -279,7 +279,9 @@ macro_rules! set_backend { ($t: ty) => { const _: () = { #[no_mangle] - extern "Rust" fn __getrandom_v03_fallback_fill_uninit(dest: &mut [::core::mem::MaybeUninit]) -> Result<(), $crate::Error> { + extern "Rust" fn __getrandom_v03_fallback_fill_uninit( + dest: &mut [::core::mem::MaybeUninit], + ) -> Result<(), $crate::Error> { <$t as $crate::Backend>::fill_uninit(dest) } From f4d8a456a2137434c980b3f3ed4a59fe9955833f Mon Sep 17 00:00:00 2001 From: Zac Harrold Date: Thu, 5 Jun 2025 14:38:23 +1000 Subject: [PATCH 7/7] Properly set working directory for fallback_test --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 86fce95a..a1b01a2f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -369,10 +369,12 @@ jobs: toolchain: 1.82 targets: thumbv6m-none-eabi # A target getrandom doesn't currently support - uses: Swatinem/rust-cache@v2 - - run: cd fallback_test - name: No Fallback (Should Fail) run: $(cargo build --target thumbv6m-none-eabi; if [ $? -ne 0 ]; then exit 0; else exit 1; fi) + working-directory: fallback_test - name: Fallback run: cargo build --target thumbv6m-none-eabi --features fallback + working-directory: fallback_test - name: Double Fallback (Should Fail) run: $(cargo build --target thumbv6m-none-eabi --features fallback,fail-double-definition; if [ $? -ne 0 ]; then exit 0; else exit 1; fi) + working-directory: fallback_test