From aa28beef85de741ad0e87c56c3ef2ffc2016b5a3 Mon Sep 17 00:00:00 2001 From: Joseph Richey Date: Mon, 1 Jul 2019 01:53:04 -0700 Subject: [PATCH 1/4] Add Structures for lock-free initialization --- Cargo.toml | 4 ---- src/lib.rs | 6 ++++++ src/linux_android.rs | 21 +++++++++----------- src/macos.rs | 15 +++----------- src/rdrand.rs | 8 +++----- src/solaris_illumos.rs | 45 ++++++++++++++---------------------------- src/util.rs | 45 ++++++++++++++++++++++++++++++++++++++++++ src/weak.rs | 45 ++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 126 insertions(+), 63 deletions(-) create mode 100644 src/util.rs create mode 100644 src/weak.rs 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..68134220 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 weak; // 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..4cc27ad8 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -9,25 +9,16 @@ //! Implementation for macOS extern crate std; +use crate::weak::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.func() { for chunk in dest.chunks_mut(256) { let ret = unsafe { fptr(chunk.as_mut_ptr(), chunk.len()) }; if ret != 0 { 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..9237c854 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -19,10 +19,10 @@ //! libc::dlsym. extern crate std; +use crate::weak::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,22 @@ 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.func() { + // 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 { fptr(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..d4668609 --- /dev/null +++ b/src/util.rs @@ -0,0 +1,45 @@ +// 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(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 usize::max_value(), the init() function + // will always be retried on a future call to unsync_init(). This makes it + // ideal for representing failure. + pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { + // Relaxed ordering is fine, as init() is allowed to run mulitple times. + if self.0.load(Ordering::Relaxed) == usize::max_value() { + self.0.store(init(), Ordering::Relaxed) + } + self.0.load(Ordering::Relaxed) + } +} + +// 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/weak.rs b/src/weak.rs new file mode 100644 index 00000000..714f8d55 --- /dev/null +++ b/src/weak.rs @@ -0,0 +1,45 @@ +// 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::marker::PhantomData; +use core::mem; + +// 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, + _marker: PhantomData, +} + +impl Weak { + // Construct a binding to a C function with a given name. This function is + // unsafe because `name` _must_ be null terminated, and if the symbol is + // present at runtime it _must_ have type F. + pub const unsafe fn new(name: &'static str) -> Self { + Self { + name, + addr: LazyUsize::new(), + _marker: PhantomData, + } + } + + // Returns the function pointer if it is present at runtime. Otherwise, + // return None, indicating the function does not exist. + pub fn func(&self) -> Option { + assert_eq!(mem::size_of::>(), mem::size_of::()); + + let addr = self.addr.unsync_init(|| unsafe { + libc::dlsym(libc::RTLD_DEFAULT, self.name.as_ptr() as *const _) as usize + }); + unsafe { mem::transmute_copy(&addr) } + } +} From 8709fd7656f416c1191b8f0fd4165a583a2a0207 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 2 Jul 2019 04:05:34 -0700 Subject: [PATCH 2/4] Fix bad explanation and rename module --- src/lib.rs | 2 +- src/macos.rs | 2 +- src/solaris_illumos.rs | 2 +- src/util.rs | 2 +- src/{weak.rs => util_libc.rs} | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/{weak.rs => util_libc.rs} (100%) diff --git a/src/lib.rs b/src/lib.rs index 68134220..e2278696 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -147,7 +147,7 @@ mod util; // These targets need weak linkage to libc randomness functions. #[cfg(any(target_os = "macos", target_os = "solaris", target_os = "illumos"))] -mod weak; +mod util_libc; // System-specific implementations. // diff --git a/src/macos.rs b/src/macos.rs index 4cc27ad8..e02117a7 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -9,7 +9,7 @@ //! Implementation for macOS extern crate std; -use crate::weak::Weak; +use crate::util_libc::Weak; use crate::{use_file, Error}; use core::num::NonZeroU32; use std::io; diff --git a/src/solaris_illumos.rs b/src/solaris_illumos.rs index 9237c854..cfaa13ff 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -19,7 +19,7 @@ //! libc::dlsym. extern crate std; -use crate::weak::Weak; +use crate::util_libc::Weak; use crate::{use_file, Error}; use core::mem; use core::num::NonZeroU32; diff --git a/src/util.rs b/src/util.rs index d4668609..8a3b64e6 100644 --- a/src/util.rs +++ b/src/util.rs @@ -23,7 +23,7 @@ impl LazyUsize { // will always be retried on a future call to unsync_init(). This makes it // ideal for representing failure. pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize { - // Relaxed ordering is fine, as init() is allowed to run mulitple times. + // Relaxed ordering is fine, as we only have a single atomic variable. if self.0.load(Ordering::Relaxed) == usize::max_value() { self.0.store(init(), Ordering::Relaxed) } diff --git a/src/weak.rs b/src/util_libc.rs similarity index 100% rename from src/weak.rs rename to src/util_libc.rs From 33764cf3ad0a35d4a780341731223acce59e4a3a Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 2 Jul 2019 04:43:26 -0700 Subject: [PATCH 3/4] Cleanup LazyUsize --- src/util.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/util.rs b/src/util.rs index 8a3b64e6..70adfa53 100644 --- a/src/util.rs +++ b/src/util.rs @@ -14,20 +14,24 @@ pub struct LazyUsize(AtomicUsize); impl LazyUsize { pub const fn new() -> Self { - Self(AtomicUsize::new(usize::max_value())) + 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 usize::max_value(), the init() function - // will always be retried on a future call to unsync_init(). This makes it - // ideal for representing failure. + // 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. - if self.0.load(Ordering::Relaxed) == usize::max_value() { - self.0.store(init(), Ordering::Relaxed) + let mut val = self.0.load(Ordering::Relaxed); + if val == Self::UNINIT { + val = init(); + self.0.store(val, Ordering::Relaxed); } - self.0.load(Ordering::Relaxed) + val } } From ea40bacb9cad38e167efdb9260f8e687c93210a1 Mon Sep 17 00:00:00 2001 From: Joe Richey Date: Tue, 2 Jul 2019 05:08:49 -0700 Subject: [PATCH 4/4] Weak: only return NonNull --- src/macos.rs | 8 +++++--- src/solaris_illumos.rs | 7 ++++--- src/util_libc.rs | 21 +++++++-------------- 3 files changed, 16 insertions(+), 20 deletions(-) diff --git a/src/macos.rs b/src/macos.rs index e02117a7..84d95565 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -11,16 +11,18 @@ extern crate std; use crate::util_libc::Weak; use crate::{use_file, Error}; +use core::mem; use core::num::NonZeroU32; use std::io; type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") }; - 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/solaris_illumos.rs b/src/solaris_illumos.rs index cfaa13ff..9d629e40 100644 --- a/src/solaris_illumos.rs +++ b/src/solaris_illumos.rs @@ -31,12 +31,13 @@ type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int; pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> { - static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") }; - if let Some(fptr) = GETRANDOM.func() { + 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 { fptr(chunk.as_mut_ptr(), chunk.len(), 0) }; + 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()); diff --git a/src/util_libc.rs b/src/util_libc.rs index 714f8d55..049fe88b 100644 --- a/src/util_libc.rs +++ b/src/util_libc.rs @@ -7,39 +7,32 @@ // except according to those terms. use crate::util::LazyUsize; -use core::marker::PhantomData; -use core::mem; +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 { +pub struct Weak { name: &'static str, addr: LazyUsize, - _marker: PhantomData, } -impl Weak { +impl Weak { // Construct a binding to a C function with a given name. This function is - // unsafe because `name` _must_ be null terminated, and if the symbol is - // present at runtime it _must_ have type F. + // unsafe because `name` _must_ be null terminated. pub const unsafe fn new(name: &'static str) -> Self { Self { name, addr: LazyUsize::new(), - _marker: PhantomData, } } - // Returns the function pointer if it is present at runtime. Otherwise, - // return None, indicating the function does not exist. - pub fn func(&self) -> Option { - assert_eq!(mem::size_of::>(), mem::size_of::()); - + // 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 }); - unsafe { mem::transmute_copy(&addr) } + NonNull::new(addr as *mut _) } }