@@ -12,141 +12,55 @@ use core::{
12
12
13
13
/// For all platforms, we use `/dev/urandom` rather than `/dev/random`.
14
14
/// For more information see the linked man pages in lib.rs.
15
- /// - On Linux, "/dev/urandom is preferred and sufficient in all use cases".
16
15
/// - On Redox, only /dev/urandom is provided.
17
16
/// - On AIX, /dev/urandom will "provide cryptographically secure output".
18
17
/// - On Haiku and QNX Neutrino they are identical.
19
18
const FILE_PATH : & [ u8 ] = b"/dev/urandom\0 " ;
20
19
21
- // Do not inline this when it is the fallback implementation, but don't mark it
22
- // `#[cold]` because it is hot when it is actually used.
23
- #[ cfg_attr( any( target_os = "android" , target_os = "linux" ) , inline( never) ) ]
20
+ // std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor.
21
+ const FD_UNINIT : libc:: c_int = -1 ;
22
+
23
+ // In theory `libc::c_int` could be something other than `i32`, but for the
24
+ // targets we currently support that use `use_file`, it is always `i32`.
25
+ // If/when we add support for a target where that isn't the case, we may
26
+ // need to use a different atomic type or make other accomodations. The
27
+ // compiler will let us know if/when that is the case, because the
28
+ // `FD.store(fd)` would fail to compile.
29
+ //
30
+ // The opening of the file, by libc/libstd/etc. may write some unknown
31
+ // state into in-process memory. (Such state may include some sanitizer
32
+ // bookkeeping, or we might be operating in a unikernal-like environment
33
+ // where all the "kernel" file descriptor bookkeeping is done in our
34
+ // process.) `get_fd_locked` stores into FD using `Ordering::Release` to
35
+ // ensure any such state is synchronized. `get_fd` loads from `FD` with
36
+ // `Ordering::Acquire` to synchronize with it.
37
+ static FD : AtomicI32 = AtomicI32 :: new ( FD_UNINIT ) ;
38
+
39
+ static FD_MUTEX : Mutex = Mutex :: new ( ) ;
40
+
24
41
pub fn getrandom_inner ( dest : & mut [ MaybeUninit < u8 > ] ) -> Result < ( ) , Error > {
25
- let fd = get_rng_fd ( ) ?;
42
+ let mut fd = FD . load ( Ordering :: Acquire ) ;
43
+ if fd == FD_UNINIT {
44
+ fd = open_or_wait ( ) ?;
45
+ }
26
46
sys_fill_exact ( dest, |buf| unsafe {
27
47
libc:: read ( fd, buf. as_mut_ptr ( ) . cast :: < c_void > ( ) , buf. len ( ) )
28
48
} )
29
49
}
30
50
31
- // Returns the file descriptor for the device file used to retrieve random
32
- // bytes. The file will be opened exactly once. All subsequent calls will
33
- // return the same file descriptor. This file descriptor is never closed.
34
- fn get_rng_fd ( ) -> Result < libc:: c_int , Error > {
35
- // std::os::fd::{BorrowedFd, OwnedFd} guarantee that -1 is not a valid file descriptor.
36
- const FD_UNINIT : libc:: c_int = -1 ;
37
-
38
- // In theory `libc::c_int` could be something other than `i32`, but for the
39
- // targets we currently support that use `use_file`, it is always `i32`.
40
- // If/when we add support for a target where that isn't the case, we may
41
- // need to use a different atomic type or make other accomodations. The
42
- // compiler will let us know if/when that is the case, because the
43
- // `FD.store(fd)` would fail to compile.
44
- //
45
- // The opening of the file, by libc/libstd/etc. may write some unknown
46
- // state into in-process memory. (Such state may include some sanitizer
47
- // bookkeeping, or we might be operating in a unikernal-like environment
48
- // where all the "kernel" file descriptor bookkeeping is done in our
49
- // process.) `get_fd_locked` stores into FD using `Ordering::Release` to
50
- // ensure any such state is synchronized. `get_fd` loads from `FD` with
51
- // `Ordering::Acquire` to synchronize with it.
52
- static FD : AtomicI32 = AtomicI32 :: new ( FD_UNINIT ) ;
53
-
54
- fn get_fd ( ) -> Option < libc:: c_int > {
55
- match FD . load ( Ordering :: Acquire ) {
56
- FD_UNINIT => None ,
57
- val => Some ( val) ,
51
+ #[ cold]
52
+ 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
58
59
}
59
- }
60
-
61
- #[ cold]
62
- fn get_fd_locked ( ) -> Result < libc:: c_int , Error > {
63
- // This mutex is used to prevent multiple threads from opening file
64
- // descriptors concurrently, which could run into the limit on the
65
- // number of open file descriptors. Our goal is to have no more than one
66
- // file descriptor open, ever.
67
- //
68
- // SAFETY: We use the mutex only in this method, and we always unlock it
69
- // before returning, making sure we don't violate the pthread_mutex_t API.
70
- static MUTEX : Mutex = Mutex :: new ( ) ;
71
- unsafe { MUTEX . lock ( ) } ;
72
- let _guard = DropGuard ( || unsafe { MUTEX . unlock ( ) } ) ;
73
-
74
- if let Some ( fd) = get_fd ( ) {
75
- return Ok ( fd) ;
76
- }
77
-
78
- // On Linux, /dev/urandom might return insecure values.
79
- #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
80
- wait_until_rng_ready ( ) ?;
81
-
82
- let fd = open_readonly ( FILE_PATH ) ?;
83
- debug_assert ! ( fd != FD_UNINIT ) ;
84
- FD . store ( fd, Ordering :: Release ) ;
85
-
86
- Ok ( fd)
87
- }
88
-
89
- // Use double-checked locking to avoid acquiring the lock if possible.
90
- if let Some ( fd) = get_fd ( ) {
91
- Ok ( fd)
92
- } else {
93
- get_fd_locked ( )
94
- }
95
- }
96
-
97
- // Polls /dev/random to make sure it is ok to read from /dev/urandom.
98
- //
99
- // Polling avoids draining the estimated entropy from /dev/random;
100
- // short-lived processes reading even a single byte from /dev/random could
101
- // be problematic if they are being executed faster than entropy is being
102
- // collected.
103
- //
104
- // OTOH, reading a byte instead of polling is more compatible with
105
- // sandboxes that disallow `poll()` but which allow reading /dev/random,
106
- // e.g. sandboxes that assume that `poll()` is for network I/O. This way,
107
- // fewer applications will have to insert pre-sandbox-initialization logic.
108
- // Often (blocking) file I/O is not allowed in such early phases of an
109
- // application for performance and/or security reasons.
110
- //
111
- // It is hard to write a sandbox policy to support `libc::poll()` because
112
- // it may invoke the `poll`, `ppoll`, `ppoll_time64` (since Linux 5.1, with
113
- // newer versions of glibc), and/or (rarely, and probably only on ancient
114
- // systems) `select`. depending on the libc implementation (e.g. glibc vs
115
- // musl), libc version, potentially the kernel version at runtime, and/or
116
- // the target architecture.
117
- //
118
- // BoringSSL and libstd don't try to protect against insecure output from
119
- // `/dev/urandom'; they don't open `/dev/random` at all.
120
- //
121
- // OpenSSL uses `libc::select()` unless the `dev/random` file descriptor
122
- // is too large; if it is too large then it does what we do here.
123
- //
124
- // libsodium uses `libc::poll` similarly to this.
125
- #[ cfg( any( target_os = "android" , target_os = "linux" ) ) ]
126
- fn wait_until_rng_ready ( ) -> Result < ( ) , Error > {
127
- let fd = open_readonly ( b"/dev/random\0 " ) ?;
128
- let mut pfd = libc:: pollfd {
129
- fd,
130
- events : libc:: POLLIN ,
131
- revents : 0 ,
60
+ fd => fd,
132
61
} ;
133
- let _guard = DropGuard ( || unsafe {
134
- libc:: close ( fd) ;
135
- } ) ;
136
-
137
- loop {
138
- // A negative timeout means an infinite timeout.
139
- let res = unsafe { libc:: poll ( & mut pfd, 1 , -1 ) } ;
140
- if res >= 0 {
141
- debug_assert_eq ! ( res, 1 ) ; // We only used one fd, and cannot timeout.
142
- return Ok ( ( ) ) ;
143
- }
144
- let err = crate :: util_libc:: last_os_error ( ) ;
145
- match err. raw_os_error ( ) {
146
- Some ( libc:: EINTR ) | Some ( libc:: EAGAIN ) => continue ,
147
- _ => return Err ( err) ,
148
- }
149
- }
62
+ debug_assert ! ( fd >= 0 ) ;
63
+ Ok ( fd)
150
64
}
151
65
152
66
struct Mutex ( UnsafeCell < libc:: pthread_mutex_t > ) ;
@@ -155,22 +69,21 @@ impl Mutex {
155
69
const fn new ( ) -> Self {
156
70
Self ( UnsafeCell :: new ( libc:: PTHREAD_MUTEX_INITIALIZER ) )
157
71
}
158
- unsafe fn lock ( & self ) {
159
- let r = libc:: pthread_mutex_lock ( self . 0 . get ( ) ) ;
160
- debug_assert_eq ! ( r, 0 ) ;
161
- }
162
- unsafe fn unlock ( & self ) {
163
- let r = libc:: pthread_mutex_unlock ( self . 0 . get ( ) ) ;
72
+
73
+ fn lock ( & self ) -> MutexGuard < ' _ > {
74
+ let r = unsafe { libc:: pthread_mutex_lock ( self . 0 . get ( ) ) } ;
164
75
debug_assert_eq ! ( r, 0 ) ;
76
+ MutexGuard ( self )
165
77
}
166
78
}
167
79
168
80
unsafe impl Sync for Mutex { }
169
81
170
- struct DropGuard < F : FnMut ( ) > ( F ) ;
82
+ struct MutexGuard < ' a > ( & ' a Mutex ) ;
171
83
172
- impl < F : FnMut ( ) > Drop for DropGuard < F > {
84
+ impl < ' a > Drop for MutexGuard < ' a > {
173
85
fn drop ( & mut self ) {
174
- self . 0 ( )
86
+ let r = unsafe { libc:: pthread_mutex_unlock ( self . 0 . 0 . get ( ) ) } ;
87
+ debug_assert_eq ! ( r, 0 ) ;
175
88
}
176
89
}
0 commit comments