diff --git a/Cargo.toml b/Cargo.toml index ee22ce15..e13ab078 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,9 @@ rustc-dep-of-std = ["dep:compiler_builtins", "dep:core"] # this flag *and* set getrandom_backend=wasm_js (see README). wasm_js = ["dep:wasm-bindgen", "dep:js-sys"] +[build-dependencies] +cc = "1.2.12" + [dependencies] cfg-if = "1" @@ -85,6 +88,7 @@ check-cfg = [ 'cfg(getrandom_msan)', 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_netbsd_fallback)', + 'cfg(has_libc_getrandom)', ] [package.metadata.docs.rs] diff --git a/build.rs b/build.rs index 15d41919..c351a755 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,8 @@ +use std::{ + io::{BufRead as _, BufReader, Write as _}, + process::{Child, Stdio}, +}; + // Automatically detect cfg(sanitize = "memory") even if cfg(sanitize) isn't // supported. Build scripts get cfg() info, even if the cfg is unstable. fn main() { @@ -6,4 +11,42 @@ fn main() { if santizers.contains("memory") { println!("cargo:rustc-cfg=getrandom_msan"); } + + let mut child = cc::Build::new() + .get_compiler() + .to_command() + .args(["-x", "c", "-", "-o", "-"]) + .stdin(Stdio::piped()) + .stdout(Stdio::null()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + let Child { stdin, stderr, .. } = &mut child; + let mut stdin = stdin.take().unwrap(); + stdin + .write_all( + r#"#include +int main() { + char buf[1]; + return getrandom(buf, sizeof(buf), 0) == -1; +}"# + .as_bytes(), + ) + .unwrap(); + std::mem::drop(stdin); // Send EOF. + + // Trampoline stdout to cargo warnings. + let stderr = stderr.take().expect("stderr"); + let stderr = BufReader::new(stderr); + for line in stderr.lines() { + let line = line.expect("read line"); + println!("cargo:warning={line}"); + } + + let status = child.wait().unwrap(); + if status.code() != Some(0) { + } else { + println!("cargo:rustc-cfg=has_libc_getrandom"); + } } diff --git a/src/backends/linux_android_with_fallback.rs b/src/backends/linux_android_with_fallback.rs index 33dabd2e..92a283db 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -1,86 +1,59 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback use super::use_file; use crate::Error; -use core::{ - ffi::c_void, - mem::{self, MaybeUninit}, - ptr::{self, NonNull}, - sync::atomic::{AtomicPtr, Ordering}, -}; -use use_file::util_libc; +use core::mem::MaybeUninit; pub use crate::util::{inner_u32, inner_u64}; -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] -#[inline(never)] -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, - }; - - GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release); - res_ptr -} - -// prevent inlining of the fallback implementation -#[inline(never)] -fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { +#[cfg(not(has_libc_getrandom))] +#[inline] +pub fn fill_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { use_file::fill_inner(dest) } +#[cfg(has_libc_getrandom)] #[inline] pub fn fill_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(), - }; + use use_file::util_libc; + + #[path = "../lazy.rs"] + mod lazy; + + static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new(); + + #[cold] + #[inline(never)] + fn is_getrandom_good() -> bool { + let dangling_ptr = core::ptr::NonNull::dangling().as_ptr(); + // Check that `getrandom` syscall is supported by kernel + let res = unsafe { libc::getrandom(dangling_ptr, 0, 0) }; + if cfg!(getrandom_test_linux_fallback) { + false + } else if res.is_negative() { + match util_libc::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 + } + } + + #[inline(never)] + fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { + use_file::fill_inner(dest) + } - if fptr == NOT_AVAILABLE { + if !GETRANDOM_GOOD.unsync_init(is_getrandom_good) { use_file_fallback(dest) } else { - // note: `transmute` is currently the only way to convert a pointer into a 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) + libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }) } }