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); }
}