Skip to content

Commit

Permalink
Add Structures for lock-free initialization
Browse files Browse the repository at this point in the history
  • Loading branch information
josephlr committed Jul 2, 2019
1 parent e02e946 commit aa28bee
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 63 deletions.
4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
6 changes: 6 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand Down
21 changes: 9 additions & 12 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<usize, io::Error> {
Expand All @@ -29,18 +29,15 @@ fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<usize, io::Error> {
}

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)
}
}

Expand Down
15 changes: 3 additions & 12 deletions src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<GetEntropyFn> {
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<GetEntropyFn> = fetch_getentropy();
}
if let Some(fptr) = *GETENTROPY_FUNC {
static GETENTROPY: Weak<GetEntropyFn> = 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 {
Expand Down
8 changes: 3 additions & 5 deletions src/rdrand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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> {
Expand Down
45 changes: 15 additions & 30 deletions src/solaris_illumos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,33 @@
//! 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")]
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;

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<GetRandomFn> = 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<GetRandomFn> = 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<GetRandomFn> {
let name = "getrandom\0";
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) };
unsafe { mem::transmute(addr) }
}

#[inline(always)]
Expand Down
45 changes: 45 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2019 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, 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
}
}
45 changes: 45 additions & 0 deletions src/weak.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2019 Developers of the Rand project.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, 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<F> {
name: &'static str,
addr: LazyUsize,
_marker: PhantomData<F>,
}

impl<F> Weak<F> {
// 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<F> {
assert_eq!(mem::size_of::<Option<F>>(), mem::size_of::<usize>());

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) }
}
}

0 comments on commit aa28bee

Please sign in to comment.