Skip to content

Commit

Permalink
use_file: Use once_cell::sync::OnceCell instead of libpthreads.
Browse files Browse the repository at this point in the history
pthreads mutexes are not safe to move. While it is very unlikely that
the mutex we create will ever be moved, we don't actively do anything
to actively prevent it from being moved. (libstd, when it used/uses
pthreads mutexes, would box them to prevent them from being moved.)

Also, now on Linux and Android (and many other targets for which we
don't use use_std), libstd uses futexes instead of pthreads mutexes.
Thus using libstd's Mutex will be more efficient and avoid adding an
often-otherwise-unnecessary libpthreads dependency on these targets.

      * Linux, Android: Futex [1].
      * Haiku, Redox, NTO, AIX: pthreads [2].
      * others: not using `use_file`.

This will not affect our plans for *-*-linux-none, since we don't
plan to use `use_file` for it.

This breaks 32-bit x86 QNX Neutrino, which doesn't have libstd
because the target itself is abandoned [3]. the other QNX Neutrino
targets didn't get libstd support until Rust 1.69, so this
effectively raises the MSRV for them to 1.69.

I tried to use `std::sync::Once` to avoid adding an external dependency
but it doesn't support fallible initialization. `OnceLock` wasn't added
until 1.70, and even then `OnceLock::get_or_try_init` is still
unstable.
  • Loading branch information
briansmith committed Jun 19, 2024
1 parent 5d8bbce commit 6e6f903
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 105 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ wasi = { version = "0.11", default-features = false }
[target.'cfg(all(windows, not(target_vendor = "win7")))'.dependencies]
windows-targets = "0.52"

[target.'cfg(any(target_os = "aix", target_os = "android", target_os = "haiku", target_os = "linux", target_os = "nto", target_os = "redox"))'.dependencies]
once_cell = { version = "1.19.0", default-features = false, features = ["std"]}

[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 Down
139 changes: 34 additions & 105 deletions src/use_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,10 @@
extern crate std;

use crate::{util_libc::sys_fill_exact, Error};
use core::{
cell::UnsafeCell,
ffi::c_void,
mem::MaybeUninit,
sync::atomic::{AtomicI32, Ordering},
};
use std::{
fs, io,
os::fd::{IntoRawFd as _, RawFd},
};
use core::{ffi::c_void, mem::MaybeUninit};
use std::{fs::File, io, os::unix::io::AsRawFd as _};
// TODO(MSRV feature(once_cell_try)): Use std::sync::OnceLock instead.
use once_cell::sync::OnceCell as OnceLock;

/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
/// For more information see the linked man pages in lib.rs.
Expand All @@ -26,78 +20,41 @@ const FILE_PATH: &str = "/dev/urandom";
// `#[cold]` because it is hot when it is actually used.
#[cfg_attr(any(target_os = "android", target_os = "linux"), inline(never))]
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())
})
}

// 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<RawFd, Error> {
// std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor.
const FD_UNINIT: RawFd = -1;

// In theory `RawFd` could be something other than `i32`, but for the
// targets we currently support that use `use_file`, it is always `i32`.
// If/when we add support for a target where that isn't the case, we may
// need to use a different atomic type or make other accomodations. The
// compiler will let us know if/when that is the case, because the
// `FD.store(fd)` would fail to compile.
//
// The opening of the file, by libc/libstd/etc. may write some unknown
// state into in-process memory. (Such state may include some sanitizer
// bookkeeping, or we might be operating in a unikernal-like environment
// where all the "kernel" file descriptor bookkeeping is done in our
// process.) `get_fd_locked` stores into FD using `Ordering::Release` to
// ensure any such state is synchronized. `get_fd` loads from `FD` with
// `Ordering::Acquire` to synchronize with it.
static FD: AtomicI32 = AtomicI32::new(FD_UNINIT);

fn get_fd() -> Option<RawFd> {
match FD.load(Ordering::Acquire) {
FD_UNINIT => None,
val => Some(val),
}
}

#[cold]
fn get_fd_locked() -> Result<RawFd, Error> {
// This mutex is used to prevent multiple threads from opening file
// descriptors concurrently, which could run into the limit on the
// number of open file descriptors. Our goal is to have no more than one
// file descriptor open, ever.
//
// SAFETY: We use the mutex only in this method, and we always unlock it
// before returning, making sure we don't violate the pthread_mutex_t API.
static MUTEX: Mutex = Mutex::new();
unsafe { MUTEX.lock() };
let _guard = DropGuard(|| unsafe { MUTEX.unlock() });

if let Some(fd) = get_fd() {
return Ok(fd);
}

// On Linux, /dev/urandom might return insecure values.
#[cfg(any(target_os = "android", target_os = "linux"))]
wait_until_rng_ready()?;

let file = fs::File::open(FILE_PATH).map_err(map_io_error)?;

let fd = file.into_raw_fd();
debug_assert!(fd != FD_UNINIT);
FD.store(fd, Ordering::Release);
// process.) Thus we avoid using (relaxed) atomics like we use in other
// parts of the library.
//
// We prevent multiple threads from opening file descriptors concurrently,
// which could run into the limit on the number of open file descriptors.
// Our goal is to have no more than one file descriptor open, ever.
//
// We assume any call to `OnceLock::get_or_try_init` synchronizes-with
// (Ordering::Acquire) the preceding call to `OnceLock::get_or_try_init`
// after `init()` returns an `Ok` result (Ordering::Release). See
// https://github.com/rust-lang/rust/issues/126239.
static FILE: OnceLock<File> = OnceLock::new();
let file = FILE.get_or_try_init(init)?;

// TODO(MSRV feature(read_buf)): Use `std::io::Read::read_buf`
sys_fill_exact(dest, |buf| unsafe {
libc::read(
file.as_raw_fd(),
buf.as_mut_ptr().cast::<c_void>(),
buf.len(),
)
})
}

Ok(fd)
}
#[cold]
fn init() -> Result<File, Error> {
// On Linux, /dev/urandom might return insecure values.
#[cfg(any(target_os = "android", target_os = "linux"))]
wait_until_rng_ready()?;

// Use double-checked locking to avoid acquiring the lock if possible.
if let Some(fd) = get_fd() {
Ok(fd)
} else {
get_fd_locked()
}
File::open(FILE_PATH).map_err(map_io_error)
}

// Polls /dev/random to make sure it is ok to read from /dev/urandom.
Expand Down Expand Up @@ -130,9 +87,7 @@ fn get_rng_fd() -> Result<RawFd, Error> {
// libsodium uses `libc::poll` similarly to this.
#[cfg(any(target_os = "android", target_os = "linux"))]
fn wait_until_rng_ready() -> Result<(), Error> {
use std::os::unix::io::AsRawFd as _;

let file = fs::File::open("/dev/random").map_err(map_io_error)?;
let file = File::open("/dev/random").map_err(map_io_error)?;
let mut pfd = libc::pollfd {
fd: file.as_raw_fd(),
events: libc::POLLIN,
Expand Down Expand Up @@ -171,29 +126,3 @@ fn map_io_error(err: io::Error) -> Error {
}
})
}

struct Mutex(UnsafeCell<libc::pthread_mutex_t>);

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

unsafe impl Sync for Mutex {}

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

impl<F: FnMut()> Drop for DropGuard<F> {
fn drop(&mut self) {
self.0()
}
}

0 comments on commit 6e6f903

Please sign in to comment.