diff --git a/.clippy.toml b/.clippy.toml index 5cccb362..abe19b3a 100644 --- a/.clippy.toml +++ b/.clippy.toml @@ -1 +1 @@ -msrv = "1.57" +msrv = "1.59" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f3831d5c..54af9750 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.59] # Only Test macOS on stable to reduce macOS CI jobs include: # x86_64-apple-darwin. @@ -61,6 +61,8 @@ jobs: - uses: Swatinem/rust-cache@v2 - run: cargo test - run: cargo test --features=std + # This is assumed to be the same code path as x86_64-*-linux-none, since + # we don't/can't run tests for that target yet. - run: cargo test --features=linux_disable_fallback - run: cargo test --features=custom # custom should do nothing here - if: ${{ matrix.toolchain == 'nightly' }} @@ -361,6 +363,8 @@ jobs: features: ["rdrand"] - target: i686-unknown-hurd-gnu features: ["std"] + - target: x86_64-unknown-linux-none + features: ["rdrand"] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@nightly # Required to build libcore diff --git a/Cargo.toml b/Cargo.toml index b9357bce..5995cf06 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.59" # 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" @@ -18,7 +18,11 @@ cfg-if = "1" compiler_builtins = { version = "0.1", optional = true } core = { version = "1.0", optional = true, package = "rustc-std-workspace-core" } -[target.'cfg(unix)'.dependencies] +# XXX: Additionally, we don't use libc when feature `linux_disable_fallback` is +# enabled on `x86_64-{*-linux-*,linux-android}`, but we can't express this. In +# that case, we require libc to be built, and we force it to be linked, but we +# don't actually use anything from it. +[target.'cfg(all(unix, not(all(target_arch = "x86_64", target_os = "linux", target_env = ""))))'.dependencies] libc = { version = "0.2.154", default-features = false } [target.'cfg(target_os = "wasi")'.dependencies] diff --git a/README.md b/README.md index 56af89dd..608d5dc8 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.59.0 or later. ## Platform Support diff --git a/src/getrandom.rs b/src/getrandom.rs index 88c077c6..5b66e568 100644 --- a/src/getrandom.rs +++ b/src/getrandom.rs @@ -15,11 +15,12 @@ //! GRND_RANDOM is not recommended. On NetBSD/FreeBSD/Dragonfly/3ds, it does //! nothing. On illumos, the default pool is used to implement getentropy(2), //! so we assume it is acceptable here. -use crate::{util_libc::sys_fill_exact, Error}; +use crate::{util_libc::last_os_error, util_unix::sys_fill_exact, Error}; use core::{ffi::c_void, mem::MaybeUninit}; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { sys_fill_exact(dest, |buf| unsafe { - libc::getrandom(buf.as_mut_ptr().cast::(), buf.len(), 0) + let ret: isize = libc::getrandom(buf.as_mut_ptr().cast::(), buf.len(), 0); + usize::try_from(ret).map_err(|_| last_os_error()) }) } diff --git a/src/lib.rs b/src/lib.rs index d1e9ef09..a4fcd556 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -217,6 +217,8 @@ use core::mem::MaybeUninit; mod error; mod util; +#[cfg(unix)] +mod util_unix; // To prevent a breaking change when targets are added, we always export the // register_custom_getrandom macro, so old Custom RNG crates continue to build. #[cfg(feature = "custom")] @@ -257,7 +259,12 @@ cfg_if! { mod util_libc; #[path = "getrandom.rs"] mod imp; } else if #[cfg(all( - not(feature = "linux_disable_fallback"), + // Always treat feature="linux_disable_fallback" identically to + // all(target_os = "linux", target_env="") to ensure the code paths are + // the same. This is important because we can't run tests for + // *-*-linux-none (yet). Note that target_env="" for common Android + // targets. + not(any(feature = "linux_disable_fallback", all(target_os = "linux", target_env=""))), any( // Rust supports Android API level 19 (KitKat) [0] and the next upgrade targets // level 21 (Lollipop) [1], while `getrandom(2)` was added only in @@ -302,8 +309,8 @@ cfg_if! { mod linux_android; #[path = "linux_android_with_fallback.rs"] mod imp; } else if #[cfg(any(target_os = "android", target_os = "linux"))] { - mod util_libc; - #[path = "linux_android.rs"] mod imp; + mod linux_android; + use linux_android as imp; } else if #[cfg(target_os = "solaris")] { mod util_libc; #[path = "solaris.rs"] mod imp; diff --git a/src/linux_android.rs b/src/linux_android.rs index 7c1fede4..c7b041c8 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -1,19 +1,84 @@ //! Implementation for Linux / Android without `/dev/urandom` fallback -use crate::{util_libc, Error}; +use crate::{util_unix::sys_fill_exact, Error}; use core::mem::MaybeUninit; pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { - util_libc::sys_fill_exact(dest, getrandom_syscall) + sys_fill_exact(dest, getrandom_syscall) } +// The value of `EINTR` is not architecture-specific. It is checked against +// `libc::EINTR` by linux_android_with_fallback.rs. +pub const EINTR: i32 = 4; + // Also used by linux_android_with_fallback to check if the syscall is available. -pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> libc::ssize_t { - unsafe { - libc::syscall( - libc::SYS_getrandom, - buf.as_mut_ptr().cast::(), - buf.len(), - 0, - ) as libc::ssize_t +cfg_if! { + // TODO: Expand inilne assembly to other architectures to avoid depending + // on libc on Linux. + if #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] { + type Word = u64; + type IWord = i64; + + // TODO(MSRV(1.78 feature(target_abi))): Enable this and remove `target_pointer_width` + // restriction above. + // + // #[cfg(target_abi = "x32")] + // const __X32_SYSCALL_BIT: Word = 0x40000000; + // + // #[cfg(target_abi = "x832")] + // #[allow(non_upper_case_globals)] + // pub const SYS_getrandom: IWord = 318 | __X32_SYSCALL_BIT; + + // #[cfg(not(target_abi = "x832"))] + #[allow(non_upper_case_globals)] + pub const SYS_getrandom: IWord = 318; + + pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> Result { + // Clamp request length to word size; no-op on regular (non-x32) x86_64. + assert!(core::mem::size_of::() <= core::mem::size_of::()); + let buflen: Word = buf.len() as Word; + let mut ret: IWord; + let flags = 0; + unsafe { + core::arch::asm!( + "syscall", + inout("rax") SYS_getrandom => ret, + in("rdi") buf.as_mut_ptr(), + in("rsi") buflen, + in("rdx") flags, + lateout("rcx") _, + lateout("r11") _, + options(nostack), + ); + } + match Word::try_from(ret) { + Ok(written) => { + // `buflen` can from a usize and the return value won't be + // larger than what we requested (otherwise that would be a + // buffer overflow), so this cast is lossless even if + // `usize` is smaller. + Ok(written as usize) + }, + Err(_) => { + Err(u32::try_from(ret.unsigned_abs()).map_or( + Error::UNEXPECTED, Error::from_os_error)) + } + } + } + } else { + use crate::util_libc::last_os_error; + pub use libc::SYS_getrandom; + + pub fn getrandom_syscall(buf: &mut [MaybeUninit]) -> Result { + let ret: libc::c_long = unsafe { + libc::syscall( + SYS_getrandom, + buf.as_mut_ptr().cast::(), + buf.len(), + 0, + ) + }; + const _:() = assert!(core::mem::size_of::() == core::mem::size_of::()); + usize::try_from(ret as isize).map_err(|_| last_os_error()) + } } } diff --git a/src/linux_android_with_fallback.rs b/src/linux_android_with_fallback.rs index 98fa15e8..3c3f134b 100644 --- a/src/linux_android_with_fallback.rs +++ b/src/linux_android_with_fallback.rs @@ -1,7 +1,10 @@ //! Implementation for Linux / Android with `/dev/urandom` fallback -use crate::{lazy::LazyBool, linux_android, use_file, util_libc::last_os_error, Error}; +use crate::{lazy::LazyBool, linux_android, use_file, Error}; use core::mem::MaybeUninit; +const _: () = assert!(linux_android::EINTR == libc::EINTR); +const _: () = assert!(linux_android::SYS_getrandom == libc::SYS_getrandom); + pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { // getrandom(2) was introduced in Linux 3.17 static HAS_GETRANDOM: LazyBool = LazyBool::new(); @@ -13,17 +16,13 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { } fn is_getrandom_available() -> bool { - if linux_android::getrandom_syscall(&mut []) < 0 { - match 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. - #[cfg(target_os = "linux")] - Some(libc::EPERM) => false, // Blocked by seccomp - _ => true, - } - } else { - true + match linux_android::getrandom_syscall(&mut []) { + Err(err) if err.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. + #[cfg(target_os = "linux")] + Err(err) if err.raw_os_error() == Some(libc::EPERM) => false, // Blocked by seccomp + _ => true, } } diff --git a/src/netbsd.rs b/src/netbsd.rs index c4ea75e4..8f71ab97 100644 --- a/src/netbsd.rs +++ b/src/netbsd.rs @@ -1,8 +1,8 @@ //! Implementation for NetBSD -use crate::{lazy::LazyPtr, util_libc::sys_fill_exact, Error}; +use crate::{lazy::LazyPtr, util_libc::last_os_error, util_unix::sys_fill_exact, Error}; use core::{ffi::c_void, mem::MaybeUninit, ptr}; -fn kern_arnd(buf: &mut [MaybeUninit]) -> libc::ssize_t { +fn kern_arnd(buf: &mut [MaybeUninit]) -> Result { static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND]; let mut len = buf.len(); let ret = unsafe { @@ -16,9 +16,9 @@ fn kern_arnd(buf: &mut [MaybeUninit]) -> libc::ssize_t { ) }; if ret == -1 { - -1 + Err(last_os_error()) } else { - len as libc::ssize_t + Ok(len) } } @@ -38,7 +38,8 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit]) -> Result<(), Error> { if !fptr.is_null() { let func: GetRandomFn = unsafe { core::mem::transmute(fptr) }; return sys_fill_exact(dest, |buf| unsafe { - func(buf.as_mut_ptr().cast::(), buf.len(), 0) + let ret: isize = func(buf.as_mut_ptr().cast::(), buf.len(), 0); + usize::try_from(ret as isize).map_err(|_| last_os_error()) }); } diff --git a/src/use_file.rs b/src/use_file.rs index 36210dee..dae3c790 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -1,6 +1,7 @@ //! Implementations that just need to read from a file use crate::{ - util_libc::{open_readonly, sys_fill_exact}, + util_libc::{last_os_error, open_readonly}, + util_unix::sys_fill_exact, Error, }; use core::{ @@ -25,7 +26,8 @@ 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().cast::(), buf.len()) + let ret: isize = libc::read(fd, buf.as_mut_ptr().cast::(), buf.len()); + usize::try_from(ret).map_err(|_| last_os_error()) }) } diff --git a/src/util_libc.rs b/src/util_libc.rs index 7708bfcc..a2c07e7d 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -1,6 +1,5 @@ #![allow(dead_code)] use crate::Error; -use core::mem::MaybeUninit; cfg_if! { if #[cfg(any(target_os = "netbsd", target_os = "openbsd", target_os = "android"))] { @@ -46,33 +45,6 @@ pub fn last_os_error() -> Error { } } -// Fill a buffer by repeatedly invoking a system call. The `sys_fill` function: -// - should return -1 and set errno on failure -// - should return the number of bytes written on success -pub fn sys_fill_exact( - mut buf: &mut [MaybeUninit], - sys_fill: impl Fn(&mut [MaybeUninit]) -> libc::ssize_t, -) -> Result<(), Error> { - while !buf.is_empty() { - let res = sys_fill(buf); - match res { - res if res > 0 => buf = buf.get_mut(res as usize..).ok_or(Error::UNEXPECTED)?, - -1 => { - let err = last_os_error(); - // We should try again if the call was interrupted. - if err.raw_os_error() != Some(libc::EINTR) { - return Err(err); - } - } - // Negative return codes not equal to -1 should be impossible. - // EOF (ret = 0) should be impossible, as the data we are reading - // should be an infinite stream of random bytes. - _ => return Err(Error::UNEXPECTED), - } - } - Ok(()) -} - /// Open a file in read-only mode. /// /// # Panics diff --git a/src/util_unix.rs b/src/util_unix.rs new file mode 100644 index 00000000..2b81f921 --- /dev/null +++ b/src/util_unix.rs @@ -0,0 +1,34 @@ +#![allow(dead_code)] +use crate::Error; +use core::mem::MaybeUninit; + +// Fill a buffer by repeatedly invoking a system call. The `sys_fill` function +// must return `Ok(written)` where `written` is the number of bytes written, +// or otherwise an error. +pub fn sys_fill_exact( + mut buf: &mut [MaybeUninit], + sys_fill: impl Fn(&mut [MaybeUninit]) -> Result, +) -> Result<(), Error> { + // Avoid depending on libc for Linux/Android. + #[cfg(any(target_os = "android", target_os = "linux"))] + use crate::linux_android::EINTR; + #[cfg(not(any(target_os = "android", target_os = "linux")))] + use libc::EINTR; + + while !buf.is_empty() { + match sys_fill(buf) { + Ok(res) if res > 0 => buf = buf.get_mut(res..).ok_or(Error::UNEXPECTED)?, + Err(err) => { + // We should try again if the call was interrupted. + if err.raw_os_error() != Some(EINTR) { + return Err(err); + } + } + // Negative return codes not equal to -1 should be impossible. + // EOF (ret = 0) should be impossible, as the data we are reading + // should be an infinite stream of random bytes. + _ => return Err(Error::UNEXPECTED), + } + } + Ok(()) +}