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:: { open_readonly, sys_fill_exact} ;
1112use crate :: Error ;
13+ use core:: cell:: UnsafeCell ;
14+ use core:: sync:: atomic:: { AtomicUsize , Ordering :: Relaxed } ;
1215
1316#[ cfg( target_os = "redox" ) ]
1417const FILE_PATH : & str = "rand:\0 " ;
@@ -21,10 +24,11 @@ const FILE_PATH: &str = "rand:\0";
2124 target_os = "illumos"
2225) ) ]
2326const FILE_PATH : & str = "/dev/random\0 " ;
27+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
28+ const FILE_PATH : & str = "/dev/urandom\0 " ;
2429
2530pub 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) ?;
31+ let fd = get_rng_fd ( ) ?;
2832 let read = |buf : & mut [ u8 ] | unsafe { libc:: read ( fd, buf. as_mut_ptr ( ) as * mut _ , buf. len ( ) ) } ;
2933
3034 if cfg ! ( target_os = "emscripten" ) {
@@ -38,36 +42,96 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
3842 Ok ( ( ) )
3943}
4044
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- } ;
50-
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
45+ // Returns the file descriptor for the device file used to retrieve random
46+ // bytes. The file will be opened exactly once. All successful calls will
47+ // return the same file descriptor. This file descriptor is never closed.
48+ fn get_rng_fd ( ) -> Result < libc:: c_int , Error > {
49+ static FD : AtomicUsize = AtomicUsize :: new ( LazyUsize :: UNINIT ) ;
50+ fn get_fd ( ) -> Option < libc:: c_int > {
51+ match FD . load ( Relaxed ) {
52+ LazyUsize :: UNINIT => None ,
53+ val => Some ( val as libc:: c_int ) ,
6754 }
68- } else {
69- fn init_file( ) -> Option <libc:: c_int> {
70- unsafe { open_readonly( FILE_PATH ) }
55+ }
56+
57+ // Use double-checked locking to avoid acquiring the lock if possible.
58+ if let Some ( fd) = get_fd ( ) {
59+ return Ok ( fd) ;
60+ }
61+
62+ // SAFETY: We use the mutex only in this method, and we always unlock it
63+ // before returning, making sure we don't violate the pthread_mutex_t API.
64+ static MUTEX : Mutex = Mutex :: new ( ) ;
65+ unsafe { MUTEX . lock ( ) } ;
66+ let _guard = DropGuard ( || unsafe { MUTEX . unlock ( ) } ) ;
67+
68+ if let Some ( fd) = get_fd ( ) {
69+ return Ok ( fd) ;
70+ }
71+
72+ // On Linux, /dev/urandom might return insecure values.
73+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
74+ wait_until_rng_ready ( ) ?;
75+
76+ let fd = unsafe { open_readonly ( FILE_PATH ) ? } ;
77+ // The fd always fits in a usize without conflicting with UNINIT.
78+ debug_assert ! ( fd >= 0 && ( fd as usize ) < LazyUsize :: UNINIT ) ;
79+ FD . store ( fd as usize , Relaxed ) ;
80+
81+ Ok ( fd)
82+ }
83+
84+ // Succeeds once /dev/urandom is safe to read from
85+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
86+ fn wait_until_rng_ready ( ) -> Result < ( ) , Error > {
87+ // Poll /dev/random to make sure it is ok to read from /dev/urandom.
88+ let fd = unsafe { open_readonly ( "/dev/random\0 " ) ? } ;
89+ let mut pfd = libc:: pollfd {
90+ fd,
91+ events : libc:: POLLIN ,
92+ revents : 0 ,
93+ } ;
94+ let _guard = DropGuard ( || unsafe {
95+ libc:: close ( fd) ;
96+ } ) ;
97+
98+ loop {
99+ // A negative timeout means an infinite timeout.
100+ let res = unsafe { libc:: poll ( & mut pfd, 1 , -1 ) } ;
101+ if res >= 0 {
102+ assert_eq ! ( res, 1 ) ; // We only used one fd, and cannot timeout.
103+ return Ok ( ( ) ) ;
71104 }
105+ let err = crate :: util_libc:: last_os_error ( ) ;
106+ match err. raw_os_error ( ) {
107+ Some ( libc:: EINTR ) | Some ( libc:: EAGAIN ) => continue ,
108+ _ => return Err ( err) ,
109+ }
110+ }
111+ }
112+
113+ struct Mutex ( UnsafeCell < libc:: pthread_mutex_t > ) ;
114+
115+ impl Mutex {
116+ const fn new ( ) -> Self {
117+ Self ( UnsafeCell :: new ( libc:: PTHREAD_MUTEX_INITIALIZER ) )
118+ }
119+ unsafe fn lock ( & self ) {
120+ let r = libc:: pthread_mutex_lock ( self . 0 . get ( ) ) ;
121+ debug_assert_eq ! ( r, 0 ) ;
122+ }
123+ unsafe fn unlock ( & self ) {
124+ let r = libc:: pthread_mutex_unlock ( self . 0 . get ( ) ) ;
125+ debug_assert_eq ! ( r, 0 ) ;
126+ }
127+ }
128+
129+ unsafe impl Sync for Mutex { }
130+
131+ struct DropGuard < F : FnMut ( ) > ( F ) ;
132+
133+ impl < F : FnMut ( ) > Drop for DropGuard < F > {
134+ fn drop ( & mut self ) {
135+ self . 0 ( )
72136 }
73137}
0 commit comments