|
10 | 10 | extern crate libc; |
11 | 11 |
|
12 | 12 | use Error; |
13 | | -use utils::use_init; |
14 | 13 | use std::fs::File; |
15 | | -use std::io; |
| 14 | +use std::{io, mem, thread}; |
16 | 15 | use std::io::Read; |
17 | | -use std::cell::RefCell; |
18 | 16 | use std::num::NonZeroU32; |
19 | | -use std::sync::atomic::{AtomicBool, Ordering}; |
| 17 | +use std::sync::atomic::{AtomicUsize, Ordering}; |
| 18 | +use std::os::unix::io::{AsRawFd, FromRawFd}; |
20 | 19 |
|
21 | | -static RNG_INIT: AtomicBool = AtomicBool::new(false); |
| 20 | +// replace with AtomicU8 on stabilization and MSRV bump |
| 21 | +static RNG_STATE: AtomicUsize = AtomicUsize::new(0); |
| 22 | +// replace with AtomicI32 on stabilization and MSRV bump |
| 23 | +static RNG_FD: AtomicUsize = AtomicUsize::new(0); |
22 | 24 |
|
23 | | -enum RngSource { |
24 | | - GetRandom, |
25 | | - Device(File), |
| 25 | +const STATE_INIT_ONGOING: usize = 1 << 0; |
| 26 | +const STATE_USE_SYSCALL: usize = 1 << 1; |
| 27 | +const STATE_USE_FD: usize = 1 << 2; |
| 28 | + |
| 29 | +pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { |
| 30 | + let state = RNG_STATE.load(Ordering::Acquire); |
| 31 | + if state & STATE_USE_SYSCALL != 0 { |
| 32 | + use_syscall(dest) |
| 33 | + } else if state & STATE_USE_FD != 0 { |
| 34 | + use_fd(dest) |
| 35 | + } else { |
| 36 | + init_loop(dest) |
| 37 | + } |
26 | 38 | } |
27 | 39 |
|
28 | | -thread_local!( |
29 | | - static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None); |
30 | | -); |
| 40 | +fn init_loop(dest: &mut [u8]) -> Result<(), Error> { |
| 41 | + loop { |
| 42 | + let state = RNG_STATE.fetch_or(STATE_INIT_ONGOING, Ordering::AcqRel); |
31 | 43 |
|
32 | | -fn syscall_getrandom(dest: &mut [u8]) -> Result<(), io::Error> { |
33 | | - let ret = unsafe { |
34 | | - libc::syscall(libc::SYS_getrandom, dest.as_mut_ptr(), dest.len(), 0) |
35 | | - }; |
36 | | - if ret < 0 || (ret as usize) != dest.len() { |
37 | | - error!("Linux getrandom syscall failed with return value {}", ret); |
38 | | - return Err(io::Error::last_os_error()); |
| 44 | + if state & STATE_INIT_ONGOING != 0 { |
| 45 | + thread::yield_now(); |
| 46 | + continue; |
| 47 | + } |
| 48 | + return if state & STATE_USE_SYSCALL != 0 { |
| 49 | + use_syscall(dest) |
| 50 | + } else if state & STATE_USE_FD != 0 { |
| 51 | + use_fd(dest) |
| 52 | + } else { |
| 53 | + init(dest) |
| 54 | + }; |
39 | 55 | } |
40 | | - Ok(()) |
41 | 56 | } |
42 | 57 |
|
43 | | -pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { |
44 | | - RNG_SOURCE.with(|f| { |
45 | | - use_init(f, |
46 | | - || { |
47 | | - let s = if is_getrandom_available() { |
48 | | - RngSource::GetRandom |
49 | | - } else { |
50 | | - // read one byte from "/dev/random" to ensure that |
51 | | - // OS RNG has initialized |
52 | | - if !RNG_INIT.load(Ordering::Relaxed) { |
53 | | - File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; |
54 | | - RNG_INIT.store(true, Ordering::Relaxed) |
| 58 | +fn init(dest: &mut [u8]) -> Result<(), Error> { |
| 59 | + match use_syscall(&mut []) { |
| 60 | + Ok(()) => { |
| 61 | + RNG_STATE.store(STATE_USE_SYSCALL, Ordering::Release); |
| 62 | + use_syscall(dest) |
| 63 | + }, |
| 64 | + Err(err) if err.code().get() as i32 == libc::ENOSYS => { |
| 65 | + match init_fd() { |
| 66 | + Ok(fd) => { |
| 67 | + RNG_FD.store(fd as usize, Ordering::Release); |
| 68 | + RNG_STATE.store(STATE_USE_FD, Ordering::Release); |
| 69 | + use_fd(dest) |
| 70 | + }, |
| 71 | + Err(err) => { |
| 72 | + RNG_STATE.store(0, Ordering::Release); |
| 73 | + Err(err.into()) |
55 | 74 | } |
56 | | - RngSource::Device(File::open("/dev/urandom")?) |
57 | | - }; |
58 | | - Ok(s) |
59 | | - }, |f| { |
60 | | - match f { |
61 | | - RngSource::GetRandom => syscall_getrandom(dest), |
62 | | - RngSource::Device(f) => f.read_exact(dest), |
63 | | - }.map_err(From::from) |
64 | | - }) |
65 | | - }) |
| 75 | + } |
| 76 | + }, |
| 77 | + Err(err) => Err(err), |
| 78 | + } |
66 | 79 | } |
67 | 80 |
|
68 | | -fn is_getrandom_available() -> bool { |
69 | | - use std::sync::{Once, ONCE_INIT}; |
70 | | - |
71 | | - static CHECKER: Once = ONCE_INIT; |
72 | | - static AVAILABLE: AtomicBool = AtomicBool::new(false); |
| 81 | +fn init_fd() -> io::Result<i32> { |
| 82 | + File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; |
| 83 | + let f = File::open("/dev/urandom")?; |
| 84 | + let fd = f.as_raw_fd(); |
| 85 | + mem::forget(f); |
| 86 | + Ok(fd) |
| 87 | +} |
73 | 88 |
|
74 | | - CHECKER.call_once(|| { |
75 | | - let mut buf: [u8; 0] = []; |
76 | | - let available = match syscall_getrandom(&mut buf) { |
77 | | - Ok(()) => true, |
78 | | - Err(err) => err.raw_os_error() != Some(libc::ENOSYS), |
79 | | - }; |
80 | | - AVAILABLE.store(available, Ordering::Relaxed); |
81 | | - }); |
| 89 | +fn use_syscall(dest: &mut [u8]) -> Result<(), Error> { |
| 90 | + let ret = unsafe { |
| 91 | + libc::syscall(libc::SYS_getrandom, dest.as_mut_ptr(), dest.len(), 0) |
| 92 | + }; |
| 93 | + if ret < 0 || (ret as usize) != dest.len() { |
| 94 | + return Err(io::Error::last_os_error().into()); |
| 95 | + } |
| 96 | + Ok(()) |
| 97 | +} |
82 | 98 |
|
83 | | - AVAILABLE.load(Ordering::Relaxed) |
| 99 | +fn use_fd(dest: &mut [u8]) -> Result<(), Error> { |
| 100 | + unsafe { |
| 101 | + let fd = RNG_FD.load(Ordering::Acquire) as i32; |
| 102 | + let mut f = File::from_raw_fd(fd); |
| 103 | + f.read_exact(dest)?; |
| 104 | + mem::forget(f); |
| 105 | + } |
| 106 | + Ok(()) |
84 | 107 | } |
85 | 108 |
|
86 | 109 | #[inline(always)] |
|
0 commit comments