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,21 @@ 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+ // On Linux, check that /dev/urandom will not return insecure values.
31+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
32+ {
33+ static RANDOM_INIT : LazyUsize = LazyUsize :: new ( ) ;
34+ if RANDOM_INIT . unsync_init ( random_init) == LazyUsize :: UNINIT {
35+ return Err ( last_os_error ( ) ) ;
36+ }
37+ }
38+
39+ static FD : RngFd = RngFd :: new ( ) ;
40+ let fd = FD . sync_init ( ) . ok_or_else ( last_os_error) ?;
2841 let read = |buf : & mut [ u8 ] | unsafe { libc:: read ( fd, buf. as_mut_ptr ( ) as * mut _ , buf. len ( ) ) } ;
2942
3043 if cfg ! ( target_os = "emscripten" ) {
@@ -38,36 +51,78 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
3851 Ok ( ( ) )
3952}
4053
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- } ;
54+ // Handle to the device file used to retrive random numbers
55+ struct RngFd ( AtomicUsize ) ;
56+
57+ impl RngFd {
58+ const fn new ( ) -> Self {
59+ Self ( AtomicUsize :: new ( LazyUsize :: UNINIT ) )
60+ }
5061
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
62+ // Initializes and returns the file descriptor. Similar to open_readonly(),
63+ // None is returned on failure. On success, this function will always return
64+ // the same file descriptor, even from different threads. Note that this
65+ // does not mean only one file is ever opened, just that one "wins".
66+ fn sync_init ( & self ) -> Option < libc:: c_int > {
67+ // Common and fast path with no contention. Don't wast time on CAS.
68+ match self . 0 . load ( Relaxed ) {
69+ LazyUsize :: UNINIT => { }
70+ current_val => return Some ( current_val as libc:: c_int ) ,
6771 }
68- } else {
69- fn init_file( ) -> Option <libc:: c_int> {
70- unsafe { open_readonly( FILE_PATH ) }
72+
73+ let fd = unsafe { open_readonly ( FILE_PATH ) ? } ;
74+ // The fd always fits in a usize without conflicting with UNINIT.
75+ debug_assert ! ( fd >= 0 && ( fd as usize ) < LazyUsize :: UNINIT ) ;
76+ let new_val = fd as usize ;
77+ // Relaxed ordering is fine, as we only have a single atomic variable.
78+ match self . 0 . compare_and_swap ( LazyUsize :: UNINIT , new_val, Relaxed ) {
79+ // We won the race and initilaized the fd, so we are done.
80+ LazyUsize :: UNINIT => Some ( fd) ,
81+ // Some other thread beat us to initilizing the fd. This is quite
82+ // rare as open(2) does not block when opening the random devices.
83+ // In this case, cleanup the unnecessary fd we opened.
84+ current_val => {
85+ unsafe { libc:: close ( fd) } ;
86+ Some ( current_val as libc:: c_int )
87+ }
88+ }
89+ }
90+ }
91+
92+ // Returns 0 if urandom is safe to read from, LazyUsize::UNINIT otherwise.
93+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
94+ fn random_init ( ) -> usize {
95+ // Make sure the temporary fd used for polling is always closed.
96+ struct FdGuard ( libc:: c_int ) ;
97+ impl Drop for FdGuard {
98+ fn drop ( & mut self ) {
99+ unsafe { libc:: close ( self . 0 ) } ;
100+ }
101+ }
102+
103+ // Poll /dev/random to make sure it is ok to read from /dev/urandom.
104+ let fd = match unsafe { open_readonly ( "/dev/random\0 " ) } {
105+ Some ( fd) => FdGuard ( fd) ,
106+ None => return LazyUsize :: UNINIT ,
107+ } ;
108+ let mut pfd = libc:: pollfd {
109+ fd : fd. 0 ,
110+ events : libc:: POLLIN ,
111+ revents : 0 ,
112+ } ;
113+
114+ loop {
115+ // A negative timeout means an infinite timeout.
116+ let res = unsafe { libc:: poll ( & mut pfd, 1 , -1 ) } ;
117+ if res == 1 {
118+ return 0 ;
119+ } else if res < 0 {
120+ let e = last_os_error ( ) . raw_os_error ( ) ;
121+ if e == Some ( libc:: EINTR ) || e == Some ( libc:: EAGAIN ) {
122+ continue ;
123+ }
71124 }
125+ // We either hard failed, or poll() returned the wrong pfd.
126+ return LazyUsize :: UNINIT ;
72127 }
73128}
0 commit comments