std: switch to faster random sources on macOS and most BSDs

This commit is contained in:
joboet 2024-08-17 16:54:01 +02:00
parent 5c1c725724
commit b9d47cfa9b
No known key found for this signature in database
GPG Key ID: 704E0149B0194B3C
9 changed files with 94 additions and 106 deletions

View File

@ -24,35 +24,34 @@
/// 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`]
/// Windows | [`ProcessPrng`](https://learn.microsoft.com/en-us/windows/win32/seccng/processprng)
/// Apple | `CCRandomGenerateBytes`
/// DragonFly | [`arc4random_buf`](https://man.dragonflybsd.org/?command=arc4random&section=ANY)
/// 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&apropos=0&sektion=0&manpath=FreeBSD+15.0-CURRENT&arch=default&format=html)
/// 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`
/// NetBSD before 10.0 | [`kern.arandom`]
/// Redox | `/scheme/rand`
/// SGX | [`rdrand`]
/// SGX | [`rdrand`](https://en.wikipedia.org/wiki/RDRAND)
/// SOLID | `SOLID_RNG_SampleRandomBytes`
/// TEEOS | `TEE_GenerateRandom`
/// UEFI | [`EFI_RNG_PROTOCOL`]
/// 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`
/// 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`
///
/// **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;

View File

@ -1,20 +1,13 @@
//! Random data on non-macOS Apple platforms.
//! Random data on 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
//! `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`.
//!
//! [^1]: <https://support.apple.com/en-gb/guide/security/seca0c73a75b/web>
//! 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()) };

View 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()) }
}

View 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");
}
}

View File

@ -63,9 +63,9 @@
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::sync::OnceLock;
use crate::sys::pal::os::errno;
use crate::sys::pal::weak::syscall;

View File

@ -6,24 +6,25 @@
} 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")))] {
} 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;
@ -34,7 +35,7 @@
mod hermit;
pub use hermit::fill_bytes;
} else if #[cfg(target_os = "horizon")] {
// FIXME: add getentropy to shim-3ds
// FIXME: add arc4random_buf to shim-3ds
mod horizon;
pub use horizon::fill_bytes;
} else if #[cfg(any(
@ -44,10 +45,6 @@
))] {
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;

View File

@ -1,19 +0,0 @@
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");
}
}

View File

@ -1,33 +0,0 @@
//! 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 | <https://man.openbsd.org/getentropy.2>
//! FreeBSD | 12.0 | <https://man.freebsd.org/cgi/man.cgi?query=getentropy&manpath=FreeBSD+15.0-CURRENT>
//! macOS | 10.12 | <https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/man/man2/getentropy.2#L4>
//! NetBSD | 10.0 | <https://man.netbsd.org/getentropy.3>
//! DragonFly | 6.1 | <https://man.dragonflybsd.org/?command=getentropy&section=3>
//! Illumos | ? | <https://www.illumos.org/man/3C/getentropy>
//! Solaris | ? | <https://docs.oracle.com/cd/E88353_01/html/E37841/getentropy-2.html>
//!
//! 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]: <https://man.netbsd.org/getrandom.2>
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");
}
}

View File

@ -3,8 +3,8 @@
//! 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.
//! 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;