diff --git a/Cargo.toml b/Cargo.toml index 45eeefa4..9a4be7d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,10 +27,6 @@ libc = "0.2.54" [target.'cfg(any(unix, target_os = "redox"))'.dependencies] lazy_static = "1.3.0" -# For caching result of CPUID check for RDRAND -[target.'cfg(target_os = "uefi")'.dependencies] -lazy_static = { version = "1.3.0", features = ["spin_no_std"] } - [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = { version = "0.2.29", optional = true } stdweb = { version = "0.4.9", optional = true } diff --git a/src/lib.rs b/src/lib.rs index 9d3e583b..e2278696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -142,6 +142,12 @@ extern crate std; mod error; pub use crate::error::Error; +#[allow(dead_code)] +mod util; + +// These targets need weak linkage to libc randomness functions. +#[cfg(any(target_os = "macos", target_os = "solaris", target_os = "illumos"))] +mod util_libc; // System-specific implementations. // diff --git a/src/linux_android.rs b/src/linux_android.rs index 8ab69eaa..d7c046da 100644 --- a/src/linux_android.rs +++ b/src/linux_android.rs @@ -9,9 +9,9 @@ //! Implementation for Linux / Android extern crate std; +use crate::util::LazyBool; use crate::{use_file, Error}; use core::num::NonZeroU32; -use lazy_static::lazy_static; use std::io; fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result { @@ -29,18 +29,15 @@ fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result { } pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - 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(()) + static HAS_GETRANDOM: LazyBool = LazyBool::new(); + if HAS_GETRANDOM.unsync_init(is_getrandom_available) { + let mut start = 0; + while start < dest.len() { + start += syscall_getrandom(&mut dest[start..], true)?; } - false => use_file::getrandom_inner(dest), + Ok(()) + } else { + use_file::getrandom_inner(dest) } } diff --git a/src/macos.rs b/src/macos.rs index 29078a86..84d95565 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -9,27 +9,20 @@ //! Implementation for macOS extern crate std; +use crate::util_libc::Weak; use crate::{use_file, Error}; use core::mem; use core::num::NonZeroU32; -use lazy_static::lazy_static; use std::io; type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int; -fn fetch_getentropy() -> Option { - let name = "getentropy\0"; - let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) }; - unsafe { mem::transmute(addr) } -} - pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - lazy_static! { - static ref GETENTROPY_FUNC: Option = fetch_getentropy(); - } - if let Some(fptr) = *GETENTROPY_FUNC { + static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") }; + if let Some(fptr) = GETENTROPY.ptr() { + let func: GetEntropyFn = unsafe { mem::transmute(fptr) }; for chunk in dest.chunks_mut(256) { - let ret = unsafe { fptr(chunk.as_mut_ptr(), chunk.len()) }; + let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len()) }; if ret != 0 { error!("getentropy syscall failed with ret={}", ret); return Err(io::Error::last_os_error().into()); diff --git a/src/rdrand.rs b/src/rdrand.rs index b9be8535..12843735 100644 --- a/src/rdrand.rs +++ b/src/rdrand.rs @@ -7,6 +7,7 @@ // except according to those terms. //! Implementation for SGX using RDRAND instruction +use crate::util::LazyBool; use crate::Error; use core::arch::x86_64::_rdrand64_step; use core::mem; @@ -55,13 +56,10 @@ fn is_rdrand_supported() -> bool { #[cfg(not(target_feature = "rdrand"))] fn is_rdrand_supported() -> bool { use core::arch::x86_64::__cpuid; - use lazy_static::lazy_static; // SAFETY: All x86_64 CPUs support CPUID leaf 1 const FLAG: u32 = 1 << 30; - lazy_static! { - static ref HAS_RDRAND: bool = unsafe { __cpuid(1).ecx & FLAG != 0 }; - } - *HAS_RDRAND + static HAS_RDRAND: LazyBool = LazyBool::new(); + HAS_RDRAND.unsync_init(|| unsafe { (__cpuid(1).ecx & FLAG) != 0 }) } pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs index 7390c063..9d629e40 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -19,10 +19,10 @@ //! libc::dlsym. extern crate std; +use crate::util_libc::Weak; use crate::{use_file, Error}; use core::mem; use core::num::NonZeroU32; -use lazy_static::lazy_static; use std::io; #[cfg(target_os = "illumos")] @@ -30,37 +30,23 @@ type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> #[cfg(target_os = "solaris")] type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int; -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 }; - - if ret == -1 || ret != dest.len() as libc::ssize_t { - error!("getrandom syscall failed with ret={}", ret); - Err(io::Error::last_os_error().into()) - } else { - Ok(()) - } -} - 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. - for chunk in dest.chunks_mut(256) { - match *GETRANDOM_FUNC { - Some(fptr) => libc_getrandom(fptr, chunk)?, - None => use_file::getrandom_inner(chunk)?, - }; + static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; + if let Some(fptr) = GETRANDOM.ptr() { + let func: GetRandomFn = unsafe { mem::transmute(fptr) }; + // 256 bytes is the lowest common denominator across all the Solaris + // derived platforms for atomically obtaining random data. + for chunk in dest.chunks_mut(256) { + let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len(), 0) }; + if ret != chunk.len() as _ { + error!("getrandom syscall failed with ret={}", ret); + return Err(io::Error::last_os_error().into()); + } + } + Ok(()) + } else { + use_file::getrandom_inner(dest) } - Ok(()) -} - -fn fetch_getrandom() -> Option { - 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/util.rs b/src/util.rs new file mode 100644 index 00000000..70adfa53 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,49 @@ +// Copyright 2019 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 core::sync::atomic::{AtomicUsize, Ordering}; + +// This structure represents a laziliy initialized static usize value. Useful +// when it is perferable to just rerun initialization instead of locking. +pub struct LazyUsize(AtomicUsize); + +impl LazyUsize { + pub const fn new() -> Self { + Self(AtomicUsize::new(Self::UNINIT)) + } + + // The initialization is not completed. + pub const UNINIT: usize = usize::max_value(); + + // Runs the init() function at least once, returning the value of some run + // of init(). Unlike std::sync::Once, the init() function may be run + // multiple times. If init() returns UNINIT, future calls to unsync_init() + // will always retry. This makes UNINIT ideal for representing failure. + pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + // Relaxed ordering is fine, as we only have a single atomic variable. + let mut val = self.0.load(Ordering::Relaxed); + if val == Self::UNINIT { + val = init(); + self.0.store(val, Ordering::Relaxed); + } + val + } +} + +// Identical to LazyUsize except with bool instead of usize. +pub struct LazyBool(LazyUsize); + +impl LazyBool { + pub const fn new() -> Self { + Self(LazyUsize::new()) + } + + pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool { + self.0.unsync_init(|| init() as usize) != 0 + } +} diff --git a/src/util_libc.rs b/src/util_libc.rs new file mode 100644 index 00000000..049fe88b --- /dev/null +++ b/src/util_libc.rs @@ -0,0 +1,38 @@ +// Copyright 2019 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::util::LazyUsize; +use core::ptr::NonNull; + +// A "weak" binding to a C function that may or may not be present at runtime. +// Used for supporting newer OS features while still building on older systems. +// F must be a function pointer of type `unsafe extern "C" fn`. Based off of the +// weak! macro in libstd. +pub struct Weak { + name: &'static str, + addr: LazyUsize, +} + +impl Weak { + // Construct a binding to a C function with a given name. This function is + // unsafe because `name` _must_ be null terminated. + pub const unsafe fn new(name: &'static str) -> Self { + Self { + name, + addr: LazyUsize::new(), + } + } + + // Return a function pointer if present at runtime. Otherwise, return null. + pub fn ptr(&self) -> Option> { + let addr = self.addr.unsync_init(|| unsafe { + libc::dlsym(libc::RTLD_DEFAULT, self.name.as_ptr() as *const _) as usize + }); + NonNull::new(addr as *mut _) + } +}