Skip to content

Add Structures for lock-free initialization #51

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 util_libc;

// 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::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<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::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")]
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 we only have a single atomic variable.
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/util_libc.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) }
}
}