| 1 | //! Random data generation with the Linux kernel. |
| 2 | //! |
| 3 | //! The first interface random data interface to be introduced on Linux were |
| 4 | //! the `/dev/random` and `/dev/urandom` special files. As paths can become |
| 5 | //! unreachable when inside a chroot and when the file descriptors are exhausted, |
| 6 | //! this was not enough to provide userspace with a reliable source of randomness, |
| 7 | //! so when the OpenBSD 5.6 introduced the `getentropy` syscall, Linux 3.17 got |
| 8 | //! its very own `getrandom` syscall to match.[^1] Unfortunately, even if our |
| 9 | //! minimum supported version were high enough, we still couldn't rely on the |
| 10 | //! syscall being available, as it is blocked in `seccomp` by default. |
| 11 | //! |
| 12 | //! The question is therefore which of the random sources to use. Historically, |
| 13 | //! the kernel contained two pools: the blocking and non-blocking pool. The |
| 14 | //! blocking pool used entropy estimation to limit the amount of available |
| 15 | //! bytes, while the non-blocking pool, once initialized using the blocking |
| 16 | //! pool, uses a CPRNG to return an unlimited number of random bytes. With a |
| 17 | //! strong enough CPRNG however, the entropy estimation didn't contribute that |
| 18 | //! much towards security while being an excellent vector for DoS attacs. Thus, |
| 19 | //! the blocking pool was removed in kernel version 5.6.[^2] That patch did not |
| 20 | //! magically increase the quality of the non-blocking pool, however, so we can |
| 21 | //! safely consider it strong enough even in older kernel versions and use it |
| 22 | //! unconditionally. |
| 23 | //! |
| 24 | //! One additional consideration to make is that the non-blocking pool is not |
| 25 | //! always initialized during early boot. We want the best quality of randomness |
| 26 | //! for the output of `DefaultRandomSource` so we simply wait until it is |
| 27 | //! initialized. When `HashMap` keys however, this represents a potential source |
| 28 | //! of deadlocks, as the additional entropy may only be generated once the |
| 29 | //! program makes forward progress. In that case, we just use the best random |
| 30 | //! data the system has available at the time. |
| 31 | //! |
| 32 | //! So in conclusion, we always want the output of the non-blocking pool, but |
| 33 | //! may need to wait until it is initalized. The default behavior of `getrandom` |
| 34 | //! is to wait until the non-blocking pool is initialized and then draw from there, |
| 35 | //! so if `getrandom` is available, we use its default to generate the bytes. For |
| 36 | //! `HashMap`, however, we need to specify the `GRND_INSECURE` flags, but that |
| 37 | //! is only available starting with kernel version 5.6. Thus, if we detect that |
| 38 | //! the flag is unsupported, we try `GRND_NONBLOCK` instead, which will only |
| 39 | //! succeed if the pool is initialized. If it isn't, we fall back to the file |
| 40 | //! access method. |
| 41 | //! |
| 42 | //! The behavior of `/dev/urandom` is inverse to that of `getrandom`: it always |
| 43 | //! yields data, even when the pool is not initialized. For generating `HashMap` |
| 44 | //! keys, this is not important, so we can use it directly. For secure data |
| 45 | //! however, we need to wait until initialization, which we can do by `poll`ing |
| 46 | //! `/dev/random`. |
| 47 | //! |
| 48 | //! TLDR: our fallback strategies are: |
| 49 | //! |
| 50 | //! Secure data | `HashMap` keys |
| 51 | //! --------------------------------------------|------------------ |
| 52 | //! getrandom(0) | getrandom(GRND_INSECURE) |
| 53 | //! poll("/dev/random") && read("/dev/urandom") | getrandom(GRND_NONBLOCK) |
| 54 | //! | read("/dev/urandom") |
| 55 | //! |
| 56 | //! [^1]: <https://lwn.net/Articles/606141/> |
| 57 | //! [^2]: <https://lwn.net/Articles/808575/> |
| 58 | //! |
| 59 | // FIXME(in 2040 or so): once the minimum kernel version is 5.6, remove the |
| 60 | // `GRND_NONBLOCK` fallback and use `/dev/random` instead of `/dev/urandom` |
| 61 | // when secure data is required. |
| 62 | |
| 63 | use crate::fs::File; |
| 64 | use crate::io::Read; |
| 65 | use crate::os::fd::AsRawFd; |
| 66 | use crate::sync::OnceLock; |
| 67 | use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release}; |
| 68 | use crate::sync::atomic::{Atomic, AtomicBool}; |
| 69 | use crate::sys::pal::os::errno; |
| 70 | use crate::sys::pal::weak::syscall; |
| 71 | |
| 72 | fn getrandom(mut bytes: &mut [u8], insecure: bool) { |
| 73 | // A weak symbol allows interposition, e.g. for perf measurements that want to |
| 74 | // disable randomness for consistency. Otherwise, we'll try a raw syscall. |
| 75 | // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28) |
| 76 | syscall!( |
| 77 | fn getrandom( |
| 78 | buffer: *mut libc::c_void, |
| 79 | length: libc::size_t, |
| 80 | flags: libc::c_uint, |
| 81 | ) -> libc::ssize_t; |
| 82 | ); |
| 83 | |
| 84 | static GETRANDOM_AVAILABLE: Atomic<bool> = AtomicBool::new(true); |
| 85 | static GRND_INSECURE_AVAILABLE: Atomic<bool> = AtomicBool::new(true); |
| 86 | static URANDOM_READY: Atomic<bool> = AtomicBool::new(false); |
| 87 | static DEVICE: OnceLock<File> = OnceLock::new(); |
| 88 | |
| 89 | if GETRANDOM_AVAILABLE.load(Relaxed) { |
| 90 | loop { |
| 91 | if bytes.is_empty() { |
| 92 | return; |
| 93 | } |
| 94 | |
| 95 | let flags = if insecure { |
| 96 | if GRND_INSECURE_AVAILABLE.load(Relaxed) { |
| 97 | libc::GRND_INSECURE |
| 98 | } else { |
| 99 | libc::GRND_NONBLOCK |
| 100 | } |
| 101 | } else { |
| 102 | 0 |
| 103 | }; |
| 104 | |
| 105 | let ret = unsafe { getrandom(bytes.as_mut_ptr().cast(), bytes.len(), flags) }; |
| 106 | if ret != -1 { |
| 107 | bytes = &mut bytes[ret as usize..]; |
| 108 | } else { |
| 109 | match errno() { |
| 110 | libc::EINTR => continue, |
| 111 | // `GRND_INSECURE` is not available, try |
| 112 | // `GRND_NONBLOCK`. |
| 113 | libc::EINVAL if flags == libc::GRND_INSECURE => { |
| 114 | GRND_INSECURE_AVAILABLE.store(false, Relaxed); |
| 115 | continue; |
| 116 | } |
| 117 | // The pool is not initialized yet, fall back to |
| 118 | // /dev/urandom for now. |
| 119 | libc::EAGAIN if flags == libc::GRND_NONBLOCK => break, |
| 120 | // `getrandom` is unavailable or blocked by seccomp. |
| 121 | // Don't try it again and fall back to /dev/urandom. |
| 122 | libc::ENOSYS | libc::EPERM => { |
| 123 | GETRANDOM_AVAILABLE.store(false, Relaxed); |
| 124 | break; |
| 125 | } |
| 126 | _ => panic!("failed to generate random data" ), |
| 127 | } |
| 128 | } |
| 129 | } |
| 130 | } |
| 131 | |
| 132 | // When we want cryptographic strength, we need to wait for the CPRNG-pool |
| 133 | // to become initialized. Do this by polling `/dev/random` until it is ready. |
| 134 | if !insecure { |
| 135 | if !URANDOM_READY.load(Acquire) { |
| 136 | let random = File::open("/dev/random" ).expect("failed to open /dev/random" ); |
| 137 | let mut fd = libc::pollfd { fd: random.as_raw_fd(), events: libc::POLLIN, revents: 0 }; |
| 138 | |
| 139 | while !URANDOM_READY.load(Acquire) { |
| 140 | let ret = unsafe { libc::poll(&mut fd, 1, -1) }; |
| 141 | match ret { |
| 142 | 1 => { |
| 143 | assert_eq!(fd.revents, libc::POLLIN); |
| 144 | URANDOM_READY.store(true, Release); |
| 145 | break; |
| 146 | } |
| 147 | -1 if errno() == libc::EINTR => continue, |
| 148 | _ => panic!("poll( \"/dev/random \") failed" ), |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | } |
| 153 | |
| 154 | DEVICE |
| 155 | .get_or_try_init(|| File::open("/dev/urandom" )) |
| 156 | .and_then(|mut dev| dev.read_exact(bytes)) |
| 157 | .expect("failed to generate random data" ); |
| 158 | } |
| 159 | |
| 160 | pub fn fill_bytes(bytes: &mut [u8]) { |
| 161 | getrandom(bytes, insecure:false); |
| 162 | } |
| 163 | |
| 164 | pub fn hashmap_random_keys() -> (u64, u64) { |
| 165 | let mut bytes: [u8; 16] = [0; 16]; |
| 166 | getrandom(&mut bytes, insecure:true); |
| 167 | let k1: u64 = u64::from_ne_bytes(bytes[..8].try_into().unwrap()); |
| 168 | let k2: u64 = u64::from_ne_bytes(bytes[8..].try_into().unwrap()); |
| 169 | (k1, k2) |
| 170 | } |
| 171 | |