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