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

Add rustix backend #470

Closed
wants to merge 3 commits into from
Closed
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 .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.36]
toolchain: [nightly, beta, stable, 1.63]
# Only Test macOS on stable to reduce macOS CI jobs
include:
# x86_64-apple-darwin.
Expand Down
7 changes: 6 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -17,12 +18,15 @@ 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]
[target.'cfg(all(unix, not(target_os = "linux")))'.dependencies]
libc = { version = "0.2.154", default-features = false }

[target.'cfg(target_os = "wasi")'.dependencies]
wasi = { version = "0.11", default-features = false }

[target.'cfg(target_os = "linux")'.dependencies]
rustix = { version = "0.38.0", features = ["event", "fs", "rand", "thread"], default-features = false }
Copy link
Contributor

Choose a reason for hiding this comment

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

We should only need event, fs, and thread if we're going to use the file-based fallback, but unfortunately that's very hard to specify in Cargo.toml, as you can see by the actual cfg logic in lib.rs.

I guess there must be a reason why Rustix makes those things optional features. (Why?) Would people who don't want those features activated care that getrandom would be activating them?

Copy link
Author

Choose a reason for hiding this comment

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

I don't think so, the features are mostly to reduce code size.


[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 }
Expand All @@ -46,6 +50,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
Expand Down
37 changes: 36 additions & 1 deletion src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,42 @@ impl Error {
}

cfg_if! {
if #[cfg(unix)] {
if #[cfg(target_os = "linux")] {
fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> {
use core::mem;
use fmt::Write;

// Convert Rustix errno to string.
struct Writer<'a> {
slot: &'a mut [u8],
start: *const u8,
len: usize,
}

impl fmt::Write for Writer<'_> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let (slot, rest) = mem::take(&mut self.slot).split_at_mut(s.len());
self.slot = rest;
slot.copy_from_slice(s.as_bytes());
self.len += s.len();
Ok(())
}
}

let mut writer = Writer {
start: buf.as_ptr(),
slot: buf,
len: 0
};
core::write!(&mut writer, "{:?}", rustix::io::Errno::from_raw_os_error(errno)).ok()?;
Some(unsafe {
core::str::from_utf8_unchecked(core::slice::from_raw_parts(
writer.start,
writer.len,
))
})
}
} else if #[cfg(unix)] {
fn os_err(errno: i32, buf: &mut [u8]) -> Option<&str> {
let buf_ptr = buf.as_mut_ptr() as *mut libc::c_char;
if unsafe { libc::strerror_r(errno, buf_ptr, buf.len()) } != 0 {
Expand Down
7 changes: 7 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,11 +294,18 @@ cfg_if! {
)
),
))] {
#[cfg(target_os = "linux")]
mod util_rustix;
#[cfg(not(target_os = "linux"))]
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(target_os = "linux")]
mod util_rustix;
#[cfg(not(target_os = "linux"))]
mod util_libc;
mod util_libc;
#[path = "linux_android.rs"] mod imp;
} else if #[cfg(target_os = "solaris")] {
Expand Down
7 changes: 6 additions & 1 deletion src/linux_android.rs
Original file line number Diff line number Diff line change
@@ -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(target_os = "linux"))]
use crate::util_libc;
#[cfg(target_os = "linux")]
use crate::util_rustix as util_libc;

pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
util_libc::sys_fill_exact(dest, util_libc::getrandom_syscall)
}
21 changes: 20 additions & 1 deletion src/linux_android_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -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(target_os = "linux"))]
use crate::util_libc::{getrandom_syscall, last_os_error, sys_fill_exact};

#[cfg(target_os = "linux")]
use {
crate::util_rustix::{getrandom_syscall, sys_fill_exact},
rustix::{io::Errno, rand},
};

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 @@ -16,6 +24,7 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
}
}

#[cfg(not(target_os = "linux"))]
fn is_getrandom_available() -> bool {
if getrandom_syscall(&mut []) < 0 {
match last_os_error().raw_os_error() {
Expand All @@ -31,3 +40,13 @@ fn is_getrandom_available() -> bool {
true
}
}

#[cfg(target_os = "linux")]
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,
}
}
83 changes: 57 additions & 26 deletions src/use_file.rs
Original file line number Diff line number Diff line change
@@ -1,39 +1,62 @@
//! 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(target_os = "linux"))]
use crate::util_libc::{open_readonly, sys_fill_exact, Mutex};
#[cfg(target_os = "linux")]
use crate::util_rustix::{open_readonly, sys_fill_exact, Mutex};

/// 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(target_os = "linux"))]
const FILE_PATH: &str = "/dev/urandom\0";
#[cfg(target_os = "linux")]
const FILE_PATH: &str = "/dev/urandom";
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() as *mut libc::c_void, buf.len())
})
sys_fill_exact(dest, |buf| read_from_fd(fd, buf))
}

#[cfg(not(target_os = "linux"))]
type SysFd = libc::c_int;
#[cfg(target_os = "linux")]
type SysFd = rustix::fd::BorrowedFd<'static>;

#[cfg(not(target_os = "linux"))]
fn read_from_fd(fd: SysFd, buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
unsafe { libc::read(fd, buf.as_mut_ptr() as *mut libc::c_void, buf.len()) }
}

#[cfg(target_os = "linux")]
fn read_from_fd(
fd: SysFd,
buf: &mut [MaybeUninit<u8>],
) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), rustix::io::Errno> {
rustix::io::read_uninit(fd, buf)
}

// 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<libc::c_int, Error> {
fn get_rng_fd() -> Result<SysFd, Error> {
static FD: AtomicUsize = AtomicUsize::new(FD_UNINIT);
fn get_fd() -> Option<libc::c_int> {
fn get_fd() -> Option<SysFd> {
match FD.load(Relaxed) {
FD_UNINIT => None,
#[cfg(not(target_os = "linux"))]
val => Some(val as libc::c_int),
#[cfg(target_os = "linux")]
val => Some(unsafe { rustix::fd::BorrowedFd::borrow_raw(val as _) }),
}
}

Expand All @@ -56,16 +79,23 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
#[cfg(any(target_os = "android", target_os = "linux"))]
wait_until_rng_ready()?;

#[allow(unused_unsafe)]
let fd = unsafe { open_readonly(FILE_PATH)? };
#[cfg(target_os = "linux")]
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);

Ok(fd)
#[cfg(not(target_os = "linux"))]
return Ok(fd);

#[cfg(target_os = "linux")]
return Ok(unsafe { rustix::fd::BorrowedFd::borrow_raw(fd) });
}

// Succeeds once /dev/urandom is safe to read from
#[cfg(any(target_os = "android", target_os = "linux"))]
#[cfg(not(target_os = "linux"))]
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")? };
Expand Down Expand Up @@ -93,24 +123,25 @@ fn wait_until_rng_ready() -> Result<(), Error> {
}
}

struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
// Succeeds once /dev/urandom is safe to read from
#[cfg(target_os = "linux")]
fn wait_until_rng_ready() -> Result<(), Error> {
use rustix::event;

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);
// 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)),
}
}
}

unsafe impl Sync for Mutex {}

struct DropGuard<F: FnMut()>(F);

impl<F: FnMut()> Drop for DropGuard<F> {
Expand Down
21 changes: 21 additions & 0 deletions src/util_libc.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(dead_code)]
use crate::Error;
use core::{
cell::UnsafeCell,
mem::MaybeUninit,
num::NonZeroU32,
ptr::NonNull,
Expand Down Expand Up @@ -160,3 +161,23 @@ pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
) as libc::ssize_t
}
}

#[allow(dead_code)]
pub(crate) struct Mutex(UnsafeCell<libc::pthread_mutex_t>);

#[allow(dead_code)]
impl Mutex {
pub(crate) const fn new() -> Self {
Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))
}
pub(crate) unsafe fn lock(&self) {
let r = libc::pthread_mutex_lock(self.0.get());
debug_assert_eq!(r, 0);
}
pub(crate) unsafe fn unlock(&self) {
let r = libc::pthread_mutex_unlock(self.0.get());
debug_assert_eq!(r, 0);
}
}

unsafe impl Sync for Mutex {}
Loading