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