Skip to content

Commit 5485481

Browse files
committed
use_file: Remove use of spin-locks
Remove the general purpose spin-lock from getrandom, and don't spin when polling /dev/random. We also remove the use of spin locks when opening the persistent fd for platforms that require it. For both these cases, we can just use the pthread lock/unlock methods in libc. We also do some minor cleanup to better make use of Result types and DropGuards. Signed-off-by: Joe Richey <joerichey@google.com>
1 parent d661aa7 commit 5485481

3 files changed

Lines changed: 84 additions & 97 deletions

File tree

src/use_file.rs

Lines changed: 81 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77
// except according to those terms.
88

99
//! Implementations that just need to read from a file
10-
use crate::util_libc::{last_os_error, open_readonly, sys_fill_exact, LazyFd};
10+
use crate::util::LazyUsize;
11+
use crate::util_libc::{last_os_error, open_readonly, sys_fill_exact};
1112
use crate::Error;
13+
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
1214

1315
#[cfg(target_os = "redox")]
1416
const FILE_PATH: &str = "rand:\0";
@@ -21,10 +23,11 @@ const FILE_PATH: &str = "rand:\0";
2123
target_os = "illumos"
2224
))]
2325
const FILE_PATH: &str = "/dev/random\0";
26+
#[cfg(any(target_os = "android", target_os = "linux"))]
27+
const FILE_PATH: &str = "/dev/urandom\0";
2428

2529
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
26-
static FD: LazyFd = LazyFd::new();
27-
let fd = FD.init(init_file).ok_or_else(last_os_error)?;
30+
let fd = get_rng_fd()?;
2831
let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) };
2932

3033
if cfg!(target_os = "emscripten") {
@@ -38,36 +41,83 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
3841
Ok(())
3942
}
4043

41-
cfg_if! {
42-
if #[cfg(any(target_os = "android", target_os = "linux"))] {
43-
fn init_file() -> Option<libc::c_int> {
44-
// Poll /dev/random to make sure it is ok to read from /dev/urandom.
45-
let mut pfd = libc::pollfd {
46-
fd: unsafe { open_readonly("/dev/random\0")? },
47-
events: libc::POLLIN,
48-
revents: 0,
49-
};
44+
// Returns the field descriptor for the device file used to retrieve random
45+
// numbers. The file will be opened exactly once. All successful calls will
46+
// return the same file descriptor. This file descriptor is never closed.
47+
fn get_rng_fd() -> Result<libc::c_int, Error> {
48+
static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT);
49+
fn get_fd() -> Option<libc::c_int> {
50+
match FD.load(Relaxed) {
51+
LazyUsize::UNINIT => None,
52+
val => Some(val as libc::c_int),
53+
}
54+
}
55+
56+
// Use double-checked locking to avoid acquiring the lock if possible.
57+
if let Some(fd) = get_fd() {
58+
return Ok(fd);
59+
}
60+
61+
// SAFETY: Only a raw pointer is created from MUTEX to pass to libc.
62+
static mut MUTEX: libc::pthread_mutex_t = libc::PTHREAD_MUTEX_INITIALIZER;
63+
let r = unsafe { libc::pthread_mutex_lock(&mut MUTEX as *mut _) };
64+
debug_assert_eq!(r, 0);
65+
let _guard = DropGuard(|| {
66+
let r = unsafe { libc::pthread_mutex_unlock(&mut MUTEX as *mut _) };
67+
debug_assert_eq!(r, 0);
68+
});
69+
70+
if let Some(fd) = get_fd() {
71+
return Ok(fd);
72+
}
73+
74+
// On Linux, /dev/urandom might return insecure values.
75+
#[cfg(any(target_os = "android", target_os = "linux"))]
76+
wait_until_rng_ready()?;
77+
78+
let fd = unsafe { open_readonly(FILE_PATH)? };
79+
// The fd always fits in a usize without conflicting with UNINIT.
80+
debug_assert!(fd >= 0 && (fd as usize) < LazyUsize::UNINIT);
81+
FD.store(fd as usize, Relaxed);
5082

51-
let ret = loop {
52-
// A negative timeout means an infinite timeout.
53-
let res = unsafe { libc::poll(&mut pfd, 1, -1) };
54-
if res == 1 {
55-
break unsafe { open_readonly("/dev/urandom\0") };
56-
} else if res < 0 {
57-
let e = last_os_error().raw_os_error();
58-
if e == Some(libc::EINTR) || e == Some(libc::EAGAIN) {
59-
continue;
60-
}
61-
}
62-
// We either hard failed, or poll() returned the wrong pfd.
63-
break None;
64-
};
65-
unsafe { libc::close(pfd.fd) };
66-
ret
83+
Ok(fd)
84+
}
85+
86+
// Succeeds once /dev/urandom is safe to read from
87+
#[cfg(any(target_os = "android", target_os = "linux"))]
88+
fn wait_until_rng_ready() -> Result<(), Error> {
89+
// Poll /dev/random to make sure it is ok to read from /dev/urandom.
90+
let fd = unsafe { open_readonly("/dev/random\0")? };
91+
let mut pfd = libc::pollfd {
92+
fd,
93+
events: libc::POLLIN,
94+
revents: 0,
95+
};
96+
let _guard = DropGuard(|| unsafe {
97+
libc::close(fd);
98+
});
99+
100+
loop {
101+
// A negative timeout means an infinite timeout.
102+
let res = unsafe { libc::poll(&mut pfd, 1, -1) };
103+
if res == 1 {
104+
return Ok(());
67105
}
68-
} else {
69-
fn init_file() -> Option<libc::c_int> {
70-
unsafe { open_readonly(FILE_PATH) }
106+
let err = last_os_error();
107+
if res >= 0 {
108+
return Err(err); // poll() returned the wrong pfd
109+
}
110+
match err.raw_os_error() {
111+
Some(libc::EINTR) | Some(libc::EAGAIN) => continue,
112+
_ => return Err(err), // poll() hard failed
71113
}
72114
}
73115
}
116+
117+
struct DropGuard<F: FnMut()>(F);
118+
119+
impl<F: FnMut()> Drop for DropGuard<F> {
120+
fn drop(&mut self) {
121+
self.0()
122+
}
123+
}

src/util.rs

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,6 @@ impl LazyUsize {
3535

3636
// The initialization is not completed.
3737
pub const UNINIT: usize = usize::max_value();
38-
// The initialization is currently running.
39-
pub const ACTIVE: usize = usize::max_value() - 1;
4038

4139
// Runs the init() function at least once, returning the value of some run
4240
// of init(). Multiple callers can run their init() functions in parallel.
@@ -50,36 +48,6 @@ impl LazyUsize {
5048
}
5149
val
5250
}
53-
54-
// Synchronously runs the init() function. Only one caller will have their
55-
// init() function running at a time, and exactly one successful call will
56-
// be run. init() returning UNINIT or ACTIVE will be considered a failure,
57-
// and future calls to sync_init will rerun their init() function.
58-
pub fn sync_init(&self, init: impl FnOnce() -> usize, mut wait: impl FnMut()) -> usize {
59-
// Common and fast path with no contention. Don't wast time on CAS.
60-
match self.0.load(Relaxed) {
61-
Self::UNINIT | Self::ACTIVE => {}
62-
val => return val,
63-
}
64-
// Relaxed ordering is fine, as we only have a single atomic variable.
65-
loop {
66-
match self.0.compare_and_swap(Self::UNINIT, Self::ACTIVE, Relaxed) {
67-
Self::UNINIT => {
68-
let val = init();
69-
self.0.store(
70-
match val {
71-
Self::UNINIT | Self::ACTIVE => Self::UNINIT,
72-
val => val,
73-
},
74-
Relaxed,
75-
);
76-
return val;
77-
}
78-
Self::ACTIVE => wait(),
79-
val => return val,
80-
}
81-
}
82-
}
8351
}
8452

8553
// Identical to LazyUsize except with bool instead of usize.

src/util_libc.rs

Lines changed: 3 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -89,37 +89,6 @@ impl Weak {
8989
}
9090
}
9191

92-
pub struct LazyFd(LazyUsize);
93-
94-
impl LazyFd {
95-
pub const fn new() -> Self {
96-
Self(LazyUsize::new())
97-
}
98-
99-
// If init() returns Some(x), x should be nonnegative.
100-
pub fn init(&self, init: impl FnOnce() -> Option<libc::c_int>) -> Option<libc::c_int> {
101-
let fd = self.0.sync_init(
102-
|| match init() {
103-
// OK as val >= 0 and val <= c_int::MAX < usize::MAX
104-
Some(val) => val as usize,
105-
None => LazyUsize::UNINIT,
106-
},
107-
|| unsafe {
108-
// We are usually waiting on an open(2) syscall to complete,
109-
// which typically takes < 10us if the file is a device.
110-
// However, we might end up waiting much longer if the entropy
111-
// pool isn't initialized, but even in that case, this loop will
112-
// consume a negligible amount of CPU on most platforms.
113-
libc::usleep(10);
114-
},
115-
);
116-
match fd {
117-
LazyUsize::UNINIT => None,
118-
val => Some(val as libc::c_int),
119-
}
120-
}
121-
}
122-
12392
cfg_if! {
12493
if #[cfg(any(target_os = "linux", target_os = "emscripten"))] {
12594
use libc::open64 as open;
@@ -129,15 +98,15 @@ cfg_if! {
12998
}
13099

131100
// SAFETY: path must be null terminated, FD must be manually closed.
132-
pub unsafe fn open_readonly(path: &str) -> Option<libc::c_int> {
101+
pub unsafe fn open_readonly(path: &str) -> Result<libc::c_int, Error> {
133102
debug_assert!(path.as_bytes().last() == Some(&0));
134103
let fd = open(path.as_ptr() as *mut _, libc::O_RDONLY | libc::O_CLOEXEC);
135104
if fd < 0 {
136-
return None;
105+
return Err(last_os_error());
137106
}
138107
// O_CLOEXEC works on all Unix targets except for older Linux kernels (pre
139108
// 2.6.23), so we also use an ioctl to make sure FD_CLOEXEC is set.
140109
#[cfg(target_os = "linux")]
141110
libc::ioctl(fd, libc::FIOCLEX);
142-
Some(fd)
111+
Ok(fd)
143112
}

0 commit comments

Comments
 (0)