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,94 @@ 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+ #[ 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+ }
50101}
51102
52103/// Generate random numbers using the fallback RNG function (RtlGenRandom)
53104#[ cfg( not( target_vendor = "uwp" ) ) ]
54105#[ inline( never) ]
55- fn fallback_rng ( ) -> ( u64 , u64 ) {
106+ fn fallback_rng ( rng_status : c :: NTSTATUS ) -> ( u64 , u64 ) {
56107 let mut v = ( 0 , 0 ) ;
57108 let ret =
58109 unsafe { c:: RtlGenRandom ( & mut v as * mut _ as * mut u8 , mem:: size_of_val ( & v) as c:: ULONG ) } ;
59110
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+ }
61119}
62120
63121/// We can't use RtlGenRandom with UWP, so there is no fallback
64122#[ cfg( target_vendor = "uwp" ) ]
65123#[ 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" ) ;
68126}
0 commit comments