22
22
//! [`RtlGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
23
23
//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
24
24
//! [Pseudo-handle]: https://docs.microsoft.com/en-us/windows/win32/seccng/cng-algorithm-pseudo-handles
25
- use crate :: io;
26
25
use crate :: mem;
27
26
use crate :: ptr;
28
27
use crate :: sys:: c;
@@ -34,35 +33,94 @@ use crate::sys::c;
34
33
/// [`HashMap`]: crate::collections::HashMap
35
34
/// [`RandomState`]: crate::collections::hash_map::RandomState
36
35
pub fn hashmap_random_keys ( ) -> ( u64 , u64 ) {
37
- let mut v = ( 0 , 0 ) ;
38
- let ret = unsafe {
39
- let size = mem:: size_of_val ( & v) . try_into ( ) . unwrap ( ) ;
40
- c:: BCryptGenRandom (
41
- // BCRYPT_RNG_ALG_HANDLE is only supported in Windows 10+.
42
- // So for Windows 8.1 and Windows 7 we'll need a fallback when this fails.
43
- ptr:: invalid_mut ( c:: BCRYPT_RNG_ALG_HANDLE ) ,
44
- ptr:: addr_of_mut!( v) . cast ( ) ,
45
- size,
46
- 0 ,
47
- )
48
- } ;
49
- if ret != 0 { fallback_rng ( ) } else { v }
36
+ Rng :: open ( ) . and_then ( |rng| rng. gen_random_keys ( ) ) . unwrap_or_else ( fallback_rng)
37
+ }
38
+
39
+ struct Rng ( c:: BCRYPT_ALG_HANDLE ) ;
40
+ impl Rng {
41
+ #[ cfg( miri) ]
42
+ fn open ( ) -> Result < Self , c:: NTSTATUS > {
43
+ const BCRYPT_RNG_ALG_HANDLE : c:: BCRYPT_ALG_HANDLE = ptr:: invalid_mut ( 0x81 ) ;
44
+ let _ = (
45
+ c:: BCryptOpenAlgorithmProvider ,
46
+ c:: BCryptCloseAlgorithmProvider ,
47
+ c:: BCRYPT_RNG_ALGORITHM ,
48
+ c:: STATUS_NOT_SUPPORTED ,
49
+ ) ;
50
+ Ok ( Self ( BCRYPT_RNG_ALG_HANDLE ) )
51
+ }
52
+ #[ cfg( not( miri) ) ]
53
+ // Open a handle to the RNG algorithm.
54
+ fn open ( ) -> Result < Self , c:: NTSTATUS > {
55
+ use crate :: sync:: atomic:: AtomicPtr ;
56
+ use crate :: sync:: atomic:: Ordering :: { Acquire , Release } ;
57
+ const ERROR_VALUE : c:: LPVOID = ptr:: invalid_mut ( usize:: MAX ) ;
58
+
59
+ // An atomic is used so we don't need to reopen the handle every time.
60
+ static HANDLE : AtomicPtr < crate :: ffi:: c_void > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
61
+
62
+ let mut handle = HANDLE . load ( Acquire ) ;
63
+ // We use a sentinel value to designate an error occurred last time.
64
+ if handle == ERROR_VALUE {
65
+ Err ( c:: STATUS_NOT_SUPPORTED )
66
+ } else if handle. is_null ( ) {
67
+ let status = unsafe {
68
+ c:: BCryptOpenAlgorithmProvider (
69
+ & mut handle,
70
+ c:: BCRYPT_RNG_ALGORITHM . as_ptr ( ) ,
71
+ ptr:: null ( ) ,
72
+ 0 ,
73
+ )
74
+ } ;
75
+ if c:: nt_success ( status) {
76
+ // If another thread opens a handle first then use that handle instead.
77
+ let result = HANDLE . compare_exchange ( ptr:: null_mut ( ) , handle, Release , Acquire ) ;
78
+ if let Err ( previous_handle) = result {
79
+ // Close our handle and return the previous one.
80
+ unsafe { c:: BCryptCloseAlgorithmProvider ( handle, 0 ) } ;
81
+ handle = previous_handle;
82
+ }
83
+ Ok ( Self ( handle) )
84
+ } else {
85
+ HANDLE . store ( ERROR_VALUE , Release ) ;
86
+ Err ( status)
87
+ }
88
+ } else {
89
+ Ok ( Self ( handle) )
90
+ }
91
+ }
92
+
93
+ fn gen_random_keys ( self ) -> Result < ( u64 , u64 ) , c:: NTSTATUS > {
94
+ let mut v = ( 0 , 0 ) ;
95
+ let status = unsafe {
96
+ let size = mem:: size_of_val ( & v) . try_into ( ) . unwrap ( ) ;
97
+ c:: BCryptGenRandom ( self . 0 , ptr:: addr_of_mut!( v) . cast ( ) , size, 0 )
98
+ } ;
99
+ if c:: nt_success ( status) { Ok ( v) } else { Err ( status) }
100
+ }
50
101
}
51
102
52
103
/// Generate random numbers using the fallback RNG function (RtlGenRandom)
53
104
#[ cfg( not( target_vendor = "uwp" ) ) ]
54
105
#[ inline( never) ]
55
- fn fallback_rng ( ) -> ( u64 , u64 ) {
106
+ fn fallback_rng ( rng_status : c :: NTSTATUS ) -> ( u64 , u64 ) {
56
107
let mut v = ( 0 , 0 ) ;
57
108
let ret =
58
109
unsafe { c:: RtlGenRandom ( & mut v as * mut _ as * mut u8 , mem:: size_of_val ( & v) as c:: ULONG ) } ;
59
110
60
- if ret != 0 { v } else { panic ! ( "fallback RNG broken: {}" , io:: Error :: last_os_error( ) ) }
111
+ if ret != 0 {
112
+ v
113
+ } else {
114
+ panic ! (
115
+ "RNG broken: {rng_status:#x}, fallback RNG broken: {}" ,
116
+ crate :: io:: Error :: last_os_error( )
117
+ )
118
+ }
61
119
}
62
120
63
121
/// We can't use RtlGenRandom with UWP, so there is no fallback
64
122
#[ cfg( target_vendor = "uwp" ) ]
65
123
#[ inline( never) ]
66
- fn fallback_rng ( ) -> ( u64 , u64 ) {
67
- panic ! ( "fallback RNG broken: RtlGenRandom() not supported on UWP" ) ;
124
+ fn fallback_rng ( rng_status : c :: NTSTATUS ) -> ( u64 , u64 ) {
125
+ panic ! ( "RNG broken: {rng_status:#x} fallback RNG broken: RtlGenRandom() not supported on UWP" ) ;
68
126
}
0 commit comments