@@ -4,21 +4,22 @@ use crate::{
4
4
Error ,
5
5
} ;
6
6
use core:: {
7
- cell:: UnsafeCell ,
8
7
ffi:: c_void,
9
8
mem:: MaybeUninit ,
10
9
sync:: atomic:: { AtomicI32 , Ordering } ,
11
10
} ;
12
11
13
12
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
14
13
/// For more information see the linked man pages in lib.rs.
14
+ /// - On Linux, "/dev/urandom is preferred and sufficient in all use cases".
15
15
/// - On Redox, only /dev/urandom is provided.
16
16
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
17
17
/// - On Haiku and QNX Neutrino they are identical.
18
18
const FILE_PATH : & [ u8 ] = b"/dev/urandom\0 " ;
19
19
20
- // std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor .
20
+ // File descriptor is a "nonnegative integer", so we can safely use negative sentinel values .
21
21
const FD_UNINIT : libc:: c_int = -1 ;
22
+ const FD_ONGOING_INIT : libc:: c_int = -2 ;
22
23
23
24
// In theory `libc::c_int` could be something other than `i32`, but for the
24
25
// targets we currently support that use `use_file`, it is always `i32`.
@@ -36,11 +37,9 @@ const FD_UNINIT: libc::c_int = -1;
36
37
// `Ordering::Acquire` to synchronize with it.
37
38
static FD : AtomicI32 = AtomicI32 :: new ( FD_UNINIT ) ;
38
39
39
- static FD_MUTEX : Mutex = Mutex :: new ( ) ;
40
-
41
40
pub fn getrandom_inner ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
42
41
let mut fd = FD . load ( Ordering :: Acquire ) ;
43
- if fd == FD_UNINIT {
42
+ if fd == FD_UNINIT || fd == FD_ONGOING_INIT {
44
43
fd = open_or_wait ( ) ?;
45
44
}
46
45
sys_fill_exact ( dest, |buf| unsafe {
@@ -50,40 +49,142 @@ pub fn getrandom_inner(dest: &mut [MaybeUninit<u8>]) -> Result<(), Error> {
50
49
51
50
#[ cold]
52
51
fn open_or_wait ( ) -> Result < libc:: c_int , Error > {
53
- let _guard = FD_MUTEX . lock ( ) ;
54
- let fd = match FD . load ( Ordering :: Acquire ) {
55
- FD_UNINIT => {
56
- let fd = open_readonly ( FILE_PATH ) ?;
57
- FD . store ( fd, Ordering :: Release ) ;
58
- fd
52
+ loop {
53
+ match FD . load ( Ordering :: Acquire ) {
54
+ FD_UNINIT => {
55
+ let res = FD . compare_exchange_weak (
56
+ FD_UNINIT ,
57
+ FD_ONGOING_INIT ,
58
+ Ordering :: AcqRel ,
59
+ Ordering :: Relaxed ,
60
+ ) ;
61
+ if res. is_ok ( ) {
62
+ break ;
63
+ }
64
+ }
65
+ FD_ONGOING_INIT => sync:: wait ( ) ,
66
+ fd => return Ok ( fd) ,
59
67
}
60
- fd => fd,
68
+ }
69
+
70
+ let res = open_fd ( ) ;
71
+ let val = match res {
72
+ Ok ( fd) => fd,
73
+ Err ( _) => FD_UNINIT ,
61
74
} ;
75
+ FD . store ( val, Ordering :: Release ) ;
76
+ sync:: wake ( ) ;
77
+ res
78
+ }
79
+
80
+ fn open_fd ( ) -> Result < libc:: c_int , Error > {
81
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
82
+ sync:: wait_until_rng_ready ( ) ?;
83
+ let fd = open_readonly ( FILE_PATH ) ?;
62
84
debug_assert ! ( fd >= 0 ) ;
63
85
Ok ( fd)
64
86
}
65
87
66
- struct Mutex ( UnsafeCell < libc:: pthread_mutex_t > ) ;
67
-
68
- impl Mutex {
69
- const fn new ( ) -> Self {
70
- Self ( UnsafeCell :: new ( libc:: PTHREAD_MUTEX_INITIALIZER ) )
88
+ #[ cfg( not( any( target_os = "android" , target_os = "linux" ) ) ) ]
89
+ mod sync {
90
+ // On non-Linux targets the critical section only opens file,
91
+ // which should not block, so in the unlikely contended case,
92
+ // we can sleep-wait for the opening operation to finish.
93
+ pub ( super ) fn wait ( ) {
94
+ let rqtp = libc:: timespec {
95
+ tv_sec : 0 ,
96
+ tv_nsec : 1_000_000 ,
97
+ } ;
98
+ let mut rmtp = libc:: timespec {
99
+ tv_sec : 0 ,
100
+ tv_nsec : 0 ,
101
+ } ;
102
+ // We ignore return value since we do not care
103
+ // if sleep is interrupted
104
+ unsafe {
105
+ libc:: nanosleep ( & rqtp, & mut rmtp) ;
106
+ }
71
107
}
72
108
73
- fn lock ( & self ) -> MutexGuard < ' _ > {
74
- let r = unsafe { libc:: pthread_mutex_lock ( self . 0 . get ( ) ) } ;
75
- debug_assert_eq ! ( r, 0 ) ;
76
- MutexGuard ( self )
77
- }
109
+ pub ( super ) fn wake ( ) { }
78
110
}
79
111
80
- unsafe impl Sync for Mutex { }
112
+ #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
113
+ mod sync {
114
+ use super :: { Error , FD , FD_ONGOING_INIT } ;
115
+ use crate :: util_libc:: { last_os_error, open_readonly} ;
116
+
117
+ pub ( super ) fn wait ( ) {
118
+ let op = libc:: FUTEX_WAIT | libc:: FUTEX_PRIVATE_FLAG ;
119
+ let timeout_ptr = core:: ptr:: null :: < libc:: timespec > ( ) ;
120
+ let ret = unsafe { libc:: syscall ( libc:: SYS_futex , & FD , op, FD_ONGOING_INIT , timeout_ptr) } ;
121
+ // FUTEX_WAIT should return either 0 or EAGAIN error
122
+ debug_assert ! ( {
123
+ match ret {
124
+ 0 => true ,
125
+ -1 => last_os_error( ) . raw_os_error( ) == Some ( libc:: EAGAIN ) ,
126
+ _ => false ,
127
+ }
128
+ } ) ;
129
+ }
130
+
131
+ pub ( super ) fn wake ( ) {
132
+ let op = libc:: FUTEX_WAKE | libc:: FUTEX_PRIVATE_FLAG ;
133
+ let ret = unsafe { libc:: syscall ( libc:: SYS_futex , & FD , op, libc:: INT_MAX ) } ;
134
+ debug_assert ! ( ret >= 0 ) ;
135
+ }
81
136
82
- struct MutexGuard < ' a > ( & ' a Mutex ) ;
137
+ // Polls /dev/random to make sure it is ok to read from /dev/urandom.
138
+ //
139
+ // Polling avoids draining the estimated entropy from /dev/random;
140
+ // short-lived processes reading even a single byte from /dev/random could
141
+ // be problematic if they are being executed faster than entropy is being
142
+ // collected.
143
+ //
144
+ // OTOH, reading a byte instead of polling is more compatible with
145
+ // sandboxes that disallow `poll()` but which allow reading /dev/random,
146
+ // e.g. sandboxes that assume that `poll()` is for network I/O. This way,
147
+ // fewer applications will have to insert pre-sandbox-initialization logic.
148
+ // Often (blocking) file I/O is not allowed in such early phases of an
149
+ // application for performance and/or security reasons.
150
+ //
151
+ // It is hard to write a sandbox policy to support `libc::poll()` because
152
+ // it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with
153
+ // newer versions of glibc), and/or (rarely, and probably only on ancient
154
+ // systems) `select`. depending on the libc implementation (e.g. glibc vs
155
+ // musl), libc version, potentially the kernel version at runtime, and/or
156
+ // the target architecture.
157
+ //
158
+ // BoringSSL and libstd don't try to protect against insecure output from
159
+ // `/dev/urandom'; they don't open `/dev/random` at all.
160
+ //
161
+ // OpenSSL uses `libc::select()` unless the `dev/random` file descriptor
162
+ // is too large; if it is too large then it does what we do here.
163
+ //
164
+ // libsodium uses `libc::poll` similarly to this.
165
+ pub ( super ) fn wait_until_rng_ready ( ) -> Result < ( ) , Error > {
166
+ let fd = open_readonly ( b"/dev/random\0 " ) ?;
167
+ let mut pfd = libc:: pollfd {
168
+ fd,
169
+ events : libc:: POLLIN ,
170
+ revents : 0 ,
171
+ } ;
83
172
84
- impl < ' a > Drop for MutexGuard < ' a > {
85
- fn drop ( & mut self ) {
86
- let r = unsafe { libc:: pthread_mutex_unlock ( self . 0 . 0 . get ( ) ) } ;
87
- debug_assert_eq ! ( r, 0 ) ;
173
+ let res = loop {
174
+ // A negative timeout means an infinite timeout.
175
+ let res = unsafe { libc:: poll ( & mut pfd, 1 , -1 ) } ;
176
+ if res >= 0 {
177
+ // We only used one fd, and cannot timeout.
178
+ debug_assert_eq ! ( res, 1 ) ;
179
+ break Ok ( ( ) ) ;
180
+ }
181
+ let err = last_os_error ( ) ;
182
+ match err. raw_os_error ( ) {
183
+ Some ( libc:: EINTR ) | Some ( libc:: EAGAIN ) => continue ,
184
+ _ => break Err ( err) ,
185
+ }
186
+ } ;
187
+ unsafe { libc:: close ( fd) } ;
188
+ res
88
189
}
89
190
}
0 commit comments