@@ -45,26 +45,66 @@ compile_error!(
4545 "SGX targets require 'rdrand' target feature. Enable by using -C target-feature=+rdrand."
4646) ;
4747
48- #[ cfg( target_feature = "rdrand" ) ]
49- fn is_rdrand_supported ( ) -> bool {
50- true
48+ // Run a small self-test to make sure we aren't repeating values
49+ // Adapted from Linux's test in arch/x86/kernel/cpu/rdrand.c
50+ // Fails with probability < 2^(-90) on 32-bit systems
51+ #[ target_feature( enable = "rdrand" ) ]
52+ unsafe fn self_test ( ) -> bool {
53+ // On AMD, RDRAND returns usize::MAX on failure, count it as a collision.
54+ let mut prev = usize:: MAX ;
55+ let mut fails = 0 ;
56+ for _ in 0 ..8 {
57+ match rdrand ( ) {
58+ Some ( val) if val == prev => fails += 1 ,
59+ Some ( val) => prev = val,
60+ None => return false ,
61+ } ;
62+ }
63+ fails <= 2
5164}
5265
53- // TODO use is_x86_feature_detected!("rdrand") when that works in core. See:
54- // https://github.com/rust-lang-nursery/stdsimd/issues/464
55- #[ cfg( not( target_feature = "rdrand" ) ) ]
56- fn is_rdrand_supported ( ) -> bool {
57- use crate :: util:: LazyBool ;
66+ fn is_rdrand_good ( ) -> bool {
67+ #[ cfg( not( target_feature = "rdrand" ) ) ]
68+ {
69+ // SAFETY: All Rust x86 targets are new enough to have CPUID, and we
70+ // check that leaf 1 is supported before using it.
71+ let cpuid0 = unsafe { arch:: __cpuid ( 0 ) } ;
72+ if cpuid0. eax < 1 {
73+ return false ;
74+ }
75+ let cpuid1 = unsafe { arch:: __cpuid ( 1 ) } ;
76+
77+ let vendor_id = [
78+ cpuid0. ebx . to_le_bytes ( ) ,
79+ cpuid0. edx . to_le_bytes ( ) ,
80+ cpuid0. ecx . to_le_bytes ( ) ,
81+ ] ;
82+ if vendor_id == [ * b"Auth" , * b"enti" , * b"cAMD" ] {
83+ let mut family = ( cpuid1. eax >> 8 ) & 0xF ;
84+ if family == 0xF {
85+ family += ( cpuid1. eax >> 20 ) & 0xFF ;
86+ }
87+ // AMD CPUs families before 17h (Zen) sometimes fail to set CF when
88+ // RDRAND fails after suspend. Don't use RDRAND on those families.
89+ // See https://bugzilla.redhat.com/show_bug.cgi?id=1150286
90+ if family < 0x17 {
91+ return false ;
92+ }
93+ }
94+
95+ const RDRAND_FLAG : u32 = 1 << 30 ;
96+ if cpuid1. ecx & RDRAND_FLAG == 0 {
97+ return false ;
98+ }
99+ }
58100
59- // SAFETY: All Rust x86 targets are new enough to have CPUID, and if CPUID
60- // is supported, CPUID leaf 1 is always supported.
61- const FLAG : u32 = 1 << 30 ;
62- static HAS_RDRAND : LazyBool = LazyBool :: new ( ) ;
63- HAS_RDRAND . unsync_init ( || unsafe { ( arch:: __cpuid ( 1 ) . ecx & FLAG ) != 0 } )
101+ // SAFETY: We have already checked that rdrand is available.
102+ unsafe { self_test ( ) }
64103}
65104
66105pub fn getrandom_inner ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
67- if !is_rdrand_supported ( ) {
106+ static RDRAND_GOOD : LazyBool = LazyBool :: new ( ) ;
107+ if !RDRAND_GOOD . unsync_init ( is_rdrand_good) {
68108 return Err ( Error :: NO_RDRAND ) ;
69109 }
70110 rdrand_exact ( dest) . ok_or ( Error :: FAILED_RDRAND )
0 commit comments