77// except according to those terms.
88
99//! Implementations that just need to read from a file
10- use crate :: util_libc:: { last_os_error, open_readonly, sys_fill_exact, LazyFd } ;
10+ use crate :: util:: LazyUsize ;
11+ use crate :: util_libc:: { last_os_error, open_readonly, sys_fill_exact} ;
1112use crate :: Error ;
13+ use core:: sync:: atomic:: { AtomicUsize , Ordering :: Relaxed } ;
1214
1315#[ cfg( target_os = "redox" ) ]
1416const FILE_PATH : & str = "rand:\0 " ;
@@ -21,10 +23,11 @@ const FILE_PATH: &str = "rand:\0";
2123 target_os = "illumos"
2224) ) ]
2325const FILE_PATH : & str = "/dev/random\0 " ;
26+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
27+ const FILE_PATH : & str = "/dev/urandom\0 " ;
2428
2529pub fn getrandom_inner ( dest : & mut [ u8 ] ) -> Result < ( ) , Error > {
26- static FD : LazyFd = LazyFd :: new ( ) ;
27- let fd = FD . init ( init_file) . ok_or_else ( last_os_error) ?;
30+ let fd = get_rng_fd ( ) ?;
2831 let read = |buf : & mut [ u8 ] | unsafe { libc:: read ( fd, buf. as_mut_ptr ( ) as * mut _ , buf. len ( ) ) } ;
2932
3033 if cfg ! ( target_os = "emscripten" ) {
@@ -38,36 +41,83 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
3841 Ok ( ( ) )
3942}
4043
41- cfg_if ! {
42- if #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ] {
43- fn init_file( ) -> Option <libc:: c_int> {
44- // Poll /dev/random to make sure it is ok to read from /dev/urandom.
45- let mut pfd = libc:: pollfd {
46- fd: unsafe { open_readonly( "/dev/random\0 " ) ? } ,
47- events: libc:: POLLIN ,
48- revents: 0 ,
49- } ;
44+ // Returns the field descriptor for the device file used to retrieve random
45+ // numbers. The file will be opened exactly once. All successful calls will
46+ // return the same file descriptor. This file descriptor is never closed.
47+ fn get_rng_fd ( ) -> Result < libc:: c_int , Error > {
48+ static FD : AtomicUsize = AtomicUsize :: new ( LazyUsize :: UNINIT ) ;
49+ fn get_fd ( ) -> Option < libc:: c_int > {
50+ match FD . load ( Relaxed ) {
51+ LazyUsize :: UNINIT => None ,
52+ val => Some ( val as libc:: c_int ) ,
53+ }
54+ }
55+
56+ // Use double-checked locking to avoid acquiring the lock if possible.
57+ if let Some ( fd) = get_fd ( ) {
58+ return Ok ( fd) ;
59+ }
60+
61+ // SAFETY: Only a raw pointer is created from MUTEX to pass to libc.
62+ static mut MUTEX : libc:: pthread_mutex_t = libc:: PTHREAD_MUTEX_INITIALIZER ;
63+ let r = unsafe { libc:: pthread_mutex_lock ( & mut MUTEX as * mut _ ) } ;
64+ debug_assert_eq ! ( r, 0 ) ;
65+ let _guard = DropGuard ( || {
66+ let r = unsafe { libc:: pthread_mutex_unlock ( & mut MUTEX as * mut _ ) } ;
67+ debug_assert_eq ! ( r, 0 ) ;
68+ } ) ;
69+
70+ if let Some ( fd) = get_fd ( ) {
71+ return Ok ( fd) ;
72+ }
73+
74+ // On Linux, /dev/urandom might return insecure values.
75+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
76+ wait_until_rng_ready ( ) ?;
77+
78+ let fd = unsafe { open_readonly ( FILE_PATH ) ? } ;
79+ // The fd always fits in a usize without conflicting with UNINIT.
80+ debug_assert ! ( fd >= 0 && ( fd as usize ) < LazyUsize :: UNINIT ) ;
81+ FD . store ( fd as usize , Relaxed ) ;
5082
51- let ret = loop {
52- // A negative timeout means an infinite timeout.
53- let res = unsafe { libc:: poll( & mut pfd, 1 , -1 ) } ;
54- if res == 1 {
55- break unsafe { open_readonly( "/dev/urandom\0 " ) } ;
56- } else if res < 0 {
57- let e = last_os_error( ) . raw_os_error( ) ;
58- if e == Some ( libc:: EINTR ) || e == Some ( libc:: EAGAIN ) {
59- continue ;
60- }
61- }
62- // We either hard failed, or poll() returned the wrong pfd.
63- break None ;
64- } ;
65- unsafe { libc:: close( pfd. fd) } ;
66- ret
83+ Ok ( fd)
84+ }
85+
86+ // Succeeds once /dev/urandom is safe to read from
87+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
88+ fn wait_until_rng_ready ( ) -> Result < ( ) , Error > {
89+ // Poll /dev/random to make sure it is ok to read from /dev/urandom.
90+ let fd = unsafe { open_readonly ( "/dev/random\0 " ) ? } ;
91+ let mut pfd = libc:: pollfd {
92+ fd,
93+ events : libc:: POLLIN ,
94+ revents : 0 ,
95+ } ;
96+ let _guard = DropGuard ( || unsafe {
97+ libc:: close ( fd) ;
98+ } ) ;
99+
100+ loop {
101+ // A negative timeout means an infinite timeout.
102+ let res = unsafe { libc:: poll ( & mut pfd, 1 , -1 ) } ;
103+ if res == 1 {
104+ return Ok ( ( ) ) ;
67105 }
68- } else {
69- fn init_file( ) -> Option <libc:: c_int> {
70- unsafe { open_readonly( FILE_PATH ) }
106+ let err = last_os_error ( ) ;
107+ if res >= 0 {
108+ return Err ( err) ; // poll() returned the wrong pfd
109+ }
110+ match err. raw_os_error ( ) {
111+ Some ( libc:: EINTR ) | Some ( libc:: EAGAIN ) => continue ,
112+ _ => return Err ( err) , // poll() hard failed
71113 }
72114 }
73115}
116+
117+ struct DropGuard < F : FnMut ( ) > ( F ) ;
118+
119+ impl < F : FnMut ( ) > Drop for DropGuard < F > {
120+ fn drop ( & mut self ) {
121+ self . 0 ( )
122+ }
123+ }
0 commit comments