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,82 @@ 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
+ // Open a handle to the RNG algorithm.
42
+ fn open ( ) -> Result < Self , c:: NTSTATUS > {
43
+ use crate :: sync:: atomic:: AtomicPtr ;
44
+ use crate :: sync:: atomic:: Ordering :: { Acquire , Release } ;
45
+ const ERROR_VALUE : c:: LPVOID = ptr:: invalid_mut ( usize:: MAX ) ;
46
+
47
+ // An atomic is used so we don't need to reopen the handle every time.
48
+ static HANDLE : AtomicPtr < crate :: ffi:: c_void > = AtomicPtr :: new ( ptr:: null_mut ( ) ) ;
49
+
50
+ let mut handle = HANDLE . load ( Acquire ) ;
51
+ // We use a sentinel value to designate an error occurred last time.
52
+ if handle == ERROR_VALUE {
53
+ Err ( c:: STATUS_NOT_SUPPORTED )
54
+ } else if handle. is_null ( ) {
55
+ let status = unsafe {
56
+ c:: BCryptOpenAlgorithmProvider (
57
+ & mut handle,
58
+ c:: BCRYPT_RNG_ALGORITHM . as_ptr ( ) ,
59
+ ptr:: null ( ) ,
60
+ 0 ,
61
+ )
62
+ } ;
63
+ if c:: nt_success ( status) {
64
+ // If another thread opens a handle first then use that handle instead.
65
+ let result = HANDLE . compare_exchange ( ptr:: null_mut ( ) , handle, Release , Acquire ) ;
66
+ if let Err ( previous_handle) = result {
67
+ // Close our handle and return the previous one.
68
+ unsafe { c:: BCryptCloseAlgorithmProvider ( handle, 0 ) } ;
69
+ handle = previous_handle;
70
+ }
71
+ Ok ( Self ( handle) )
72
+ } else {
73
+ HANDLE . store ( ERROR_VALUE , Release ) ;
74
+ Err ( status)
75
+ }
76
+ } else {
77
+ Ok ( Self ( handle) )
78
+ }
79
+ }
80
+
81
+ fn gen_random_keys ( self ) -> Result < ( u64 , u64 ) , c:: NTSTATUS > {
82
+ let mut v = ( 0 , 0 ) ;
83
+ let status = unsafe {
84
+ let size = mem:: size_of_val ( & v) . try_into ( ) . unwrap ( ) ;
85
+ c:: BCryptGenRandom ( self . 0 , ptr:: addr_of_mut!( v) . cast ( ) , size, 0 )
86
+ } ;
87
+ if c:: nt_success ( status) { Ok ( v) } else { Err ( status) }
88
+ }
50
89
}
51
90
52
91
/// Generate random numbers using the fallback RNG function (RtlGenRandom)
53
92
#[ cfg( not( target_vendor = "uwp" ) ) ]
54
93
#[ inline( never) ]
55
- fn fallback_rng ( ) -> ( u64 , u64 ) {
94
+ fn fallback_rng ( rng_status : c :: NTSTATUS ) -> ( u64 , u64 ) {
56
95
let mut v = ( 0 , 0 ) ;
57
96
let ret =
58
97
unsafe { c:: RtlGenRandom ( & mut v as * mut _ as * mut u8 , mem:: size_of_val ( & v) as c:: ULONG ) } ;
59
98
60
- if ret != 0 { v } else { panic ! ( "fallback RNG broken: {}" , io:: Error :: last_os_error( ) ) }
99
+ if ret != 0 {
100
+ v
101
+ } else {
102
+ panic ! (
103
+ "RNG broken: {rng_status:#x}, fallback RNG broken: {}" ,
104
+ crate :: io:: Error :: last_os_error( )
105
+ )
106
+ }
61
107
}
62
108
63
109
/// We can't use RtlGenRandom with UWP, so there is no fallback
64
110
#[ cfg( target_vendor = "uwp" ) ]
65
111
#[ inline( never) ]
66
- fn fallback_rng ( ) -> ( u64 , u64 ) {
67
- panic ! ( "fallback RNG broken: RtlGenRandom() not supported on UWP" ) ;
112
+ fn fallback_rng ( rng_status : c :: NTSTATUS ) -> ( u64 , u64 ) {
113
+ panic ! ( "RNG broken: {rng_status:#x} fallback RNG broken: RtlGenRandom() not supported on UWP" ) ;
68
114
}
0 commit comments