diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d65f582..5723bea2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,9 +2,7 @@ name: Build on: push: - branches: master pull_request: - branches: master schedule: - cron: "0 12 * * 1" diff --git a/.github/workflows/nopanic.yaml b/.github/workflows/nopanic.yaml index 5903c2e2..5226bc87 100644 --- a/.github/workflows/nopanic.yaml +++ b/.github/workflows/nopanic.yaml @@ -11,9 +11,7 @@ name: No panic on: push: - branches: master pull_request: - branches: master permissions: contents: read diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cb3766a9..098bf2b2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,9 +2,7 @@ name: Test on: push: - branches: master pull_request: - branches: master schedule: - cron: "0 12 * * 1" diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 99b0cbaa..aebe6d38 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -2,9 +2,7 @@ name: Workspace on: push: - branches: master pull_request: - branches: master permissions: contents: read diff --git a/Cargo.toml b/Cargo.toml index 3037a445..33c35039 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -89,6 +89,7 @@ check-cfg = [ 'cfg(getrandom_test_linux_fallback)', 'cfg(getrandom_test_linux_without_fallback)', 'cfg(getrandom_test_netbsd_fallback)', + 'cfg(has_libc_getrandom)', 'cfg(target_os, values("cygwin"))', # TODO(MSRV 1.86): Remove this. ] diff --git a/build.rs b/build.rs index e8509a6b..54c28c15 100644 --- a/build.rs +++ b/build.rs @@ -6,4 +6,61 @@ fn main() { if sanitizers.contains("memory") { println!("cargo:rustc-cfg=getrandom_msan"); } + + if cfg!(target_feature = "crt-static") { + match std::process::Command::new(std::env::var_os("RUSTC").unwrap()) + .arg("--target") + .arg(std::env::var("TARGET").unwrap()) + .arg("--out-dir") + .arg(std::env::var("OUT_DIR").unwrap()) + .args(["--crate-type=bin", "-"]) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::piped()) + .spawn() + { + Err(err) => { + println!("cargo:warning=failed to spawn compiler: {}", err); + } + Ok(mut child) => { + use std::io::{BufRead as _, Write as _}; + + let std::process::Child { stdin, stderr, .. } = &mut child; + let mut stdin = stdin.take().unwrap(); + stdin + .write_all( + r#" + use std::ffi::{c_uint, c_void}; + extern "C" { + fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> isize; + } + fn main() -> std::io::Result<()> { + use std::convert::TryFrom as _; + let mut buf = [0; 1]; + let _: usize = usize::try_from(unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) }) + .map_err(|std::num::TryFromIntError { .. }| std::io::Error::last_os_error())?; + Ok(()) + } + "# + .as_bytes(), + ) + .unwrap(); + + std::mem::drop(stdin); // Send EOF. + + // Trampoline stdout to cargo warnings. + let stderr = stderr.take().unwrap(); + let stderr = std::io::BufReader::new(stderr); + for line in stderr.lines() { + let line = line.unwrap(); + println!("cargo:warning={line}"); + } + + let status = child.wait().unwrap(); + if status.code() == Some(0) { + 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 d4ae6f24..ed563781 100644 --- a/src/backends/linux_android_with_fallback.rs +++ b/src/backends/linux_android_with_fallback.rs @@ -4,69 +4,53 @@ use crate::Error; use core::{ ffi::c_void, mem::{MaybeUninit, transmute}, - ptr::NonNull, - sync::atomic::{AtomicPtr, Ordering}, + ptr, }; use use_file::util_libc; 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) }; +#[path = "../lazy.rs"] +mod lazy; -static GETRANDOM_FN: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); +type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; #[cold] #[inline(never)] -fn init() -> NonNull { - // Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else - #[cfg(not(target_env = "musl"))] - let raw_ptr = { - static NAME: &[u8] = b"getrandom\0"; - let name_ptr = NAME.as_ptr().cast::(); - unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) } - }; - #[cfg(target_env = "musl")] - let raw_ptr = { - let fptr: GetRandomFn = libc::getrandom; - unsafe { transmute::(fptr) } - }; - - let res_ptr = match NonNull::new(raw_ptr) { - Some(fptr) => { - let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; - let dangling_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 +fn is_getrandom_good(getrandom_fn: GetRandomFn) -> bool { + if cfg!(getrandom_test_linux_fallback) { + false + } else { + // Check that `getrandom` syscall is supported by kernel + let res = unsafe { getrandom_fn(ptr::dangling_mut(), 0, 0) }; + if !res.is_negative() { + true + } else { + 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. + Some(libc::EPERM) if cfg!(target_os = "linux") => false, // Blocked by seccomp + _ => true, } } - None => NOT_AVAILABLE, - }; - - #[cfg(getrandom_test_linux_without_fallback)] - if res_ptr == NOT_AVAILABLE { - panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`") } +} - GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release); - res_ptr +fn to_getrandom_fn(getrandom_fn: usize) -> GetRandomFn { + unsafe { transmute::(getrandom_fn) } +} + +#[cold] +#[inline(never)] +fn init() -> Option { + ptr::NonNull::new(unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) }).and_then( + |getrandom_fn| { + let getrandom_fn = to_getrandom_fn(getrandom_fn.as_ptr() as usize); + is_getrandom_good(getrandom_fn).then_some(getrandom_fn as usize) + }, + ) } // Prevent inlining of the fallback implementation @@ -75,29 +59,45 @@ fn use_file_fallback(dest: &mut [MaybeUninit]) -> Result<(), Error> { use_file::fill_inner(dest) } +fn with_unpoison_linux_gerandom_result( + dest: &mut [MaybeUninit], + getrandom_fn: GetRandomFn, +) -> Result<(), Error> { + util_libc::sys_fill_exact(dest, |buf| unsafe { + let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); + sanitizer::unpoison_linux_getrandom_result(buf, ret); + ret + }) +} + #[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(), - }; + if cfg!(not(target_feature = "crt-static")) { + static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new(); - if fptr == NOT_AVAILABLE { - use_file_fallback(dest) + const NOT_AVAILABLE: usize = usize::MAX; + + match GETRANDOM_FN.unsync_init(|| init().unwrap_or(NOT_AVAILABLE)) { + NOT_AVAILABLE => { + if cfg!(getrandom_test_linux_without_fallback) { + panic!("fallback is triggered with `getrandom_test_linux_without_fallback`"); + } + use_file_fallback(dest) + } + getrandom_fn => { + let getrandom_fn = to_getrandom_fn(getrandom_fn); + with_unpoison_linux_gerandom_result(dest, getrandom_fn) + } + } + } else if cfg!(has_libc_getrandom) { + use_file::fill_inner(dest) } else { - // note: `transmute` is currently the only way to convert a pointer into a function reference - let getrandom_fn = unsafe { transmute::, GetRandomFn>(fptr) }; - util_libc::sys_fill_exact(dest, |buf| unsafe { - let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0); - sanitizer::unpoison_linux_getrandom_result(buf, ret); - ret - }) + static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new(); + + if GETRANDOM_GOOD.unsync_init(|| is_getrandom_good(libc::getrandom)) { + with_unpoison_linux_gerandom_result(dest, libc::getrandom) + } else { + use_file_fallback(dest) + } } } diff --git a/src/backends/netbsd.rs b/src/backends/netbsd.rs index f228a8b1..c410e98d 100644 --- a/src/backends/netbsd.rs +++ b/src/backends/netbsd.rs @@ -9,11 +9,13 @@ use core::{ ffi::c_void, mem::{self, MaybeUninit}, ptr, - sync::atomic::{AtomicPtr, Ordering}, }; pub use crate::util::{inner_u32, inner_u64}; +#[expect(dead_code, reason = "LazyBool is not used")] +#[path = "../lazy.rs"] +mod lazy; #[path = "../util_libc.rs"] mod util_libc; @@ -42,36 +44,24 @@ unsafe extern "C" fn polyfill_using_kern_arand( type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t; -static GETRANDOM: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - #[cold] #[inline(never)] -fn init() -> *mut c_void { - static NAME: &[u8] = b"getrandom\0"; - let name_ptr = NAME.as_ptr().cast::(); - let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }; +fn init() -> usize { + let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) }; if ptr.is_null() || cfg!(getrandom_test_netbsd_fallback) { // Verify `polyfill_using_kern_arand` has the right signature. const POLYFILL: GetRandomFn = polyfill_using_kern_arand; ptr = POLYFILL as *mut c_void; } - GETRANDOM.store(ptr, Ordering::Release); - ptr + ptr as usize } #[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 mut fptr = GETRANDOM.load(Ordering::Acquire); - if fptr.is_null() { - fptr = init(); - } - let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) }; + static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new(); + + let fptr = GETRANDOM_FN.unsync_init(init); + let fptr = unsafe { mem::transmute::(fptr) }; util_libc::sys_fill_exact(dest, |buf| unsafe { fptr(buf.as_mut_ptr().cast::(), buf.len(), 0) }) diff --git a/src/lazy.rs b/src/lazy.rs index b191aa6d..e1ef399b 100644 --- a/src/lazy.rs +++ b/src/lazy.rs @@ -19,20 +19,20 @@ use core::sync::atomic::{AtomicUsize, Ordering}; // } // the effects of c() or writes to shared memory will not necessarily be // observed and additional synchronization methods may be needed. -struct LazyUsize(AtomicUsize); +pub(crate) struct LazyUsize(AtomicUsize); impl LazyUsize { // The initialization is not completed. const UNINIT: usize = usize::MAX; - const fn new() -> Self { + pub const fn new() -> Self { Self(AtomicUsize::new(Self::UNINIT)) } // Runs the init() function at most once, returning the value of some run of // init(). Multiple callers can run their init() functions in parallel. // init() should always return the same value, if it succeeds. - fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { #[cold] fn do_init(this: &LazyUsize, init: impl FnOnce() -> usize) -> usize { let val = init();