5
5
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6
6
// option. This file may not be copied, modified, or distributed
7
7
// 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 } ;
12
13
13
14
cfg_if ! {
14
15
if #[ cfg( target_arch = "x86_64" ) ] {
@@ -24,74 +25,106 @@ cfg_if! {
24
25
// Implementation Guide" - Section 5.2.1 and "Intel® 64 and IA-32 Architectures
25
26
// Software Developer’s Manual" - Volume 1 - Section 7.3.17.1.
26
27
const RETRY_LIMIT : usize = 10 ;
27
- const WORD_SIZE : usize = mem:: size_of :: < usize > ( ) ;
28
28
29
29
#[ target_feature( enable = "rdrand" ) ]
30
- unsafe fn rdrand ( ) -> Result < [ u8 ; WORD_SIZE ] , Error > {
30
+ unsafe fn rdrand ( ) -> Option < usize > {
31
31
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 ) ;
43
35
}
44
36
}
45
- Err ( Error :: FAILED_RDRAND )
37
+ None
46
38
}
47
39
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.
49
41
#[ cfg( all( target_env = "sgx" , not( target_feature = "rdrand" ) ) ) ]
50
42
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 ."
52
44
) ;
53
45
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
57
62
}
58
63
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 ) } ;
64
74
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 ( ) }
70
101
}
71
102
72
103
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) {
74
106
return Err ( Error :: NO_RDRAND ) ;
75
107
}
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 )
80
110
}
81
111
112
+ // TODO: make this function safe when we have feature(target_feature_11)
82
113
#[ 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 < ( ) > {
84
115
// We use chunks_exact_mut instead of chunks_mut as it allows almost all
85
116
// 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 > ( ) ) ;
87
118
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) ) ;
89
121
}
90
122
91
123
let tail = chunks. into_remainder ( ) ;
92
124
let n = tail. len ( ) ;
93
125
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] ) ) ;
95
128
}
96
- Ok ( ( ) )
129
+ Some ( ( ) )
97
130
}
0 commit comments