diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 40db0124..d3b30d10 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.36] + toolchain: [nightly, beta, stable, 1.63] # Only Test macOS on stable to reduce macOS CI jobs include: # x86_64-apple-darwin. diff --git a/Cargo.toml b/Cargo.toml index 4de6dcfd..156ebd19 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ documentation = "https://docs.rs/getrandom" repository = "https://github.com/rust-random/getrandom" categories = ["os", "no-std"] exclude = [".*"] +rust-version = "1.63" [dependencies] cfg-if = "1" @@ -23,6 +24,9 @@ libc = { version = "0.2.154", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] wasi = { version = "0.11", default-features = false } +[target.'cfg(any(target_os = "linux", target_os = "android"))'.dependencies] +rustix = { version = "0.38.0", features = ["event", "fs", "rand"], default-features = false, optional = true } + [target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dependencies] wasm-bindgen = { version = "0.2.62", default-features = false, optional = true } js-sys = { version = "0.3", optional = true } @@ -30,6 +34,7 @@ js-sys = { version = "0.3", optional = true } wasm-bindgen-test = "0.3.18" [features] +default = ["rustix"] # Implement std-only traits for getrandom::Error std = [] # Disable `/dev/urandom` fallback for Linux and Android targets. @@ -46,6 +51,7 @@ rustc-dep-of-std = [ "compiler_builtins", "core", "libc/rustc-dep-of-std", + "rustix?/rustc-dep-of-std", "wasi/rustc-dep-of-std", ] # Unstable/test-only feature to run wasm-bindgen tests in a browser diff --git a/src/lib.rs b/src/lib.rs index bc3695b6..a1777696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,11 +294,15 @@ cfg_if! { ) ), ))] { + #[cfg(feature = "rustix")] + mod util_rustix; mod util_libc; mod use_file; mod lazy; #[path = "linux_android_with_fallback.rs"] mod imp; } else if #[cfg(any(target_os = "android", target_os = "linux"))] { + #[cfg(feature = "rustix")] + mod util_rustix; mod util_libc; #[path = "linux_android.rs"] mod imp; } else if #[cfg(target_os = "solaris")] { diff --git a/src/linux_android.rs b/src/linux_android.rs index 93a64945..88574129 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -1,7 +1,12 @@ //! Implementation for Linux / Android without `/dev/urandom` fallback -use crate::{util_libc, Error}; +use crate::Error; use core::mem::MaybeUninit; +#[cfg(not(feature = "rustix"))] +use crate::util_libc; +#[cfg(feature = "rustix")] +use crate::util_rustix as util_libc; + pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall) } diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index 0f5ea8a9..dde87295 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -1,11 +1,19 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback use crate::{ lazy::LazyBool, - util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}, {use_file, Error}, }; use core::mem::MaybeUninit; +#[cfg(not(feature = "rustix"))] +use crate::util_libc::{getrandom_syscall, last_os_error, sys_fill_exact}; + +#[cfg(feature = "rustix")] +use { + crate::util_rustix::{getrandom_syscall, sys_fill_exact}, + rustix::{io::Errno, rand}, +}; + pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getrandom(2) was introduced in Linux 3.17 static HAS_GETRANDOM: LazyBool = LazyBool::new(); @@ -16,6 +24,7 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } } +#[cfg(not(feature = "rustix"))] fn is_getrandom_available() -> bool { if getrandom_syscall(&mut []) < 0 { match last_os_error().raw_os_error() { @@ -31,3 +40,13 @@ fn is_getrandom_available() -> bool { true } } + +#[cfg(feature = "rustix")] +fn is_getrandom_available() -> bool { + match rand::getrandom(&mut [], rand::GetRandomFlags::empty()) { + Err(Errno::NOSYS) => false, + #[cfg(target_os = "linux")] + Err(Errno::PERM) => false, + _ => true, + } +} diff --git a/src/use_file.rs b/src/use_file.rs index bd643ae5..dc33014c 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -1,28 +1,44 @@ //! Implementations that just need to read from a file -use crate::{ - util_libc::{open_readonly, sys_fill_exact}, - Error, -}; +use crate::Error; use core::{ cell::UnsafeCell, mem::MaybeUninit, sync::atomic::{AtomicUsize, Ordering::Relaxed}, }; +#[cfg(not(all(any(target_os = "linux", target_os = "android"), feature = "rustix")))] +use crate::util_libc::{open_readonly, sys_fill_exact}; +#[cfg(all(any(target_os = "linux", target_os = "android"), feature = "rustix"))] +use crate::util_rustix::{open_readonly, sys_fill_exact}; + /// For all platforms, we use `/dev/urandom` rather than `/dev/random`. /// For more information see the linked man pages in lib.rs. /// - On Linux, "/dev/urandom is preferred and sufficient in all use cases". /// - On Redox, only /dev/urandom is provided. /// - On AIX, /dev/urandom will "provide cryptographically secure output". /// - On Haiku and QNX Neutrino they are identical. +#[cfg(not(feature = "rustix"))] const FILE_PATH: &str = "/dev/urandom\0"; +#[cfg(feature = "rustix")] +const FILE_PATH: &str = "/dev/urandom"; const FD_UNINIT: usize = usize::max_value(); 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() as *mut libc::c_void, buf.len()) - }) + sys_fill_exact(dest, |buf| read_from_fd(fd, buf)) +} + +#[cfg(not(feature = "rustix"))] +fn read_from_fd(fd: libc::c_int, buf: &mut [MaybeUninit]) -> libc::ssize_t { + unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) } +} + +#[cfg(feature = "rustix")] +fn read_from_fd( + fd: libc::c_int, + buf: &mut [MaybeUninit], +) -> Result<(&mut [u8], &mut [MaybeUninit]), rustix::io::Errno> { + rustix::io::read_uninit(unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) }, buf) } // Returns the file descriptor for the device file used to retrieve random @@ -56,7 +72,10 @@ fn get_rng_fd() -> Result { #[cfg(any(target_os = "android", target_os = "linux"))] wait_until_rng_ready()?; + #[allow(unused_unsafe)] let fd = unsafe { open_readonly(FILE_PATH)? }; + #[cfg(feature = "rustix")] + let fd = rustix::fd::IntoRawFd::into_raw_fd(fd); // The fd always fits in a usize without conflicting with FD_UNINIT. debug_assert!(fd >= 0 && (fd as usize) < FD_UNINIT); FD.store(fd as usize, Relaxed); @@ -65,7 +84,10 @@ fn get_rng_fd() -> Result { } // Succeeds once /dev/urandom is safe to read from -#[cfg(any(target_os = "android", target_os = "linux"))] +#[cfg(all( + any(target_os = "android", target_os = "linux"), + not(feature = "rustix") +))] fn wait_until_rng_ready() -> Result<(), Error> { // Poll /dev/random to make sure it is ok to read from /dev/urandom. let fd = unsafe { open_readonly("/dev/random\0")? }; @@ -93,6 +115,25 @@ fn wait_until_rng_ready() -> Result<(), Error> { } } +// Succeeds once /dev/urandom is safe to read from +#[cfg(all(any(target_os = "android", target_os = "linux"), feature = "rustix"))] +fn wait_until_rng_ready() -> Result<(), Error> { + use rustix::event; + + // Open the file. + let fd = crate::util_rustix::open_readonly("/dev/random")?; + + // Poll it until it is ready. + let mut pfd = [event::PollFd::new(&fd, event::PollFlags::IN)]; + loop { + match event::poll(&mut pfd, -1) { + Ok(_) => return Ok(()), + Err(rustix::io::Errno::INTR) => continue, + Err(err) => return Err(crate::util_rustix::cvt(err)), + } + } +} + struct Mutex(UnsafeCell); impl Mutex { diff --git a/src/util_rustix.rs b/src/util_rustix.rs new file mode 100644 index 00000000..ce297247 --- /dev/null +++ b/src/util_rustix.rs @@ -0,0 +1,63 @@ +//! Utilities for using rustix. +//! +//! At this point in time it is only used on Linux-like operating systems. + +use crate::Error; +use core::convert::TryInto; +use core::mem::MaybeUninit; +use core::num::NonZeroU32; + +use rustix::fd::OwnedFd; +use rustix::fs; +use rustix::io::Errno; +use rustix::rand; + +/// Convert a Rustix error to one of our errors. +pub(crate) fn cvt(err: Errno) -> Error { + match TryInto::::try_into(err.raw_os_error()) + .ok() + .and_then(NonZeroU32::new) + { + Some(code) => Error::from(code), + None => Error::ERRNO_NOT_POSITIVE, + } +} + +/// Fill a buffer by repeatedly invoking a `rustix` call. +pub(crate) fn sys_fill_exact( + mut buf: &mut [MaybeUninit], + fill: impl Fn(&mut [MaybeUninit]) -> Result<(&mut [u8], &mut [MaybeUninit]), Errno>, +) -> Result<(), Error> { + while !buf.is_empty() { + // Read into the buffer. + match fill(buf) { + Err(err) => return Err(cvt(err)), + Ok((_filled, unfilled)) => { + buf = unfilled; + } + } + } + + Ok(()) +} + +/// Open a file as read-only. +pub(crate) fn open_readonly(path: &str) -> Result { + loop { + match fs::open( + path, + fs::OFlags::CLOEXEC | fs::OFlags::RDONLY, + fs::Mode::empty(), + ) { + Ok(file) => return Ok(file), + Err(Errno::INTR) => continue, + Err(err) => return Err(cvt(err)), + } + } +} + +pub(crate) fn getrandom_syscall( + buf: &mut [MaybeUninit], +) -> Result<(&mut [u8], &mut [MaybeUninit]), Errno> { + rand::getrandom_uninit(buf, rand::GetRandomFlags::empty()) +}