Rollup merge of #129201 - joboet:random_faster_sources, r=joshtriplett
std: implement the `random` feature (alternative version) Implements the ACP rust-lang/libs-team#393. This PR is an alternative version of #129120 that replaces `getentropy` with `CCRandomGenerateBytes` (on macOS) and `arc4random_buf` (other BSDs), since that function is not suited for generating large amounts of data and should only be used to seed other CPRNGs. `CCRandomGenerateBytes`/`arc4random_buf` on the other hand is (on modern platforms) just as secure and uses its own, very strong CPRNG (ChaCha20 on the BSDs, AES on macOS) periodically seeded with `getentropy`.
This commit is contained in:
commit
1d369312e4
@ -394,6 +394,8 @@ pub mod assert_matches {
|
||||
#[unstable(feature = "core_pattern_types", issue = "123646")]
|
||||
pub mod pat;
|
||||
pub mod pin;
|
||||
#[unstable(feature = "random", issue = "130703")]
|
||||
pub mod random;
|
||||
#[unstable(feature = "new_range_api", issue = "125687")]
|
||||
pub mod range;
|
||||
pub mod result;
|
||||
|
62
library/core/src/random.rs
Normal file
62
library/core/src/random.rs
Normal file
@ -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 = "130703")]
|
||||
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 = "130703")]
|
||||
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);
|
@ -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 @@ pub fn new() -> 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| {
|
||||
|
@ -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 @@
|
||||
#[unstable(feature = "anonymous_pipe", issue = "127154")]
|
||||
pub mod pipe;
|
||||
pub mod process;
|
||||
#[unstable(feature = "random", issue = "130703")]
|
||||
pub mod random;
|
||||
pub mod sync;
|
||||
pub mod time;
|
||||
|
||||
|
104
library/std/src/random.rs
Normal file
104
library/std/src/random.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! Random value generation.
|
||||
//!
|
||||
//! The [`Random`] trait allows generating a random value for a type using a
|
||||
//! given [`RandomSource`].
|
||||
|
||||
#[unstable(feature = "random", issue = "130703")]
|
||||
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 on some targets. 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`](https://learn.microsoft.com/en-us/windows/win32/seccng/processprng)
|
||||
/// Apple | `CCRandomGenerateBytes`
|
||||
/// DragonFly | [`arc4random_buf`](https://man.dragonflybsd.org/?command=arc4random)
|
||||
/// ESP-IDF | [`esp_fill_random`](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/system/random.html#_CPPv415esp_fill_randomPv6size_t)
|
||||
/// FreeBSD | [`arc4random_buf`](https://man.freebsd.org/cgi/man.cgi?query=arc4random)
|
||||
/// Fuchsia | [`cprng_draw`](https://fuchsia.dev/reference/syscalls/cprng_draw)
|
||||
/// Haiku | `arc4random_buf`
|
||||
/// Illumos | [`arc4random_buf`](https://www.illumos.org/man/3C/arc4random)
|
||||
/// NetBSD | [`arc4random_buf`](https://man.netbsd.org/arc4random.3)
|
||||
/// OpenBSD | [`arc4random_buf`](https://man.openbsd.org/arc4random.3)
|
||||
/// Solaris | [`arc4random_buf`](https://docs.oracle.com/cd/E88353_01/html/E37843/arc4random-3c.html)
|
||||
/// Vita | `arc4random_buf`
|
||||
/// Hermit | `read_entropy`
|
||||
/// Horizon | `getrandom` shim
|
||||
/// Hurd, L4Re, QNX | `/dev/urandom`
|
||||
/// Redox | `/scheme/rand`
|
||||
/// SGX | [`rdrand`](https://en.wikipedia.org/wiki/RDRAND)
|
||||
/// SOLID | `SOLID_RNG_SampleRandomBytes`
|
||||
/// TEEOS | `TEE_GenerateRandom`
|
||||
/// UEFI | [`EFI_RNG_PROTOCOL`](https://uefi.org/specs/UEFI/2.10/37_Secure_Technologies.html#random-number-generator-protocol)
|
||||
/// VxWorks | `randABytes` after waiting for `randSecure` to become ready
|
||||
/// WASI | [`random_get`](https://github.com/WebAssembly/WASI/blob/main/legacy/preview1/docs.md#-random_getbuf-pointeru8-buf_len-size---result-errno)
|
||||
/// ZKVM | `sys_rand`
|
||||
///
|
||||
/// Note that the sources used might change over time.
|
||||
///
|
||||
/// Consult the documentation for the underlying operations on your supported
|
||||
/// targets to determine whether they provide any particular desired properties,
|
||||
/// such as support for reseeding on VM fork operations.
|
||||
///
|
||||
/// [`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
|
||||
#[derive(Default, Debug, Clone, Copy)]
|
||||
#[unstable(feature = "random", issue = "130703")]
|
||||
pub struct DefaultRandomSource;
|
||||
|
||||
#[unstable(feature = "random", issue = "130703")]
|
||||
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: u128 = 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)
|
||||
#[unstable(feature = "random", issue = "130703")]
|
||||
pub fn random<T: Random>() -> T {
|
||||
T::random(&mut DefaultRandomSource)
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
pub mod exit_guard;
|
||||
pub mod os_str;
|
||||
pub mod path;
|
||||
pub mod random;
|
||||
pub mod sync;
|
||||
pub mod thread_local;
|
||||
|
||||
|
@ -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))]
|
||||
|
@ -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<u64> {
|
||||
// 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 _;
|
||||
}
|
||||
}
|
||||
|
@ -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<Inner>: Sized {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 pipe;
|
||||
#[path = "../unsupported/process.rs"]
|
||||
pub mod process;
|
||||
mod rand;
|
||||
pub mod stdio;
|
||||
pub mod thread;
|
||||
#[allow(non_upper_case_globals)]
|
||||
|
@ -1,21 +0,0 @@
|
||||
pub fn hashmap_random_keys() -> (u64, u64) {
|
||||
const KEY_LEN: usize = core::mem::size_of::<u64>();
|
||||
|
||||
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::<u8>()) }
|
||||
}
|
||||
}
|
@ -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::<rng::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();
|
||||
|
@ -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 mod os;
|
||||
pub mod pipe;
|
||||
pub mod process;
|
||||
pub mod rand;
|
||||
pub mod stack_overflow;
|
||||
pub mod stdio;
|
||||
pub mod thread;
|
||||
|
@ -1,302 +0,0 @@
|
||||
pub fn hashmap_random_keys() -> (u64, u64) {
|
||||
const KEY_LEN: usize = core::mem::size_of::<u64>();
|
||||
|
||||
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<File> = 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<Result<()>> {
|
||||
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()) }
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
|
@ -47,4 +47,4 @@
|
||||
// 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};
|
||||
|
@ -50,6 +50,6 @@
|
||||
// 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;
|
||||
|
@ -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 os;
|
||||
pub mod pipe;
|
||||
pub mod process;
|
||||
pub mod rand;
|
||||
pub mod stdio;
|
||||
pub mod thread;
|
||||
pub mod time;
|
||||
|
@ -2,12 +2,13 @@
|
||||
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::<Vec<_>>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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::<u8>(), 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::<c_void>(), mem::size_of_val(&v) as u32)
|
||||
};
|
||||
|
||||
if ret != 0 { v } else { panic!("RNG broken: {}", io::Error::last_os_error()) }
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
15
library/std/src/sys/random/apple.rs
Normal file
15
library/std/src/sys/random/apple.rs
Normal file
@ -0,0 +1,15 @@
|
||||
//! Random data on Apple platforms.
|
||||
//!
|
||||
//! `CCRandomGenerateBytes` calls into `CCRandomCopyBytes` with `kCCRandomDefault`.
|
||||
//! `CCRandomCopyBytes` manages a CSPRNG which is seeded from the kernel's CSPRNG.
|
||||
//! We use `CCRandomGenerateBytes` instead of `SecCopyBytes` because it is accessible via
|
||||
//! `libSystem` (libc) while the other needs to link to `Security.framework`.
|
||||
//!
|
||||
//! Note that technically, `arc4random_buf` is available as well, but that calls
|
||||
//! into the same system service anyway, and `CCRandomGenerateBytes` has been
|
||||
//! proven to be App Store-compatible.
|
||||
|
||||
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");
|
||||
}
|
34
library/std/src/sys/random/arc4random.rs
Normal file
34
library/std/src/sys/random/arc4random.rs
Normal file
@ -0,0 +1,34 @@
|
||||
//! Random data generation with `arc4random_buf`.
|
||||
//!
|
||||
//! Contrary to its name, `arc4random` doesn't actually use the horribly-broken
|
||||
//! RC4 cypher anymore, at least not on modern systems, but rather something
|
||||
//! like ChaCha20 with continual reseeding from the OS. That makes it an ideal
|
||||
//! source of large quantities of cryptographically secure data, which is exactly
|
||||
//! what we need for `DefaultRandomSource`. Unfortunately, it's not available
|
||||
//! on all UNIX systems, most notably Linux (until recently, but it's just a
|
||||
//! wrapper for `getrandom`. Since we need to hook into `getrandom` directly
|
||||
//! for `HashMap` keys anyway, we just keep our version).
|
||||
|
||||
#[cfg(not(any(
|
||||
target_os = "haiku",
|
||||
target_os = "illumos",
|
||||
target_os = "solaris",
|
||||
target_os = "vita",
|
||||
)))]
|
||||
use libc::arc4random_buf;
|
||||
|
||||
// FIXME: move these to libc (except Haiku, that one needs to link to libbsd.so).
|
||||
#[cfg(any(
|
||||
target_os = "haiku", // See https://git.haiku-os.org/haiku/tree/headers/compatibility/bsd/stdlib.h
|
||||
target_os = "illumos", // See https://www.illumos.org/man/3C/arc4random
|
||||
target_os = "solaris", // See https://docs.oracle.com/cd/E88353_01/html/E37843/arc4random-3c.html
|
||||
target_os = "vita", // See https://github.com/vitasdk/newlib/blob/b89e5bc183b516945f9ee07eef483ecb916e45ff/newlib/libc/include/stdlib.h#L74
|
||||
))]
|
||||
#[cfg_attr(target_os = "haiku", link(name = "bsd"))]
|
||||
extern "C" {
|
||||
fn arc4random_buf(buf: *mut core::ffi::c_void, nbytes: libc::size_t);
|
||||
}
|
||||
|
||||
pub fn fill_bytes(bytes: &mut [u8]) {
|
||||
unsafe { arc4random_buf(bytes.as_mut_ptr().cast(), bytes.len()) }
|
||||
}
|
9
library/std/src/sys/random/espidf.rs
Normal file
9
library/std/src/sys/random/espidf.rs
Normal file
@ -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()) }
|
||||
}
|
13
library/std/src/sys/random/fuchsia.rs
Normal file
13
library/std/src/sys/random/fuchsia.rs
Normal file
@ -0,0 +1,13 @@
|
||||
//! Random data generation using the Zircon kernel.
|
||||
//!
|
||||
//! Fuchsia, as always, is quite nice and provides exactly the API we need:
|
||||
//! <https://fuchsia.dev/reference/syscalls/cprng_draw>.
|
||||
|
||||
#[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()) }
|
||||
}
|
17
library/std/src/sys/random/getentropy.rs
Normal file
17
library/std/src/sys/random/getentropy.rs
Normal file
@ -0,0 +1,17 @@
|
||||
//! 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. Unfortunately, it's only
|
||||
//! meant to be used to seed other CPRNGs, which we don't have, so we only use
|
||||
//! it where `arc4random_buf` and friends aren't available or secure (currently
|
||||
//! that's only the case on Emscripten).
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
7
library/std/src/sys/random/hermit.rs
Normal file
7
library/std/src/sys/random/hermit.rs
Normal file
@ -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..];
|
||||
}
|
||||
}
|
7
library/std/src/sys/random/horizon.rs
Normal file
7
library/std/src/sys/random/horizon.rs
Normal file
@ -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..];
|
||||
}
|
||||
}
|
170
library/std/src/sys/random/linux.rs
Normal file
170
library/std/src/sys/random/linux.rs
Normal file
@ -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]: <https://lwn.net/Articles/606141/>
|
||||
//! [^2]: <https://lwn.net/Articles/808575/>
|
||||
//!
|
||||
// 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::OnceLock;
|
||||
use crate::sync::atomic::AtomicBool;
|
||||
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
|
||||
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<File> = 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)
|
||||
}
|
95
library/std/src/sys/random/mod.rs
Normal file
95
library/std/src/sys/random/mod.rs
Normal file
@ -0,0 +1,95 @@
|
||||
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(target_vendor = "apple")] {
|
||||
mod apple;
|
||||
pub use apple::fill_bytes;
|
||||
// Others, in alphabetical ordering.
|
||||
} else if #[cfg(any(
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "haiku",
|
||||
target_os = "illumos",
|
||||
target_os = "netbsd",
|
||||
target_os = "openbsd",
|
||||
target_os = "solaris",
|
||||
target_os = "vita",
|
||||
))] {
|
||||
mod arc4random;
|
||||
pub use arc4random::fill_bytes;
|
||||
} else if #[cfg(target_os = "emscripten")] {
|
||||
mod getentropy;
|
||||
pub use getentropy::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 arc4random_buf 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(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)
|
||||
}
|
12
library/std/src/sys/random/redox.rs
Normal file
12
library/std/src/sys/random/redox.rs
Normal file
@ -0,0 +1,12 @@
|
||||
use crate::fs::File;
|
||||
use crate::io::Read;
|
||||
use crate::sync::OnceLock;
|
||||
|
||||
static SCHEME: OnceLock<File> = 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");
|
||||
}
|
67
library/std/src/sys/random/sgx.rs
Normal file
67
library/std/src/sys/random/sgx.rs
Normal file
@ -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;
|
||||
}
|
||||
}
|
8
library/std/src/sys/random/solid.rs
Normal file
8
library/std/src/sys/random/solid.rs
Normal file
@ -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");
|
||||
}
|
||||
}
|
7
library/std/src/sys/random/teeos.rs
Normal file
7
library/std/src/sys/random/teeos.rs
Normal file
@ -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()) }
|
||||
}
|
27
library/std/src/sys/random/uefi.rs
Normal file
27
library/std/src/sys/random/uefi.rs
Normal file
@ -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::<rng::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");
|
||||
}
|
20
library/std/src/sys/random/unix_legacy.rs
Normal file
20
library/std/src/sys/random/unix_legacy.rs
Normal file
@ -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 support neither `arc4random_buf` nor `getentropy`
|
||||
//! yet, we just read from the file.
|
||||
|
||||
use crate::fs::File;
|
||||
use crate::io::Read;
|
||||
use crate::sync::OnceLock;
|
||||
|
||||
static DEVICE: OnceLock<File> = 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");
|
||||
}
|
15
library/std/src/sys/random/unsupported.rs
Normal file
15
library/std/src/sys/random/unsupported.rs
Normal file
@ -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)
|
||||
}
|
25
library/std/src/sys/random/vxworks.rs
Normal file
25
library/std/src/sys/random/vxworks.rs
Normal file
@ -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..];
|
||||
}
|
||||
}
|
5
library/std/src/sys/random/wasi.rs
Normal file
5
library/std/src/sys/random/wasi.rs
Normal file
@ -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")
|
||||
}
|
||||
}
|
20
library/std/src/sys/random/windows.rs
Normal file
20
library/std/src/sys/random/windows.rs
Normal file
@ -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..];
|
||||
}
|
||||
}
|
21
library/std/src/sys/random/zkvm.rs
Normal file
21
library/std/src/sys/random/zkvm.rs
Normal file
@ -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::<u32>() };
|
||||
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::<u32>() - 1) / size_of::<u32>();
|
||||
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()]);
|
||||
}
|
@ -78,6 +78,16 @@ fn emulate_foreign_item_inner(
|
||||
this.write_pointer(environ, dest)?;
|
||||
}
|
||||
|
||||
// Random data generation
|
||||
"CCRandomGenerateBytes" => {
|
||||
let [bytes, count] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
let bytes = this.read_pointer(bytes)?;
|
||||
let count = this.read_target_usize(count)?;
|
||||
let success = this.eval_libc_i32("kCCSuccess");
|
||||
this.gen_random(bytes, count)?;
|
||||
this.write_int(success, dest)?;
|
||||
}
|
||||
|
||||
// Time related shims
|
||||
"mach_absolute_time" => {
|
||||
let [] = this.check_shim(abi, Abi::C { unwind: false }, link_name, args)?;
|
||||
|
@ -0,0 +1,7 @@
|
||||
//@only-target: apple # This directly tests apple-only functions
|
||||
|
||||
fn main() {
|
||||
let mut bytes = [0u8; 24];
|
||||
let ret = unsafe { libc::CCRandomGenerateBytes(bytes.as_mut_ptr().cast(), bytes.len()) };
|
||||
assert_eq!(ret, libc::kCCSuccess);
|
||||
}
|
Loading…
Reference in New Issue
Block a user