Skip to content

Commit 8e46d0c

Browse files
authoredFeb 9, 2023
Merge pull request #335 from rust-random/rdrand
rdrand: Remove checking for 0 and !0 and instead check CPU family and do a self-test
2 parents 9ceb7e5 + 5f7aadf commit 8e46d0c

File tree

1 file changed

+76
-43
lines changed

1 file changed

+76
-43
lines changed
 

‎src/rdrand.rs

+76-43
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
66
// option. This file may not be copied, modified, or distributed
77
// except according to those terms.
8-
9-
//! Implementation for SGX using RDRAND instruction
10-
use crate::{util::slice_as_uninit, Error};
11-
use core::mem::{self, MaybeUninit};
8+
use crate::{
9+
util::{slice_as_uninit, LazyBool},
10+
Error,
11+
};
12+
use core::mem::{size_of, MaybeUninit};
1213

1314
cfg_if! {
1415
if #[cfg(target_arch = "x86_64")] {
@@ -24,74 +25,106 @@ cfg_if! {
2425
// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
2526
// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
2627
const RETRY_LIMIT: usize = 10;
27-
const WORD_SIZE: usize = mem::size_of::<usize>();
2828

2929
#[target_feature(enable = "rdrand")]
30-
unsafe fn rdrand() -> Result<[u8; WORD_SIZE], Error> {
30+
unsafe fn rdrand() -> Option<usize> {
3131
for _ in 0..RETRY_LIMIT {
32-
let mut el = mem::zeroed();
33-
if rdrand_step(&mut el) == 1 {
34-
// AMD CPUs from families 14h to 16h (pre Ryzen) sometimes fail to
35-
// set CF on bogus random data, so we check these values explicitly.
36-
// See https://github.com/systemd/systemd/issues/11810#issuecomment-489727505
37-
// We perform this check regardless of target to guard against
38-
// any implementation that incorrectly fails to set CF.
39-
if el != 0 && el != !0 {
40-
return Ok(el.to_ne_bytes());
41-
}
42-
// Keep looping in case this was a false positive.
32+
let mut val = 0;
33+
if rdrand_step(&mut val) == 1 {
34+
return Some(val as usize);
4335
}
4436
}
45-
Err(Error::FAILED_RDRAND)
37+
None
4638
}
4739

48-
// "rdrand" target feature requires "+rdrnd" flag, see https://github.com/rust-lang/rust/issues/49653.
40+
// "rdrand" target feature requires "+rdrand" flag, see https://github.com/rust-lang/rust/issues/49653.
4941
#[cfg(all(target_env = "sgx", not(target_feature = "rdrand")))]
5042
compile_error!(
51-
"SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrnd."
43+
"SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrand."
5244
);
5345

54-
#[cfg(target_feature = "rdrand")]
55-
fn is_rdrand_supported() -> bool {
56-
true
46+
// Run a small self-test to make sure we aren't repeating values
47+
// Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c
48+
// Fails with probability < 2^(-90) on 32-bit systems
49+
#[target_feature(enable = "rdrand")]
50+
unsafe fn self_test() -> bool {
51+
// On AMD, RDRAND returns 0xFF...FF on failure, count it as a collision.
52+
let mut prev = !0; // TODO(MSRV 1.43): Move to usize::MAX
53+
let mut fails = 0;
54+
for _ in 0..8 {
55+
match rdrand() {
56+
Some(val) if val == prev => fails += 1,
57+
Some(val) => prev = val,
58+
None => return false,
59+
};
60+
}
61+
fails <= 2
5762
}
5863

59-
// TODO use is_x86_feature_detected!("rdrand") when that works in core. See:
60-
// https://github.com/rust-lang-nursery/stdsimd/issues/464
61-
#[cfg(not(target_feature = "rdrand"))]
62-
fn is_rdrand_supported() -> bool {
63-
use crate::util::LazyBool;
64+
fn is_rdrand_good() -> bool {
65+
#[cfg(not(target_feature = "rdrand"))]
66+
{
67+
// SAFETY: All Rust x86 targets are new enough to have CPUID, and we
68+
// check that leaf 1 is supported before using it.
69+
let cpuid0 = unsafe { arch::__cpuid(0) };
70+
if cpuid0.eax < 1 {
71+
return false;
72+
}
73+
let cpuid1 = unsafe { arch::__cpuid(1) };
6474

65-
// SAFETY: All Rust x86 targets are new enough to have CPUID, and if CPUID
66-
// is supported, CPUID leaf 1 is always supported.
67-
const FLAG: u32 = 1 << 30;
68-
static HAS_RDRAND: LazyBool = LazyBool::new();
69-
HAS_RDRAND.unsync_init(|| unsafe { (arch::__cpuid(1).ecx & FLAG) != 0 })
75+
let vendor_id = [
76+
cpuid0.ebx.to_le_bytes(),
77+
cpuid0.edx.to_le_bytes(),
78+
cpuid0.ecx.to_le_bytes(),
79+
];
80+
if vendor_id == [*b"Auth", *b"enti", *b"cAMD"] {
81+
let mut family = (cpuid1.eax >> 8) & 0xF;
82+
if family == 0xF {
83+
family += (cpuid1.eax >> 20) & 0xFF;
84+
}
85+
// AMD CPUs families before 17h (Zen) sometimes fail to set CF when
86+
// RDRAND fails after suspend. Don't use RDRAND on those families.
87+
// See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
88+
if family < 0x17 {
89+
return false;
90+
}
91+
}
92+
93+
const RDRAND_FLAG: u32 = 1 << 30;
94+
if cpuid1.ecx & RDRAND_FLAG == 0 {
95+
return false;
96+
}
97+
}
98+
99+
// SAFETY: We have already checked that rdrand is available.
100+
unsafe { self_test() }
70101
}
71102

72103
pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
73-
if !is_rdrand_supported() {
104+
static RDRAND_GOOD: LazyBool = LazyBool::new();
105+
if !RDRAND_GOOD.unsync_init(is_rdrand_good) {
74106
return Err(Error::NO_RDRAND);
75107
}
76-
77-
// SAFETY: After this point, rdrand is supported, so calling the rdrand
78-
// functions is not undefined behavior.
79-
unsafe { rdrand_exact(dest) }
108+
// SAFETY: After this point, we know rdrand is supported.
109+
unsafe { rdrand_exact(dest) }.ok_or(Error::FAILED_RDRAND)
80110
}
81111

112+
// TODO: make this function safe when we have feature(target_feature_11)
82113
#[target_feature(enable = "rdrand")]
83-
unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
114+
unsafe fn rdrand_exact(dest: &mut [MaybeUninit<u8>]) -> Option<()> {
84115
// We use chunks_exact_mut instead of chunks_mut as it allows almost all
85116
// calls to memcpy to be elided by the compiler.
86-
let mut chunks = dest.chunks_exact_mut(WORD_SIZE);
117+
let mut chunks = dest.chunks_exact_mut(size_of::<usize>());
87118
for chunk in chunks.by_ref() {
88-
chunk.copy_from_slice(slice_as_uninit(&rdrand()?));
119+
let src = rdrand()?.to_ne_bytes();
120+
chunk.copy_from_slice(slice_as_uninit(&src));
89121
}
90122

91123
let tail = chunks.into_remainder();
92124
let n = tail.len();
93125
if n > 0 {
94-
tail.copy_from_slice(slice_as_uninit(&rdrand()?[..n]));
126+
let src = rdrand()?.to_ne_bytes();
127+
tail.copy_from_slice(slice_as_uninit(&src[..n]));
95128
}
96-
Ok(())
129+
Some(())
97130
}

0 commit comments

Comments
 (0)
Please sign in to comment.