Skip to content

Commit 1c3abfb

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 1c3abfb

File tree

1 file changed

+116
-0
lines changed

1 file changed

+116
-0
lines changed

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

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,121 @@ pub fn fill_bytes(bytes: &mut [u8]) {
2323
}
2424
}
2525

26+
// Fallback to rdrand if rng protocol missing.
27+
//
28+
// For real-world example, see [issue-13825](https://github.com/rust-lang/rust/issues/138252#issuecomment-2891270323)
29+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
30+
if rdrand::fill_bytes(bytes) {
31+
return;
32+
}
33+
2634
panic!("failed to generate random data");
2735
}
36+
37+
/// Port from [getrandom](https://github.com/rust-random/getrandom/blob/master/src/backends/rdrand.rs)
38+
#[cfg(any(target_arch = "x86_64", target_arch = "x86"))]
39+
mod rdrand {
40+
cfg_if::cfg_if! {
41+
if #[cfg(target_arch = "x86_64")] {
42+
use crate::arch::x86_64 as arch;
43+
use arch::_rdrand64_step as rdrand_step;
44+
type Word = u64;
45+
} else if #[cfg(target_arch = "x86")] {
46+
use crate::arch::x86 as arch;
47+
use arch::_rdrand32_step as rdrand_step;
48+
type Word = u32;
49+
}
50+
}
51+
52+
static RDRAND_GOOD: crate::sync::LazyLock<bool> = crate::sync::LazyLock::new(is_rdrand_good);
53+
54+
// Recommendation from "Intel® Digital Random Number Generator (DRNG) Software
55+
// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
56+
// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
57+
const RETRY_LIMIT: usize = 10;
58+
59+
unsafe fn rdrand() -> Option<Word> {
60+
for _ in 0..RETRY_LIMIT {
61+
let mut val = 0;
62+
if unsafe { rdrand_step(&mut val) } == 1 {
63+
return Some(val);
64+
}
65+
}
66+
None
67+
}
68+
69+
// Run a small self-test to make sure we aren't repeating values
70+
// Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c
71+
// Fails with probability < 2^(-90) on 32-bit systems
72+
unsafe fn self_test() -> bool {
73+
// On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision.
74+
let mut prev = Word::MAX;
75+
let mut fails = 0;
76+
for _ in 0..8 {
77+
match unsafe { rdrand() } {
78+
Some(val) if val == prev => fails += 1,
79+
Some(val) => prev = val,
80+
None => return false,
81+
};
82+
}
83+
fails <= 2
84+
}
85+
86+
fn is_rdrand_good() -> bool {
87+
#[cfg(not(target_feature = "rdrand"))]
88+
{
89+
// SAFETY: All Rust x86 targets are new enough to have CPUID, and we
90+
// check that leaf 1 is supported before using it.
91+
let cpuid0 = unsafe { arch::__cpuid(0) };
92+
if cpuid0.eax < 1 {
93+
return false;
94+
}
95+
let cpuid1 = unsafe { arch::__cpuid(1) };
96+
97+
let vendor_id =
98+
[cpuid0.ebx.to_le_bytes(), cpuid0.edx.to_le_bytes(), cpuid0.ecx.to_le_bytes()];
99+
if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
100+
let mut family = (cpuid1.eax >> 8) & 0xF;
101+
if family == 0xF {
102+
family += (cpuid1.eax >> 20) & 0xFF;
103+
}
104+
// AMD CPUs families before 17h (Zen) sometimes fail to set CF when
105+
// RDRAND fails after suspend. Don't use RDRAND on those families.
106+
// See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
107+
if family < 0x17 {
108+
return false;
109+
}
110+
}
111+
112+
const RDRAND_FLAG: u32 = 1 << 30;
113+
if cpuid1.ecx & RDRAND_FLAG == 0 {
114+
return false;
115+
}
116+
}
117+
118+
// SAFETY: We have already checked that rdrand is available.
119+
unsafe { self_test() }
120+
}
121+
122+
unsafe fn rdrand_exact(dest: &mut [u8]) -> Option<()> {
123+
// We use chunks_exact_mut instead of chunks_mut as it allows almost all
124+
// calls to memcpy to be elided by the compiler.
125+
let mut chunks = dest.chunks_exact_mut(size_of::<Word>());
126+
for chunk in chunks.by_ref() {
127+
let src = unsafe { rdrand() }?.to_ne_bytes();
128+
chunk.copy_from_slice(&src);
129+
}
130+
131+
let tail = chunks.into_remainder();
132+
let n = tail.len();
133+
if n > 0 {
134+
let src = unsafe { rdrand() }?.to_ne_bytes();
135+
tail.copy_from_slice(&src[..n]);
136+
}
137+
Some(())
138+
}
139+
140+
pub(crate) fn fill_bytes(bytes: &mut [u8]) -> bool {
141+
if *RDRAND_GOOD { unsafe { rdrand_exact(bytes).is_some() } } else { false }
142+
}
143+
}

0 commit comments

Comments
 (0)