Skip to content

Commit

Permalink
linux_android_with_fallback: detect getrandom
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
tamird committed Feb 5, 2025
1 parent ce3b017 commit d1f2012
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 68 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ rustc-dep-of-std = ["dep:compiler_builtins", "dep:core"]
# this flag *and* set getrandom_backend=wasm_js (see README).
wasm_js = ["dep:wasm-bindgen", "dep:js-sys"]

[build-dependencies]
cc = "1.2.12"

[dependencies]
cfg-if = "1"

Expand Down Expand Up @@ -85,6 +88,7 @@ check-cfg = [
'cfg(getrandom_msan)',
'cfg(getrandom_test_linux_fallback)',
'cfg(getrandom_test_netbsd_fallback)',
'cfg(has_libc_getrandom)',
]

[package.metadata.docs.rs]
Expand Down
43 changes: 43 additions & 0 deletions build.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
use std::{
io::{BufRead as _, BufReader, Write as _},
process::{Child, Stdio},
};

// Automatically detect cfg(sanitize = "memory") even if cfg(sanitize) isn't
// supported. Build scripts get cfg() info, even if the cfg is unstable.
fn main() {
Expand All @@ -6,4 +11,42 @@ fn main() {
if santizers.contains("memory") {
println!("cargo:rustc-cfg=getrandom_msan");
}

let mut child = cc::Build::new()
.get_compiler()
.to_command()
.args(["-x", "c", "-", "-o", "-"])
.stdin(Stdio::piped())
.stdout(Stdio::null())
.stderr(Stdio::piped())
.spawn()
.unwrap();

let Child { stdin, stderr, .. } = &mut child;
let mut stdin = stdin.take().unwrap();
stdin
.write_all(
r#"#include <sys/random.h>
int main() {
char buf[1];
return getrandom(buf, sizeof(buf), 0) == -1;
}"#
.as_bytes(),
)
.unwrap();
std::mem::drop(stdin); // Send EOF.

// Trampoline stdout to cargo warnings.
let stderr = stderr.take().expect("stderr");
let stderr = BufReader::new(stderr);
for line in stderr.lines() {
let line = line.expect("read line");
println!("cargo:warning={line}");
}

let status = child.wait().unwrap();
if status.code() != Some(0) {
} else {
println!("cargo:rustc-cfg=has_libc_getrandom");
}
}
109 changes: 41 additions & 68 deletions src/backends/linux_android_with_fallback.rs
Original file line number Diff line number Diff line change
@@ -1,86 +1,59 @@
//! Implementation for Linux / Android with `/dev/urandom` fallback
use super::use_file;
use crate::Error;
use core::{
ffi::c_void,
mem::{self, MaybeUninit},
ptr::{self, NonNull},
sync::atomic::{AtomicPtr, Ordering},
};
use use_file::util_libc;
use core::mem::MaybeUninit;

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

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

/// Sentinel value which indicates that `libc::getrandom` either not available,
/// or not supported by kernel.
const NOT_AVAILABLE: NonNull<c_void> = unsafe { NonNull::new_unchecked(usize::MAX as *mut c_void) };

static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());

#[cold]
#[inline(never)]
fn init() -> NonNull<c_void> {
static NAME: &[u8] = b"getrandom\0";
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
let raw_ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
let res_ptr = match NonNull::new(raw_ptr) {
Some(fptr) => {
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
let dangling_ptr = ptr::NonNull::dangling().as_ptr();
// Check that `getrandom` syscall is supported by kernel
let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
if cfg!(getrandom_test_linux_fallback) {
NOT_AVAILABLE
} else if res.is_negative() {
match util_libc::last_os_error().raw_os_error() {
Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
// The fallback on EPERM is intentionally not done on Android since this workaround
// seems to be needed only for specific Linux-based products that aren't based
// on Android. See https://github.com/rust-random/getrandom/issues/229.
#[cfg(target_os = "linux")]
Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
_ => fptr,
}
} else {
fptr
}
}
None => NOT_AVAILABLE,
};

GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
res_ptr
}

// prevent inlining of the fallback implementation
#[inline(never)]
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
#[cfg(not(has_libc_getrandom))]
#[inline]
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
use_file::fill_inner(dest)
}

#[cfg(has_libc_getrandom)]
#[inline]
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
// Despite being only a single atomic variable, we still cannot always use
// Ordering::Relaxed, as we need to make sure a successful call to `init`
// is "ordered before" any data read through the returned pointer (which
// occurs when the function is called). Our implementation mirrors that of
// the one in libstd, meaning that the use of non-Relaxed operations is
// probably unnecessary.
let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
let fptr = match NonNull::new(raw_ptr) {
Some(p) => p,
None => init(),
};
use use_file::util_libc;

#[path = "../lazy.rs"]
mod lazy;

static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new();

#[cold]
#[inline(never)]
fn is_getrandom_good() -> bool {
let dangling_ptr = core::ptr::NonNull::dangling().as_ptr();
// Check that `getrandom` syscall is supported by kernel
let res = unsafe { libc::getrandom(dangling_ptr, 0, 0) };
if cfg!(getrandom_test_linux_fallback) {
false
} else if res.is_negative() {
match util_libc::last_os_error().raw_os_error() {
Some(libc::ENOSYS) => false, // No kernel support
// The fallback on EPERM is intentionally not done on Android since this workaround
// seems to be needed only for specific Linux-based products that aren't based
// on Android. See https://github.com/rust-random/getrandom/issues/229.
#[cfg(target_os = "linux")]
Some(libc::EPERM) => false, // Blocked by seccomp
_ => true,
}
} else {
true
}
}

#[inline(never)]
fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
use_file::fill_inner(dest)
}

if fptr == NOT_AVAILABLE {
if !GETRANDOM_GOOD.unsync_init(is_getrandom_good) {
use_file_fallback(dest)
} else {
// note: `transmute` is currently the only way to convert a pointer into a function reference
let getrandom_fn = unsafe { mem::transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
util_libc::sys_fill_exact(dest, |buf| unsafe {
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0)
})
}
}

0 comments on commit d1f2012

Please sign in to comment.