//! 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 attacks. 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 initialized. The default behavior 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 behavior 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::OnceLock; use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; use crate::sync::atomic::{Atomic, AtomicBool}; 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: Atomic = AtomicBool::new(true); static GRND_INSECURE_AVAILABLE: Atomic = AtomicBool::new(true); static URANDOM_READY: Atomic = 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) }