1 | pub fn hashmap_random_keys() -> (u64, u64) { |
2 | const KEY_LEN: usize = core::mem::size_of::<u64>(); |
3 | |
4 | let mut v: [u8; 16] = [0u8; KEY_LEN * 2]; |
5 | imp::fill_bytes(&mut v); |
6 | |
7 | let key1: [u8; 8] = v[0..KEY_LEN].try_into().unwrap(); |
8 | let key2: [u8; 8] = v[KEY_LEN..].try_into().unwrap(); |
9 | |
10 | (u64::from_ne_bytes(key1), u64::from_ne_bytes(key2)) |
11 | } |
12 | |
13 | #[cfg (all( |
14 | unix, |
15 | not(target_os = "macos" ), |
16 | not(target_os = "ios" ), |
17 | not(target_os = "tvos" ), |
18 | not(target_os = "watchos" ), |
19 | not(target_os = "visionos" ), |
20 | not(target_os = "openbsd" ), |
21 | not(target_os = "netbsd" ), |
22 | not(target_os = "fuchsia" ), |
23 | not(target_os = "redox" ), |
24 | not(target_os = "vxworks" ), |
25 | not(target_os = "emscripten" ), |
26 | not(target_os = "vita" ), |
27 | ))] |
28 | mod imp { |
29 | use crate::fs::File; |
30 | use crate::io::Read; |
31 | |
32 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
33 | use crate::sys::weak::syscall; |
34 | |
35 | #[cfg (any(target_os = "linux" , target_os = "android" ))] |
36 | fn getrandom(buf: &mut [u8]) -> libc::ssize_t { |
37 | use crate::sync::atomic::{AtomicBool, Ordering}; |
38 | use crate::sys::os::errno; |
39 | |
40 | // A weak symbol allows interposition, e.g. for perf measurements that want to |
41 | // disable randomness for consistency. Otherwise, we'll try a raw syscall. |
42 | // (`getrandom` was added in glibc 2.25, musl 1.1.20, android API level 28) |
43 | syscall! { |
44 | fn getrandom( |
45 | buffer: *mut libc::c_void, |
46 | length: libc::size_t, |
47 | flags: libc::c_uint |
48 | ) -> libc::ssize_t |
49 | } |
50 | |
51 | // This provides the best quality random numbers available at the given moment |
52 | // without ever blocking, and is preferable to falling back to /dev/urandom. |
53 | static GRND_INSECURE_AVAILABLE: AtomicBool = AtomicBool::new(true); |
54 | if GRND_INSECURE_AVAILABLE.load(Ordering::Relaxed) { |
55 | let ret = unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_INSECURE) }; |
56 | if ret == -1 && errno() as libc::c_int == libc::EINVAL { |
57 | GRND_INSECURE_AVAILABLE.store(false, Ordering::Relaxed); |
58 | } else { |
59 | return ret; |
60 | } |
61 | } |
62 | |
63 | unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), libc::GRND_NONBLOCK) } |
64 | } |
65 | |
66 | #[cfg (any(target_os = "espidf" , target_os = "horizon" , target_os = "freebsd" , netbsd10))] |
67 | fn getrandom(buf: &mut [u8]) -> libc::ssize_t { |
68 | unsafe { libc::getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) } |
69 | } |
70 | |
71 | #[cfg (target_os = "dragonfly" )] |
72 | fn getrandom(buf: &mut [u8]) -> libc::ssize_t { |
73 | extern "C" { |
74 | fn getrandom( |
75 | buf: *mut libc::c_void, |
76 | buflen: libc::size_t, |
77 | flags: libc::c_uint, |
78 | ) -> libc::ssize_t; |
79 | } |
80 | unsafe { getrandom(buf.as_mut_ptr().cast(), buf.len(), 0) } |
81 | } |
82 | |
83 | #[cfg (not(any( |
84 | target_os = "linux" , |
85 | target_os = "android" , |
86 | target_os = "espidf" , |
87 | target_os = "horizon" , |
88 | target_os = "freebsd" , |
89 | target_os = "dragonfly" , |
90 | netbsd10 |
91 | )))] |
92 | fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool { |
93 | false |
94 | } |
95 | |
96 | #[cfg (any( |
97 | target_os = "linux" , |
98 | target_os = "android" , |
99 | target_os = "espidf" , |
100 | target_os = "horizon" , |
101 | target_os = "freebsd" , |
102 | target_os = "dragonfly" , |
103 | netbsd10 |
104 | ))] |
105 | fn getrandom_fill_bytes(v: &mut [u8]) -> bool { |
106 | use crate::sync::atomic::{AtomicBool, Ordering}; |
107 | use crate::sys::os::errno; |
108 | |
109 | static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false); |
110 | if GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) { |
111 | return false; |
112 | } |
113 | |
114 | let mut read = 0; |
115 | while read < v.len() { |
116 | let result = getrandom(&mut v[read..]); |
117 | if result == -1 { |
118 | let err = errno() as libc::c_int; |
119 | if err == libc::EINTR { |
120 | continue; |
121 | } else if err == libc::ENOSYS || err == libc::EPERM { |
122 | // Fall back to reading /dev/urandom if `getrandom` is not |
123 | // supported on the current kernel. |
124 | // |
125 | // Also fall back in case it is disabled by something like |
126 | // seccomp or inside of docker. |
127 | // |
128 | // If the `getrandom` syscall is not implemented in the current kernel version it should return an |
129 | // `ENOSYS` error. Docker also blocks the whole syscall inside unprivileged containers, and |
130 | // returns `EPERM` (instead of `ENOSYS`) when a program tries to invoke the syscall. Because of |
131 | // that we need to check for *both* `ENOSYS` and `EPERM`. |
132 | // |
133 | // Note that Docker's behavior is breaking other projects (notably glibc), so they're planning |
134 | // to update their filtering to return `ENOSYS` in a future release: |
135 | // |
136 | // https://github.com/moby/moby/issues/42680 |
137 | // |
138 | GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed); |
139 | return false; |
140 | } else if err == libc::EAGAIN { |
141 | return false; |
142 | } else { |
143 | panic!("unexpected getrandom error: {err}" ); |
144 | } |
145 | } else { |
146 | read += result as usize; |
147 | } |
148 | } |
149 | true |
150 | } |
151 | |
152 | pub fn fill_bytes(v: &mut [u8]) { |
153 | // getrandom_fill_bytes here can fail if getrandom() returns EAGAIN, |
154 | // meaning it would have blocked because the non-blocking pool (urandom) |
155 | // has not initialized in the kernel yet due to a lack of entropy. The |
156 | // fallback we do here is to avoid blocking applications which could |
157 | // depend on this call without ever knowing they do and don't have a |
158 | // work around. The PRNG of /dev/urandom will still be used but over a |
159 | // possibly predictable entropy pool. |
160 | if getrandom_fill_bytes(v) { |
161 | return; |
162 | } |
163 | |
164 | // getrandom failed because it is permanently or temporarily (because |
165 | // of missing entropy) unavailable. Open /dev/urandom, read from it, |
166 | // and close it again. |
167 | let mut file = File::open("/dev/urandom" ).expect("failed to open /dev/urandom" ); |
168 | file.read_exact(v).expect("failed to read /dev/urandom" ) |
169 | } |
170 | } |
171 | |
172 | #[cfg (target_vendor = "apple" )] |
173 | mod imp { |
174 | use crate::io; |
175 | use libc::{c_int, c_void, size_t}; |
176 | |
177 | #[inline (always)] |
178 | fn random_failure() -> ! { |
179 | panic!("unexpected random generation error: {}" , io::Error::last_os_error()); |
180 | } |
181 | |
182 | #[cfg (target_os = "macos" )] |
183 | fn getentropy_fill_bytes(v: &mut [u8]) { |
184 | extern "C" { |
185 | fn getentropy(bytes: *mut c_void, count: size_t) -> c_int; |
186 | } |
187 | |
188 | // getentropy(2) permits a maximum buffer size of 256 bytes |
189 | for s in v.chunks_mut(256) { |
190 | let ret = unsafe { getentropy(s.as_mut_ptr().cast(), s.len()) }; |
191 | if ret == -1 { |
192 | random_failure() |
193 | } |
194 | } |
195 | } |
196 | |
197 | #[cfg (not(target_os = "macos" ))] |
198 | fn ccrandom_fill_bytes(v: &mut [u8]) { |
199 | extern "C" { |
200 | fn CCRandomGenerateBytes(bytes: *mut c_void, count: size_t) -> c_int; |
201 | } |
202 | |
203 | let ret = unsafe { CCRandomGenerateBytes(v.as_mut_ptr().cast(), v.len()) }; |
204 | if ret == -1 { |
205 | random_failure() |
206 | } |
207 | } |
208 | |
209 | pub fn fill_bytes(v: &mut [u8]) { |
210 | // All supported versions of macOS (10.12+) support getentropy. |
211 | // |
212 | // `getentropy` is measurably faster (via Divan) then the other alternatives so its preferred |
213 | // when usable. |
214 | #[cfg (target_os = "macos" )] |
215 | getentropy_fill_bytes(v); |
216 | |
217 | // On Apple platforms, `CCRandomGenerateBytes` and `SecRandomCopyBytes` simply |
218 | // call into `CCRandomCopyBytes` with `kCCRandomDefault`. `CCRandomCopyBytes` |
219 | // manages a CSPRNG which is seeded from the kernel's CSPRNG and which runs on |
220 | // its own thread accessed via GCD. This seems needlessly heavyweight for our purposes |
221 | // so we only use it on non-Mac OSes where the better entrypoints are blocked. |
222 | // |
223 | // `CCRandomGenerateBytes` is used instead of `SecRandomCopyBytes` because the former is accessible |
224 | // via `libSystem` (libc) while the other needs to link to `Security.framework`. |
225 | // |
226 | // Note that while `getentropy` has a available attribute in the macOS headers, the lack |
227 | // of a header in the iOS (and others) SDK means that its can cause app store rejections. |
228 | // Just use `CCRandomGenerateBytes` instead. |
229 | #[cfg (not(target_os = "macos" ))] |
230 | ccrandom_fill_bytes(v); |
231 | } |
232 | } |
233 | |
234 | #[cfg (any(target_os = "openbsd" , target_os = "emscripten" , target_os = "vita" ))] |
235 | mod imp { |
236 | use crate::sys::os::errno; |
237 | |
238 | pub fn fill_bytes(v: &mut [u8]) { |
239 | // getentropy(2) permits a maximum buffer size of 256 bytes |
240 | for s in v.chunks_mut(256) { |
241 | let ret = unsafe { libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len()) }; |
242 | if ret == -1 { |
243 | panic!("unexpected getentropy error: {}" , errno()); |
244 | } |
245 | } |
246 | } |
247 | } |
248 | |
249 | // FIXME: once the 10.x release becomes the minimum, this can be dropped for simplification. |
250 | #[cfg (all(target_os = "netbsd" , not(netbsd10)))] |
251 | mod imp { |
252 | use crate::ptr; |
253 | |
254 | pub fn fill_bytes(v: &mut [u8]) { |
255 | let mib = [libc::CTL_KERN, libc::KERN_ARND]; |
256 | // kern.arandom permits a maximum buffer size of 256 bytes |
257 | for s in v.chunks_mut(256) { |
258 | let mut s_len = s.len(); |
259 | let ret = unsafe { |
260 | libc::sysctl( |
261 | mib.as_ptr(), |
262 | mib.len() as libc::c_uint, |
263 | s.as_mut_ptr() as *mut _, |
264 | &mut s_len, |
265 | ptr::null(), |
266 | 0, |
267 | ) |
268 | }; |
269 | if ret == -1 || s_len != s.len() { |
270 | panic!( |
271 | "kern.arandom sysctl failed! (returned {}, s.len() {}, oldlenp {})" , |
272 | ret, |
273 | s.len(), |
274 | s_len |
275 | ); |
276 | } |
277 | } |
278 | } |
279 | } |
280 | |
281 | #[cfg (target_os = "fuchsia" )] |
282 | mod imp { |
283 | #[link (name = "zircon" )] |
284 | extern "C" { |
285 | fn zx_cprng_draw(buffer: *mut u8, len: usize); |
286 | } |
287 | |
288 | pub fn fill_bytes(v: &mut [u8]) { |
289 | unsafe { zx_cprng_draw(v.as_mut_ptr(), v.len()) } |
290 | } |
291 | } |
292 | |
293 | #[cfg (target_os = "redox" )] |
294 | mod imp { |
295 | use crate::fs::File; |
296 | use crate::io::Read; |
297 | |
298 | pub fn fill_bytes(v: &mut [u8]) { |
299 | // Open rand:, read from it, and close it again. |
300 | let mut file = File::open("rand:" ).expect("failed to open rand:" ); |
301 | file.read_exact(v).expect("failed to read rand:" ) |
302 | } |
303 | } |
304 | |
305 | #[cfg (target_os = "vxworks" )] |
306 | mod imp { |
307 | use crate::io; |
308 | use core::sync::atomic::{AtomicBool, Ordering::Relaxed}; |
309 | |
310 | pub fn fill_bytes(v: &mut [u8]) { |
311 | static RNG_INIT: AtomicBool = AtomicBool::new(false); |
312 | while !RNG_INIT.load(Relaxed) { |
313 | let ret = unsafe { libc::randSecure() }; |
314 | if ret < 0 { |
315 | panic!("couldn't generate random bytes: {}" , io::Error::last_os_error()); |
316 | } else if ret > 0 { |
317 | RNG_INIT.store(true, Relaxed); |
318 | break; |
319 | } |
320 | unsafe { libc::usleep(10) }; |
321 | } |
322 | let ret = unsafe { |
323 | libc::randABytes(v.as_mut_ptr() as *mut libc::c_uchar, v.len() as libc::c_int) |
324 | }; |
325 | if ret < 0 { |
326 | panic!("couldn't generate random bytes: {}" , io::Error::last_os_error()); |
327 | } |
328 | } |
329 | } |
330 | |