Skip to content

Commit 792ac28

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 792ac28

File tree

6 files changed

+75
-64
lines changed

6 files changed

+75
-64
lines changed

.github/workflows/build.yml

+3
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ jobs:
128128
- env:
129129
RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback
130130
run: cargo build --features=std
131+
- env:
132+
RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback_no_devurandom
133+
run: cargo build --features=std
131134
- env:
132135
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
133136
run: cargo build --features=std

.github/workflows/tests.yml

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ jobs:
6161
RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback
6262
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback
6363
run: cargo test --features=std
64+
- env:
65+
RUSTFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback_no_dev_urandom
66+
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_test_linux_fallback_no_dev_urandom
67+
run: cargo test --features=std
6468
- env:
6569
RUSTFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"
6670
RUSTDOCFLAGS: -Dwarnings --cfg getrandom_backend="rdrand"

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ check-cfg = [
8484
'cfg(getrandom_backend, values("custom", "rdrand", "rndr", "linux_getrandom", "wasm_js"))',
8585
'cfg(getrandom_msan)',
8686
'cfg(getrandom_test_linux_fallback)',
87+
'cfg(getrandom_test_linux_fallback_no_dev_urandom)',
8788
'cfg(getrandom_test_netbsd_fallback)',
8889
]
8990

src/backends/linux_android_with_fallback.rs

+51-54
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,22 @@ 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(),
75-
};
63+
static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new();
64+
65+
cfg_if! {
66+
if #[cfg(target_env = "musl")] {
67+
let getrandom_fn = libc::getrandom;
68+
} else {
69+
let getrandom_fn = match get_getrandom_fn() {
70+
Some(f) => f,
71+
None => return use_file_fallback(dest),
72+
};
73+
}
74+
}
7675

77-
if fptr == NOT_AVAILABLE {
76+
if !GETRANDOM_GOOD.unsync_init(|| check_getrandom(getrandom_fn)) {
7877
use_file_fallback(dest)
7978
} 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) };
8279
util_libc::sys_fill_exact(dest, |buf| unsafe {
8380
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
8481
})

src/backends/use_file.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,19 @@ pub use crate::util::{inner_u32, inner_u64};
1212
#[path = "../util_libc.rs"]
1313
pub(super) mod util_libc;
1414

15-
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
16-
/// For more information see the linked man pages in lib.rs.
17-
/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases".
18-
/// - On Redox, only /dev/urandom is provided.
19-
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
20-
/// - On Haiku and QNX Neutrino they are identical.
21-
const FILE_PATH: &[u8] = b"/dev/urandom\0";
15+
cfg_if! {
16+
if #[cfg(getrandom_test_linux_fallback_no_dev_urandom)] {
17+
const FILE_PATH: &[u8] = b"/does/not/exist/lol\0";
18+
} else {
19+
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
20+
/// For more information see the linked man pages in lib.rs.
21+
/// - On Linux, "/dev/urandom is preferred and sufficient in all use cases".
22+
/// - On Redox, only /dev/urandom is provided.
23+
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
24+
/// - On Haiku and QNX Neutrino they are identical.
25+
const FILE_PATH: &[u8] = b"/dev/urandom\0";
26+
}
27+
}
2228

2329
// File descriptor is a "nonnegative integer", so we can safely use negative sentinel values.
2430
const FD_UNINIT: libc::c_int = -1;

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)