getrandom/backends/
linux_android_with_fallback.rs

1//! Implementation for Linux / Android with `/dev/urandom` fallback
2use super::use_file;
3use crate::Error;
4use core::{
5    ffi::c_void,
6    mem::{self, MaybeUninit},
7    ptr::{self, NonNull},
8    sync::atomic::{AtomicPtr, Ordering},
9};
10use use_file::util_libc;
11
12pub use crate::util::{inner_u32, inner_u64};
13
14type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
15
16/// Sentinel value which indicates that `libc::getrandom` either not available,
17/// or not supported by kernel.
18const NOT_AVAILABLE: NonNull<c_void> = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) };
19
20static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
21
22#[cold]
23fn init() -> NonNull<c_void> {
24    static NAME: &[u8] = b"getrandom\0";
25    let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
26    let raw_ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
27    let res_ptr = match NonNull::new(raw_ptr) {
28        Some(fptr) => {
29            let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
30            let dangling_ptr = ptr::NonNull::dangling().as_ptr();
31            // Check that `getrandom` syscall is supported by kernel
32            let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
33            if cfg!(getrandom_test_linux_fallback) {
34                NOT_AVAILABLE
35            } else if res.is_negative() {
36                match util_libc::last_os_error().raw_os_error() {
37                    Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
38                    // The fallback on EPERM is intentionally not done on Android since this workaround
39                    // seems to be needed only for specific Linux-based products that aren't based
40                    // on Android. See https://github.com/rust-random/getrandom/issues/229.
41                    #[cfg(target_os = "linux")]
42                    Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
43                    _ => fptr,
44                }
45            } else {
46                fptr
47            }
48        }
49        None => NOT_AVAILABLE,
50    };
51
52    GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
53    res_ptr
54}
55
56// prevent inlining of the fallback implementation
57#[inline(never)]
58fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
59    use_file::fill_inner(dest)
60}
61
62pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
63    // Despite being only a single atomic variable, we still cannot always use
64    // Ordering::Relaxed, as we need to make sure a successful call to `init`
65    // is "ordered before" any data read through the returned pointer (which
66    // occurs when the function is called). Our implementation mirrors that of
67    // the one in libstd, meaning that the use of non-Relaxed operations is
68    // probably unnecessary.
69    let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
70    let fptr = match NonNull::new(raw_ptr) {
71        Some(p) => p,
72        None => init(),
73    };
74
75    if fptr == NOT_AVAILABLE {
76        use_file_fallback(dest)
77    } else {
78        // note: `transume` is currently the only way to convert pointer into function reference
79        let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
80        util_libc::sys_fill_exact(dest, |buf| unsafe {
81            getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
82        })
83    }
84}