Skip to content

Commit b1e8154

Browse files
josephlrnewpavlov
authored andcommitted
Add Structures for lock-free initialization (#51)
1 parent 61cd3c2 commit b1e8154

File tree

8 files changed

+126
-63
lines changed

8 files changed

+126
-63
lines changed

Cargo.toml

-4
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,6 @@ libc = "0.2.54"
2727
[target.'cfg(any(unix, target_os = "redox"))'.dependencies]
2828
lazy_static = "1.3.0"
2929

30-
# For caching result of CPUID check for RDRAND
31-
[target.'cfg(target_os = "uefi")'.dependencies]
32-
lazy_static = { version = "1.3.0", features = ["spin_no_std"] }
33-
3430
[target.wasm32-unknown-unknown.dependencies]
3531
wasm-bindgen = { version = "0.2.29", optional = true }
3632
stdweb = { version = "0.4.9", optional = true }

src/lib.rs

+6
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,12 @@ extern crate std;
142142

143143
mod error;
144144
pub use crate::error::Error;
145+
#[allow(dead_code)]
146+
mod util;
147+
148+
// These targets need weak linkage to libc randomness functions.
149+
#[cfg(any(target_os = "macos", target_os = "solaris", target_os = "illumos"))]
150+
mod util_libc;
145151

146152
// System-specific implementations.
147153
//

src/linux_android.rs

+9-12
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
//! Implementation for Linux / Android
1010
extern crate std;
1111

12+
use crate::util::LazyBool;
1213
use crate::{use_file, Error};
1314
use core::num::NonZeroU32;
14-
use lazy_static::lazy_static;
1515
use std::io;
1616

1717
fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<usize, io::Error> {
@@ -29,18 +29,15 @@ fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<usize, io::Error> {
2929
}
3030

3131
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
32-
lazy_static! {
33-
static ref HAS_GETRANDOM: bool = is_getrandom_available();
34-
}
35-
match *HAS_GETRANDOM {
36-
true => {
37-
let mut start = 0;
38-
while start < dest.len() {
39-
start += syscall_getrandom(&mut dest[start..], true)?;
40-
}
41-
Ok(())
32+
static HAS_GETRANDOM: LazyBool = LazyBool::new();
33+
if HAS_GETRANDOM.unsync_init(is_getrandom_available) {
34+
let mut start = 0;
35+
while start < dest.len() {
36+
start += syscall_getrandom(&mut dest[start..], true)?;
4237
}
43-
false => use_file::getrandom_inner(dest),
38+
Ok(())
39+
} else {
40+
use_file::getrandom_inner(dest)
4441
}
4542
}
4643

src/macos.rs

+5-12
Original file line numberDiff line numberDiff line change
@@ -9,27 +9,20 @@
99
//! Implementation for macOS
1010
extern crate std;
1111

12+
use crate::util_libc::Weak;
1213
use crate::{use_file, Error};
1314
use core::mem;
1415
use core::num::NonZeroU32;
15-
use lazy_static::lazy_static;
1616
use std::io;
1717

1818
type GetEntropyFn = unsafe extern "C" fn(*mut u8, libc::size_t) -> libc::c_int;
1919

20-
fn fetch_getentropy() -> Option<GetEntropyFn> {
21-
let name = "getentropy\0";
22-
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) };
23-
unsafe { mem::transmute(addr) }
24-
}
25-
2620
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
27-
lazy_static! {
28-
static ref GETENTROPY_FUNC: Option<GetEntropyFn> = fetch_getentropy();
29-
}
30-
if let Some(fptr) = *GETENTROPY_FUNC {
21+
static GETENTROPY: Weak = unsafe { Weak::new("getentropy\0") };
22+
if let Some(fptr) = GETENTROPY.ptr() {
23+
let func: GetEntropyFn = unsafe { mem::transmute(fptr) };
3124
for chunk in dest.chunks_mut(256) {
32-
let ret = unsafe { fptr(chunk.as_mut_ptr(), chunk.len()) };
25+
let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len()) };
3326
if ret != 0 {
3427
error!("getentropy syscall failed with ret={}", ret);
3528
return Err(io::Error::last_os_error().into());

src/rdrand.rs

+3-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// except according to those terms.
88

99
//! Implementation for SGX using RDRAND instruction
10+
use crate::util::LazyBool;
1011
use crate::Error;
1112
use core::arch::x86_64::_rdrand64_step;
1213
use core::mem;
@@ -55,13 +56,10 @@ fn is_rdrand_supported() -> bool {
5556
#[cfg(not(target_feature = "rdrand"))]
5657
fn is_rdrand_supported() -> bool {
5758
use core::arch::x86_64::__cpuid;
58-
use lazy_static::lazy_static;
5959
// SAFETY: All x86_64 CPUs support CPUID leaf 1
6060
const FLAG: u32 = 1 << 30;
61-
lazy_static! {
62-
static ref HAS_RDRAND: bool = unsafe { __cpuid(1).ecx & FLAG != 0 };
63-
}
64-
*HAS_RDRAND
61+
static HAS_RDRAND: LazyBool = LazyBool::new();
62+
HAS_RDRAND.unsync_init(|| unsafe { (__cpuid(1).ecx & FLAG) != 0 })
6563
}
6664

6765
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {

src/solaris_illumos.rs

+16-30
Original file line numberDiff line numberDiff line change
@@ -19,48 +19,34 @@
1919
//! libc::dlsym.
2020
extern crate std;
2121

22+
use crate::util_libc::Weak;
2223
use crate::{use_file, Error};
2324
use core::mem;
2425
use core::num::NonZeroU32;
25-
use lazy_static::lazy_static;
2626
use std::io;
2727

2828
#[cfg(target_os = "illumos")]
2929
type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::ssize_t;
3030
#[cfg(target_os = "solaris")]
3131
type GetRandomFn = unsafe extern "C" fn(*mut u8, libc::size_t, libc::c_uint) -> libc::c_int;
3232

33-
fn libc_getrandom(rand: GetRandomFn, dest: &mut [u8]) -> Result<(), Error> {
34-
let ret = unsafe { rand(dest.as_mut_ptr(), dest.len(), 0) as libc::ssize_t };
35-
36-
if ret == -1 || ret != dest.len() as libc::ssize_t {
37-
error!("getrandom syscall failed with ret={}", ret);
38-
Err(io::Error::last_os_error().into())
39-
} else {
40-
Ok(())
41-
}
42-
}
43-
4433
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
45-
lazy_static! {
46-
static ref GETRANDOM_FUNC: Option<GetRandomFn> = fetch_getrandom();
47-
}
48-
49-
// 256 bytes is the lowest common denominator across all the Solaris
50-
// derived platforms for atomically obtaining random data.
51-
for chunk in dest.chunks_mut(256) {
52-
match *GETRANDOM_FUNC {
53-
Some(fptr) => libc_getrandom(fptr, chunk)?,
54-
None => use_file::getrandom_inner(chunk)?,
55-
};
34+
static GETRANDOM: Weak = unsafe { Weak::new("getrandom\0") };
35+
if let Some(fptr) = GETRANDOM.ptr() {
36+
let func: GetRandomFn = unsafe { mem::transmute(fptr) };
37+
// 256 bytes is the lowest common denominator across all the Solaris
38+
// derived platforms for atomically obtaining random data.
39+
for chunk in dest.chunks_mut(256) {
40+
let ret = unsafe { func(chunk.as_mut_ptr(), chunk.len(), 0) };
41+
if ret != chunk.len() as _ {
42+
error!("getrandom syscall failed with ret={}", ret);
43+
return Err(io::Error::last_os_error().into());
44+
}
45+
}
46+
Ok(())
47+
} else {
48+
use_file::getrandom_inner(dest)
5649
}
57-
Ok(())
58-
}
59-
60-
fn fetch_getrandom() -> Option<GetRandomFn> {
61-
let name = "getrandom\0";
62-
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) };
63-
unsafe { mem::transmute(addr) }
6450
}
6551

6652
#[inline(always)]

src/util.rs

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2019 Developers of the Rand project.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
use core::sync::atomic::{AtomicUsize, Ordering};
10+
11+
// This structure represents a laziliy initialized static usize value. Useful
12+
// when it is perferable to just rerun initialization instead of locking.
13+
pub struct LazyUsize(AtomicUsize);
14+
15+
impl LazyUsize {
16+
pub const fn new() -> Self {
17+
Self(AtomicUsize::new(Self::UNINIT))
18+
}
19+
20+
// The initialization is not completed.
21+
pub const UNINIT: usize = usize::max_value();
22+
23+
// Runs the init() function at least once, returning the value of some run
24+
// of init(). Unlike std::sync::Once, the init() function may be run
25+
// multiple times. If init() returns UNINIT, future calls to unsync_init()
26+
// will always retry. This makes UNINIT ideal for representing failure.
27+
pub fn unsync_init(&self, init: impl FnOnce() -> usize) -> usize {
28+
// Relaxed ordering is fine, as we only have a single atomic variable.
29+
let mut val = self.0.load(Ordering::Relaxed);
30+
if val == Self::UNINIT {
31+
val = init();
32+
self.0.store(val, Ordering::Relaxed);
33+
}
34+
val
35+
}
36+
}
37+
38+
// Identical to LazyUsize except with bool instead of usize.
39+
pub struct LazyBool(LazyUsize);
40+
41+
impl LazyBool {
42+
pub const fn new() -> Self {
43+
Self(LazyUsize::new())
44+
}
45+
46+
pub fn unsync_init(&self, init: impl FnOnce() -> bool) -> bool {
47+
self.0.unsync_init(|| init() as usize) != 0
48+
}
49+
}

src/util_libc.rs

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2019 Developers of the Rand project.
2+
//
3+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
5+
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
6+
// option. This file may not be copied, modified, or distributed
7+
// except according to those terms.
8+
9+
use crate::util::LazyUsize;
10+
use core::ptr::NonNull;
11+
12+
// A "weak" binding to a C function that may or may not be present at runtime.
13+
// Used for supporting newer OS features while still building on older systems.
14+
// F must be a function pointer of type `unsafe extern "C" fn`. Based off of the
15+
// weak! macro in libstd.
16+
pub struct Weak {
17+
name: &'static str,
18+
addr: LazyUsize,
19+
}
20+
21+
impl Weak {
22+
// Construct a binding to a C function with a given name. This function is
23+
// unsafe because `name` _must_ be null terminated.
24+
pub const unsafe fn new(name: &'static str) -> Self {
25+
Self {
26+
name,
27+
addr: LazyUsize::new(),
28+
}
29+
}
30+
31+
// Return a function pointer if present at runtime. Otherwise, return null.
32+
pub fn ptr(&self) -> Option<NonNull<libc::c_void>> {
33+
let addr = self.addr.unsync_init(|| unsafe {
34+
libc::dlsym(libc::RTLD_DEFAULT, self.name.as_ptr() as *const _) as usize
35+
});
36+
NonNull::new(addr as *mut _)
37+
}
38+
}

0 commit comments

Comments
 (0)