Skip to content

Commit

Permalink
m: Add rustix backend
Browse files Browse the repository at this point in the history
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 rust-random#401

Signed-off-by: John Nunley <[email protected]>
  • Loading branch information
notgull committed Jun 8, 2024
1 parent cf65e83 commit dd3a01f
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 11 deletions.
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
6 changes: 6 additions & 0 deletions 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 @@ -23,13 +24,17 @@ 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 }
[target.'cfg(all(any(target_arch = "wasm32", target_arch = "wasm64"), target_os = "unknown"))'.dev-dependencies]
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.
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")] {
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(feature = "rustix"))]
use crate::util_libc;
#[cfg(feature = "rustix")]
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(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<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(feature = "rustix"))]
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(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,
}
}
57 changes: 49 additions & 8 deletions src/use_file.rs
Original file line number Diff line number Diff line change
@@ -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<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(feature = "rustix"))]
fn read_from_fd(fd: libc::c_int, buf: &mut [MaybeUninit<u8>]) -> 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<u8>],
) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), 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
Expand Down Expand Up @@ -56,7 +72,10 @@ 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(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);
Expand All @@ -65,7 +84,10 @@ fn get_rng_fd() -> Result<libc::c_int, Error> {
}

// 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")? };
Expand Down Expand Up @@ -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<libc::pthread_mutex_t>);

impl Mutex {
Expand Down
63 changes: 63 additions & 0 deletions src/util_rustix.rs
Original file line number Diff line number Diff line change
@@ -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::<u32>::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<u8>],
fill: impl Fn(&mut [MaybeUninit<u8>]) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), 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<OwnedFd, Error> {
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<u8>],
) -> Result<(&mut [u8], &mut [MaybeUninit<u8>]), Errno> {
rand::getrandom_uninit(buf, rand::GetRandomFlags::empty())
}

0 comments on commit dd3a01f

Please sign in to comment.