Change the syntax to include parameter names and a trailing semicolon. Motivation: - Mirror the `syscall!` macro. - Allow rustfmt to format it (when wrapped in parentheses). - For better documentation (having the parameter names available in the source code is a bit nicer). - Allow future improvements to this macro where we can sometimes use the symbol directly when it's statically known to be available.
170 lines
7.5 KiB
Rust
170 lines
7.5 KiB
Rust
//! 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 attacs. 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 initalized. 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::AtomicBool;
|
|
use crate::sync::atomic::Ordering::{Acquire, Relaxed, Release};
|
|
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: AtomicBool = AtomicBool::new(true);
|
|
static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true);
|
|
static URANDOM_READY: AtomicBool = 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;
|
|
}
|
|
_ => 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)
|
|
}
|