2222//! [`RtlGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/ntsecapi/nf-ntsecapi-rtlgenrandom
2323//! [`BCryptGenRandom`]: https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
2424//! [Pseudo-handle]: https://docs.microsoft.com/en-us/windows/win32/seccng/cng-algorithm-pseudo-handles
25- use crate :: io;
2625use crate :: mem;
2726use crate :: ptr;
2827use crate :: sys:: c;
@@ -34,35 +33,82 @@ use crate::sys::c;
3433/// [`HashMap`]: crate::collections::HashMap
3534/// [`RandomState`]: crate::collections::hash_map::RandomState
3635pub 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+ }
5089}
5190
5291/// Generate random numbers using the fallback RNG function (RtlGenRandom)
5392#[ cfg( not( target_vendor = "uwp" ) ) ]
5493#[ inline( never) ]
55- fn fallback_rng ( ) -> ( u64 , u64 ) {
94+ fn fallback_rng ( rng_status : c :: NTSTATUS ) -> ( u64 , u64 ) {
5695 let mut v = ( 0 , 0 ) ;
5796 let ret =
5897 unsafe { c:: RtlGenRandom ( & mut v as * mut _ as * mut u8 , mem:: size_of_val ( & v) as c:: ULONG ) } ;
5998
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+ }
61107}
62108
63109/// We can't use RtlGenRandom with UWP, so there is no fallback
64110#[ cfg( target_vendor = "uwp" ) ]
65111#[ 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" ) ;
68114}
0 commit comments