Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSRV 1.59: Remove libc requirement for x86_64-*-linux-none #461

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .clippy.toml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
msrv = "1.57"
msrv = "1.59"
6 changes: 5 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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' }}
Expand Down Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions src/getrandom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>]) -> Result<(), Error> {
sys_fill_exact(dest, |buf| unsafe {
libc::getrandom(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0)
let ret: isize = libc::getrandom(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0);
usize::try_from(ret).map_err(|_| last_os_error())
})
}
13 changes: 10 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")]
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
85 changes: 75 additions & 10 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
@@ -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<u8>]) -> 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<u8>]) -> libc::ssize_t {
unsafe {
libc::syscall(
libc::SYS_getrandom,
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't clear to me that we actually need to use __X32_SYSCALL_BIT for x86_64-unknown-linux-gnux32 and other x32 targets. It seems like we could just use the 64-bit version of the syscall instead (i.e. same syscall number). But I am not so sure and it's hard for me to test x32.


// #[cfg(not(target_abi = "x832"))]
#[allow(non_upper_case_globals)]
pub const SYS_getrandom: IWord = 318;

pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> Result<usize, Error> {
// Clamp request length to word size; no-op on regular (non-x32) x86_64.
assert!(core::mem::size_of::<usize>() <= core::mem::size_of::<Word>());
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),
briansmith marked this conversation as resolved.
Show resolved Hide resolved
);
}
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<u8>]) -> Result<usize, Error> {
let ret: libc::c_long = unsafe {
libc::syscall(
SYS_getrandom,
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
buf.len(),
0,
)
};
const _:() = assert!(core::mem::size_of::<libc::c_long>() == core::mem::size_of::<isize>());
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...and here.

usize::try_from(ret as isize).map_err(|_| last_os_error())
}
}
}
25 changes: 12 additions & 13 deletions src/linux_android_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -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<u8>]) -> Result<(), Error> {
// getrandom(2) was introduced in Linux 3.17
static HAS_GETRANDOM: LazyBool = LazyBool::new();
Expand All @@ -13,17 +16,13 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> 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,
}
}
11 changes: 6 additions & 5 deletions src/netbsd.rs
Original file line number Diff line number Diff line change
@@ -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<u8>]) -> libc::ssize_t {
fn kern_arnd(buf: &mut [MaybeUninit<u8>]) -> Result<usize, Error> {
static MIB: [libc::c_int; 2] = [libc::CTL_KERN, libc::KERN_ARND];
let mut len = buf.len();
let ret = unsafe {
Expand All @@ -16,9 +16,9 @@ fn kern_arnd(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
)
};
if ret == -1 {
-1
Err(last_os_error())
} else {
len as libc::ssize_t
Ok(len)
}
}

Expand All @@ -38,7 +38,8 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> 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::<u8>(), buf.len(), 0)
let ret: isize = func(buf.as_mut_ptr().cast::<u8>(), buf.len(), 0);
usize::try_from(ret as isize).map_err(|_| last_os_error())
});
}

Expand Down
6 changes: 4 additions & 2 deletions src/use_file.rs
Original file line number Diff line number Diff line change
@@ -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::{
Expand All @@ -25,7 +26,8 @@ const FD_UNINIT: usize = usize::max_value();
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
let fd = get_rng_fd()?;
sys_fill_exact(dest, |buf| unsafe {
libc::read(fd, buf.as_mut_ptr().cast::<c_void>(), buf.len())
let ret: isize = libc::read(fd, buf.as_mut_ptr().cast::<c_void>(), buf.len());
usize::try_from(ret).map_err(|_| last_os_error())
})
}

Expand Down
28 changes: 0 additions & 28 deletions src/util_libc.rs
Original file line number Diff line number Diff line change
@@ -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"))] {
Expand Down Expand Up @@ -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<u8>],
sys_fill: impl Fn(&mut [MaybeUninit<u8>]) -> 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
Expand Down
34 changes: 34 additions & 0 deletions src/util_unix.rs
Original file line number Diff line number Diff line change
@@ -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<u8>],
sys_fill: impl Fn(&mut [MaybeUninit<u8>]) -> Result<usize, Error>,
) -> 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(())
}
Loading