diff --git a/src/lib.rs b/src/lib.rs index 5cc40941..9b171a3a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -367,10 +367,8 @@ cfg_if! { ), ) ))] { - mod lazy; mod util_libc; mod use_file; - mod linux_android; #[path = "linux_android_with_fallback.rs"] mod imp; } else if #[cfg(any(target_os = "android", target_os = "linux"))] { mod util_libc; diff --git a/src/linux_android.rs b/src/linux_android.rs index 401d3dfc..c57368b0 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -3,21 +3,7 @@ use crate::{util_libc, Error}; use core::mem::MaybeUninit; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::sys_fill_exact(dest, getrandom_syscall) -} - -pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> libc::ssize_t { - let res: libc::c_long = unsafe { - libc::syscall( - libc::SYS_getrandom, - buf.as_mut_ptr().cast::(), - buf.len(), - 0, - ) - }; - - const _: () = - assert!(core::mem::size_of::() == core::mem::size_of::()); - res.try_into() - .expect("c_long to ssize_t conversion is lossless") + util_libc::sys_fill_exact(dest, |buf| unsafe { + libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) + }) } diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index ec7a1216..dd401caa 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -1,37 +1,80 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback -use crate::{lazy::LazyBool, linux_android, use_file, util_libc::last_os_error, Error}; -use core::mem::MaybeUninit; +use crate::{use_file, util_libc, Error}; +use core::{ + ffi::c_void, + mem::{self, MaybeUninit}, + ptr::{self, NonNull}, + sync::atomic::{AtomicPtr, Ordering}, +}; -pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - // getrandom(2) was introduced in Linux 3.17 - static HAS_GETRANDOM: LazyBool = LazyBool::new(); - if HAS_GETRANDOM.unsync_init(is_getrandom_available) { - linux_android::getrandom_inner(dest) - } else { - // prevent inlining of the fallback implementation - #[inline(never)] - fn inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - use_file::getrandom_inner(dest) +type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; + +/// Sentinel value which indicates that `libc::getrandom` either not available, +/// or not supported by kernel. +const NOT_AVAILABLE: NonNull = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) }; + +static GETRANDOM_FN: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +#[cold] +fn init() -> NonNull { + static NAME: &[u8] = b"getrandom\0"; + let name_ptr = NAME.as_ptr().cast::(); + let raw_ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }; + let res_ptr = match NonNull::new(raw_ptr) { + Some(fptr) => { + let getrandom_fn = unsafe { mem::transmute::, GetRandomFn>(fptr) }; + let dangling_ptr = ptr::NonNull::dangling().as_ptr(); + // Check that `getrandom` syscall is supported by kernel + let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) }; + if cfg!(getrandom_test_linux_fallback) { + NOT_AVAILABLE + } else if res.is_negative() { + match util_libc::last_os_error().raw_os_error() { + Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support + // The fallback on EPERM is intentionally not done on Android since this workaround + // seems to be needed only for specific Linux-based products that aren't based + // on Android. See https://github.com/rust-random/getrandom/issues/229. + #[cfg(target_os = "linux")] + Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp + _ => fptr, + } + } else { + fptr + } } + None => NOT_AVAILABLE, + }; - inner(dest) - } + GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release); + res_ptr } -fn is_getrandom_available() -> bool { - if cfg!(getrandom_test_linux_fallback) { - false - } else if linux_android::getrandom_syscall(&mut []) < 0 { - match last_os_error().raw_os_error() { - Some(libc::ENOSYS) => false, // No kernel support - // The fallback on EPERM is intentionally not done on Android since this workaround - // seems to be needed only for specific Linux-based products that aren't based - // on Android. See https://github.com/rust-random/getrandom/issues/229. - #[cfg(target_os = "linux")] - Some(libc::EPERM) => false, // Blocked by seccomp - _ => true, - } +// prevent inlining of the fallback implementation +#[inline(never)] +fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { + use_file::getrandom_inner(dest) +} + +pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { + // Despite being only a single atomic variable, we still cannot always use + // Ordering::Relaxed, as we need to make sure a successful call to `init` + // is "ordered before" any data read through the returned pointer (which + // occurs when the function is called). Our implementation mirrors that of + // the one in libstd, meaning that the use of non-Relaxed operations is + // probably unnecessary. + let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); + let fptr = match NonNull::new(raw_ptr) { + Some(p) => p, + None => init(), + }; + + if fptr == NOT_AVAILABLE { + use_file_fallback(dest) } else { - true + // note: `transume` is currently the only way to convert pointer into function reference + let getrandom_fn = unsafe { mem::transmute::, GetRandomFn>(fptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0) + }) } }