diff --git a/Cargo.toml b/Cargo.toml index 7cb539da..92baaa23 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ log = { version = "0.4", optional = true } [target.'cfg(unix)'.dependencies] libc = "0.2.34" +lazy_static = "1.3.0" [target.'cfg(windows)'.dependencies] winapi = { version = "0.3.6", features = ["minwindef", "ntsecapi", "winnt"] } @@ -32,9 +33,13 @@ cloudabi = "0.0.3" [target.'cfg(fuchsia)'.dependencies] fuchsia-cprng = "0.1" +[target.'cfg(target_os = "redox")'.dependencies] +lazy_static = "1.3.0" + [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = { version = "0.2.29", optional = true } stdweb = { version = "0.4.9", optional = true } +lazy_static = "1.3.0" [target.wasm32-wasi.dependencies] libc = "0.2.54" diff --git a/src/lib.rs b/src/lib.rs index 32ccfdf2..9fa592b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,21 +132,6 @@ macro_rules! error { ($($x:tt)*) => () } #[cfg(target_arch = "wasm32")] extern crate std; -#[cfg(any( - target_os = "android", - target_os = "netbsd", - target_os = "solaris", - target_os = "illumos", - target_os = "redox", - target_os = "dragonfly", - target_os = "haiku", - target_os = "linux", - all( - target_arch = "wasm32", - not(target_os = "wasi") - ), -))] -mod utils; mod error; pub use crate::error::Error; @@ -172,6 +157,15 @@ macro_rules! mod_use { ))] mod error_impls; +// These targets read from a file as a fallback method. +#[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "solaris", + target_os = "illumos", +))] +mod use_file; + mod_use!(cfg(target_os = "android"), linux_android); mod_use!(cfg(target_os = "bitrig"), openbsd_bitrig); mod_use!(cfg(target_os = "cloudabi"), cloudabi); @@ -233,16 +227,12 @@ mod_use!( target_os = "redox", target_os = "solaris", all(target_arch = "x86_64", target_os = "uefi"), + target_os = "wasi", target_env = "sgx", windows, all( target_arch = "wasm32", - any( - target_os = "emscripten", - target_os = "wasi", - feature = "wasm-bindgen", - feature = "stdweb", - ), + any(feature = "wasm-bindgen", feature = "stdweb"), ), ))), dummy diff --git a/src/linux_android.rs b/src/linux_android.rs index bd967de7..f5825aca 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -9,24 +9,10 @@ //! Implementation for Linux / Android extern crate std; -use crate::Error; -use crate::utils::use_init; -use std::{thread_local, io::{self, Read}, fs::File}; -use core::cell::RefCell; +use crate::{use_file, Error}; use core::num::NonZeroU32; -use core::sync::atomic::{AtomicBool, Ordering}; - -// This flag tells getrandom() to return EAGAIN instead of blocking. -static RNG_INIT: AtomicBool = AtomicBool::new(false); - -enum RngSource { - GetRandom, - Device(File), -} - -thread_local!( - static RNG_SOURCE: RefCell> = RefCell::new(None); -); +use lazy_static::lazy_static; +use std::io; fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result { let flags = if block { 0 } else { libc::GRND_NONBLOCK }; @@ -45,52 +31,28 @@ fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result { } pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - RNG_SOURCE.with(|f| { - use_init(f, - || { - let s = if is_getrandom_available() { - RngSource::GetRandom - } else { - // read one byte from "/dev/random" to ensure that - // OS RNG has initialized - if !RNG_INIT.load(Ordering::Relaxed) { - File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; - RNG_INIT.store(true, Ordering::Relaxed) - } - RngSource::Device(File::open("/dev/urandom")?) - }; - Ok(s) - }, |f| { - match f { - RngSource::GetRandom => { - let mut start = 0; - while start < dest.len() { - start += syscall_getrandom(&mut dest[start..], true)?; - } - Ok(()) - } - RngSource::Device(f) => f.read_exact(dest).map_err(From::from), + lazy_static! { static ref HAS_GETRANDOM: bool = is_getrandom_available(); } + match *HAS_GETRANDOM { + true => { + let mut start = 0; + while start < dest.len() { + start += syscall_getrandom(&mut dest[start..], true)?; } - }) - }) + Ok(()) + }, + false => use_file::getrandom_inner(dest), + } } fn is_getrandom_available() -> bool { - use std::sync::{Once, ONCE_INIT}; - - static CHECKER: Once = ONCE_INIT; - static AVAILABLE: AtomicBool = AtomicBool::new(false); - - CHECKER.call_once(|| { - let mut buf: [u8; 0] = []; - let available = match syscall_getrandom(&mut buf, false) { - Ok(_) => true, - Err(err) => err.raw_os_error() != Some(libc::ENOSYS), - }; - AVAILABLE.store(available, Ordering::Relaxed); - }); - - AVAILABLE.load(Ordering::Relaxed) + match syscall_getrandom(&mut [], false) { + Err(err) => match err.raw_os_error() { + Some(libc::ENOSYS) => false, // No kernel support + Some(libc::EPERM) => false, // Blocked by seccomp + _ => true, + } + Ok(_) => true, + } } #[inline(always)] diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs index 5484dd0f..fdb270a5 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -19,26 +19,17 @@ //! libc::dlsym. extern crate std; -use crate::Error; -use crate::utils::use_init; -use std::{thread_local, io::{self, Read}, fs::File}; -use core::cell::RefCell; +use crate::{use_file, Error}; +use core::mem; use core::num::NonZeroU32; +use lazy_static::lazy_static; +use std::io; #[cfg(target_os = "illumos")] type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t; #[cfg(target_os = "solaris")] type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int; -enum RngSource { - GetRandom(GetRandomFn), - Device(File), -} - -thread_local!( - static RNG_SOURCE: RefCell> = RefCell::new(None); -); - fn libc_getrandom(rand: GetRandomFn, dest: &mut [u8]) -> Result<(), Error> { let ret = unsafe { rand(dest.as_mut_ptr(), dest.len(), 0) as libc::ssize_t }; @@ -51,51 +42,23 @@ fn libc_getrandom(rand: GetRandomFn, dest: &mut [u8]) -> Result<(), Error> { } pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { + lazy_static! { static ref GETRANDOM_FUNC: Option = fetch_getrandom(); } + // 256 bytes is the lowest common denominator across all the Solaris // derived platforms for atomically obtaining random data. - RNG_SOURCE.with(|f| { - use_init( - f, - || { - let s = match fetch_getrandom() { - Some(fptr) => RngSource::GetRandom(fptr), - None => RngSource::Device(File::open("/dev/random")?), - }; - Ok(s) - }, - |f| { - match f { - RngSource::GetRandom(rp) => { - for chunk in dest.chunks_mut(256) { - libc_getrandom(*rp, chunk)? - } - } - RngSource::Device(randf) => { - for chunk in dest.chunks_mut(256) { - randf.read_exact(chunk)? - } - } - }; - Ok(()) - }, - ) - }) + for chunk in dest.chunks_mut(256) { + match *GETRANDOM_FUNC { + Some(fptr) => libc_getrandom(fptr, chunk)?, + None => use_file::getrandom_inner(chunk)?, + }; + } + Ok(()) } fn fetch_getrandom() -> Option { - use std::mem; - use std::sync::atomic::{AtomicUsize, Ordering}; - - static FPTR: AtomicUsize = AtomicUsize::new(1); - - if FPTR.load(Ordering::SeqCst) == 1 { - let name = "getrandom\0"; - let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize }; - FPTR.store(addr, Ordering::SeqCst); - } - - let ptr = FPTR.load(Ordering::SeqCst); - unsafe { mem::transmute::>(ptr) } + let name = "getrandom\0"; + let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) }; + unsafe { mem::transmute(addr) } } #[inline(always)] diff --git a/src/use_file.rs b/src/use_file.rs index 404c13b2..df5d4457 100644 --- a/src/use_file.rs +++ b/src/use_file.rs @@ -6,31 +6,35 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. -//! Implementation for DragonFly / Haiku +//! Implementations that just need to read from a file extern crate std; use crate::Error; -use crate::utils::use_init; -use std::{thread_local, io::Read, fs::File}; -use core::cell::RefCell; use core::num::NonZeroU32; - -thread_local!(static RNG_FILE: RefCell> = RefCell::new(None)); +use lazy_static::lazy_static; +use std::{ + fs::File, + io::Read, + os::unix::io::{FromRawFd, IntoRawFd, RawFd}, +}; #[cfg(target_os = "redox")] const FILE_PATH: &str = "rand:"; -#[cfg(target_os = "netbsd")] +#[cfg(any(target_os = "android", target_os = "linux", target_os = "netbsd"))] const FILE_PATH: &str = "/dev/urandom"; -#[cfg(any(target_os = "dragonfly", target_os = "emscripten", target_os = "haiku"))] +#[cfg(any( + target_os = "dragonfly", + target_os = "emscripten", + target_os = "haiku", + target_os = "solaris", + target_os = "illumos" +))] const FILE_PATH: &str = "/dev/random"; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - RNG_FILE.with(|f| { - use_init(f, || init_file(), |f| use_file(f, dest)) - }) -} + lazy_static! { static ref RNG_FD: Result = init_file(); } + let mut f = unsafe { File::from_raw_fd((*RNG_FD)?) }; -fn use_file(f: &mut File, dest: &mut [u8]) -> Result<(), Error> { if cfg!(target_os = "emscripten") { // `Crypto.getRandomValues` documents `dest` should be at most 65536 bytes. for chunk in dest.chunks_mut(65536) { @@ -39,18 +43,17 @@ fn use_file(f: &mut File, dest: &mut [u8]) -> Result<(), Error> { } else { f.read_exact(dest)?; } - core::mem::forget(f); Ok(()) } -fn init_file() -> Result { - if cfg!(target_os = "netbsd") { +fn init_file() -> Result { + if FILE_PATH == "/dev/urandom" { // read one byte from "/dev/random" to ensure that OS RNG has initialized File::open("/dev/random")?.read_exact(&mut [0u8; 1])?; } - let f = File::open(FILE_PATH)?; - Ok(f) + Ok(File::open(FILE_PATH)?.into_raw_fd()) } #[inline(always)] +#[allow(dead_code)] pub fn error_msg_inner(_: NonZeroU32) -> Option<&'static str> { None } diff --git a/src/utils.rs b/src/utils.rs deleted file mode 100644 index 1d8f40a5..00000000 --- a/src/utils.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 Developers of the Rand project. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -use crate::Error; -use core::cell::RefCell; -use core::ops::DerefMut; - -/// If `f` contains `Some(T)` call `use_f` using contents of `f` as an argument, -/// otherwise initialize `f` value using `init_f`, store resulting value in `f` -/// and call `use_f`. -pub(crate) fn use_init(f: &RefCell>, init_f: F, mut use_f: U) - -> Result<(), Error> - where F: FnOnce() -> Result, U: FnMut(&mut T) -> Result<(), Error> -{ - let mut f = f.borrow_mut(); - let f: &mut Option = f.deref_mut(); - match f { - None => *f = Some(init_f()?), - _ => (), - } - - match f { - Some(f) => use_f(f), - None => unreachable!(), - } -} diff --git a/src/wasm32_bindgen.rs b/src/wasm32_bindgen.rs index 7c835383..cade00b6 100644 --- a/src/wasm32_bindgen.rs +++ b/src/wasm32_bindgen.rs @@ -7,8 +7,6 @@ // except according to those terms. //! Implementation for WASM via wasm-bindgen -extern crate std; - use core::cell::RefCell; use core::mem; use core::num::NonZeroU32; @@ -18,7 +16,6 @@ use wasm_bindgen::prelude::*; use crate::Error; use crate::error::CODE_PREFIX; -use crate::utils::use_init; const CODE_CRYPTO_UNDEF: u32 = CODE_PREFIX | 0x80; const CODE_GRV_UNDEF: u32 = CODE_PREFIX | 0x81; @@ -29,6 +26,8 @@ enum RngSource { Browser(BrowserCrypto), } +// JsValues are always per-thread, so we initialize RngSource for each thread. +// See: https://github.com/rustwasm/wasm-bindgen/pull/955 thread_local!( static RNG_SOURCE: RefCell> = RefCell::new(None); ); @@ -37,25 +36,27 @@ pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { assert_eq!(mem::size_of::(), 4); RNG_SOURCE.with(|f| { - use_init(f, getrandom_init, |source| { - match *source { - RngSource::Node(ref n) => n.random_fill_sync(dest), - RngSource::Browser(ref n) => { - // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues - // - // where it says: - // - // > A QuotaExceededError DOMException is thrown if the - // > requested length is greater than 65536 bytes. - for chunk in dest.chunks_mut(65536) { - n.get_random_values(chunk) - } + let mut source = f.borrow_mut(); + if source.is_none() { + *source = Some(getrandom_init()?); + } + + match source.as_ref().unwrap() { + RngSource::Node(n) => n.random_fill_sync(dest), + RngSource::Browser(n) => { + // see https://developer.mozilla.org/en-US/docs/Web/API/Crypto/getRandomValues + // + // where it says: + // + // > A QuotaExceededError DOMException is thrown if the + // > requested length is greater than 65536 bytes. + for chunk in dest.chunks_mut(65536) { + n.get_random_values(chunk) } } - Ok(()) - }) + }; + Ok(()) }) - } fn getrandom_init() -> Result { diff --git a/src/wasm32_stdweb.rs b/src/wasm32_stdweb.rs index 02f89aa0..28607239 100644 --- a/src/wasm32_stdweb.rs +++ b/src/wasm32_stdweb.rs @@ -7,35 +7,27 @@ // except according to those terms. //! Implementation for WASM via stdweb -use core::cell::RefCell; use core::mem; use core::num::NonZeroU32; -use std::thread_local; use stdweb::{js, _js_impl}; use stdweb::unstable::TryInto; use stdweb::web::error::Error as WebError; use crate::Error; -use crate::utils::use_init; +use lazy_static::lazy_static; -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] enum RngSource { Browser, Node } -thread_local!( - static RNG_SOURCE: RefCell> = RefCell::new(None); -); - pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { assert_eq!(mem::size_of::(), 4); - RNG_SOURCE.with(|f| { - use_init(f, getrandom_init, |source| getrandom_fill(source, dest)) - }) - + lazy_static! { static ref RNG_SOURCE: Result = getrandom_init(); } + getrandom_fill((*RNG_SOURCE)?, dest) } fn getrandom_init() -> Result { @@ -72,7 +64,7 @@ fn getrandom_init() -> Result { } } -fn getrandom_fill(source: &mut RngSource, dest: &mut [u8]) -> Result<(), Error> { +fn getrandom_fill(source: RngSource, dest: &mut [u8]) -> Result<(), Error> { for chunk in dest.chunks_mut(65536) { let len = chunk.len() as u32; let ptr = chunk.as_mut_ptr() as i32;