1//! Implementation for Linux / Android with `/dev/urandom` fallback
2use super::use_file;
3use crate::Error;
4use core::{
5 ffi::c_void,
6 mem::{transmute, MaybeUninit},
7 ptr::NonNull,
8 sync::atomic::{AtomicPtr, Ordering},
9};
10use use_file::util_libc;
1112pub use crate::util::{inner_u32, inner_u64};
1314type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
1516/// 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) };
1920static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
2122#[cold]
23#[inline(never)]
24fn init() -> NonNull<c_void> {
25// Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else
26#[cfg(not(target_env = "musl"))]
27let raw_ptr = {
28static NAME: &[u8] = b"getrandom\0";
29let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
30unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }
31 };
32#[cfg(target_env = "musl")]
33let raw_ptr = {
34let fptr: GetRandomFn = libc::getrandom;
35unsafe { transmute::<GetRandomFn, *mut c_void>(fptr) }
36 };
3738let res_ptr = match NonNull::new(raw_ptr) {
39Some(fptr) => {
40let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
41let dangling_ptr = NonNull::dangling().as_ptr();
42// Check that `getrandom` syscall is supported by kernel
43let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
44if cfg!(getrandom_test_linux_fallback) {
45 NOT_AVAILABLE
46 } else if res.is_negative() {
47match util_libc::last_os_error().raw_os_error() {
48Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
49 // The fallback on EPERM is intentionally not done on Android since this workaround
50 // seems to be needed only for specific Linux-based products that aren't based
51 // on Android. See https://github.com/rust-random/getrandom/issues/229.
52#[cfg(target_os = "linux")]
53Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
54_ => fptr,
55 }
56 } else {
57 fptr
58 }
59 }
60None => NOT_AVAILABLE,
61 };
6263#[cfg(getrandom_test_linux_without_fallback)]
64if res_ptr == NOT_AVAILABLE {
65panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`")
66 }
6768 GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
69 res_ptr
70}
7172// Prevent inlining of the fallback implementation
73#[inline(never)]
74fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
75 use_file::fill_inner(dest)
76}
7778#[inline]
79pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
80// Despite being only a single atomic variable, we still cannot always use
81 // Ordering::Relaxed, as we need to make sure a successful call to `init`
82 // is "ordered before" any data read through the returned pointer (which
83 // occurs when the function is called). Our implementation mirrors that of
84 // the one in libstd, meaning that the use of non-Relaxed operations is
85 // probably unnecessary.
86let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
87let fptr = match NonNull::new(raw_ptr) {
88Some(p) => p,
89None => init(),
90 };
9192if fptr == NOT_AVAILABLE {
93 use_file_fallback(dest)
94 } else {
95// note: `transmute` is currently the only way to convert a pointer into a function reference
96let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
97 util_libc::sys_fill_exact(dest, |buf| unsafe {
98 getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
99 })
100 }
101}