| 1 | //! Implementation for Linux / Android with `/dev/urandom` fallback |
| 2 | use super::use_file; |
| 3 | use crate::Error; |
| 4 | use core::{ |
| 5 | ffi::c_void, |
| 6 | mem::{transmute, MaybeUninit}, |
| 7 | ptr::NonNull, |
| 8 | sync::atomic::{AtomicPtr, Ordering}, |
| 9 | }; |
| 10 | use use_file::util_libc; |
| 11 | |
| 12 | pub use crate::util::{inner_u32, inner_u64}; |
| 13 | |
| 14 | type 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. |
| 18 | const NOT_AVAILABLE: NonNull<c_void> = unsafe { NonNull::new_unchecked(ptr:usize::MAX as *mut c_void) }; |
| 19 | |
| 20 | static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut()); |
| 21 | |
| 22 | #[cold ] |
| 23 | #[inline (never)] |
| 24 | fn init() -> NonNull<c_void> { |
| 25 | // Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else |
| 26 | #[cfg (not(target_env = "musl" ))] |
| 27 | let raw_ptr = { |
| 28 | static NAME: &[u8] = b"getrandom \0" ; |
| 29 | let name_ptr = NAME.as_ptr().cast::<libc::c_char>(); |
| 30 | unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) } |
| 31 | }; |
| 32 | #[cfg (target_env = "musl" )] |
| 33 | let raw_ptr = { |
| 34 | let fptr: GetRandomFn = libc::getrandom; |
| 35 | unsafe { transmute::<GetRandomFn, *mut c_void>(fptr) } |
| 36 | }; |
| 37 | |
| 38 | let res_ptr = match NonNull::new(raw_ptr) { |
| 39 | Some(fptr) => { |
| 40 | let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) }; |
| 41 | let dangling_ptr = NonNull::dangling().as_ptr(); |
| 42 | // Check that `getrandom` syscall is supported by kernel |
| 43 | let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) }; |
| 44 | if cfg!(getrandom_test_linux_fallback) { |
| 45 | NOT_AVAILABLE |
| 46 | } else if res.is_negative() { |
| 47 | match util_libc::last_os_error().raw_os_error() { |
| 48 | Some(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" )] |
| 53 | Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp |
| 54 | _ => fptr, |
| 55 | } |
| 56 | } else { |
| 57 | fptr |
| 58 | } |
| 59 | } |
| 60 | None => NOT_AVAILABLE, |
| 61 | }; |
| 62 | |
| 63 | #[cfg (getrandom_test_linux_without_fallback)] |
| 64 | if res_ptr == NOT_AVAILABLE { |
| 65 | panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`" ) |
| 66 | } |
| 67 | |
| 68 | GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release); |
| 69 | res_ptr |
| 70 | } |
| 71 | |
| 72 | // Prevent inlining of the fallback implementation |
| 73 | #[inline (never)] |
| 74 | fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> { |
| 75 | use_file::fill_inner(dest) |
| 76 | } |
| 77 | |
| 78 | #[inline ] |
| 79 | pub 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. |
| 86 | let raw_ptr: *mut c_void = GETRANDOM_FN.load(order:Ordering::Acquire); |
| 87 | let fptr: NonNull = match NonNull::new(raw_ptr) { |
| 88 | Some(p: NonNull) => p, |
| 89 | None => init(), |
| 90 | }; |
| 91 | |
| 92 | if 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 |
| 96 | let getrandom_fn: unsafe fn(*mut c_void, usize, …) -> … = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(src:fptr) }; |
| 97 | util_libc::sys_fill_exact(buf:dest, |buf: &mut [MaybeUninit]| unsafe { |
| 98 | getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0) |
| 99 | }) |
| 100 | } |
| 101 | } |
| 102 | |