Skip to content

Commit 869a4f0

Browse files
authored
linux_android: use libc::getrandom (#508)
Use of `libc::getrandom` will automatically give us optimizations like vDSO (#503) and can help with testing of fallback logic (#289). It was also requested in #285. In that discussion we decided against using this approach, but in the light of the vDSO optimization it may be worth to reconsider it. In `linux_android_with_fallback` use of `libc::syscall` is replaced by `dlsym`-based code similar to what we use in the `netbsd` backend.
1 parent f622215 commit 869a4f0

File tree

3 files changed

+74
-47
lines changed

3 files changed

+74
-47
lines changed

src/lib.rs

-2
Original file line numberDiff line numberDiff line change
@@ -384,10 +384,8 @@ cfg_if! {
384384
),
385385
)
386386
))] {
387-
mod lazy;
388387
mod util_libc;
389388
mod use_file;
390-
mod linux_android;
391389
#[path = "linux_android_with_fallback.rs"] mod imp;
392390
} else if #[cfg(any(target_os = "android", target_os = "linux"))] {
393391
mod util_libc;

src/linux_android.rs

+3-17
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,7 @@ use core::mem::MaybeUninit;
66
compile_error!("`linux_getrandom` backend can be enabled only for Linux/Android targets!");
77

88
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
9-
util_libc::sys_fill_exact(dest, getrandom_syscall)
10-
}
11-
12-
pub fn getrandom_syscall(buf: &mut [MaybeUninit<u8>]) -> libc::ssize_t {
13-
let res: libc::c_long = unsafe {
14-
libc::syscall(
15-
libc::SYS_getrandom,
16-
buf.as_mut_ptr().cast::<core::ffi::c_void>(),
17-
buf.len(),
18-
0,
19-
)
20-
};
21-
22-
const _: () =
23-
assert!(core::mem::size_of::<libc::c_long>() == core::mem::size_of::<libc::ssize_t>());
24-
res.try_into()
25-
.expect("c_long to ssize_t conversion is lossless")
9+
util_libc::sys_fill_exact(dest, |buf| unsafe {
10+
libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0)
11+
})
2612
}

src/linux_android_with_fallback.rs

+71-28
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,80 @@
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::{use_file, util_libc, Error};
3+
use core::{
4+
ffi::c_void,
5+
mem::{self, MaybeUninit},
6+
ptr::{self, NonNull},
7+
sync::atomic::{AtomicPtr, Ordering},
8+
};
49

5-
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
6-
// getrandom(2) was introduced in Linux 3.17
7-
static HAS_GETRANDOM: LazyBool = LazyBool::new();
8-
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
9-
linux_android::getrandom_inner(dest)
10-
} else {
11-
// prevent inlining of the fallback implementation
12-
#[inline(never)]
13-
fn inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
14-
use_file::getrandom_inner(dest)
10+
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
11+
12+
/// Sentinel value which indicates that `libc::getrandom` either not available,
13+
/// or not supported by kernel.
14+
const NOT_AVAILABLE: NonNull<c_void> = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) };
15+
16+
static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
17+
18+
#[cold]
19+
fn init() -> NonNull<c_void> {
20+
static NAME: &[u8] = b"getrandom\0";
21+
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
22+
let raw_ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
23+
let res_ptr = match NonNull::new(raw_ptr) {
24+
Some(fptr) => {
25+
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
26+
let dangling_ptr = ptr::NonNull::dangling().as_ptr();
27+
// Check that `getrandom` syscall is supported by kernel
28+
let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
29+
if cfg!(getrandom_test_linux_fallback) {
30+
NOT_AVAILABLE
31+
} else if res.is_negative() {
32+
match util_libc::last_os_error().raw_os_error() {
33+
Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
34+
// The fallback on EPERM is intentionally not done on Android since this workaround
35+
// seems to be needed only for specific Linux-based products that aren't based
36+
// on Android. See https://github.com/rust-random/getrandom/issues/229.
37+
#[cfg(target_os = "linux")]
38+
Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
39+
_ => fptr,
40+
}
41+
} else {
42+
fptr
43+
}
1544
}
45+
None => NOT_AVAILABLE,
46+
};
1647

17-
inner(dest)
18-
}
48+
GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
49+
res_ptr
1950
}
2051

21-
fn is_getrandom_available() -> bool {
22-
if cfg!(getrandom_test_linux_fallback) {
23-
false
24-
} else if linux_android::getrandom_syscall(&mut []) < 0 {
25-
match last_os_error().raw_os_error() {
26-
Some(libc::ENOSYS) => false, // No kernel support
27-
// The fallback on EPERM is intentionally not done on Android since this workaround
28-
// seems to be needed only for specific Linux-based products that aren't based
29-
// on Android. See https://github.com/rust-random/getrandom/issues/229.
30-
#[cfg(target_os = "linux")]
31-
Some(libc::EPERM) => false, // Blocked by seccomp
32-
_ => true,
33-
}
52+
// prevent inlining of the fallback implementation
53+
#[inline(never)]
54+
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
55+
use_file::getrandom_inner(dest)
56+
}
57+
58+
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
59+
// Despite being only a single atomic variable, we still cannot always use
60+
// Ordering::Relaxed, as we need to make sure a successful call to `init`
61+
// is "ordered before" any data read through the returned pointer (which
62+
// occurs when the function is called). Our implementation mirrors that of
63+
// the one in libstd, meaning that the use of non-Relaxed operations is
64+
// probably unnecessary.
65+
let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
66+
let fptr = match NonNull::new(raw_ptr) {
67+
Some(p) => p,
68+
None => init(),
69+
};
70+
71+
if fptr == NOT_AVAILABLE {
72+
use_file_fallback(dest)
3473
} else {
35-
true
74+
// note: `transume` is currently the only way to convert pointer into function reference
75+
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
76+
util_libc::sys_fill_exact(dest, |buf| unsafe {
77+
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
78+
})
3679
}
3780
}

0 commit comments

Comments
 (0)