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:: { open_readonly, sys_fill_exact} ;
11
12
use crate :: Error ;
13
+ use core:: cell:: UnsafeCell ;
14
+ use core:: sync:: atomic:: { AtomicUsize , Ordering :: Relaxed } ;
12
15
13
16
#[ cfg( target_os = "redox" ) ]
14
17
const FILE_PATH : & str = "rand:\0 " ;
@@ -21,10 +24,11 @@ const FILE_PATH: &str = "rand:\0";
21
24
target_os = "illumos"
22
25
) ) ]
23
26
const FILE_PATH : & str = "/dev/random\0 " ;
27
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
28
+ const FILE_PATH : & str = "/dev/urandom\0 " ;
24
29
25
30
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) ?;
31
+ let fd = get_rng_fd ( ) ?;
28
32
let read = |buf : & mut [ u8 ] | unsafe { libc:: read ( fd, buf. as_mut_ptr ( ) as * mut _ , buf. len ( ) ) } ;
29
33
30
34
if cfg ! ( target_os = "emscripten" ) {
@@ -38,36 +42,96 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
38
42
Ok ( ( ) )
39
43
}
40
44
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 ) ,
67
54
}
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 ( ( ) ) ;
71
104
}
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 ( )
72
136
}
73
137
}
0 commit comments