From dd3a01fe10ecc28f67578da3e8bf0a2640267c41 Mon Sep 17 00:00:00 2001 From: John Nunley Date: Sat, 8 Jun 2024 13:32:34 -0700 Subject: [PATCH] m: Add rustix backend This commit adds a backend based on the rustix crate. The main advantage of rustix, in addition to greater safety, is that it uses raw syscalls instead of going through libc. I've tried to modify the existing libc code as little as possible, in order to make this change as auditable as possible. I haven't touched the pthreads code yet, as that's a little tricky. This exists for discussion purposes. cc #401 Signed-off-by: John Nunley --- .github/workflows/tests.yml | 2 +- Cargo.toml | 6 +++ src/lib.rs | 4 ++ src/linux_android.rs | 7 +++- src/linux_android_with_fallback.rs | 21 +++++++++- src/use_file.rs | 57 +++++++++++++++++++++++---- src/util_rustix.rs | 63 ++++++++++++++++++++++++++++++ 7 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 src/util_rustix.rs 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()) +}