Skip to content

Commit 46963aa

Browse files
josephlrnewpavlov
authored andcommitted
use_file: Remove use of spin-locks
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. This includes adding Mutex and DropGuard abstractions. Signed-off-by: Joe Richey <[email protected]>
1 parent 245b5b2 commit 46963aa

File tree

1 file changed

+96
-32
lines changed

1 file changed

+96
-32
lines changed

src/use_file.rs

Lines changed: 96 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
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::{open_readonly, sys_fill_exact};
1112
use crate::Error;
13+
use core::cell::UnsafeCell;
14+
use core::sync::atomic::{AtomicUsize, Ordering::Relaxed};
1215

1316
#[cfg(target_os = "redox")]
1417
const FILE_PATH: &str = "rand:\0";
@@ -21,10 +24,11 @@ const FILE_PATH: &str = "rand:\0";
2124
target_os = "illumos"
2225
))]
2326
const FILE_PATH: &str = "/dev/random\0";
27+
#[cfg(any(target_os = "android", target_os = "linux"))]
28+
const FILE_PATH: &str = "/dev/urandom\0";
2429

2530
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)?;
31+
let fd = get_rng_fd()?;
2832
let read = |buf: &mut [u8]| unsafe { libc::read(fd, buf.as_mut_ptr() as *mut _, buf.len()) };
2933

3034
if cfg!(target_os = "emscripten") {
@@ -38,36 +42,96 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
3842
Ok(())
3943
}
4044

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-
};
50-
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
45+
// Returns the file descriptor for the device file used to retrieve random
46+
// bytes. The file will be opened exactly once. All successful calls will
47+
// return the same file descriptor. This file descriptor is never closed.
48+
fn get_rng_fd() -> Result<libc::c_int, Error> {
49+
static FD: AtomicUsize = AtomicUsize::new(LazyUsize::UNINIT);
50+
fn get_fd() -> Option<libc::c_int> {
51+
match FD.load(Relaxed) {
52+
LazyUsize::UNINIT => None,
53+
val => Some(val as libc::c_int),
6754
}
68-
} else {
69-
fn init_file() -> Option<libc::c_int> {
70-
unsafe { open_readonly(FILE_PATH) }
55+
}
56+
57+
// Use double-checked locking to avoid acquiring the lock if possible.
58+
if let Some(fd) = get_fd() {
59+
return Ok(fd);
60+
}
61+
62+
// SAFETY: We use the mutex only in this method, and we always unlock it
63+
// before returning, making sure we don't violate the pthread_mutex_t API.
64+
static MUTEX: Mutex = Mutex::new();
65+
unsafe { MUTEX.lock() };
66+
let _guard = DropGuard(|| unsafe { MUTEX.unlock() });
67+
68+
if let Some(fd) = get_fd() {
69+
return Ok(fd);
70+
}
71+
72+
// On Linux, /dev/urandom might return insecure values.
73+
#[cfg(any(target_os = "android", target_os = "linux"))]
74+
wait_until_rng_ready()?;
75+
76+
let fd = unsafe { open_readonly(FILE_PATH)? };
77+
// The fd always fits in a usize without conflicting with UNINIT.
78+
debug_assert!(fd >= 0 && (fd as usize) < LazyUsize::UNINIT);
79+
FD.store(fd as usize, Relaxed);
80+
81+
Ok(fd)
82+
}
83+
84+
// Succeeds once /dev/urandom is safe to read from
85+
#[cfg(any(target_os = "android", target_os = "linux"))]
86+
fn wait_until_rng_ready() -> Result<(), Error> {
87+
// Poll /dev/random to make sure it is ok to read from /dev/urandom.
88+
let fd = unsafe { open_readonly("/dev/random\0")? };
89+
let mut pfd = libc::pollfd {
90+
fd,
91+
events: libc::POLLIN,
92+
revents: 0,
93+
};
94+
let _guard = DropGuard(|| unsafe {
95+
libc::close(fd);
96+
});
97+
98+
loop {
99+
// A negative timeout means an infinite timeout.
100+
let res = unsafe { libc::poll(&mut pfd, 1, -1) };
101+
if res >= 0 {
102+
assert_eq!(res, 1); // We only used one fd, and cannot timeout.
103+
return Ok(());
71104
}
105+
let err = crate::util_libc::last_os_error();
106+
match err.raw_os_error() {
107+
Some(libc::EINTR) | Some(libc::EAGAIN) => continue,
108+
_ => return Err(err),
109+
}
110+
}
111+
}
112+
113+
struct Mutex(UnsafeCell<libc::pthread_mutex_t>);
114+
115+
impl Mutex {
116+
const fn new() -> Self {
117+
Self(UnsafeCell::new(libc::PTHREAD_MUTEX_INITIALIZER))
118+
}
119+
unsafe fn lock(&self) {
120+
let r = libc::pthread_mutex_lock(self.0.get());
121+
debug_assert_eq!(r, 0);
122+
}
123+
unsafe fn unlock(&self) {
124+
let r = libc::pthread_mutex_unlock(self.0.get());
125+
debug_assert_eq!(r, 0);
126+
}
127+
}
128+
129+
unsafe impl Sync for Mutex {}
130+
131+
struct DropGuard<F: FnMut()>(F);
132+
133+
impl<F: FnMut()> Drop for DropGuard<F> {
134+
fn drop(&mut self) {
135+
self.0()
72136
}
73137
}

0 commit comments

Comments
 (0)