Skip to content

Commit 92753dd

Browse files
committed
linux_android_with_fallback: detect getrandom
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. Replace this with build-time feature checking by attempting to link to getrandom in build.rs.
1 parent ce3b017 commit 92753dd

File tree

3 files changed

+118
-68
lines changed

3 files changed

+118
-68
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ check-cfg = [
8585
'cfg(getrandom_msan)',
8686
'cfg(getrandom_test_linux_fallback)',
8787
'cfg(getrandom_test_netbsd_fallback)',
88+
'cfg(has_libc_getrandom)',
8889
]
8990

9091
[package.metadata.docs.rs]

build.rs

+76
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
use std::{
2+
io::{BufRead as _, BufReader, Write as _},
3+
process::{Child, Command, Stdio},
4+
};
5+
16
// Automatically detect cfg(sanitize = "memory") even if cfg(sanitize) isn't
27
// supported. Build scripts get cfg() info, even if the cfg is unstable.
38
fn main() {
@@ -6,4 +11,75 @@ fn main() {
611
if santizers.contains("memory") {
712
println!("cargo:rustc-cfg=getrandom_msan");
813
}
14+
15+
let mut cmd = match std::env::var_os("RUSTC") {
16+
Some(rustc) => Command::new(rustc),
17+
None => Command::new("rustc"),
18+
};
19+
20+
if let Some(linker) = std::env::var_os("RUSTC_LINKER") {
21+
let mut flag = std::ffi::OsString::new();
22+
flag.push("linker=");
23+
flag.push(linker);
24+
cmd.arg("-C").arg(flag);
25+
}
26+
27+
if let Some(target) = std::env::var_os("TARGET") {
28+
cmd.arg("--target").arg(target);
29+
}
30+
31+
if let Some(out_dir) = std::env::var_os("OUT_DIR") {
32+
cmd.arg("--out-dir").arg(out_dir);
33+
}
34+
35+
let mut child = cmd
36+
.args(["--crate-type", "bin", "-"])
37+
.stdin(Stdio::piped())
38+
.stdout(Stdio::null())
39+
.stderr(Stdio::piped())
40+
.spawn()
41+
.unwrap();
42+
43+
let Child { stdin, stderr, .. } = &mut child;
44+
let mut stdin = stdin.take().unwrap();
45+
stdin
46+
.write_all(
47+
r#"
48+
use std::ffi::{c_uint, c_void};
49+
50+
// TODO(https://github.com/rust-lang/rust/issues/88345): remove when available in core.
51+
#[allow(non_camel_case_types)]
52+
mod types {
53+
pub type c_size_t = usize;
54+
pub type c_ssize_t = isize;
55+
}
56+
use types::*;
57+
58+
extern "C" {
59+
pub fn getrandom(buf: *mut c_void, buflen: c_size_t, flags: c_uint) -> c_ssize_t;
60+
}
61+
62+
fn main() {
63+
let mut buf = [0u8; 1];
64+
unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), 0); }
65+
}
66+
"#
67+
.as_bytes(),
68+
)
69+
.unwrap();
70+
std::mem::drop(stdin); // Send EOF.
71+
72+
// Trampoline stdout to cargo warnings.
73+
let stderr = stderr.take().expect("stderr");
74+
let stderr = BufReader::new(stderr);
75+
for line in stderr.lines() {
76+
let line = line.expect("read line");
77+
println!("cargo:warning={line}");
78+
}
79+
80+
let status = child.wait().unwrap();
81+
if status.code() != Some(0) {
82+
} else {
83+
println!("cargo:rustc-cfg=has_libc_getrandom");
84+
}
985
}
+41-68
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,59 @@
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-
};
10-
use use_file::util_libc;
4+
use core::mem::MaybeUninit;
115

126
pub use crate::util::{inner_u32, inner_u64};
137

14-
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
15-
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) };
19-
20-
static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
21-
22-
#[cold]
23-
#[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-
}
49-
}
50-
None => NOT_AVAILABLE,
51-
};
52-
53-
GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
54-
res_ptr
55-
}
56-
57-
// prevent inlining of the fallback implementation
58-
#[inline(never)]
59-
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
8+
#[cfg(not(has_libc_getrandom))]
9+
#[inline]
10+
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
6011
use_file::fill_inner(dest)
6112
}
6213

14+
#[cfg(has_libc_getrandom)]
6315
#[inline]
6416
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-
};
17+
use use_file::util_libc;
18+
19+
#[path = "../lazy.rs"]
20+
mod lazy;
21+
22+
static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new();
23+
24+
#[cold]
25+
#[inline(never)]
26+
fn is_getrandom_good() -> bool {
27+
let dangling_ptr = core::ptr::NonNull::dangling().as_ptr();
28+
// Check that `getrandom` syscall is supported by kernel
29+
let res = unsafe { libc::getrandom(dangling_ptr, 0, 0) };
30+
if cfg!(getrandom_test_linux_fallback) {
31+
false
32+
} else if res.is_negative() {
33+
match util_libc::last_os_error().raw_os_error() {
34+
Some(libc::ENOSYS) => false, // No kernel support
35+
// The fallback on EPERM is intentionally not done on Android since this workaround
36+
// seems to be needed only for specific Linux-based products that aren't based
37+
// on Android. See https://github.com/rust-random/getrandom/issues/229.
38+
#[cfg(target_os = "linux")]
39+
Some(libc::EPERM) => false, // Blocked by seccomp
40+
_ => true,
41+
}
42+
} else {
43+
true
44+
}
45+
}
46+
47+
#[inline(never)]
48+
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
49+
use_file::fill_inner(dest)
50+
}
7651

77-
if fptr == NOT_AVAILABLE {
52+
if !GETRANDOM_GOOD.unsync_init(is_getrandom_good) {
7853
use_file_fallback(dest)
7954
} 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) };
8255
util_libc::sys_fill_exact(dest, |buf| unsafe {
83-
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
56+
libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0)
8457
})
8558
}
8659
}

0 commit comments

Comments
 (0)