std/sys/random/
linux.rs

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
63use crate::fs::File;
64use crate::io::Read;
65use crate::os::fd::AsRawFd;
66use crate::sync::OnceLock;
67use crate::sync::atomic::AtomicBool;
68use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
69use crate::sys::pal::os::errno;
70use crate::sys::pal::weak::syscall;
71
72fn 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: AtomicBool = AtomicBool::new(true);
85    static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true);
86    static URANDOM_READY: AtomicBool = 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
160pub fn fill_bytes(bytes: &mut [u8]) {
161    getrandom(bytes, false);
162}
163
164pub fn hashmap_random_keys() -> (u64, u64) {
165    let mut bytes = [0; 16];
166    getrandom(&mut bytes, true);
167    let k1 = u64::from_ne_bytes(bytes[..8].try_into().unwrap());
168    let k2 = u64::from_ne_bytes(bytes[8..].try_into().unwrap());
169    (k1, k2)
170}