From a095a76d3a7b688adf127bd2ca997b9c58398d5d Mon Sep 17 00:00:00 2001 From: joboet Date: Thu, 15 Aug 2024 13:28:02 +0200 Subject: [PATCH] std: implement the `random` feature Implements the ACP https://github.com/rust-lang/libs-team/issues/393. --- core/src/lib.rs | 2 + core/src/random.rs | 62 +++++ std/src/hash/random.rs | 5 +- std/src/lib.rs | 4 + std/src/random.rs | 100 ++++++++ std/src/sys/mod.rs | 1 + std/src/sys/pal/hermit/mod.rs | 14 -- std/src/sys/pal/sgx/abi/usercalls/mod.rs | 4 +- std/src/sys/pal/sgx/mod.rs | 18 -- std/src/sys/pal/solid/mod.rs | 10 - std/src/sys/pal/teeos/mod.rs | 3 - std/src/sys/pal/teeos/rand.rs | 21 -- std/src/sys/pal/uefi/mod.rs | 33 --- std/src/sys/pal/unix/mod.rs | 2 - std/src/sys/pal/unix/rand.rs | 302 ----------------------- std/src/sys/pal/unsupported/common.rs | 4 - std/src/sys/pal/wasi/helpers.rs | 12 +- std/src/sys/pal/wasi/mod.rs | 2 +- std/src/sys/pal/wasip2/mod.rs | 2 +- std/src/sys/pal/windows/mod.rs | 2 - std/src/sys/pal/windows/pipe.rs | 7 +- std/src/sys/pal/windows/rand.rs | 27 -- std/src/sys/pal/zkvm/mod.rs | 8 - std/src/sys/random/apple.rs | 22 ++ std/src/sys/random/espidf.rs | 9 + std/src/sys/random/fuchsia.rs | 13 + std/src/sys/random/hermit.rs | 7 + std/src/sys/random/horizon.rs | 7 + std/src/sys/random/linux.rs | 170 +++++++++++++ std/src/sys/random/mod.rs | 98 ++++++++ std/src/sys/random/netbsd.rs | 19 ++ std/src/sys/random/redox.rs | 12 + std/src/sys/random/sgx.rs | 67 +++++ std/src/sys/random/solid.rs | 8 + std/src/sys/random/teeos.rs | 7 + std/src/sys/random/uefi.rs | 27 ++ std/src/sys/random/unix.rs | 33 +++ std/src/sys/random/unix_legacy.rs | 20 ++ std/src/sys/random/unsupported.rs | 15 ++ std/src/sys/random/vxworks.rs | 25 ++ std/src/sys/random/wasi.rs | 5 + std/src/sys/random/windows.rs | 20 ++ std/src/sys/random/zkvm.rs | 21 ++ 43 files changed, 786 insertions(+), 464 deletions(-) create mode 100644 core/src/random.rs create mode 100644 std/src/random.rs delete mode 100644 std/src/sys/pal/teeos/rand.rs delete mode 100644 std/src/sys/pal/unix/rand.rs delete mode 100644 std/src/sys/pal/windows/rand.rs create mode 100644 std/src/sys/random/apple.rs create mode 100644 std/src/sys/random/espidf.rs create mode 100644 std/src/sys/random/fuchsia.rs create mode 100644 std/src/sys/random/hermit.rs create mode 100644 std/src/sys/random/horizon.rs create mode 100644 std/src/sys/random/linux.rs create mode 100644 std/src/sys/random/mod.rs create mode 100644 std/src/sys/random/netbsd.rs create mode 100644 std/src/sys/random/redox.rs create mode 100644 std/src/sys/random/sgx.rs create mode 100644 std/src/sys/random/solid.rs create mode 100644 std/src/sys/random/teeos.rs create mode 100644 std/src/sys/random/uefi.rs create mode 100644 std/src/sys/random/unix.rs create mode 100644 std/src/sys/random/unix_legacy.rs create mode 100644 std/src/sys/random/unsupported.rs create mode 100644 std/src/sys/random/vxworks.rs create mode 100644 std/src/sys/random/wasi.rs create mode 100644 std/src/sys/random/windows.rs create mode 100644 std/src/sys/random/zkvm.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index a30b57c19d402..b4393889e7523 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -394,6 +394,8 @@ pub mod panicking; #[unstable(feature = "core_pattern_types", issue = "123646")] pub mod pat; pub mod pin; +#[unstable(feature = "random", issue = "none")] +pub mod random; #[unstable(feature = "new_range_api", issue = "125687")] pub mod range; pub mod result; diff --git a/core/src/random.rs b/core/src/random.rs new file mode 100644 index 0000000000000..9e0d0d0c58bbc --- /dev/null +++ b/core/src/random.rs @@ -0,0 +1,62 @@ +//! Random value generation. +//! +//! The [`Random`] trait allows generating a random value for a type using a +//! given [`RandomSource`]. + +/// A source of randomness. +#[unstable(feature = "random", issue = "none")] +pub trait RandomSource { + /// Fills `bytes` with random bytes. + fn fill_bytes(&mut self, bytes: &mut [u8]); +} + +/// A trait for getting a random value for a type. +/// +/// **Warning:** Be careful when manipulating random values! The +/// [`random`](Random::random) method on integers samples them with a uniform +/// distribution, so a value of 1 is just as likely as [`i32::MAX`]. By using +/// modulo operations, some of the resulting values can become more likely than +/// others. Use audited crates when in doubt. +#[unstable(feature = "random", issue = "none")] +pub trait Random: Sized { + /// Generates a random value. + fn random(source: &mut (impl RandomSource + ?Sized)) -> Self; +} + +impl Random for bool { + fn random(source: &mut (impl RandomSource + ?Sized)) -> Self { + u8::random(source) & 1 == 1 + } +} + +macro_rules! impl_primitive { + ($t:ty) => { + impl Random for $t { + /// Generates a random value. + /// + /// **Warning:** Be careful when manipulating the resulting value! This + /// method samples according to a uniform distribution, so a value of 1 is + /// just as likely as [`MAX`](Self::MAX). By using modulo operations, some + /// values can become more likely than others. Use audited crates when in + /// doubt. + fn random(source: &mut (impl RandomSource + ?Sized)) -> Self { + let mut bytes = (0 as Self).to_ne_bytes(); + source.fill_bytes(&mut bytes); + Self::from_ne_bytes(bytes) + } + } + }; +} + +impl_primitive!(u8); +impl_primitive!(i8); +impl_primitive!(u16); +impl_primitive!(i16); +impl_primitive!(u32); +impl_primitive!(i32); +impl_primitive!(u64); +impl_primitive!(i64); +impl_primitive!(u128); +impl_primitive!(i128); +impl_primitive!(usize); +impl_primitive!(isize); diff --git a/std/src/hash/random.rs b/std/src/hash/random.rs index 8ef45172eac40..40f3a90f60c8a 100644 --- a/std/src/hash/random.rs +++ b/std/src/hash/random.rs @@ -10,7 +10,8 @@ #[allow(deprecated)] use super::{BuildHasher, Hasher, SipHasher13}; use crate::cell::Cell; -use crate::{fmt, sys}; +use crate::fmt; +use crate::sys::random::hashmap_random_keys; /// `RandomState` is the default state for [`HashMap`] types. /// @@ -65,7 +66,7 @@ impl RandomState { // increment one of the seeds on every RandomState creation, giving // every corresponding HashMap a different iteration order. thread_local!(static KEYS: Cell<(u64, u64)> = { - Cell::new(sys::hashmap_random_keys()) + Cell::new(hashmap_random_keys()) }); KEYS.with(|keys| { diff --git a/std/src/lib.rs b/std/src/lib.rs index 2add88da9a7a3..ce05d8166048a 100644 --- a/std/src/lib.rs +++ b/std/src/lib.rs @@ -318,6 +318,7 @@ // // Library features (core): // tidy-alphabetical-start +#![feature(array_chunks)] #![feature(c_str_module)] #![feature(char_internals)] #![feature(clone_to_uninit)] @@ -348,6 +349,7 @@ #![feature(prelude_2024)] #![feature(ptr_as_uninit)] #![feature(ptr_mask)] +#![feature(random)] #![feature(slice_internals)] #![feature(slice_ptr_get)] #![feature(slice_range)] @@ -595,6 +597,8 @@ pub mod path; #[unstable(feature = "anonymous_pipe", issue = "127154")] pub mod pipe; pub mod process; +#[unstable(feature = "random", issue = "none")] +pub mod random; pub mod sync; pub mod time; diff --git a/std/src/random.rs b/std/src/random.rs new file mode 100644 index 0000000000000..24d1dac4daec3 --- /dev/null +++ b/std/src/random.rs @@ -0,0 +1,100 @@ +//! Random value generation. +//! +//! The [`Random`] trait allows generating a random value for a type using a +//! given [`RandomSource`]. + +#[unstable(feature = "random", issue = "none")] +pub use core::random::*; + +use crate::sys::random as sys; + +/// The default random source. +/// +/// This asks the system for random data suitable for cryptographic purposes +/// such as key generation. If security is a concern, consult the platform +/// documentation below for the specific guarantees your target provides. +/// +/// The high quality of randomness provided by this source means it can be quite +/// slow. If you need a large quantity of random numbers and security is not a +/// concern, consider using an alternative random number generator (potentially +/// seeded from this one). +/// +/// # Underlying sources +/// +/// Platform | Source +/// -----------------------|--------------------------------------------------------------- +/// Linux | [`getrandom`] or [`/dev/urandom`] after polling `/dev/random` +/// Windows | [`ProcessPrng`] +/// macOS and other UNIXes | [`getentropy`] +/// other Apple platforms | `CCRandomGenerateBytes` +/// ESP-IDF | [`esp_fill_random`] +/// Fuchsia | [`cprng_draw`] +/// Hermit | `read_entropy` +/// Horizon | `getrandom` shim +/// Hurd, L4Re, QNX | `/dev/urandom` +/// NetBSD before 10.0 | [`kern.arandom`] +/// Redox | `/scheme/rand` +/// SGX | [`rdrand`] +/// SOLID | `SOLID_RNG_SampleRandomBytes` +/// TEEOS | `TEE_GenerateRandom` +/// UEFI | [`EFI_RNG_PROTOCOL`] +/// VxWorks | `randABytes` after waiting for `randSecure` to become ready +/// WASI | `random_get` +/// ZKVM | `sys_rand` +/// +/// **Disclaimer:** The sources used might change over time. +/// +/// [`getrandom`]: https://www.man7.org/linux/man-pages/man2/getrandom.2.html +/// [`/dev/urandom`]: https://www.man7.org/linux/man-pages/man4/random.4.html +/// [`ProcessPrng`]: https://learn.microsoft.com/en-us/windows/win32/seccng/processprng +/// [`getentropy`]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/getentropy.html +/// [`esp_fill_random`]: https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t +/// [`cprng_draw`]: https://fuchsia.dev/reference/syscalls/cprng_draw +/// [`kern.arandom`]: https://man.netbsd.org/rnd.4 +/// [`rdrand`]: https://en.wikipedia.org/wiki/RDRAND +/// [`EFI_RNG_PROTOCOL`]: https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#random-number-generator-protocol +#[derive(Default, Debug, Clone, Copy)] +#[unstable(feature = "random", issue = "none")] +pub struct DefaultRandomSource; + +#[unstable(feature = "random", issue = "none")] +impl RandomSource for DefaultRandomSource { + fn fill_bytes(&mut self, bytes: &mut [u8]) { + sys::fill_bytes(bytes) + } +} + +/// Generates a random value with the default random source. +/// +/// This is a convenience function for `T::random(&mut DefaultRandomSource)` and +/// will sample according to the same distribution as the underlying [`Random`] +/// trait implementation. +/// +/// **Warning:** Be careful when manipulating random values! The +/// [`random`](Random::random) method on integers samples them with a uniform +/// distribution, so a value of 1 is just as likely as [`i32::MAX`]. By using +/// modulo operations, some of the resulting values can become more likely than +/// others. Use audited crates when in doubt. +/// +/// # Examples +/// +/// Generating a [version 4/variant 1 UUID] represented as text: +/// ``` +/// #![feature(random)] +/// +/// use std::random::random; +/// +/// let bits = random::(); +/// let g1 = (bits >> 96) as u32; +/// let g2 = (bits >> 80) as u16; +/// let g3 = (0x4000 | (bits >> 64) & 0x0fff) as u16; +/// let g4 = (0x8000 | (bits >> 48) & 0x3fff) as u16; +/// let g5 = (bits & 0xffffffffffff) as u64; +/// let uuid = format!("{g1:08x}-{g2:04x}-{g3:04x}-{g4:04x}-{g5:012x}"); +/// println!("{uuid}"); +/// ``` +/// +/// [version 4/variant 1 UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier#Version_4_(random) +pub fn random() -> T { + T::random(&mut DefaultRandomSource) +} diff --git a/std/src/sys/mod.rs b/std/src/sys/mod.rs index 96d6f2c87c4c1..df25b84fbbe5a 100644 --- a/std/src/sys/mod.rs +++ b/std/src/sys/mod.rs @@ -15,6 +15,7 @@ pub mod dbg; pub mod exit_guard; pub mod os_str; pub mod path; +pub mod random; pub mod sync; pub mod thread_local; diff --git a/std/src/sys/pal/hermit/mod.rs b/std/src/sys/pal/hermit/mod.rs index 1f2e5d9469f5c..f49ef94717499 100644 --- a/std/src/sys/pal/hermit/mod.rs +++ b/std/src/sys/pal/hermit/mod.rs @@ -52,20 +52,6 @@ pub fn abort_internal() -> ! { unsafe { hermit_abi::abort() } } -pub fn hashmap_random_keys() -> (u64, u64) { - let mut buf = [0; 16]; - let mut slice = &mut buf[..]; - while !slice.is_empty() { - let res = cvt(unsafe { hermit_abi::read_entropy(slice.as_mut_ptr(), slice.len(), 0) }) - .expect("failed to generate random hashmap keys"); - slice = &mut slice[res as usize..]; - } - - let key1 = buf[..8].try_into().unwrap(); - let key2 = buf[8..].try_into().unwrap(); - (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2)) -} - // This function is needed by the panic runtime. The symbol is named in // pre-link args for the target specification, so keep that in sync. #[cfg(not(test))] diff --git a/std/src/sys/pal/sgx/abi/usercalls/mod.rs b/std/src/sys/pal/sgx/abi/usercalls/mod.rs index def1ccdf81ac0..90b9b59471a52 100644 --- a/std/src/sys/pal/sgx/abi/usercalls/mod.rs +++ b/std/src/sys/pal/sgx/abi/usercalls/mod.rs @@ -1,6 +1,6 @@ use crate::cmp; use crate::io::{Error as IoError, ErrorKind, IoSlice, IoSliceMut, Result as IoResult}; -use crate::sys::rand::rdrand64; +use crate::random::{DefaultRandomSource, Random}; use crate::time::{Duration, Instant}; pub(crate) mod alloc; @@ -164,7 +164,7 @@ pub fn wait(event_mask: u64, mut timeout: u64) -> IoResult { // trusted to ensure accurate timeouts. if let Ok(timeout_signed) = i64::try_from(timeout) { let tenth = timeout_signed / 10; - let deviation = (rdrand64() as i64).checked_rem(tenth).unwrap_or(0); + let deviation = i64::random(&mut DefaultRandomSource).checked_rem(tenth).unwrap_or(0); timeout = timeout_signed.saturating_add(deviation) as _; } } diff --git a/std/src/sys/pal/sgx/mod.rs b/std/src/sys/pal/sgx/mod.rs index 8d29b2ec6193e..586ccd18c2f57 100644 --- a/std/src/sys/pal/sgx/mod.rs +++ b/std/src/sys/pal/sgx/mod.rs @@ -132,24 +132,6 @@ pub extern "C" fn __rust_abort() { abort_internal(); } -pub mod rand { - pub fn rdrand64() -> u64 { - unsafe { - let mut ret: u64 = 0; - for _ in 0..10 { - if crate::arch::x86_64::_rdrand64_step(&mut ret) == 1 { - return ret; - } - } - rtabort!("Failed to obtain random data"); - } - } -} - -pub fn hashmap_random_keys() -> (u64, u64) { - (self::rand::rdrand64(), self::rand::rdrand64()) -} - pub use crate::sys_common::{AsInner, FromInner, IntoInner}; pub trait TryIntoInner: Sized { diff --git a/std/src/sys/pal/solid/mod.rs b/std/src/sys/pal/solid/mod.rs index 6ebcf5b7c48c8..d41042be51844 100644 --- a/std/src/sys/pal/solid/mod.rs +++ b/std/src/sys/pal/solid/mod.rs @@ -62,13 +62,3 @@ pub fn decode_error_kind(code: i32) -> crate::io::ErrorKind { pub fn abort_internal() -> ! { unsafe { libc::abort() } } - -pub fn hashmap_random_keys() -> (u64, u64) { - unsafe { - let mut out = crate::mem::MaybeUninit::<[u64; 2]>::uninit(); - let result = abi::SOLID_RNG_SampleRandomBytes(out.as_mut_ptr() as *mut u8, 16); - assert_eq!(result, 0, "SOLID_RNG_SampleRandomBytes failed: {result}"); - let [x1, x2] = out.assume_init(); - (x1, x2) - } -} diff --git a/std/src/sys/pal/teeos/mod.rs b/std/src/sys/pal/teeos/mod.rs index 00e3860424006..60a227afb84e3 100644 --- a/std/src/sys/pal/teeos/mod.rs +++ b/std/src/sys/pal/teeos/mod.rs @@ -6,8 +6,6 @@ #![allow(unused_variables)] #![allow(dead_code)] -pub use self::rand::hashmap_random_keys; - #[path = "../unsupported/args.rs"] pub mod args; #[path = "../unsupported/env.rs"] @@ -23,7 +21,6 @@ pub mod os; pub mod pipe; #[path = "../unsupported/process.rs"] pub mod process; -mod rand; pub mod stdio; pub mod thread; #[allow(non_upper_case_globals)] diff --git a/std/src/sys/pal/teeos/rand.rs b/std/src/sys/pal/teeos/rand.rs deleted file mode 100644 index b45c3bb40e782..0000000000000 --- a/std/src/sys/pal/teeos/rand.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub fn hashmap_random_keys() -> (u64, u64) { - const KEY_LEN: usize = core::mem::size_of::(); - - let mut v = [0u8; KEY_LEN * 2]; - imp::fill_bytes(&mut v); - - let key1 = v[0..KEY_LEN].try_into().unwrap(); - let key2 = v[KEY_LEN..].try_into().unwrap(); - - (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2)) -} - -mod imp { - extern "C" { - fn TEE_GenerateRandom(randomBuffer: *mut core::ffi::c_void, randomBufferLen: libc::size_t); - } - - pub fn fill_bytes(v: &mut [u8]) { - unsafe { TEE_GenerateRandom(v.as_mut_ptr() as _, v.len() * crate::mem::size_of::()) } - } -} diff --git a/std/src/sys/pal/uefi/mod.rs b/std/src/sys/pal/uefi/mod.rs index ac22f4ded8855..c0ab52f650aa5 100644 --- a/std/src/sys/pal/uefi/mod.rs +++ b/std/src/sys/pal/uefi/mod.rs @@ -179,39 +179,6 @@ pub extern "C" fn __rust_abort() { abort_internal(); } -#[inline] -pub fn hashmap_random_keys() -> (u64, u64) { - get_random().unwrap() -} - -fn get_random() -> Option<(u64, u64)> { - use r_efi::protocols::rng; - - let mut buf = [0u8; 16]; - let handles = helpers::locate_handles(rng::PROTOCOL_GUID).ok()?; - for handle in handles { - if let Ok(protocol) = helpers::open_protocol::(handle, rng::PROTOCOL_GUID) { - let r = unsafe { - ((*protocol.as_ptr()).get_rng)( - protocol.as_ptr(), - crate::ptr::null_mut(), - buf.len(), - buf.as_mut_ptr(), - ) - }; - if r.is_error() { - continue; - } else { - return Some(( - u64::from_le_bytes(buf[..8].try_into().ok()?), - u64::from_le_bytes(buf[8..].try_into().ok()?), - )); - } - } - } - None -} - /// Disable access to BootServices if `EVT_SIGNAL_EXIT_BOOT_SERVICES` is signaled extern "efiapi" fn exit_boot_service_handler(_e: r_efi::efi::Event, _ctx: *mut crate::ffi::c_void) { uefi::env::disable_boot_services(); diff --git a/std/src/sys/pal/unix/mod.rs b/std/src/sys/pal/unix/mod.rs index e8428eccb1691..1c9159e5fba86 100644 --- a/std/src/sys/pal/unix/mod.rs +++ b/std/src/sys/pal/unix/mod.rs @@ -1,6 +1,5 @@ #![allow(missing_docs, nonstandard_style)] -pub use self::rand::hashmap_random_keys; use crate::io::ErrorKind; #[cfg(not(target_os = "espidf"))] @@ -26,7 +25,6 @@ pub use self::l4re::net; pub mod os; pub mod pipe; pub mod process; -pub mod rand; pub mod stack_overflow; pub mod stdio; pub mod thread; diff --git a/std/src/sys/pal/unix/rand.rs b/std/src/sys/pal/unix/rand.rs deleted file mode 100644 index cc0852aab4396..0000000000000 --- a/std/src/sys/pal/unix/rand.rs +++ /dev/null @@ -1,302 +0,0 @@ -pub fn hashmap_random_keys() -> (u64, u64) { - const KEY_LEN: usize = core::mem::size_of::(); - - let mut v = [0u8; KEY_LEN * 2]; - if let Err(err) = read(&mut v) { - panic!("failed to retrieve random hash map seed: {err}"); - } - - let key1 = v[0..KEY_LEN].try_into().unwrap(); - let key2 = v[KEY_LEN..].try_into().unwrap(); - - (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2)) -} - -cfg_if::cfg_if! { - if #[cfg(any( - target_vendor = "apple", - target_os = "openbsd", - target_os = "emscripten", - target_os = "vita", - all(target_os = "netbsd", not(netbsd10)), - target_os = "fuchsia", - target_os = "vxworks", - ))] { - // Some systems have a syscall that directly retrieves random data. - // If that is guaranteed to be available, use it. - use imp::syscall as read; - } else { - // Otherwise, try the syscall to see if it exists only on some systems - // and fall back to reading from the random device otherwise. - fn read(bytes: &mut [u8]) -> crate::io::Result<()> { - use crate::fs::File; - use crate::io::Read; - use crate::sync::OnceLock; - - #[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "espidf", - target_os = "horizon", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "solaris", - target_os = "illumos", - netbsd10, - ))] - if let Some(res) = imp::syscall(bytes) { - return res; - } - - const PATH: &'static str = if cfg!(target_os = "redox") { - "/scheme/rand" - } else { - "/dev/urandom" - }; - - static FILE: OnceLock = OnceLock::new(); - - FILE.get_or_try_init(|| File::open(PATH))?.read_exact(bytes) - } - } -} - -// All these systems a `getrandom` syscall. -// -// It is not guaranteed to be available, so return None to fallback to the file -// implementation. -#[cfg(any( - target_os = "linux", - target_os = "android", - target_os = "espidf", - target_os = "horizon", - target_os = "freebsd", - target_os = "dragonfly", - target_os = "solaris", - target_os = "illumos", - netbsd10, -))] -mod imp { - use crate::io::{Error, Result}; - use crate::sync::atomic::{AtomicBool, Ordering}; - use crate::sys::os::errno; - - #[cfg(any(target_os = "linux", target_os = "android"))] - fn getrandom(buf: &mut [u8]) -> libc::ssize_t { - use crate::sys::weak::syscall; - - // A weak symbol allows interposition, e.g. for perf measurements that want to - // disable randomness for consistency. Otherwise, we'll try a raw syscall. - // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28) - syscall! { - fn getrandom( - buffer: *mut libc::c_void, - length: libc::size_t, - flags: libc::c_uint - ) -> libc::ssize_t - } - - // This provides the best quality random numbers available at the given moment - // without ever blocking, and is preferable to falling back to /dev/urandom. - static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true); - if GRND_INSECURE_AVAILABLE.load(Ordering::Relaxed) { - let ret = unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_INSECURE) }; - if ret == -1 && errno() as libc::c_int == libc::EINVAL { - GRND_INSECURE_AVAILABLE.store(false, Ordering::Relaxed); - } else { - return ret; - } - } - - unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_NONBLOCK) } - } - - #[cfg(any( - target_os = "dragonfly", - target_os = "espidf", - target_os = "horizon", - target_os = "freebsd", - netbsd10, - target_os = "illumos", - target_os = "solaris" - ))] - fn getrandom(buf: &mut [u8]) -> libc::ssize_t { - unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) } - } - - pub fn syscall(v: &mut [u8]) -> Option> { - static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false); - - if GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) { - return None; - } - - let mut read = 0; - while read < v.len() { - let result = getrandom(&mut v[read..]); - if result == -1 { - let err = errno() as libc::c_int; - if err == libc::EINTR { - continue; - } else if err == libc::ENOSYS || err == libc::EPERM { - // `getrandom` is not supported on the current system. - // - // Also fall back in case it is disabled by something like - // seccomp or inside of docker. - // - // If the `getrandom` syscall is not implemented in the current kernel version it should return an - // `ENOSYS` error. Docker also blocks the whole syscall inside unprivileged containers, and - // returns `EPERM` (instead of `ENOSYS`) when a program tries to invoke the syscall. Because of - // that we need to check for *both* `ENOSYS` and `EPERM`. - // - // Note that Docker's behavior is breaking other projects (notably glibc), so they're planning - // to update their filtering to return `ENOSYS` in a future release: - // - // https://github.com/moby/moby/issues/42680 - // - GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed); - return None; - } else if err == libc::EAGAIN { - // getrandom has failed because it would have blocked as the - // non-blocking pool (urandom) has not been initialized in - // the kernel yet due to a lack of entropy. Fallback to - // reading from `/dev/urandom` which will return potentially - // insecure random data to avoid blocking applications which - // could depend on this call without ever knowing they do and - // don't have a work around. - return None; - } else { - return Some(Err(Error::from_raw_os_error(err))); - } - } else { - read += result as usize; - } - } - - Some(Ok(())) - } -} - -#[cfg(any( - target_os = "macos", // Supported since macOS 10.12+. - target_os = "openbsd", - target_os = "emscripten", - target_os = "vita", -))] -mod imp { - use crate::io::{Error, Result}; - - pub fn syscall(v: &mut [u8]) -> Result<()> { - // getentropy(2) permits a maximum buffer size of 256 bytes - for s in v.chunks_mut(256) { - let ret = unsafe { libc::getentropy(s.as_mut_ptr().cast(), s.len()) }; - if ret == -1 { - return Err(Error::last_os_error()); - } - } - - Ok(()) - } -} - -// On Apple platforms, `CCRandomGenerateBytes` and `SecRandomCopyBytes` simply -// call into `CCRandomCopyBytes` with `kCCRandomDefault`. `CCRandomCopyBytes` -// manages a CSPRNG which is seeded from the kernel's CSPRNG and which runs on -// its own thread accessed via GCD. This seems needlessly heavyweight for our purposes -// so we only use it when `getentropy` is blocked, which appears to be the case -// on all platforms except macOS (see #102643). -// -// `CCRandomGenerateBytes` is used instead of `SecRandomCopyBytes` because the former is accessible -// via `libSystem` (libc) while the other needs to link to `Security.framework`. -#[cfg(all(target_vendor = "apple", not(target_os = "macos")))] -mod imp { - use libc::size_t; - - use crate::ffi::{c_int, c_void}; - use crate::io::{Error, Result}; - - pub fn syscall(v: &mut [u8]) -> Result<()> { - extern "C" { - fn CCRandomGenerateBytes(bytes: *mut c_void, count: size_t) -> c_int; - } - - let ret = unsafe { CCRandomGenerateBytes(v.as_mut_ptr().cast(), v.len()) }; - if ret != -1 { Ok(()) } else { Err(Error::last_os_error()) } - } -} - -// FIXME: once the 10.x release becomes the minimum, this can be dropped for simplification. -#[cfg(all(target_os = "netbsd", not(netbsd10)))] -mod imp { - use crate::io::{Error, Result}; - use crate::ptr; - - pub fn syscall(v: &mut [u8]) -> Result<()> { - let mib = [libc::CTL_KERN, libc::KERN_ARND]; - // kern.arandom permits a maximum buffer size of 256 bytes - for s in v.chunks_mut(256) { - let mut s_len = s.len(); - let ret = unsafe { - libc::sysctl( - mib.as_ptr(), - mib.len() as libc::c_uint, - s.as_mut_ptr() as *mut _, - &mut s_len, - ptr::null(), - 0, - ) - }; - if ret == -1 { - return Err(Error::last_os_error()); - } else if s_len != s.len() { - // FIXME(joboet): this can't actually happen, can it? - panic!("read less bytes than requested from kern.arandom"); - } - } - - Ok(()) - } -} - -#[cfg(target_os = "fuchsia")] -mod imp { - use crate::io::Result; - - #[link(name = "zircon")] - extern "C" { - fn zx_cprng_draw(buffer: *mut u8, len: usize); - } - - pub fn syscall(v: &mut [u8]) -> Result<()> { - unsafe { zx_cprng_draw(v.as_mut_ptr(), v.len()) }; - Ok(()) - } -} - -#[cfg(target_os = "vxworks")] -mod imp { - use core::sync::atomic::AtomicBool; - use core::sync::atomic::Ordering::Relaxed; - - use crate::io::{Error, Result}; - - pub fn syscall(v: &mut [u8]) -> Result<()> { - static RNG_INIT: AtomicBool = AtomicBool::new(false); - while !RNG_INIT.load(Relaxed) { - let ret = unsafe { libc::randSecure() }; - if ret < 0 { - return Err(Error::last_os_error()); - } else if ret > 0 { - RNG_INIT.store(true, Relaxed); - break; - } - - unsafe { libc::usleep(10) }; - } - - let ret = unsafe { - libc::randABytes(v.as_mut_ptr() as *mut libc::c_uchar, v.len() as libc::c_int) - }; - if ret >= 0 { Ok(()) } else { Err(Error::last_os_error()) } - } -} diff --git a/std/src/sys/pal/unsupported/common.rs b/std/src/sys/pal/unsupported/common.rs index 76f80291f0ea8..34a766683830d 100644 --- a/std/src/sys/pal/unsupported/common.rs +++ b/std/src/sys/pal/unsupported/common.rs @@ -27,7 +27,3 @@ pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind { pub fn abort_internal() -> ! { core::intrinsics::abort(); } - -pub fn hashmap_random_keys() -> (u64, u64) { - (1, 2) -} diff --git a/std/src/sys/pal/wasi/helpers.rs b/std/src/sys/pal/wasi/helpers.rs index 37ef17858cb96..404747f0dc756 100644 --- a/std/src/sys/pal/wasi/helpers.rs +++ b/std/src/sys/pal/wasi/helpers.rs @@ -1,6 +1,6 @@ #![forbid(unsafe_op_in_unsafe_fn)] -use crate::{io as std_io, mem}; +use crate::io as std_io; #[inline] pub fn is_interrupted(errno: i32) -> bool { @@ -108,16 +108,6 @@ pub fn abort_internal() -> ! { unsafe { libc::abort() } } -pub fn hashmap_random_keys() -> (u64, u64) { - let mut ret = (0u64, 0u64); - unsafe { - let base = &mut ret as *mut (u64, u64) as *mut u8; - let len = mem::size_of_val(&ret); - wasi::random_get(base, len).expect("random_get failure"); - } - ret -} - #[inline] pub(crate) fn err2io(err: wasi::Errno) -> std_io::Error { std_io::Error::from_raw_os_error(err.raw().into()) diff --git a/std/src/sys/pal/wasi/mod.rs b/std/src/sys/pal/wasi/mod.rs index 8051021a58897..5d54c7903065c 100644 --- a/std/src/sys/pal/wasi/mod.rs +++ b/std/src/sys/pal/wasi/mod.rs @@ -47,4 +47,4 @@ mod helpers; // then the compiler complains about conflicts. use helpers::err2io; -pub use helpers::{abort_internal, decode_error_kind, hashmap_random_keys, is_interrupted}; +pub use helpers::{abort_internal, decode_error_kind, is_interrupted}; diff --git a/std/src/sys/pal/wasip2/mod.rs b/std/src/sys/pal/wasip2/mod.rs index 546fadbe5011c..17b26543bd75f 100644 --- a/std/src/sys/pal/wasip2/mod.rs +++ b/std/src/sys/pal/wasip2/mod.rs @@ -50,6 +50,6 @@ mod helpers; // then the compiler complains about conflicts. use helpers::err2io; -pub use helpers::{abort_internal, decode_error_kind, hashmap_random_keys, is_interrupted}; +pub use helpers::{abort_internal, decode_error_kind, is_interrupted}; mod cabi_realloc; diff --git a/std/src/sys/pal/windows/mod.rs b/std/src/sys/pal/windows/mod.rs index f5ed3e4628e1f..1ea253e5e5263 100644 --- a/std/src/sys/pal/windows/mod.rs +++ b/std/src/sys/pal/windows/mod.rs @@ -1,7 +1,6 @@ #![allow(missing_docs, nonstandard_style)] #![forbid(unsafe_op_in_unsafe_fn)] -pub use self::rand::hashmap_random_keys; use crate::ffi::{OsStr, OsString}; use crate::io::ErrorKind; use crate::mem::MaybeUninit; @@ -27,7 +26,6 @@ pub mod net; pub mod os; pub mod pipe; pub mod process; -pub mod rand; pub mod stdio; pub mod thread; pub mod time; diff --git a/std/src/sys/pal/windows/pipe.rs b/std/src/sys/pal/windows/pipe.rs index 7d1b5aca1d5fe..d8200ef9ca460 100644 --- a/std/src/sys/pal/windows/pipe.rs +++ b/std/src/sys/pal/windows/pipe.rs @@ -2,12 +2,13 @@ use crate::ffi::OsStr; use crate::io::{self, BorrowedCursor, IoSlice, IoSliceMut}; use crate::os::windows::prelude::*; use crate::path::Path; +use crate::random::{DefaultRandomSource, Random}; use crate::sync::atomic::AtomicUsize; use crate::sync::atomic::Ordering::Relaxed; +use crate::sys::c; use crate::sys::fs::{File, OpenOptions}; use crate::sys::handle::Handle; use crate::sys::pal::windows::api::{self, WinError}; -use crate::sys::{c, hashmap_random_keys}; use crate::sys_common::{FromInner, IntoInner}; use crate::{mem, ptr}; @@ -79,7 +80,7 @@ pub fn anon_pipe(ours_readable: bool, their_handle_inheritable: bool) -> io::Res name = format!( r"\\.\pipe\__rust_anonymous_pipe1__.{}.{}", c::GetCurrentProcessId(), - random_number() + random_number(), ); let wide_name = OsStr::new(&name).encode_wide().chain(Some(0)).collect::>(); let mut flags = c::FILE_FLAG_FIRST_PIPE_INSTANCE | c::FILE_FLAG_OVERLAPPED; @@ -214,7 +215,7 @@ fn random_number() -> usize { return N.fetch_add(1, Relaxed); } - N.store(hashmap_random_keys().0 as usize, Relaxed); + N.store(usize::random(&mut DefaultRandomSource), Relaxed); } } diff --git a/std/src/sys/pal/windows/rand.rs b/std/src/sys/pal/windows/rand.rs deleted file mode 100644 index e366bb995626a..0000000000000 --- a/std/src/sys/pal/windows/rand.rs +++ /dev/null @@ -1,27 +0,0 @@ -use core::{mem, ptr}; - -use crate::sys::c; - -#[cfg(not(target_vendor = "win7"))] -#[inline] -pub fn hashmap_random_keys() -> (u64, u64) { - let mut v = (0, 0); - let ret = unsafe { c::ProcessPrng(ptr::addr_of_mut!(v).cast::(), mem::size_of_val(&v)) }; - // ProcessPrng is documented as always returning `TRUE`. - // https://learn.microsoft.com/en-us/windows/win32/seccng/processprng#return-value - debug_assert_eq!(ret, c::TRUE); - v -} - -#[cfg(target_vendor = "win7")] -pub fn hashmap_random_keys() -> (u64, u64) { - use crate::ffi::c_void; - use crate::io; - - let mut v = (0, 0); - let ret = unsafe { - c::RtlGenRandom(ptr::addr_of_mut!(v).cast::(), mem::size_of_val(&v) as u32) - }; - - if ret != 0 { v } else { panic!("RNG broken: {}", io::Error::last_os_error()) } -} diff --git a/std/src/sys/pal/zkvm/mod.rs b/std/src/sys/pal/zkvm/mod.rs index 20fdb7468a40d..6ea057720296d 100644 --- a/std/src/sys/pal/zkvm/mod.rs +++ b/std/src/sys/pal/zkvm/mod.rs @@ -60,11 +60,3 @@ pub fn decode_error_kind(_code: i32) -> crate::io::ErrorKind { pub fn abort_internal() -> ! { core::intrinsics::abort(); } - -pub fn hashmap_random_keys() -> (u64, u64) { - let mut buf = [0u32; 4]; - unsafe { - abi::sys_rand(buf.as_mut_ptr(), 4); - }; - ((buf[0] as u64) << 32 + buf[1] as u64, (buf[2] as u64) << 32 + buf[3] as u64) -} diff --git a/std/src/sys/random/apple.rs b/std/src/sys/random/apple.rs new file mode 100644 index 0000000000000..09b6d0d51abc6 --- /dev/null +++ b/std/src/sys/random/apple.rs @@ -0,0 +1,22 @@ +//! Random data on non-macOS Apple platforms. +//! +//! Apple recommends the usage of `getentropy` in their security documentation[^1] +//! and mark it as being available in iOS 10.0, but we cannot use it on non-macOS +//! platforms as Apple in their *infinite wisdom* decided to consider this API +//! private, meaning its use will lead to App Store rejections (see #102643). +//! +//! Thus, we need to do the next best thing: +//! +//! Both `CCRandomGenerateBytes` and `SecRandomCopyBytes` simply call into +//! `CCRandomCopyBytes` with `kCCRandomDefault`. `CCRandomCopyBytes` manages a +//! CSPRNG which is seeded from the kernel's CSPRNG and which runs on its own +//! thread accessed via GCD (this is so wasteful...). Both are available on +//! iOS, but we use `CCRandomGenerateBytes` because it is accessible via +//! `libSystem` (libc) while the other needs to link to `Security.framework`. +//! +//! [^1]: + +pub fn fill_bytes(bytes: &mut [u8]) { + let ret = unsafe { libc::CCRandomGenerateBytes(bytes.as_mut_ptr().cast(), bytes.len()) }; + assert_eq!(ret, libc::kCCSuccess, "failed to generate random data"); +} diff --git a/std/src/sys/random/espidf.rs b/std/src/sys/random/espidf.rs new file mode 100644 index 0000000000000..fd52cb5559ce5 --- /dev/null +++ b/std/src/sys/random/espidf.rs @@ -0,0 +1,9 @@ +use crate::ffi::c_void; + +extern "C" { + fn esp_fill_random(buf: *mut c_void, len: usize); +} + +pub fn fill_bytes(bytes: &mut [u8]) { + unsafe { esp_fill_random(bytes.as_mut_ptr().cast(), bytes.len()) } +} diff --git a/std/src/sys/random/fuchsia.rs b/std/src/sys/random/fuchsia.rs new file mode 100644 index 0000000000000..77d72b3c5b784 --- /dev/null +++ b/std/src/sys/random/fuchsia.rs @@ -0,0 +1,13 @@ +//! Random data generation using the Zircon kernel. +//! +//! Fuchsia, as always, is quite nice and provides exactly the API we need: +//! . + +#[link(name = "zircon")] +extern "C" { + fn zx_cprng_draw(buffer: *mut u8, len: usize); +} + +pub fn fill_bytes(bytes: &mut [u8]) { + unsafe { zx_cprng_draw(bytes.as_mut_ptr(), bytes.len()) } +} diff --git a/std/src/sys/random/hermit.rs b/std/src/sys/random/hermit.rs new file mode 100644 index 0000000000000..92c0550d2d584 --- /dev/null +++ b/std/src/sys/random/hermit.rs @@ -0,0 +1,7 @@ +pub fn fill_bytes(mut bytes: &mut [u8]) { + while !bytes.is_empty() { + let res = unsafe { hermit_abi::read_entropy(bytes.as_mut_ptr(), bytes.len(), 0) }; + assert_ne!(res, -1, "failed to generate random data"); + bytes = &mut bytes[res as usize..]; + } +} diff --git a/std/src/sys/random/horizon.rs b/std/src/sys/random/horizon.rs new file mode 100644 index 0000000000000..0be2eae20a727 --- /dev/null +++ b/std/src/sys/random/horizon.rs @@ -0,0 +1,7 @@ +pub fn fill_bytes(mut bytes: &mut [u8]) { + while !bytes.is_empty() { + let r = unsafe { libc::getrandom(bytes.as_mut_ptr().cast(), bytes.len(), 0) }; + assert_ne!(r, -1, "failed to generate random data"); + bytes = &mut bytes[r as usize..]; + } +} diff --git a/std/src/sys/random/linux.rs b/std/src/sys/random/linux.rs new file mode 100644 index 0000000000000..4ede0af649403 --- /dev/null +++ b/std/src/sys/random/linux.rs @@ -0,0 +1,170 @@ +//! Random data generation with the Linux kernel. +//! +//! The first interface random data interface to be introduced on Linux were +//! the `/dev/random` and `/dev/urandom` special files. As paths can become +//! unreachable when inside a chroot and when the file descriptors are exhausted, +//! this was not enough to provide userspace with a reliable source of randomness, +//! so when the OpenBSD 5.6 introduced the `getentropy` syscall, Linux 3.17 got +//! its very own `getrandom` syscall to match.[^1] Unfortunately, even if our +//! minimum supported version were high enough, we still couldn't rely on the +//! syscall being available, as it is blocked in `seccomp` by default. +//! +//! The question is therefore which of the random sources to use. Historically, +//! the kernel contained two pools: the blocking and non-blocking pool. The +//! blocking pool used entropy estimation to limit the amount of available +//! bytes, while the non-blocking pool, once initialized using the blocking +//! pool, uses a CPRNG to return an unlimited number of random bytes. With a +//! strong enough CPRNG however, the entropy estimation didn't contribute that +//! much towards security while being an excellent vector for DoS attacs. Thus, +//! the blocking pool was removed in kernel version 5.6.[^2] That patch did not +//! magically increase the quality of the non-blocking pool, however, so we can +//! safely consider it strong enough even in older kernel versions and use it +//! unconditionally. +//! +//! One additional consideration to make is that the non-blocking pool is not +//! always initialized during early boot. We want the best quality of randomness +//! for the output of `DefaultRandomSource` so we simply wait until it is +//! initialized. When `HashMap` keys however, this represents a potential source +//! of deadlocks, as the additional entropy may only be generated once the +//! program makes forward progress. In that case, we just use the best random +//! data the system has available at the time. +//! +//! So in conclusion, we always want the output of the non-blocking pool, but +//! may need to wait until it is initalized. The default behaviour of `getrandom` +//! is to wait until the non-blocking pool is initialized and then draw from there, +//! so if `getrandom` is available, we use its default to generate the bytes. For +//! `HashMap`, however, we need to specify the `GRND_INSECURE` flags, but that +//! is only available starting with kernel version 5.6. Thus, if we detect that +//! the flag is unsupported, we try `GRND_NONBLOCK` instead, which will only +//! succeed if the pool is initialized. If it isn't, we fall back to the file +//! access method. +//! +//! The behaviour of `/dev/urandom` is inverse to that of `getrandom`: it always +//! yields data, even when the pool is not initialized. For generating `HashMap` +//! keys, this is not important, so we can use it directly. For secure data +//! however, we need to wait until initialization, which we can do by `poll`ing +//! `/dev/random`. +//! +//! TLDR: our fallback strategies are: +//! +//! Secure data | `HashMap` keys +//! --------------------------------------------|------------------ +//! getrandom(0) | getrandom(GRND_INSECURE) +//! poll("/dev/random") && read("/dev/urandom") | getrandom(GRND_NONBLOCK) +//! | read("/dev/urandom") +//! +//! [^1]: +//! [^2]: +//! +// FIXME(in 2040 or so): once the minimum kernel version is 5.6, remove the +// `GRND_NONBLOCK` fallback and use `/dev/random` instead of `/dev/urandom` +// when secure data is required. + +use crate::fs::File; +use crate::io::Read; +use crate::os::fd::AsRawFd; +use crate::sync::atomic::AtomicBool; +use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; +use crate::sync::OnceLock; +use crate::sys::pal::os::errno; +use crate::sys::pal::weak::syscall; + +fn getrandom(mut bytes: &mut [u8], insecure: bool) { + // A weak symbol allows interposition, e.g. for perf measurements that want to + // disable randomness for consistency. Otherwise, we'll try a raw syscall. + // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28) + syscall! { + fn getrandom( + buffer: *mut libc::c_void, + length: libc::size_t, + flags: libc::c_uint + ) -> libc::ssize_t + } + + static GETRANDOM_AVAILABLE: AtomicBool = AtomicBool::new(true); + static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true); + static URANDOM_READY: AtomicBool = AtomicBool::new(false); + static DEVICE: OnceLock = OnceLock::new(); + + if GETRANDOM_AVAILABLE.load(Relaxed) { + loop { + if bytes.is_empty() { + return; + } + + let flags = if insecure { + if GRND_INSECURE_AVAILABLE.load(Relaxed) { + libc::GRND_INSECURE + } else { + libc::GRND_NONBLOCK + } + } else { + 0 + }; + + let ret = unsafe { getrandom(bytes.as_mut_ptr().cast(), bytes.len(), flags) }; + if ret != -1 { + bytes = &mut bytes[ret as usize..]; + } else { + match errno() { + libc::EINTR => continue, + // `GRND_INSECURE` is not available, try + // `GRND_NONBLOCK`. + libc::EINVAL if flags == libc::GRND_INSECURE => { + GRND_INSECURE_AVAILABLE.store(false, Relaxed); + continue; + } + // The pool is not initialized yet, fall back to + // /dev/urandom for now. + libc::EAGAIN if flags == libc::GRND_NONBLOCK => break, + // `getrandom` is unavailable or blocked by seccomp. + // Don't try it again and fall back to /dev/urandom. + libc::ENOSYS | libc::EPERM => { + GETRANDOM_AVAILABLE.store(false, Relaxed); + break; + } + _ => panic!("failed to generate random data"), + } + } + } + } + + // When we want cryptographic strength, we need to wait for the CPRNG-pool + // to become initialized. Do this by polling `/dev/random` until it is ready. + if !insecure { + if !URANDOM_READY.load(Acquire) { + let random = File::open("/dev/random").expect("failed to open /dev/random"); + let mut fd = libc::pollfd { fd: random.as_raw_fd(), events: libc::POLLIN, revents: 0 }; + + while !URANDOM_READY.load(Acquire) { + let ret = unsafe { libc::poll(&mut fd, 1, -1) }; + match ret { + 1 => { + assert_eq!(fd.revents, libc::POLLIN); + URANDOM_READY.store(true, Release); + break; + } + -1 if errno() == libc::EINTR => continue, + _ => panic!("poll(\"/dev/random\") failed"), + } + } + } + } + + DEVICE + .get_or_try_init(|| File::open("/dev/urandom")) + .and_then(|mut dev| dev.read_exact(bytes)) + .expect("failed to generate random data"); +} + +pub fn fill_bytes(bytes: &mut [u8]) { + getrandom(bytes, false); +} + +pub fn hashmap_random_keys() -> (u64, u64) { + let mut bytes = [0; 16]; + getrandom(&mut bytes, true); + let k1 = u64::from_ne_bytes(bytes[..8].try_into().unwrap()); + let k2 = u64::from_ne_bytes(bytes[8..].try_into().unwrap()); + (k1, k2) +} diff --git a/std/src/sys/random/mod.rs b/std/src/sys/random/mod.rs new file mode 100644 index 0000000000000..d39e78bb7b31c --- /dev/null +++ b/std/src/sys/random/mod.rs @@ -0,0 +1,98 @@ +cfg_if::cfg_if! { + // Tier 1 + if #[cfg(any(target_os = "linux", target_os = "android"))] { + mod linux; + pub use linux::{fill_bytes, hashmap_random_keys}; + } else if #[cfg(target_os = "windows")] { + mod windows; + pub use windows::fill_bytes; + } else if #[cfg(any( + target_os = "openbsd", + target_os = "freebsd", + target_os = "macos", + all(target_os = "netbsd", netbsd10), + target_os = "dragonfly", + target_os = "illumos", + target_os = "solaris", + target_os = "emscripten", + target_os = "vita", + target_os = "haiku", + ))] { + mod unix; + pub use unix::fill_bytes; + // Others, in alphabetical ordering. + } else if #[cfg(all(target_vendor = "apple", not(target_os = "macos")))] { + mod apple; + pub use apple::fill_bytes; + } else if #[cfg(target_os = "espidf")] { + mod espidf; + pub use espidf::fill_bytes; + } else if #[cfg(target_os = "fuchsia")] { + mod fuchsia; + pub use fuchsia::fill_bytes; + } else if #[cfg(target_os = "hermit")] { + mod hermit; + pub use hermit::fill_bytes; + } else if #[cfg(target_os = "horizon")] { + // FIXME: add getentropy to shim-3ds + mod horizon; + pub use horizon::fill_bytes; + } else if #[cfg(any( + target_os = "hurd", + target_os = "l4re", + target_os = "nto", + ))] { + mod unix_legacy; + pub use unix_legacy::fill_bytes; + } else if #[cfg(all(target_os = "netbsd", not(netbsd10)))] { + // FIXME: remove once NetBSD 10 is the minimum + mod netbsd; + pub use netbsd::fill_bytes; + } else if #[cfg(target_os = "redox")] { + mod redox; + pub use redox::fill_bytes; + } else if #[cfg(all(target_vendor = "fortanix", target_env = "sgx"))] { + mod sgx; + pub use sgx::fill_bytes; + } else if #[cfg(target_os = "solid_asp3")] { + mod solid; + pub use solid::fill_bytes; + } else if #[cfg(target_os = "teeos")] { + mod teeos; + pub use teeos::fill_bytes; + } else if #[cfg(target_os = "uefi")] { + mod uefi; + pub use uefi::fill_bytes; + } else if #[cfg(target_os = "vxworks")] { + mod vxworks; + pub use vxworks::fill_bytes; + } else if #[cfg(target_os = "wasi")] { + mod wasi; + pub use wasi::fill_bytes; + } else if #[cfg(target_os = "zkvm")] { + mod zkvm; + pub use zkvm::fill_bytes; + } else if #[cfg(any( + all(target_family = "wasm", target_os = "unknown"), + target_os = "xous", + ))] { + // FIXME: finally remove std support for wasm32-unknown-unknown + // FIXME: add random data generation to xous + mod unsupported; + pub use unsupported::{fill_bytes, hashmap_random_keys}; + } +} + +#[cfg(not(any( + target_os = "linux", + target_os = "android", + all(target_family = "wasm", target_os = "unknown"), + target_os = "xous", +)))] +pub fn hashmap_random_keys() -> (u64, u64) { + let mut buf = [0; 16]; + fill_bytes(&mut buf); + let k1 = u64::from_ne_bytes(buf[..8].try_into().unwrap()); + let k2 = u64::from_ne_bytes(buf[8..].try_into().unwrap()); + (k1, k2) +} diff --git a/std/src/sys/random/netbsd.rs b/std/src/sys/random/netbsd.rs new file mode 100644 index 0000000000000..2c5d9c72f30a1 --- /dev/null +++ b/std/src/sys/random/netbsd.rs @@ -0,0 +1,19 @@ +use crate::ptr; + +pub fn fill_bytes(bytes: &mut [u8]) { + let mib = [libc::CTL_KERN, libc::KERN_ARND]; + for chunk in bytes.chunks_mut(256) { + let mut len = chunk.len(); + let ret = unsafe { + libc::sysctl( + mib.as_ptr(), + mib.len() as libc::c_uint, + chunk.as_mut_ptr().cast(), + &mut len, + ptr::null(), + 0, + ) + }; + assert!(ret != -1 && len == chunk.len(), "failed to generate random data"); + } +} diff --git a/std/src/sys/random/redox.rs b/std/src/sys/random/redox.rs new file mode 100644 index 0000000000000..b004335a35176 --- /dev/null +++ b/std/src/sys/random/redox.rs @@ -0,0 +1,12 @@ +use crate::fs::File; +use crate::io::Read; +use crate::sync::OnceLock; + +static SCHEME: OnceLock = OnceLock::new(); + +pub fn fill_bytes(bytes: &mut [u8]) { + SCHEME + .get_or_try_init(|| File::open("/scheme/rand")) + .and_then(|mut scheme| scheme.read_exact(bytes)) + .expect("failed to generate random data"); +} diff --git a/std/src/sys/random/sgx.rs b/std/src/sys/random/sgx.rs new file mode 100644 index 0000000000000..c3647a8df220e --- /dev/null +++ b/std/src/sys/random/sgx.rs @@ -0,0 +1,67 @@ +use crate::arch::x86_64::{_rdrand16_step, _rdrand32_step, _rdrand64_step}; + +const RETRIES: u32 = 10; + +fn fail() -> ! { + panic!("failed to generate random data"); +} + +fn rdrand64() -> u64 { + unsafe { + let mut ret: u64 = 0; + for _ in 0..RETRIES { + if _rdrand64_step(&mut ret) == 1 { + return ret; + } + } + + fail(); + } +} + +fn rdrand32() -> u32 { + unsafe { + let mut ret: u32 = 0; + for _ in 0..RETRIES { + if _rdrand32_step(&mut ret) == 1 { + return ret; + } + } + + fail(); + } +} + +fn rdrand16() -> u16 { + unsafe { + let mut ret: u16 = 0; + for _ in 0..RETRIES { + if _rdrand16_step(&mut ret) == 1 { + return ret; + } + } + + fail(); + } +} + +pub fn fill_bytes(bytes: &mut [u8]) { + let mut chunks = bytes.array_chunks_mut(); + for chunk in &mut chunks { + *chunk = rdrand64().to_ne_bytes(); + } + + let mut chunks = chunks.into_remainder().array_chunks_mut(); + for chunk in &mut chunks { + *chunk = rdrand32().to_ne_bytes(); + } + + let mut chunks = chunks.into_remainder().array_chunks_mut(); + for chunk in &mut chunks { + *chunk = rdrand16().to_ne_bytes(); + } + + if let [byte] = chunks.into_remainder() { + *byte = rdrand16() as u8; + } +} diff --git a/std/src/sys/random/solid.rs b/std/src/sys/random/solid.rs new file mode 100644 index 0000000000000..545771150e284 --- /dev/null +++ b/std/src/sys/random/solid.rs @@ -0,0 +1,8 @@ +use crate::sys::pal::abi; + +pub fn fill_bytes(bytes: &mut [u8]) { + unsafe { + let result = abi::SOLID_RNG_SampleRandomBytes(bytes.as_mut_ptr(), bytes.len()); + assert_eq!(result, 0, "failed to generate random data"); + } +} diff --git a/std/src/sys/random/teeos.rs b/std/src/sys/random/teeos.rs new file mode 100644 index 0000000000000..fd6b24e19e982 --- /dev/null +++ b/std/src/sys/random/teeos.rs @@ -0,0 +1,7 @@ +extern "C" { + fn TEE_GenerateRandom(randomBuffer: *mut core::ffi::c_void, randomBufferLen: libc::size_t); +} + +pub fn fill_bytes(bytes: &mut [u8]) { + unsafe { TEE_GenerateRandom(bytes.as_mut_ptr().cast(), bytes.len()) } +} diff --git a/std/src/sys/random/uefi.rs b/std/src/sys/random/uefi.rs new file mode 100644 index 0000000000000..a4d29e66f3875 --- /dev/null +++ b/std/src/sys/random/uefi.rs @@ -0,0 +1,27 @@ +use r_efi::protocols::rng; + +use crate::sys::pal::helpers; + +pub fn fill_bytes(bytes: &mut [u8]) { + let handles = + helpers::locate_handles(rng::PROTOCOL_GUID).expect("failed to generate random data"); + for handle in handles { + if let Ok(protocol) = helpers::open_protocol::(handle, rng::PROTOCOL_GUID) { + let r = unsafe { + ((*protocol.as_ptr()).get_rng)( + protocol.as_ptr(), + crate::ptr::null_mut(), + bytes.len(), + bytes.as_mut_ptr(), + ) + }; + if r.is_error() { + continue; + } else { + return; + } + } + } + + panic!("failed to generate random data"); +} diff --git a/std/src/sys/random/unix.rs b/std/src/sys/random/unix.rs new file mode 100644 index 0000000000000..a56847e554129 --- /dev/null +++ b/std/src/sys/random/unix.rs @@ -0,0 +1,33 @@ +//! Random data generation through `getentropy`. +//! +//! Since issue 8 (2024), the POSIX specification mandates the existence of the +//! `getentropy` function, which fills a slice of up to `GETENTROPY_MAX` bytes +//! (256 on all known platforms) with random data. Luckily, this function has +//! already been available on quite some BSDs before that, having appeared with +//! OpenBSD 5.7 and spread from there: +//! +//! platform | version | man-page +//! -----------|---------|---------- +//! OpenBSD | 5.6 | +//! FreeBSD | 12.0 | +//! macOS | 10.12 | +//! NetBSD | 10.0 | +//! DragonFly | 6.1 | +//! Illumos | ? | +//! Solaris | ? | +//! +//! As it is standardized we use it whereever possible, even when `getrandom` is +//! also available. NetBSD even warns that "Applications should avoid getrandom +//! and use getentropy(2) instead; getrandom may be removed from a later +//! release."[^1]. +//! +//! [^1]: + +pub fn fill_bytes(bytes: &mut [u8]) { + // GETENTROPY_MAX isn't defined yet on most platforms, but it's mandated + // to be at least 256, so just use that as limit. + for chunk in bytes.chunks_mut(256) { + let r = unsafe { libc::getentropy(chunk.as_mut_ptr().cast(), chunk.len()) }; + assert_ne!(r, -1, "failed to generate random data"); + } +} diff --git a/std/src/sys/random/unix_legacy.rs b/std/src/sys/random/unix_legacy.rs new file mode 100644 index 0000000000000..dd6be43c173a4 --- /dev/null +++ b/std/src/sys/random/unix_legacy.rs @@ -0,0 +1,20 @@ +//! Random data from `/dev/urandom` +//! +//! Before `getentropy` was standardized in 2024, UNIX didn't have a standardized +//! way of getting random data, so systems just followed the precedent set by +//! Linux and exposed random devices at `/dev/random` and `/dev/urandom`. Thus, +//! for the few systems that do not support `getentropy` yet, we just read from +//! the file. + +use crate::fs::File; +use crate::io::Read; +use crate::sync::OnceLock; + +static DEVICE: OnceLock = OnceLock::new(); + +pub fn fill_bytes(bytes: &mut [u8]) { + DEVICE + .get_or_try_init(|| File::open("/dev/urandom")) + .and_then(|mut dev| dev.read_exact(bytes)) + .expect("failed to generate random data"); +} diff --git a/std/src/sys/random/unsupported.rs b/std/src/sys/random/unsupported.rs new file mode 100644 index 0000000000000..d68ce4a9e8703 --- /dev/null +++ b/std/src/sys/random/unsupported.rs @@ -0,0 +1,15 @@ +use crate::ptr; + +pub fn fill_bytes(_: &mut [u8]) { + panic!("this target does not support random data generation"); +} + +pub fn hashmap_random_keys() -> (u64, u64) { + // Use allocation addresses for a bit of randomness. This isn't + // particularily secure, but there isn't really an alternative. + let stack = 0u8; + let heap = Box::new(0u8); + let k1 = ptr::from_ref(&stack).addr() as u64; + let k2 = ptr::from_ref(&*heap).addr() as u64; + (k1, k2) +} diff --git a/std/src/sys/random/vxworks.rs b/std/src/sys/random/vxworks.rs new file mode 100644 index 0000000000000..d549ccebdb2cd --- /dev/null +++ b/std/src/sys/random/vxworks.rs @@ -0,0 +1,25 @@ +use crate::sync::atomic::AtomicBool; +use crate::sync::atomic::Ordering::Relaxed; + +static RNG_INIT: AtomicBool = AtomicBool::new(false); + +pub fn fill_bytes(mut bytes: &mut [u8]) { + while !RNG_INIT.load(Relaxed) { + let ret = unsafe { libc::randSecure() }; + if ret < 0 { + panic!("failed to generate random data"); + } else if ret > 0 { + RNG_INIT.store(true, Relaxed); + break; + } + + unsafe { libc::usleep(10) }; + } + + while !bytes.is_empty() { + let len = bytes.len().try_into().unwrap_or(libc::c_int::MAX); + let ret = unsafe { libc::randABytes(bytes.as_mut_ptr(), len) }; + assert!(ret >= 0, "failed to generate random data"); + bytes = &mut bytes[len as usize..]; + } +} diff --git a/std/src/sys/random/wasi.rs b/std/src/sys/random/wasi.rs new file mode 100644 index 0000000000000..d41da3751fc04 --- /dev/null +++ b/std/src/sys/random/wasi.rs @@ -0,0 +1,5 @@ +pub fn fill_bytes(bytes: &mut [u8]) { + unsafe { + wasi::random_get(bytes.as_mut_ptr(), bytes.len()).expect("failed to generate random data") + } +} diff --git a/std/src/sys/random/windows.rs b/std/src/sys/random/windows.rs new file mode 100644 index 0000000000000..7566000f9e6ff --- /dev/null +++ b/std/src/sys/random/windows.rs @@ -0,0 +1,20 @@ +use crate::sys::c; + +#[cfg(not(target_vendor = "win7"))] +#[inline] +pub fn fill_bytes(bytes: &mut [u8]) { + let ret = unsafe { c::ProcessPrng(bytes.as_mut_ptr(), bytes.len()) }; + // ProcessPrng is documented as always returning `TRUE`. + // https://learn.microsoft.com/en-us/windows/win32/seccng/processprng#return-value + debug_assert_eq!(ret, c::TRUE); +} + +#[cfg(target_vendor = "win7")] +pub fn fill_bytes(mut bytes: &mut [u8]) { + while !bytes.is_empty() { + let len = bytes.len().try_into().unwrap_or(u32::MAX); + let ret = unsafe { c::RtlGenRandom(bytes.as_mut_ptr().cast(), len) }; + assert_ne!(ret, 0, "failed to generate random data"); + bytes = &mut bytes[len as usize..]; + } +} diff --git a/std/src/sys/random/zkvm.rs b/std/src/sys/random/zkvm.rs new file mode 100644 index 0000000000000..3011942f6b26b --- /dev/null +++ b/std/src/sys/random/zkvm.rs @@ -0,0 +1,21 @@ +use crate::sys::pal::abi; + +pub fn fill_bytes(bytes: &mut [u8]) { + let (pre, words, post) = unsafe { bytes.align_to_mut::() }; + if !words.is_empty() { + unsafe { + abi::sys_rand(words.as_mut_ptr(), words.len()); + } + } + + let mut buf = [0u32; 2]; + let len = (pre.len() + post.len() + size_of::() - 1) / size_of::(); + if len != 0 { + unsafe { abi::sys_rand(buf.as_mut_ptr(), len) }; + } + + let buf = buf.map(u32::to_ne_bytes); + let buf = buf.as_flattened(); + pre.copy_from_slice(&buf[..pre.len()]); + post.copy_from_slice(&buf[pre.len()..pre.len() + post.len()]); +}