diff --git a/.clippy.toml b/.clippy.toml index 5cccb362..550d4759 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1 @@ -msrv = "1.57" +msrv = "1.63" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f3831d5c..e36d4d90 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -44,7 +44,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, windows-2022] - toolchain: [nightly, beta, stable, 1.57] + toolchain: [nightly, beta, stable, 1.63] # Only Test macOS on stable to reduce macOS CI jobs include: # x86_64-apple-darwin. @@ -348,6 +348,8 @@ jobs: ] include: # Supported tier 3 targets with libstd support + - target: aarch64-unknown-nto-qnx710 + features: ["std"] - target: x86_64-unknown-openbsd features: ["std"] - target: x86_64-unknown-dragonfly diff --git a/Cargo.toml b/Cargo.toml index b9357bce..6899c03b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "getrandom" version = "0.2.15" # Also update html_root_url in lib.rs when bumping this edition = "2021" -rust-version = "1.57" # Sync .clippy.toml, tests.yml, and README.md. +rust-version = "1.63" # Sync .clippy.toml, tests.yml, and README.md. authors = ["The Rand Project Developers"] license = "MIT OR Apache-2.0" description = "A small cross-platform library for retrieving random data from system source" diff --git a/README.md b/README.md index 56af89dd..bbab4f28 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ crate features, WASM support and Custom RNGs see the ## Minimum Supported Rust Version -This crate requires Rust 1.57.0 or later. +This crate requires Rust 1.63.0 or later. ## Platform Support diff --git a/src/error.rs b/src/error.rs index 5eff99eb..5c4f1c9e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,6 +58,8 @@ impl Error { pub const NODE_ES_MODULE: Error = internal_error(14); /// Calling Windows ProcessPrng failed. pub const WINDOWS_PROCESS_PRNG: Error = internal_error(15); + /// The mutex used when opening the random file was poisoned. + pub const UNEXPECTED_FILE_MUTEX_POISONED: Error = internal_error(16); /// Codes below this point represent OS Errors (i.e. positive i32 values). /// Codes at or above this point, but below [`Error::CUSTOM_START`] are @@ -175,6 +177,7 @@ fn internal_desc(error: Error) -> Option<&'static str> { Error::NODE_RANDOM_FILL_SYNC => Some("Calling Node.js API crypto.randomFillSync failed"), Error::NODE_ES_MODULE => Some("Node.js ES modules are not directly supported, see https://docs.rs/getrandom#nodejs-es-module-support"), Error::WINDOWS_PROCESS_PRNG => Some("ProcessPrng: Windows system function failure"), + Error::UNEXPECTED_FILE_MUTEX_POISONED => Some("File: Initialization panicked, poisoning the mutex"), _ => None, } } diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index 0f5ea8a9..3015f1c9 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -1,7 +1,7 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback use crate::{ lazy::LazyBool, - util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}, + util_libc::{getrandom_syscall, last_os_error, open_readonly, sys_fill_exact}, {use_file, Error}, }; use core::mem::MaybeUninit; @@ -31,3 +31,65 @@ fn is_getrandom_available() -> bool { true } } + +// Polls /dev/random to make sure it is ok to read from /dev/urandom. +// +// Polling avoids draining the estimated entropy from /dev/random; +// short-lived processes reading even a single byte from /dev/random could +// be problematic if they are being executed faster than entropy is being +// collected. +// +// OTOH, reading a byte instead of polling is more compatible with +// sandboxes that disallow `poll()` but which allow reading /dev/random, +// e.g. sandboxes that assume that `poll()` is for network I/O. This way, +// fewer applications will have to insert pre-sandbox-initialization logic. +// Often (blocking) file I/O is not allowed in such early phases of an +// application for performance and/or security reasons. +// +// It is hard to write a sandbox policy to support `libc::poll()` because +// it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with +// newer versions of glibc), and/or (rarely, and probably only on ancient +// systems) `select`. depending on the libc implementation (e.g. glibc vs +// musl), libc version, potentially the kernel version at runtime, and/or +// the target architecture. +// +// BoringSSL and libstd don't try to protect against insecure output from +// `/dev/urandom'; they don't open `/dev/random` at all. +// +// OpenSSL uses `libc::select()` unless the `dev/random` file descriptor +// is too large; if it is too large then it does what we do here. +// +// libsodium uses `libc::poll` similarly to this. +pub fn wait_until_rng_ready() -> Result<(), Error> { + let fd = open_readonly(b"/dev/random\0")?; + let mut pfd = libc::pollfd { + fd, + events: libc::POLLIN, + revents: 0, + }; + let _guard = DropGuard(|| unsafe { + libc::close(fd); + }); + + loop { + // A negative timeout means an infinite timeout. + let res = unsafe { libc::poll(&mut pfd, 1, -1) }; + if res >= 0 { + debug_assert_eq!(res, 1); // We only used one fd, and cannot timeout. + return Ok(()); + } + let err = crate::util_libc::last_os_error(); + match err.raw_os_error() { + Some(libc::EINTR) | Some(libc::EAGAIN) => continue, + _ => return Err(err), + } + } +} + +struct DropGuard(F); + +impl Drop for DropGuard { + fn drop(&mut self) { + self.0() + } +} diff --git a/src/use_file.rs b/src/use_file.rs index 36210dee..bae1f0fa 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -4,11 +4,12 @@ use crate::{ Error, }; use core::{ - cell::UnsafeCell, ffi::c_void, mem::MaybeUninit, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; +extern crate std; +use std::sync::{Mutex, PoisonError}; /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. /// For more information see the linked man pages in lib.rs. @@ -44,11 +45,10 @@ fn get_rng_fd() -> Result { #[cold] fn get_fd_locked() -> Result { - // SAFETY: We use the mutex only in this method, and we always unlock it - // before returning, making sure we don't violate the pthread_mutex_t API. - static MUTEX: Mutex = Mutex::new(); - unsafe { MUTEX.lock() }; - let _guard = DropGuard(|| unsafe { MUTEX.unlock() }); + static MUTEX: Mutex<()> = Mutex::new(()); + let _guard = MUTEX + .lock() + .map_err(|_: PoisonError<_>| Error::UNEXPECTED_FILE_MUTEX_POISONED)?; if let Some(fd) = get_fd() { return Ok(fd); @@ -56,7 +56,7 @@ fn get_rng_fd() -> Result { // On Linux, /dev/urandom might return insecure values. #[cfg(any(target_os = "android", target_os = "linux"))] - wait_until_rng_ready()?; + crate::imp::wait_until_rng_ready()?; let fd = open_readonly(FILE_PATH)?; // The fd always fits in a usize without conflicting with FD_UNINIT. @@ -73,84 +73,3 @@ fn get_rng_fd() -> Result { get_fd_locked() } } - -// Polls /dev/random to make sure it is ok to read from /dev/urandom. -// -// Polling avoids draining the estimated entropy from /dev/random; -// short-lived processes reading even a single byte from /dev/random could -// be problematic if they are being executed faster than entropy is being -// collected. -// -// OTOH, reading a byte instead of polling is more compatible with -// sandboxes that disallow `poll()` but which allow reading /dev/random, -// e.g. sandboxes that assume that `poll()` is for network I/O. This way, -// fewer applications will have to insert pre-sandbox-initialization logic. -// Often (blocking) file I/O is not allowed in such early phases of an -// application for performance and/or security reasons. -// -// It is hard to write a sandbox policy to support `libc::poll()` because -// it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with -// newer versions of glibc), and/or (rarely, and probably only on ancient -// systems) `select`. depending on the libc implementation (e.g. glibc vs -// musl), libc version, potentially the kernel version at runtime, and/or -// the target architecture. -// -// BoringSSL and libstd don't try to protect against insecure output from -// `/dev/urandom'; they don't open `/dev/random` at all. -// -// OpenSSL uses `libc::select()` unless the `dev/random` file descriptor -// is too large; if it is too large then it does what we do here. -// -// libsodium uses `libc::poll` similarly to this. -#[cfg(any(target_os = "android", target_os = "linux"))] -fn wait_until_rng_ready() -> Result<(), Error> { - let fd = open_readonly(b"/dev/random\0")?; - let mut pfd = libc::pollfd { - fd, - events: libc::POLLIN, - revents: 0, - }; - let _guard = DropGuard(|| unsafe { - libc::close(fd); - }); - - loop { - // A negative timeout means an infinite timeout. - let res = unsafe { libc::poll(&mut pfd, 1, -1) }; - if res >= 0 { - debug_assert_eq!(res, 1); // We only used one fd, and cannot timeout. - return Ok(()); - } - let err = crate::util_libc::last_os_error(); - match err.raw_os_error() { - Some(libc::EINTR) | Some(libc::EAGAIN) => continue, - _ => return Err(err), - } - } -} - -struct Mutex(UnsafeCell); - -impl Mutex { - const fn new() -> Self { - Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER)) - } - unsafe fn lock(&self) { - let r = libc::pthread_mutex_lock(self.0.get()); - debug_assert_eq!(r, 0); - } - unsafe fn unlock(&self) { - let r = libc::pthread_mutex_unlock(self.0.get()); - debug_assert_eq!(r, 0); - } -} - -unsafe impl Sync for Mutex {} - -struct DropGuard(F); - -impl Drop for DropGuard { - fn drop(&mut self) { - self.0() - } -}