Skip to content

Commit de32ca5

Browse files
committed
linux_android_with_fallback: detect getrandom
Avoid dlsym when statically linking crt.
1 parent 3bd0ebd commit de32ca5

File tree

5 files changed

+142
-92
lines changed

5 files changed

+142
-92
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ check-cfg = [
8989
'cfg(getrandom_test_linux_fallback)',
9090
'cfg(getrandom_test_linux_without_fallback)',
9191
'cfg(getrandom_test_netbsd_fallback)',
92+
'cfg(has_libc_getrandom)',
9293
'cfg(target_os, values("cygwin"))', # TODO(MSRV 1.86): Remove this.
9394
]
9495

build.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,61 @@ fn main() {
66
if sanitizers.contains("memory") {
77
println!("cargo:rustc-cfg=getrandom_msan");
88
}
9+
10+
if cfg!(target_feature = "crt-static") {
11+
match std::process::Command::new(std::env::var_os("RUSTC").unwrap())
12+
.arg("--target")
13+
.arg(std::env::var("TARGET").unwrap())
14+
.arg("--out-dir")
15+
.arg(std::env::var("OUT_DIR").unwrap())
16+
.args(["--crate-type=bin", "-"])
17+
.stdin(std::process::Stdio::piped())
18+
.stdout(std::process::Stdio::null())
19+
.stderr(std::process::Stdio::piped())
20+
.spawn()
21+
{
22+
Err(err) => {
23+
println!("cargo:warning=failed to spawn compiler: {}", err);
24+
}
25+
Ok(mut child) => {
26+
use std::io::{BufRead as _, Write as _};
27+
28+
let std::process::Child { stdin, stderr, .. } = &mut child;
29+
let mut stdin = stdin.take().unwrap();
30+
stdin
31+
.write_all(
32+
r#"
33+
use std::ffi::{c_uint, c_void};
34+
extern "C" {
35+
fn getrandom(buf: *mut c_void, buflen: usize, flags: c_uint) -> isize;
36+
}
37+
fn main() -> std::io::Result<()> {
38+
use std::convert::TryFrom as _;
39+
let mut buf = [0; 1];
40+
let _: usize = usize::try_from(unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) })
41+
.map_err(|std::num::TryFromIntError { .. }| std::io::Error::last_os_error())?;
42+
Ok(())
43+
}
44+
"#
45+
.as_bytes(),
46+
)
47+
.unwrap();
48+
49+
std::mem::drop(stdin); // Send EOF.
50+
51+
// Trampoline stdout to cargo warnings.
52+
let stderr = stderr.take().unwrap();
53+
let stderr = std::io::BufReader::new(stderr);
54+
for line in stderr.lines() {
55+
let line = line.unwrap();
56+
println!("cargo:warning={line}");
57+
}
58+
59+
let status = child.wait().unwrap();
60+
if status.code() == Some(0) {
61+
println!("cargo:rustc-cfg=has_libc_getrandom");
62+
}
63+
}
64+
}
65+
}
966
}

src/backends/linux_android_with_fallback.rs

Lines changed: 70 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,69 +4,53 @@ use crate::Error;
44
use core::{
55
ffi::c_void,
66
mem::{MaybeUninit, transmute},
7-
ptr::NonNull,
8-
sync::atomic::{AtomicPtr, Ordering},
7+
ptr,
98
};
109
use use_file::util_libc;
1110

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

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) };
13+
#[path = "../lazy.rs"]
14+
mod lazy;
1915

20-
static GETRANDOM_FN: AtomicPtr<c_void> = AtomicPtr::new(core::ptr::null_mut());
16+
type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint) -> libc::ssize_t;
2117

2218
#[cold]
2319
#[inline(never)]
24-
fn init() -> NonNull<c_void> {
25-
// Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else
26-
#[cfg(not(target_env = "musl"))]
27-
let raw_ptr = {
28-
static NAME: &[u8] = b"getrandom\0";
29-
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
30-
unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) }
31-
};
32-
#[cfg(target_env = "musl")]
33-
let raw_ptr = {
34-
let fptr: GetRandomFn = libc::getrandom;
35-
unsafe { transmute::<GetRandomFn, *mut c_void>(fptr) }
36-
};
37-
38-
let res_ptr = match NonNull::new(raw_ptr) {
39-
Some(fptr) => {
40-
let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
41-
let dangling_ptr = NonNull::dangling().as_ptr();
42-
// Check that `getrandom` syscall is supported by kernel
43-
let res = unsafe { getrandom_fn(dangling_ptr, 0, 0) };
44-
if cfg!(getrandom_test_linux_fallback) {
45-
NOT_AVAILABLE
46-
} else if res.is_negative() {
47-
match util_libc::last_os_error().raw_os_error() {
48-
Some(libc::ENOSYS) => NOT_AVAILABLE, // No kernel support
49-
// The fallback on EPERM is intentionally not done on Android since this workaround
50-
// seems to be needed only for specific Linux-based products that aren't based
51-
// on Android. See https://github.com/rust-random/getrandom/issues/229.
52-
#[cfg(target_os = "linux")]
53-
Some(libc::EPERM) => NOT_AVAILABLE, // Blocked by seccomp
54-
_ => fptr,
55-
}
56-
} else {
57-
fptr
20+
fn is_getrandom_good(getrandom_fn: GetRandomFn) -> bool {
21+
if cfg!(getrandom_test_linux_fallback) {
22+
false
23+
} else {
24+
// Check that `getrandom` syscall is supported by kernel
25+
let res = unsafe { getrandom_fn(ptr::dangling_mut(), 0, 0) };
26+
if !res.is_negative() {
27+
true
28+
} else {
29+
match util_libc::last_os_error().raw_os_error() {
30+
Some(libc::ENOSYS) => false, // No kernel support
31+
// The fallback on EPERM is intentionally not done on Android since this workaround
32+
// seems to be needed only for specific Linux-based products that aren't based
33+
// on Android. See https://github.com/rust-random/getrandom/issues/229.
34+
Some(libc::EPERM) if cfg!(target_os = "linux") => false, // Blocked by seccomp
35+
_ => true,
5836
}
5937
}
60-
None => NOT_AVAILABLE,
61-
};
62-
63-
#[cfg(getrandom_test_linux_without_fallback)]
64-
if res_ptr == NOT_AVAILABLE {
65-
panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`")
6638
}
39+
}
6740

68-
GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
69-
res_ptr
41+
fn to_getrandom_fn(getrandom_fn: usize) -> GetRandomFn {
42+
unsafe { transmute::<usize, GetRandomFn>(getrandom_fn) }
43+
}
44+
45+
#[cold]
46+
#[inline(never)]
47+
fn init() -> Option<usize> {
48+
ptr::NonNull::new(unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) }).and_then(
49+
|getrandom_fn| {
50+
let getrandom_fn = to_getrandom_fn(getrandom_fn.as_ptr() as usize);
51+
is_getrandom_good(getrandom_fn).then_some(getrandom_fn as usize)
52+
},
53+
)
7054
}
7155

7256
// Prevent inlining of the fallback implementation
@@ -75,29 +59,45 @@ fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
7559
use_file::fill_inner(dest)
7660
}
7761

62+
fn with_unpoison_linux_gerandom_result(
63+
dest: &mut [MaybeUninit<u8>],
64+
getrandom_fn: GetRandomFn,
65+
) -> Result<(), Error> {
66+
util_libc::sys_fill_exact(dest, |buf| unsafe {
67+
let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0);
68+
sanitizer::unpoison_linux_getrandom_result(buf, ret);
69+
ret
70+
})
71+
}
72+
7873
#[inline]
7974
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
80-
// Despite being only a single atomic variable, we still cannot always use
81-
// Ordering::Relaxed, as we need to make sure a successful call to `init`
82-
// is "ordered before" any data read through the returned pointer (which
83-
// occurs when the function is called). Our implementation mirrors that of
84-
// the one in libstd, meaning that the use of non-Relaxed operations is
85-
// probably unnecessary.
86-
let raw_ptr = GETRANDOM_FN.load(Ordering::Acquire);
87-
let fptr = match NonNull::new(raw_ptr) {
88-
Some(p) => p,
89-
None => init(),
90-
};
75+
if cfg!(not(target_feature = "crt-static")) {
76+
static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new();
9177

92-
if fptr == NOT_AVAILABLE {
93-
use_file_fallback(dest)
78+
const NOT_AVAILABLE: usize = usize::MAX;
79+
80+
match GETRANDOM_FN.unsync_init(|| init().unwrap_or(NOT_AVAILABLE)) {
81+
NOT_AVAILABLE => {
82+
if cfg!(getrandom_test_linux_without_fallback) {
83+
panic!("fallback is triggered with `getrandom_test_linux_without_fallback`");
84+
}
85+
use_file_fallback(dest)
86+
}
87+
getrandom_fn => {
88+
let getrandom_fn = to_getrandom_fn(getrandom_fn);
89+
with_unpoison_linux_gerandom_result(dest, getrandom_fn)
90+
}
91+
}
92+
} else if cfg!(has_libc_getrandom) {
93+
use_file::fill_inner(dest)
9494
} else {
95-
// note: `transmute` is currently the only way to convert a pointer into a function reference
96-
let getrandom_fn = unsafe { transmute::<NonNull<c_void>, GetRandomFn>(fptr) };
97-
util_libc::sys_fill_exact(dest, |buf| unsafe {
98-
let ret = getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0);
99-
sanitizer::unpoison_linux_getrandom_result(buf, ret);
100-
ret
101-
})
95+
static GETRANDOM_GOOD: lazy::LazyBool = lazy::LazyBool::new();
96+
97+
if GETRANDOM_GOOD.unsync_init(|| is_getrandom_good(libc::getrandom)) {
98+
with_unpoison_linux_gerandom_result(dest, libc::getrandom)
99+
} else {
100+
use_file_fallback(dest)
101+
}
102102
}
103103
}

src/backends/netbsd.rs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ use core::{
1414

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

17+
#[path = "../lazy.rs"]
18+
mod lazy;
1719
#[path = "../util_libc.rs"]
1820
mod util_libc;
1921

@@ -42,36 +44,26 @@ unsafe extern "C" fn polyfill_using_kern_arand(
4244

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

45-
static GETRANDOM: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
46-
4747
#[cold]
4848
#[inline(never)]
49-
fn init() -> *mut c_void {
50-
static NAME: &[u8] = b"getrandom\0";
51-
let name_ptr = NAME.as_ptr().cast::<libc::c_char>();
52-
let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name_ptr) };
49+
fn init() -> usize {
50+
let mut ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) };
5351
if ptr.is_null() || cfg!(getrandom_test_netbsd_fallback) {
5452
// Verify `polyfill_using_kern_arand` has the right signature.
5553
const POLYFILL: GetRandomFn = polyfill_using_kern_arand;
5654
ptr = POLYFILL as *mut c_void;
5755
}
58-
GETRANDOM.store(ptr, Ordering::Release);
59-
ptr
56+
ptr as usize
6057
}
6158

6259
#[inline]
6360
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
64-
// Despite being only a single atomic variable, we still cannot always use
65-
// Ordering::Relaxed, as we need to make sure a successful call to `init`
66-
// is "ordered before" any data read through the returned pointer (which
67-
// occurs when the function is called). Our implementation mirrors that of
68-
// the one in libstd, meaning that the use of non-Relaxed operations is
69-
// probably unnecessary.
70-
let mut fptr = GETRANDOM.load(Ordering::Acquire);
71-
if fptr.is_null() {
72-
fptr = init();
73-
}
74-
let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) };
61+
static GETRANDOM_FN: lazy::LazyUsize = lazy::LazyUsize::new();
62+
63+
const NOT_AVAILABLE: usize = usize::MAX;
64+
65+
let fptr = GETRANDOM_FN.unsync_init(init);
66+
let fptr = unsafe { mem::transmute::<usize, GetRandomFn>(fptr) };
7567
util_libc::sys_fill_exact(dest, |buf| unsafe {
7668
fptr(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0)
7769
})

src/lazy.rs

Lines changed: 3 additions & 3 deletions
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)