| //! 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 `SystemRng` 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]: <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::Ordering::{Acquire, Relaxed, Release}; | |
| use crate::sync::atomic::{Atomic, AtomicBool}; | |
| use crate::sys::io::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<bool> = AtomicBool::new(true); | |
| static GRND_INSECURE_AVAILABLE: Atomic<bool> = AtomicBool::new(true); | |
| static URANDOM_READY: Atomic<bool> = 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; | |
| } | |
| other => { | |
| panic!("failed to generate random data: errno={other:?}, flags={flags:?}") | |
| } | |
| } | |
| } | |
| } | |
| } | |
| // 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) | |
| } |