7
7
// except according to those terms.
8
8
9
9
//! 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} ;
11
12
use crate :: Error ;
13
+ use core:: sync:: atomic:: { AtomicUsize , Ordering :: Relaxed } ;
12
14
13
15
#[ cfg( target_os = "redox" ) ]
14
16
const FILE_PATH : & str = "rand:\0 " ;
@@ -21,10 +23,21 @@ const FILE_PATH: &str = "rand:\0";
21
23
target_os = "illumos"
22
24
) ) ]
23
25
const FILE_PATH : & str = "/dev/random\0 " ;
26
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
27
+ const FILE_PATH : & str = "/dev/urandom\0 " ;
24
28
25
29
pub 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) ?;
28
41
let read = |buf : & mut [ u8 ] | unsafe { libc:: read ( fd, buf. as_mut_ptr ( ) as * mut _ , buf. len ( ) ) } ;
29
42
30
43
if cfg ! ( target_os = "emscripten" ) {
@@ -38,36 +51,78 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
38
51
Ok ( ( ) )
39
52
}
40
53
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
+ }
50
61
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 ) ,
67
71
}
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
+ }
71
124
}
125
+ // We either hard failed, or poll() returned the wrong pfd.
126
+ return LazyUsize :: UNINIT ;
72
127
}
73
128
}
0 commit comments