diff --git a/src/lib.rs b/src/lib.rs index 62c2dab3..4a693f5c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -349,7 +349,6 @@ cfg_if! { ))] { 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 7c1fede4..c57368b0 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -3,17 +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) -} - -// Also used by linux_android_with_fallback to check if the syscall is available. -pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> libc::ssize_t { - unsafe { - libc::syscall( - libc::SYS_getrandom, - buf.as_mut_ptr().cast::(), - buf.len(), - 0, - ) as libc::ssize_t - } + 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 b633faad..2435181f 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -1,35 +1,76 @@ //! 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, + mem::MaybeUninit, + ptr, + 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) +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: *mut c_void = usize::MAX as *mut c_void; + +static GETRANDOM_FN: AtomicPtr = AtomicPtr::new(ptr::null_mut()); + +#[cold] +fn init() -> *mut c_void { + 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 = if raw_ptr.is_null() { + NOT_AVAILABLE } else { + let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(raw_ptr) }; + // Check that `getrandom` syscall is supported by kernel + let res = unsafe { fptr(ptr::NonNull::dangling().as_ptr(), 0, 0) }; + if res < 0 { + 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 + _ => raw_ptr, + } + } else { + raw_ptr + } + }; + + GETRANDOM_FN.store(res_ptr, Ordering::Release); + res_ptr +} + +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 mut raw_ptr = GETRANDOM_FN.load(Ordering::Acquire); + if raw_ptr.is_null() { + raw_ptr = init(); + } + + if raw_ptr == NOT_AVAILABLE { // prevent inlining of the fallback implementation - #[inline(never)] + #[cold] fn inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { use_file::getrandom_inner(dest) } inner(dest) - } -} - -fn is_getrandom_available() -> bool { - 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, - } } else { - true + // note: `transume` is currently the only way to get function pointer + let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(raw_ptr) }; + util_libc::sys_fill_exact(dest, |buf| unsafe { + fptr(buf.as_mut_ptr().cast(), buf.len(), 0) + }) } }