Skip to content

Commit 3d45f95

Browse files
committed
Unify lazy atomics in entropy backends
Replace ad-hoc atomic lazy caches with shared lazy helpers in the Linux/Android fallback, NetBSD, RDRAND, and RNDR backends. Add `LazyPtr` alongside `LazyUsize` and `LazyBool` so pointer and boolean caches use the same initialization contract. This reduces duplicated cache logic and keeps backend probing/fallback semantics aligned while preserving the existing retry-until-cached behavior.
1 parent 314fd5a commit 3d45f95

5 files changed

Lines changed: 131 additions & 157 deletions

File tree

src/backends/linux_android_with_fallback.rs

Lines changed: 30 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,7 @@ 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::utils;
1110

@@ -15,57 +14,48 @@ type GetRandomFn = unsafe extern "C" fn(*mut c_void, libc::size_t, libc::c_uint)
1514

1615
/// Sentinel value which indicates that `libc::getrandom` either not available,
1716
/// 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(core::ptr::null_mut());
17+
const NOT_AVAILABLE: *mut c_void = usize::MAX as *mut c_void;
2118

2219
#[cold]
2320
#[inline(never)]
24-
fn init() -> NonNull<c_void> {
21+
fn init() -> *mut c_void {
2522
// Use static linking to `libc::getrandom` on MUSL targets and `dlsym` everywhere else
2623
#[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-
};
24+
let fptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) };
3225
#[cfg(target_env = "musl")]
33-
let raw_ptr = {
26+
let fptr = {
3427
let fptr: GetRandomFn = libc::getrandom;
3528
unsafe { transmute::<GetRandomFn, *mut c_void>(fptr) }
3629
};
3730

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 utils::get_errno() {
48-
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-
libc::EPERM => NOT_AVAILABLE, // Blocked by seccomp
54-
_ => fptr,
55-
}
56-
} else {
57-
fptr
31+
let res_ptr = if !fptr.is_null() {
32+
let getrandom_fn = unsafe { transmute::<*mut c_void, GetRandomFn>(fptr) };
33+
// Check that `getrandom` syscall is supported by kernel
34+
let res = unsafe { getrandom_fn(ptr::dangling_mut(), 0, 0) };
35+
if cfg!(getrandom_test_linux_fallback) {
36+
NOT_AVAILABLE
37+
} else if res.is_negative() {
38+
match utils::get_errno() {
39+
libc::ENOSYS => NOT_AVAILABLE, // No kernel support
40+
// The fallback on EPERM is intentionally not done on Android since this workaround
41+
// seems to be needed only for specific Linux-based products that aren't based
42+
// on Android. See https://github.com/rust-random/getrandom/issues/229.
43+
#[cfg(target_os = "linux")]
44+
libc::EPERM => NOT_AVAILABLE, // Blocked by seccomp
45+
_ => fptr,
5846
}
47+
} else {
48+
fptr
5949
}
60-
None => NOT_AVAILABLE,
50+
} else {
51+
NOT_AVAILABLE
6152
};
6253

6354
#[cfg(getrandom_test_linux_without_fallback)]
6455
if res_ptr == NOT_AVAILABLE {
6556
panic!("Fallback is triggered with enabled `getrandom_test_linux_without_fallback`")
6657
}
6758

68-
GETRANDOM_FN.store(res_ptr.as_ptr(), Ordering::Release);
6959
res_ptr
7060
}
7161

@@ -77,23 +67,17 @@ fn use_file_fallback(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
7767

7868
#[inline]
7969
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-
};
70+
#[path = "../utils/lazy.rs"]
71+
mod lazy;
72+
73+
static GETRANDOM_FN: lazy::LazyPtr<c_void> = lazy::LazyPtr::new();
74+
let fptr = GETRANDOM_FN.unsync_init(init);
9175

9276
if fptr == NOT_AVAILABLE {
9377
use_file_fallback(dest)
9478
} else {
9579
// 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) };
80+
let getrandom_fn = unsafe { transmute::<*mut c_void, GetRandomFn>(fptr) };
9781
utils::sys_fill_exact(dest, |buf| unsafe {
9882
getrandom_fn(buf.as_mut_ptr().cast(), buf.len(), 0)
9983
})

src/backends/netbsd.rs

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use core::{
99
ffi::c_void,
1010
mem::{self, MaybeUninit},
1111
ptr,
12-
sync::atomic::{AtomicPtr, Ordering},
1312
};
1413

1514
pub use crate::util::{inner_u32, inner_u64};
@@ -42,35 +41,27 @@ unsafe extern "C" fn polyfill_using_kern_arand(
4241

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

45-
static GETRANDOM: AtomicPtr<c_void> = AtomicPtr::new(ptr::null_mut());
46-
4744
#[cold]
4845
#[inline(never)]
4946
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) };
47+
let ptr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, c"getrandom".as_ptr()) };
5348
if ptr.is_null() || cfg!(getrandom_test_netbsd_fallback) {
5449
// Verify `polyfill_using_kern_arand` has the right signature.
5550
const POLYFILL: GetRandomFn = polyfill_using_kern_arand;
56-
ptr = POLYFILL as *mut c_void;
51+
POLYFILL as *mut c_void
52+
} else {
53+
ptr
5754
}
58-
GETRANDOM.store(ptr, Ordering::Release);
59-
ptr
6055
}
6156

6257
#[inline]
6358
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-
}
59+
#[path = "../utils/lazy.rs"]
60+
mod lazy;
61+
62+
static GETRANDOM_FN: lazy::LazyPtr<c_void> = lazy::LazyPtr::new();
63+
64+
let fptr = GETRANDOM_FN.unsync_init(init);
7465
let fptr = unsafe { mem::transmute::<*mut c_void, GetRandomFn>(fptr) };
7566
utils::sys_fill_exact(dest, |buf| unsafe {
7667
fptr(buf.as_mut_ptr().cast::<c_void>(), buf.len(), 0)

src/backends/rdrand.rs

Lines changed: 45 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@
22
use crate::{Error, util::slice_as_uninit};
33
use core::mem::{MaybeUninit, size_of};
44

5-
#[path = "../utils/lazy.rs"]
6-
mod lazy;
7-
85
#[cfg(not(any(target_arch = "x86_64", target_arch = "x86")))]
96
compile_error!("`rdrand` backend can be enabled only for x86 and x86-64 targets!");
107

@@ -20,8 +17,6 @@ cfg_if! {
2017
}
2118
}
2219

23-
static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new();
24-
2520
// Recommendation from "Intel® Digital Random Number Generator (DRNG) Software
2621
// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
2722
// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
@@ -73,46 +68,53 @@ fn self_test() -> bool {
7368
}
7469

7570
fn is_rdrand_good() -> bool {
76-
#[cfg(not(target_feature = "rdrand"))]
77-
{
78-
// SAFETY: All Rust x86 targets are new enough to have CPUID, and we
79-
// check that leaf 1 is supported before using it.
80-
//
81-
// TODO(MSRV 1.94): remove allow(unused_unsafe) and the unsafe blocks for `__cpuid`.
82-
#[allow(unused_unsafe)]
83-
let cpuid0 = unsafe { arch::__cpuid(0) };
84-
if cpuid0.eax < 1 {
85-
return false;
86-
}
87-
#[allow(unused_unsafe)]
88-
let cpuid1 = unsafe { arch::__cpuid(1) };
89-
90-
let vendor_id = [
91-
cpuid0.ebx.to_le_bytes(),
92-
cpuid0.edx.to_le_bytes(),
93-
cpuid0.ecx.to_le_bytes(),
94-
];
95-
if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
96-
let mut family = (cpuid1.eax >> 8) & 0xF;
97-
if family == 0xF {
98-
family += (cpuid1.eax >> 20) & 0xFF;
99-
}
100-
// AMD CPUs families before 17h (Zen) sometimes fail to set CF when
101-
// RDRAND fails after suspend. Don't use RDRAND on those families.
102-
// See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
103-
if family < 0x17 {
71+
#[path = "../utils/lazy.rs"]
72+
mod lazy;
73+
74+
static RDRAND_GOOD: lazy::LazyBool = lazy::LazyBool::new();
75+
76+
RDRAND_GOOD.unsync_init(|| {
77+
#[cfg(not(target_feature = "rdrand"))]
78+
{
79+
// SAFETY: All Rust x86 targets are new enough to have CPUID, and we
80+
// check that leaf 1 is supported before using it.
81+
//
82+
// TODO(MSRV 1.94): remove allow(unused_unsafe) and the unsafe blocks for `__cpuid`.
83+
#[allow(unused_unsafe)]
84+
let cpuid0 = unsafe { arch::__cpuid(0) };
85+
if cpuid0.eax < 1 {
10486
return false;
10587
}
106-
}
88+
#[allow(unused_unsafe)]
89+
let cpuid1 = unsafe { arch::__cpuid(1) };
90+
91+
let vendor_id = [
92+
cpuid0.ebx.to_le_bytes(),
93+
cpuid0.edx.to_le_bytes(),
94+
cpuid0.ecx.to_le_bytes(),
95+
];
96+
if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
97+
let mut family = (cpuid1.eax >> 8) & 0xF;
98+
if family == 0xF {
99+
family += (cpuid1.eax >> 20) & 0xFF;
100+
}
101+
// AMD CPUs families before 17h (Zen) sometimes fail to set CF when
102+
// RDRAND fails after suspend. Don't use RDRAND on those families.
103+
// See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
104+
if family < 0x17 {
105+
return false;
106+
}
107+
}
107108

108-
const RDRAND_FLAG: u32 = 1 << 30;
109-
if cpuid1.ecx & RDRAND_FLAG == 0 {
110-
return false;
109+
const RDRAND_FLAG: u32 = 1 << 30;
110+
if cpuid1.ecx & RDRAND_FLAG == 0 {
111+
return false;
112+
}
111113
}
112-
}
113114

114-
// SAFETY: We have already checked that rdrand is available.
115-
unsafe { self_test() }
115+
// SAFETY: We have already checked that rdrand is available.
116+
unsafe { self_test() }
117+
})
116118
}
117119

118120
#[target_feature(enable = "rdrand")]
@@ -162,7 +164,7 @@ fn rdrand_u64() -> Option<u64> {
162164

163165
#[inline]
164166
pub fn inner_u32() -> Result<u32, Error> {
165-
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
167+
if !is_rdrand_good() {
166168
return Err(Error::NO_RDRAND);
167169
}
168170
// SAFETY: After this point, we know rdrand is supported.
@@ -171,7 +173,7 @@ pub fn inner_u32() -> Result<u32, Error> {
171173

172174
#[inline]
173175
pub fn inner_u64() -> Result<u64, Error> {
174-
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
176+
if !is_rdrand_good() {
175177
return Err(Error::NO_RDRAND);
176178
}
177179
// SAFETY: After this point, we know rdrand is supported.
@@ -180,7 +182,7 @@ pub fn inner_u64() -> Result<u64, Error> {
180182

181183
#[inline]
182184
pub fn fill_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
183-
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
185+
if !is_rdrand_good() {
184186
return Err(Error::NO_RDRAND);
185187
}
186188
// SAFETY: After this point, we know rdrand is supported.

src/backends/rndr.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ fn is_rndr_available() -> bool {
7171
fn is_rndr_available() -> bool {
7272
#[path = "../utils/lazy.rs"]
7373
mod lazy;
74+
7475
static RNDR_GOOD: lazy::LazyBool = lazy::LazyBool::new();
7576

7677
cfg_if::cfg_if! {

0 commit comments

Comments
 (0)