Skip to content
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

Add Structures for lock-free initialization #51

Merged
merged 4 commits into from
Jul 2, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
17 changes: 5 additions & 12 deletions src/macos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<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 = 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());
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
46 changes: 16 additions & 30 deletions src/solaris_illumos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,34 @@
//! 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 = 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<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
49 changes: 49 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// 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(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
}
}
38 changes: 38 additions & 0 deletions src/util_libc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// 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::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<NonNull<libc::c_void>> {
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 _)
}
}