Skip to content

Commit a6b2195

Browse files
committed
std: sys: random: uefi: Provide rdrand based fallback
Some UEFI systems based on American Megatrends Inc. v3.3 do not provide RNG support [1]. So fallback to rdrand in such cases. [1]: #138252 (comment) Signed-off-by: Ayush Singh <[email protected]>
1 parent 6cab15c commit a6b2195

File tree

1 file changed

+150
-19
lines changed

1 file changed

+150
-19
lines changed

library/std/src/sys/random/uefi.rs

Lines changed: 150 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,158 @@
1-
use r_efi::protocols::rng;
1+
pub fn fill_bytes(bytes: &mut [u8]) {
2+
// Handle zero-byte request
3+
if bytes.is_empty() {
4+
return;
5+
}
6+
7+
// Try EFI_RNG_PROTOCOL
8+
if rng_protocol::fill_bytes(bytes) {
9+
return;
10+
}
211

3-
use crate::sys::pal::helpers;
12+
// Fallback to rdrand if rng protocol missing.
13+
//
14+
// For real-world example, see [issue-13825](https://github.com/rust-lang/rust/issues/138252#issuecomment-2891270323)
15+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
16+
if rdrand::fill_bytes(bytes) {
17+
return;
18+
}
419

5-
pub fn fill_bytes(bytes: &mut [u8]) {
6-
let handles =
7-
helpers::locate_handles(rng::PROTOCOL_GUID).expect("failed to generate random data");
8-
for handle in handles {
9-
if let Ok(protocol) = helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID) {
10-
let r = unsafe {
11-
((*protocol.as_ptr()).get_rng)(
12-
protocol.as_ptr(),
13-
crate::ptr::null_mut(),
14-
bytes.len(),
15-
bytes.as_mut_ptr(),
16-
)
20+
panic!("failed to generate random data");
21+
}
22+
23+
mod rng_protocol {
24+
use r_efi::protocols::rng;
25+
26+
use crate::sys::pal::helpers;
27+
28+
pub(crate) fn fill_bytes(bytes: &mut [u8]) -> bool {
29+
if let Ok(handles) = helpers::locate_handles(rng::PROTOCOL_GUID) {
30+
for handle in handles {
31+
if let Ok(protocol) =
32+
helpers::open_protocol::<rng::Protocol>(handle, rng::PROTOCOL_GUID)
33+
{
34+
let r = unsafe {
35+
((*protocol.as_ptr()).get_rng)(
36+
protocol.as_ptr(),
37+
crate::ptr::null_mut(),
38+
bytes.len(),
39+
bytes.as_mut_ptr(),
40+
)
41+
};
42+
if r.is_error() {
43+
continue;
44+
} else {
45+
return true;
46+
}
47+
}
48+
}
49+
}
50+
51+
false
52+
}
53+
}
54+
55+
/// Port from [getrandom](https://github.com/rust-random/getrandom/blob/master/src/backends/rdrand.rs)
56+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
57+
mod rdrand {
58+
cfg_if::cfg_if! {
59+
if #[cfg(target_arch = "x86_64")] {
60+
use crate::arch::x86_64 as arch;
61+
use arch::_rdrand64_step as rdrand_step;
62+
type Word = u64;
63+
} else if #[cfg(target_arch = "x86")] {
64+
use crate::arch::x86 as arch;
65+
use arch::_rdrand32_step as rdrand_step;
66+
type Word = u32;
67+
}
68+
}
69+
70+
static RDRAND_GOOD: crate::sync::LazyLock<bool> = crate::sync::LazyLock::new(is_rdrand_good);
71+
72+
// Recommendation from "Intel® Digital Random Number Generator (DRNG) Software
73+
// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
74+
// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
75+
const RETRY_LIMIT: usize = 10;
76+
77+
unsafe fn rdrand() -> Option<Word> {
78+
for _ in 0..RETRY_LIMIT {
79+
let mut val = 0;
80+
if unsafe { rdrand_step(&mut val) } == 1 {
81+
return Some(val);
82+
}
83+
}
84+
None
85+
}
86+
87+
// Run a small self-test to make sure we aren't repeating values
88+
// Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c
89+
// Fails with probability < 2^(-90) on 32-bit systems
90+
unsafe fn self_test() -> bool {
91+
// On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision.
92+
let mut prev = Word::MAX;
93+
let mut fails = 0;
94+
for _ in 0..8 {
95+
match unsafe { rdrand() } {
96+
Some(val) if val == prev => fails += 1,
97+
Some(val) => prev = val,
98+
None => return false,
1799
};
18-
if r.is_error() {
19-
continue;
20-
} else {
21-
return;
100+
}
101+
fails <= 2
102+
}
103+
104+
fn is_rdrand_good() -> bool {
105+
#[cfg(not(target_feature = "rdrand"))]
106+
{
107+
// SAFETY: All Rust x86 targets are new enough to have CPUID, and we
108+
// check that leaf 1 is supported before using it.
109+
let cpuid0 = unsafe { arch::__cpuid(0) };
110+
if cpuid0.eax < 1 {
111+
return false;
112+
}
113+
let cpuid1 = unsafe { arch::__cpuid(1) };
114+
115+
let vendor_id =
116+
[cpuid0.ebx.to_le_bytes(), cpuid0.edx.to_le_bytes(), cpuid0.ecx.to_le_bytes()];
117+
if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
118+
let mut family = (cpuid1.eax >> 8) & 0xF;
119+
if family == 0xF {
120+
family += (cpuid1.eax >> 20) & 0xFF;
121+
}
122+
// AMD CPUs families before 17h (Zen) sometimes fail to set CF when
123+
// RDRAND fails after suspend. Don't use RDRAND on those families.
124+
// See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
125+
if family < 0x17 {
126+
return false;
127+
}
128+
}
129+
130+
const RDRAND_FLAG: u32 = 1 << 30;
131+
if cpuid1.ecx & RDRAND_FLAG == 0 {
132+
return false;
22133
}
23134
}
135+
136+
// SAFETY: We have already checked that rdrand is available.
137+
unsafe { self_test() }
24138
}
25139

26-
panic!("failed to generate random data");
140+
unsafe fn rdrand_exact(dest: &mut [u8]) -> Option<()> {
141+
let mut chunks = dest.array_chunks_mut();
142+
for chunk in &mut chunks {
143+
*chunk = unsafe { rdrand() }?.to_ne_bytes();
144+
}
145+
146+
let tail = chunks.into_remainder();
147+
let n = tail.len();
148+
if n > 0 {
149+
let src = unsafe { rdrand() }?.to_ne_bytes();
150+
tail.copy_from_slice(&src[..n]);
151+
}
152+
Some(())
153+
}
154+
155+
pub(crate) fn fill_bytes(bytes: &mut [u8]) -> bool {
156+
if *RDRAND_GOOD { unsafe { rdrand_exact(bytes).is_some() } } else { false }
157+
}
27158
}

0 commit comments

Comments
 (0)