Skip to content

Commit 4f11144

Browse files
committed
linux_android_with_fallback: avoid dlsym on musl
The dlsym-based getrandom detection added in commit 869a4f0 ("linux_android: use libc::getrandom") assumes dynamic linking, which means it never works properly on musl where static linking is the norm. Add some conditional compilation to use `libc::getrandom` directly on musl while retaining kernel support detection.
1 parent ce3b017 commit 4f11144

File tree

2 files changed

+51
-56
lines changed

2 files changed

+51
-56
lines changed

src/backends/linux_android_with_fallback.rs

+48-53
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,55 @@
11
//! Implementation for Linux / Android with `/dev/urandom` fallback
22
use super::use_file;
33
use crate::Error;
4-
use core::{
5-
ffi::c_void,
6-
mem::{self, MaybeUninit},
7-
ptr::{self, NonNull},
8-
sync::atomic::{AtomicPtr, Ordering},
9-
};
4+
use core::{ffi::c_void, mem::MaybeUninit, ptr};
105
use use_file::util_libc;
116

7+
#[path = "../lazy.rs"]
8+
mod lazy;
9+
1210
pub use crate::util::{inner_u32, inner_u64};
1311

1412
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
1513

16-
/// Sentinel value which indicates that `libc::getrandom` either not available,
17-
/// or not supported by kernel.
18-
const NOT_AVAILABLE: NonNull<c_void> = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) };
14+
#[cfg(not(target_env = "musl"))]
15+
#[cold]
16+
#[inline(never)]
17+
fn get_getrandom_fn() -> Option<GetRandomFn> {
18+
static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new();
1919

20-
static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
20+
let raw_ptr = GETRANDOM_FN.unsync_init(|| {
21+
static NAME: &[u8] = b"getrandom\0";
22+
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
23+
unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) as usize }
24+
}) as *mut c_void;
25+
26+
(!raw_ptr.is_null()).then(|| {
27+
// note: `transmute` is currently the only way to convert a pointer into a function reference
28+
unsafe { core::mem::transmute::<*mut c_void, GetRandomFn>(raw_ptr) }
29+
})
30+
}
2131

2232
#[cold]
2333
#[inline(never)]
24-
fn init() -> NonNull<c_void> {
25-
static NAME: &[u8] = b"getrandom\0";
26-
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
27-
let raw_ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
28-
let res_ptr = match NonNull::new(raw_ptr) {
29-
Some(fptr) => {
30-
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
31-
let dangling_ptr = ptr::NonNull::dangling().as_ptr();
32-
// Check that `getrandom` syscall is supported by kernel
33-
let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
34-
if cfg!(getrandom_test_linux_fallback) {
35-
NOT_AVAILABLE
36-
} else if res.is_negative() {
37-
match util_libc::last_os_error().raw_os_error() {
38-
Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
39-
// The fallback on EPERM is intentionally not done on Android since this workaround
40-
// seems to be needed only for specific Linux-based products that aren't based
41-
// on Android. See https://github.com/rust-random/getrandom/issues/229.
42-
#[cfg(target_os = "linux")]
43-
Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
44-
_ => fptr,
45-
}
46-
} else {
47-
fptr
48-
}
34+
fn check_getrandom(getrandom_fn: GetRandomFn) -> bool {
35+
let dangling_ptr = ptr::NonNull::dangling().as_ptr();
36+
// Check that `getrandom` syscall is supported by kernel
37+
let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
38+
if cfg!(getrandom_test_linux_fallback) {
39+
false
40+
} else if res.is_negative() {
41+
match util_libc::last_os_error().raw_os_error() {
42+
Some(libc::ENOSYS) => false, // No kernel support
43+
// The fallback on EPERM is intentionally not done on Android since this workaround
44+
// seems to be needed only for specific Linux-based products that aren't based
45+
// on Android. See https://github.com/rust-random/getrandom/issues/229.
46+
#[cfg(target_os = "linux")]
47+
Some(libc::EPERM) => false, // Blocked by seccomp
48+
_ => true,
4949
}
50-
None => NOT_AVAILABLE,
51-
};
52-
53-
GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
54-
res_ptr
50+
} else {
51+
true
52+
}
5553
}
5654

5755
// prevent inlining of the fallback implementation
@@ -62,23 +60,20 @@ fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
6260

6361
#[inline]
6462
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
65-
// Despite being only a single atomic variable, we still cannot always use
66-
// Ordering::Relaxed, as we need to make sure a successful call to `init`
67-
// is "ordered before" any data read through the returned pointer (which
68-
// occurs when the function is called). Our implementation mirrors that of
69-
// the one in libstd, meaning that the use of non-Relaxed operations is
70-
// probably unnecessary.
71-
let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
72-
let fptr = match NonNull::new(raw_ptr) {
73-
Some(p) => p,
74-
None => init(),
63+
static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new();
64+
65+
#[cfg(target_env = "musl")]
66+
let getrandom_fn = libc::getrandom;
67+
68+
#[cfg(not(target_env = "musl"))]
69+
let getrandom_fn = match get_getrandom_fn() {
70+
Some(f) => f,
71+
None => return use_file_fallback(dest),
7572
};
7673

77-
if fptr == NOT_AVAILABLE {
74+
if !GETRANDOM_GOOD.unsync_init(|| check_getrandom(getrandom_fn)) {
7875
use_file_fallback(dest)
7976
} else {
80-
// note: `transmute` is currently the only way to convert a pointer into a function reference
81-
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
8277
util_libc::sys_fill_exact(dest, |buf| unsafe {
8378
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
8479
})

src/lazy.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,20 @@ use core::sync::atomic::{AtomicUsize, Ordering};
1919
// }
2020
// the effects of c() or writes to shared memory will not necessarily be
2121
// observed and additional synchronization methods may be needed.
22-
struct LazyUsize(AtomicUsize);
22+
pub(crate) struct LazyUsize(AtomicUsize);
2323

2424
impl LazyUsize {
2525
// The initialization is not completed.
2626
const UNINIT: usize = usize::MAX;
2727

28-
const fn new() -> Self {
28+
pub const fn new() -> Self {
2929
Self(AtomicUsize::new(Self::UNINIT))
3030
}
3131

3232
// Runs the init() function at most once, returning the value of some run of
3333
// init(). Multiple callers can run their init() functions in parallel.
3434
// init() should always return the same value, if it succeeds.
35-
fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
35+
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
3636
#[cold]
3737
fn do_init(this: &LazyUsize, init: impl FnOnce() -> usize) -> usize {
3838
let val = init();

0 commit comments

Comments
 (0)