devela/sys/os/linux/fns/rand.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
// devela::sys::os::linux::fns::rand
//
//!
//
// - https://www.man7.org/linux/man-pages/man2/getrandom.2.html
// - https://www.gnu.org/software/libc/manual/html_node/Unpredictable-Bytes.html
use crate::{
c_uint, iif, linux_print, linux_sys_exit, linux_sys_getrandom, paste, LINUX_ERRNO as ERRNO,
};
// from `sys/random.h`
const GRND_NONBLOCK: c_uint = 0x0001;
// const GRND_RANDOM: isize = 0x0002;
const GRND_INSECURE: c_uint = 0x0004;
const RAND_FLAGS: c_uint = GRND_NONBLOCK | GRND_INSECURE;
const MAX_ATTEMPTS: usize = 15;
// generates a rand function for each given integer primitive
macro_rules! random_fns {
// $prim: the unsigned integer primitive
// $len: the length of the primitive in bytes
($($prim:ident : $len:literal),+) => { paste! { $(
#[doc = "Generates a random `" $prim "` value that may not be criptographically secure."]
///
/// It makes use of the `GRND_NONBLOCK` and `GRND_INSECURE` flags. So when the randomness
/// source is not ready, instead of blocking it may return less secure data in linux >= 5.6
/// or retry it a certain number of times, or even return 0 in some cases.
pub fn [<linux_random_ $prim>]() -> $prim {
let mut r = [0; $len];
let mut attempts = 0;
loop {
let n = unsafe { linux_sys_getrandom(r.as_mut_ptr(), $len, RAND_FLAGS) };
if n == $len {
// hot path!
break;
} else if n == -ERRNO::EAGAIN {
iif![!getrandom_try_again(&mut attempts); break];
} else { // n < 0
getrandom_failed();
}
}
$prim::from_ne_bytes(r)
}
)+ }};
}
random_fns![u8:1, u16:2, u32:4, u64:8, u128:16];
/// Fills the given `buffer` with random bytes that may not be cryptographically secure.
///
/// It makes use of the `GRND_NONBLOCK` and `GRND_INSECURE` flags. So when the randomness
/// source is not ready, instead of blocking it may return less secure data in linux >= 5.6
/// or retry it a certain number of times, or even return 0 in some cases.
///
/// # Panics
/// Panics in debug if `buffer.len() > `[`isize::MAX`]
pub fn linux_random_bytes(buffer: &mut [u8]) {
debug_assert![buffer.len() <= isize::MAX as usize];
let mut attempts = 0;
let mut offset = 0;
while offset < buffer.len() {
let n = unsafe {
linux_sys_getrandom(buffer[offset..].as_mut_ptr(), buffer.len() - offset, RAND_FLAGS)
};
if n == -ERRNO::EAGAIN {
iif![!getrandom_try_again(&mut attempts); break];
} else if n < 0 {
getrandom_failed();
} else {
// hot path!
offset += n as usize;
}
}
}
// the cold path for trying again
#[cold] #[must_use] #[rustfmt::skip]
fn getrandom_try_again(attempts: &mut usize) -> bool {
// if *attempts >= MAX_ATTEMPTS { getrandom_failed(); }
*attempts += 1;
*attempts <= MAX_ATTEMPTS
}
// the cold path for some other error
#[cold] #[rustfmt::skip]
fn getrandom_failed() {
linux_print("getrandom failed");
unsafe { linux_sys_exit(12); }
}