Skip to content

Commit ebecd9c

Browse files
committed
Use futex for Linux file fallback
1 parent 5edb045 commit ebecd9c

File tree

3 files changed

+184
-135
lines changed

3 files changed

+184
-135
lines changed

src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,6 @@ cfg_if! {
298298
),
299299
))] {
300300
mod util_libc;
301-
mod use_file;
302301
mod linux_android;
303302
#[path = "linux_android_with_fallback.rs"] mod imp;
304303
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {

src/linux_android_with_fallback.rs

+140-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
//! Implementation for Linux / Android with `/dev/urandom` fallback
2-
use crate::{lazy::LazyBool, linux_android, use_file, util_libc::last_os_error, Error};
3-
use core::mem::MaybeUninit;
2+
use crate::{
3+
lazy::LazyBool,
4+
linux_android,
5+
util_libc::{last_os_error, open_readonly, sys_fill_exact},
6+
Error,
7+
};
8+
use core::{
9+
mem::MaybeUninit,
10+
sync::atomic::{AtomicI32, Ordering},
11+
};
412

513
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
614
// getrandom(2) was introduced in Linux 3.17
715
static HAS_GETRANDOM: LazyBool = LazyBool::new();
816
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
917
linux_android::getrandom_inner(dest)
1018
} else {
11-
use_file::getrandom_inner(dest)
19+
use_file(dest)
1220
}
1321
}
1422

@@ -27,3 +35,132 @@ fn is_getrandom_available() -> bool {
2735
true
2836
}
2937
}
38+
39+
// File descriptor is a "nonnegative integer" as per `open` man.
40+
const FD_UNINIT: libc::c_int = -1;
41+
const FD_ONGOING_INIT: libc::c_int = -2;
42+
43+
// See comment for `FD` in use_file.rs
44+
static FD: AtomicI32 = AtomicI32::new(FD_UNINIT);
45+
46+
pub fn use_file(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
47+
let mut fd = FD.load(Ordering::Acquire);
48+
if fd == FD_UNINIT || fd == FD_ONGOING_INIT {
49+
fd = open_or_wait()?;
50+
}
51+
sys_fill_exact(dest, |buf| unsafe {
52+
libc::read(fd, buf.as_mut_ptr().cast(), buf.len())
53+
})
54+
}
55+
56+
#[cold]
57+
pub(super) fn open_or_wait() -> Result<libc::c_int, Error> {
58+
loop {
59+
match FD.load(Ordering::Acquire) {
60+
FD_UNINIT => {
61+
let res = FD.compare_exchange_weak(
62+
FD_UNINIT,
63+
FD_ONGOING_INIT,
64+
Ordering::AcqRel,
65+
Ordering::Relaxed,
66+
);
67+
if res.is_ok() {
68+
break;
69+
}
70+
}
71+
FD_ONGOING_INIT => futex_wait(),
72+
fd => return Ok(fd),
73+
}
74+
}
75+
76+
let res = open_fd();
77+
let val = match res {
78+
Ok(fd) => fd,
79+
Err(_) => FD_UNINIT,
80+
};
81+
FD.store(val, Ordering::Release);
82+
futex_wake();
83+
res
84+
}
85+
86+
fn futex_wait() {
87+
let op = libc::FUTEX_WAIT | libc::FUTEX_PRIVATE_FLAG;
88+
let timeout_ptr = core::ptr::null::<libc::timespec>();
89+
let ret = unsafe { libc::syscall(libc::SYS_futex, &FD, op, FD_ONGOING_INIT, timeout_ptr) };
90+
// FUTEX_WAIT should return either 0 or EAGAIN error
91+
debug_assert!({
92+
match ret {
93+
0 => true,
94+
-1 => last_os_error().raw_os_error() == Some(libc::EAGAIN),
95+
_ => false,
96+
}
97+
});
98+
}
99+
100+
fn futex_wake() {
101+
let op = libc::FUTEX_WAKE | libc::FUTEX_PRIVATE_FLAG;
102+
let ret = unsafe { libc::syscall(libc::SYS_futex, &FD, op, libc::INT_MAX) };
103+
debug_assert!(ret >= 0);
104+
}
105+
106+
fn open_fd() -> Result<libc::c_int, Error> {
107+
wait_until_rng_ready()?;
108+
// "/dev/urandom is preferred and sufficient in all use cases"
109+
let fd = open_readonly(b"/dev/urandom\0")?;
110+
debug_assert!(fd >= 0);
111+
Ok(fd)
112+
}
113+
114+
// Polls /dev/random to make sure it is ok to read from /dev/urandom.
115+
//
116+
// Polling avoids draining the estimated entropy from /dev/random;
117+
// short-lived processes reading even a single byte from /dev/random could
118+
// be problematic if they are being executed faster than entropy is being
119+
// collected.
120+
//
121+
// OTOH, reading a byte instead of polling is more compatible with
122+
// sandboxes that disallow `poll()` but which allow reading /dev/random,
123+
// e.g. sandboxes that assume that `poll()` is for network I/O. This way,
124+
// fewer applications will have to insert pre-sandbox-initialization logic.
125+
// Often (blocking) file I/O is not allowed in such early phases of an
126+
// application for performance and/or security reasons.
127+
//
128+
// It is hard to write a sandbox policy to support `libc::poll()` because
129+
// it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with
130+
// newer versions of glibc), and/or (rarely, and probably only on ancient
131+
// systems) `select`. depending on the libc implementation (e.g. glibc vs
132+
// musl), libc version, potentially the kernel version at runtime, and/or
133+
// the target architecture.
134+
//
135+
// BoringSSL and libstd don't try to protect against insecure output from
136+
// `/dev/urandom'; they don't open `/dev/random` at all.
137+
//
138+
// OpenSSL uses `libc::select()` unless the `dev/random` file descriptor
139+
// is too large; if it is too large then it does what we do here.
140+
//
141+
// libsodium uses `libc::poll` similarly to this.
142+
fn wait_until_rng_ready() -> Result<(), Error> {
143+
let fd = open_readonly(b"/dev/random\0")?;
144+
let mut pfd = libc::pollfd {
145+
fd,
146+
events: libc::POLLIN,
147+
revents: 0,
148+
};
149+
150+
let res = loop {
151+
// A negative timeout means an infinite timeout.
152+
let res = unsafe { libc::poll(&mut pfd, 1, -1) };
153+
if res >= 0 {
154+
// We only used one fd, and cannot timeout.
155+
debug_assert_eq!(res, 1);
156+
break Ok(());
157+
}
158+
let err = last_os_error();
159+
match err.raw_os_error() {
160+
Some(libc::EINTR) | Some(libc::EAGAIN) => continue,
161+
_ => break Err(err),
162+
}
163+
};
164+
unsafe { libc::close(fd) };
165+
res
166+
}

src/use_file.rs

+44-131
Original file line numberDiff line numberDiff line change
@@ -12,141 +12,55 @@ use core::{
1212

1313
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
1414
/// For more information see the linked man pages in lib.rs.
15-
/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases".
1615
/// - On Redox, only /dev/urandom is provided.
1716
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
1817
/// - On Haiku and QNX Neutrino they are identical.
1918
const FILE_PATH: &[u8] = b"/dev/urandom\0";
2019

21-
// Do not inline this when it is the fallback implementation, but don't mark it
22-
// `#[cold]` because it is hot when it is actually used.
23-
#[cfg_attr(any(target_os = "android", target_os = "linux"), inline(never))]
20+
// std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor.
21+
const FD_UNINIT: libc::c_int = -1;
22+
23+
// In theory `libc::c_int` could be something other than `i32`, but for the
24+
// targets we currently support that use `use_file`, it is always `i32`.
25+
// If/when we add support for a target where that isn't the case, we may
26+
// need to use a different atomic type or make other accomodations. The
27+
// compiler will let us know if/when that is the case, because the
28+
// `FD.store(fd)` would fail to compile.
29+
//
30+
// The opening of the file, by libc/libstd/etc. may write some unknown
31+
// state into in-process memory. (Such state may include some sanitizer
32+
// bookkeeping, or we might be operating in a unikernal-like environment
33+
// where all the "kernel" file descriptor bookkeeping is done in our
34+
// process.) `get_fd_locked` stores into FD using `Ordering::Release` to
35+
// ensure any such state is synchronized. `get_fd` loads from `FD` with
36+
// `Ordering::Acquire` to synchronize with it.
37+
static FD: AtomicI32 = AtomicI32::new(FD_UNINIT);
38+
39+
static FD_MUTEX: Mutex = Mutex::new();
40+
2441
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
25-
let fd = get_rng_fd()?;
42+
let mut fd = FD.load(Ordering::Acquire);
43+
if fd == FD_UNINIT {
44+
fd = open_or_wait()?;
45+
}
2646
sys_fill_exact(dest, |buf| unsafe {
2747
libc::read(fd, buf.as_mut_ptr().cast::<c_void>(), buf.len())
2848
})
2949
}
3050

31-
// Returns the file descriptor for the device file used to retrieve random
32-
// bytes. The file will be opened exactly once. All subsequent calls will
33-
// return the same file descriptor. This file descriptor is never closed.
34-
fn get_rng_fd() -> Result<libc::c_int, Error> {
35-
// std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor.
36-
const FD_UNINIT: libc::c_int = -1;
37-
38-
// In theory `libc::c_int` could be something other than `i32`, but for the
39-
// targets we currently support that use `use_file`, it is always `i32`.
40-
// If/when we add support for a target where that isn't the case, we may
41-
// need to use a different atomic type or make other accomodations. The
42-
// compiler will let us know if/when that is the case, because the
43-
// `FD.store(fd)` would fail to compile.
44-
//
45-
// The opening of the file, by libc/libstd/etc. may write some unknown
46-
// state into in-process memory. (Such state may include some sanitizer
47-
// bookkeeping, or we might be operating in a unikernal-like environment
48-
// where all the "kernel" file descriptor bookkeeping is done in our
49-
// process.) `get_fd_locked` stores into FD using `Ordering::Release` to
50-
// ensure any such state is synchronized. `get_fd` loads from `FD` with
51-
// `Ordering::Acquire` to synchronize with it.
52-
static FD: AtomicI32 = AtomicI32::new(FD_UNINIT);
53-
54-
fn get_fd() -> Option<libc::c_int> {
55-
match FD.load(Ordering::Acquire) {
56-
FD_UNINIT => None,
57-
val => Some(val),
51+
#[cold]
52+
fn open_or_wait() -> Result<libc::c_int, Error> {
53+
let _guard = FD_MUTEX.lock();
54+
let fd = match FD.load(Ordering::Acquire) {
55+
FD_UNINIT => {
56+
let fd = open_readonly(FILE_PATH)?;
57+
FD.store(fd, Ordering::Release);
58+
fd
5859
}
59-
}
60-
61-
#[cold]
62-
fn get_fd_locked() -> Result<libc::c_int, Error> {
63-
// This mutex is used to prevent multiple threads from opening file
64-
// descriptors concurrently, which could run into the limit on the
65-
// number of open file descriptors. Our goal is to have no more than one
66-
// file descriptor open, ever.
67-
//
68-
// SAFETY: We use the mutex only in this method, and we always unlock it
69-
// before returning, making sure we don't violate the pthread_mutex_t API.
70-
static MUTEX: Mutex = Mutex::new();
71-
unsafe { MUTEX.lock() };
72-
let _guard = DropGuard(|| unsafe { MUTEX.unlock() });
73-
74-
if let Some(fd) = get_fd() {
75-
return Ok(fd);
76-
}
77-
78-
// On Linux, /dev/urandom might return insecure values.
79-
#[cfg(any(target_os = "android", target_os = "linux"))]
80-
wait_until_rng_ready()?;
81-
82-
let fd = open_readonly(FILE_PATH)?;
83-
debug_assert!(fd != FD_UNINIT);
84-
FD.store(fd, Ordering::Release);
85-
86-
Ok(fd)
87-
}
88-
89-
// Use double-checked locking to avoid acquiring the lock if possible.
90-
if let Some(fd) = get_fd() {
91-
Ok(fd)
92-
} else {
93-
get_fd_locked()
94-
}
95-
}
96-
97-
// Polls /dev/random to make sure it is ok to read from /dev/urandom.
98-
//
99-
// Polling avoids draining the estimated entropy from /dev/random;
100-
// short-lived processes reading even a single byte from /dev/random could
101-
// be problematic if they are being executed faster than entropy is being
102-
// collected.
103-
//
104-
// OTOH, reading a byte instead of polling is more compatible with
105-
// sandboxes that disallow `poll()` but which allow reading /dev/random,
106-
// e.g. sandboxes that assume that `poll()` is for network I/O. This way,
107-
// fewer applications will have to insert pre-sandbox-initialization logic.
108-
// Often (blocking) file I/O is not allowed in such early phases of an
109-
// application for performance and/or security reasons.
110-
//
111-
// It is hard to write a sandbox policy to support `libc::poll()` because
112-
// it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with
113-
// newer versions of glibc), and/or (rarely, and probably only on ancient
114-
// systems) `select`. depending on the libc implementation (e.g. glibc vs
115-
// musl), libc version, potentially the kernel version at runtime, and/or
116-
// the target architecture.
117-
//
118-
// BoringSSL and libstd don't try to protect against insecure output from
119-
// `/dev/urandom'; they don't open `/dev/random` at all.
120-
//
121-
// OpenSSL uses `libc::select()` unless the `dev/random` file descriptor
122-
// is too large; if it is too large then it does what we do here.
123-
//
124-
// libsodium uses `libc::poll` similarly to this.
125-
#[cfg(any(target_os = "android", target_os = "linux"))]
126-
fn wait_until_rng_ready() -> Result<(), Error> {
127-
let fd = open_readonly(b"/dev/random\0")?;
128-
let mut pfd = libc::pollfd {
129-
fd,
130-
events: libc::POLLIN,
131-
revents: 0,
60+
fd => fd,
13261
};
133-
let _guard = DropGuard(|| unsafe {
134-
libc::close(fd);
135-
});
136-
137-
loop {
138-
// A negative timeout means an infinite timeout.
139-
let res = unsafe { libc::poll(&mut pfd, 1, -1) };
140-
if res >= 0 {
141-
debug_assert_eq!(res, 1); // We only used one fd, and cannot timeout.
142-
return Ok(());
143-
}
144-
let err = crate::util_libc::last_os_error();
145-
match err.raw_os_error() {
146-
Some(libc::EINTR) | Some(libc::EAGAIN) => continue,
147-
_ => return Err(err),
148-
}
149-
}
62+
debug_assert!(fd >= 0);
63+
Ok(fd)
15064
}
15165

15266
struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
@@ -155,22 +69,21 @@ impl Mutex {
15569
const fn new() -> Self {
15670
Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))
15771
}
158-
unsafe fn lock(&self) {
159-
let r = libc::pthread_mutex_lock(self.0.get());
160-
debug_assert_eq!(r, 0);
161-
}
162-
unsafe fn unlock(&self) {
163-
let r = libc::pthread_mutex_unlock(self.0.get());
72+
73+
fn lock(&self) -> MutexGuard<'_> {
74+
let r = unsafe { libc::pthread_mutex_lock(self.0.get()) };
16475
debug_assert_eq!(r, 0);
76+
MutexGuard(self)
16577
}
16678
}
16779

16880
unsafe impl Sync for Mutex {}
16981

170-
struct DropGuard<F: FnMut()>(F);
82+
struct MutexGuard<'a>(&'a Mutex);
17183

172-
impl<F: FnMut()> Drop for DropGuard<F> {
84+
impl<'a> Drop for MutexGuard<'a> {
17385
fn drop(&mut self) {
174-
self.0()
86+
let r = unsafe { libc::pthread_mutex_unlock(self.0 .0.get()) };
87+
debug_assert_eq!(r, 0);
17588
}
17689
}

0 commit comments

Comments
 (0)