diff --git a/.clippy.toml b/.clippy.toml index 13f202e9..550d4759 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1 @@ -msrv = "1.60" +msrv = "1.63" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1d86ee27..46dd784a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ jobs: strategy: matrix: os: [ubuntu-22.04, windows-2022] - toolchain: [nightly, beta, stable, "1.60"] + toolchain: [nightly, beta, stable, "1.63"] # Only Test macOS on stable to reduce macOS CI jobs include: # x86_64-apple-darwin. diff --git a/.github/workflows/workspace.yml b/.github/workflows/workspace.yml index 5c029aad..989a5b0d 100644 --- a/.github/workflows/workspace.yml +++ b/.github/workflows/workspace.yml @@ -56,7 +56,7 @@ jobs: - name: SOLID (solid.rs) run: cargo clippy -Zbuild-std=core --target aarch64-kmc-solid_asp3 - name: Redox (use_file.rs) - run: cargo clippy -Zbuild-std=core --target x86_64-unknown-redox + run: cargo clippy -Zbuild-std=std --target x86_64-unknown-redox - name: VxWorks (vxworks.rs) run: cargo clippy -Zbuild-std=core --target x86_64-wrs-vxworks - name: WASI (wasi.rs) diff --git a/Cargo.toml b/Cargo.toml index 47c7fd28..f2b7445b 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.60" # 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 ef8a6ce2..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.60.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/use_file.rs b/src/use_file.rs index 4fdbe3d7..54d98d0a 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -1,14 +1,20 @@ //! Implementations that just need to read from a file -use crate::{ - util_libc::{open_readonly, sys_fill_exact}, - Error, -}; + +extern crate std; + +use crate::{util_libc::sys_fill_exact, Error}; use core::{ - cell::UnsafeCell, ffi::c_void, mem::MaybeUninit, sync::atomic::{AtomicI32, Ordering}, }; +use std::{ + fs, + io, + // TODO(MSRV 1.66): use `std::os::fd` instead of `std::unix::io`. + os::unix::io::{AsRawFd as _, BorrowedFd, IntoRawFd as _, RawFd}, + 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. @@ -16,7 +22,7 @@ use core::{ /// - On Redox, only /dev/urandom is provided. /// - On AIX, /dev/urandom will "provide cryptographically secure output". /// - On Haiku and QNX Neutrino they are identical. -const FILE_PATH: &[u8] = b"/dev/urandom\0"; +const FILE_PATH: &str = "/dev/urandom"; // Do not inline this when it is the fallback implementation, but don't mark it // `#[cold]` because it is hot when it is actually used. @@ -24,18 +30,18 @@ const FILE_PATH: &[u8] = b"/dev/urandom\0"; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { let fd = get_rng_fd()?; sys_fill_exact(dest, |buf| unsafe { - libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()) + libc::read(fd.as_raw_fd(), buf.as_mut_ptr().cast::(), buf.len()) }) } // Returns the file descriptor for the device file used to retrieve random // bytes. The file will be opened exactly once. All subsequent calls will // return the same file descriptor. This file descriptor is never closed. -fn get_rng_fd() -> Result { +fn get_rng_fd() -> Result, Error> { // std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor. - const FD_UNINIT: libc::c_int = -1; + const FD_UNINIT: RawFd = -1; - // In theory `libc::c_int` could be something other than `i32`, but for the + // In theory `RawFd` could be something other than `i32`, but for the // targets we currently support that use `use_file`, it is always `i32`. // If/when we add support for a target where that isn't the case, we may // need to use a different atomic type or make other accomodations. The @@ -49,27 +55,34 @@ fn get_rng_fd() -> Result { // process.) `get_fd_locked` stores into FD using `Ordering::Release` to // ensure any such state is synchronized. `get_fd` loads from `FD` with // `Ordering::Acquire` to synchronize with it. + // + // TODO(MSRV feature(once_cell_try)): Use `OnceLock::get_or_try_init` + // instead. static FD: AtomicI32 = AtomicI32::new(FD_UNINIT); - fn get_fd() -> Option { + fn get_fd() -> Option> { match FD.load(Ordering::Acquire) { FD_UNINIT => None, - val => Some(val), + val => Some(unsafe { BorrowedFd::borrow_raw(val) }), } } #[cold] - fn get_fd_locked() -> Result { + fn get_fd_locked() -> Result, Error> { // This mutex is used to prevent multiple threads from opening file // descriptors concurrently, which could run into the limit on the // number of open file descriptors. Our goal is to have no more than one // file descriptor open, ever. // - // 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() }); + // We assume any call to `Mutex::lock` synchronizes-with + // (Ordering::Acquire) the preceding dropping of a `MutexGuard` that + // unlocks the mutex (Ordering::Release) and that `Mutex` doesn't have + // any special treatment for what's "inside" the mutex (the `T` in + // `Mutex`). See `https://github.com/rust-lang/rust/issues/126239. + 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); @@ -79,11 +92,13 @@ fn get_rng_fd() -> Result { #[cfg(any(target_os = "android", target_os = "linux"))] wait_until_rng_ready()?; - let fd = open_readonly(FILE_PATH)?; + let file = fs::File::open(FILE_PATH).map_err(map_io_error)?; + + let fd = file.into_raw_fd(); debug_assert!(fd != FD_UNINIT); FD.store(fd, Ordering::Release); - Ok(fd) + Ok(unsafe { BorrowedFd::borrow_raw(fd) }) } // Use double-checked locking to avoid acquiring the lock if possible. @@ -124,15 +139,12 @@ fn get_rng_fd() -> Result { // 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 file = fs::File::open("/dev/random").map_err(map_io_error)?; let mut pfd = libc::pollfd { - fd, + fd: file.as_raw_fd(), events: libc::POLLIN, revents: 0, }; - let _guard = DropGuard(|| unsafe { - libc::close(fd); - }); loop { // A negative timeout means an infinite timeout. @@ -149,28 +161,20 @@ fn wait_until_rng_ready() -> Result<(), Error> { } } -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() - } +fn map_io_error(err: io::Error) -> Error { + // TODO(MSRV feature(raw_os_error_ty)): Use `std::io::RawOsError`. + type RawOsError = i32; + + err.raw_os_error() + .map_or(Error::UNEXPECTED, |errno: RawOsError| { + // RawOsError-to-u32 conversion is lossless for nonnegative values + // if they are the same size. + const _: () = + assert!(core::mem::size_of::() == core::mem::size_of::()); + + match u32::try_from(errno) { + Ok(code) if code != 0 => Error::from_os_error(code), + _ => Error::ERRNO_NOT_POSITIVE, + } + }) } diff --git a/src/util_libc.rs b/src/util_libc.rs index 7708bfcc..95c36958 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -72,30 +72,3 @@ pub fn sys_fill_exact( } Ok(()) } - -/// Open a file in read-only mode. -/// -/// # Panics -/// If `path` does not contain any zeros. -// TODO: Move `path` to `CStr` and use `CStr::from_bytes_until_nul` (MSRV 1.69) -// or C-string literals (MSRV 1.77) for statics -#[inline(always)] -pub fn open_readonly(path: &[u8]) -> Result { - assert!(path.iter().any(|&b| b == 0)); - loop { - let fd = unsafe { - libc::open( - path.as_ptr().cast::(), - libc::O_RDONLY | libc::O_CLOEXEC, - ) - }; - if fd >= 0 { - return Ok(fd); - } - let err = last_os_error(); - // We should try again if open() was interrupted. - if err.raw_os_error() != Some(libc::EINTR) { - return Err(err); - } - } -}