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
1314cfg_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.
2627const 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" ) ) ) ]
5042compile_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
72103pub 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