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::{self, MaybeUninit}, |
7 | ptr::{self, 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(ptr::null_mut()); |
21 | |
22 | #[cold ] |
23 | fn 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)] |
58 | fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> { |
59 | use_file::fill_inner(dest) |
60 | } |
61 | |
62 | pub 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: *mut c_void = GETRANDOM_FN.load(order:Ordering::Acquire); |
70 | let fptr: NonNull = match NonNull::new(raw_ptr) { |
71 | Some(p: NonNull) => 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 fn(*mut c_void, usize, …) -> … = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(src:fptr) }; |
80 | util_libc::sys_fill_exact(buf:dest, |buf: &mut [MaybeUninit]| unsafe { |
81 | getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0) |
82 | }) |
83 | } |
84 | } |
85 | |