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

Remove TLS #14

Closed
wants to merge 6 commits into from
Closed
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
31 changes: 6 additions & 25 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,10 @@
//! features are activated for this crate. Note that if both features are
//! enabled `wasm-bindgen` will be used. If neither feature is enabled,
//! `getrandom` will always fail.
//!
//!
//! The WASI target `wasm32-wasi` uses the `__wasi_random_get` function defined
//! by the WASI standard.
//!
//!
//!
//! ## Early boot
//!
Expand Down Expand Up @@ -132,21 +132,6 @@ macro_rules! error { ($($x:tt)*) => () }
#[cfg(target_arch = "wasm32")]
extern crate std;

#[cfg(any(
target_os = "android",
target_os = "netbsd",
target_os = "solaris",
target_os = "illumos",
target_os = "redox",
target_os = "dragonfly",
target_os = "haiku",
target_os = "linux",
all(
target_arch = "wasm32",
not(target_os = "wasi")
),
))]
mod utils;
mod error;
pub use crate::error::Error;

Expand Down Expand Up @@ -188,9 +173,9 @@ mod_use!(cfg(target_os = "netbsd"), use_file);
mod_use!(cfg(target_os = "openbsd"), openbsd_bitrig);
mod_use!(cfg(target_os = "redox"), use_file);
mod_use!(cfg(target_os = "solaris"), solaris_illumos);
mod_use!(cfg(windows), windows);
mod_use!(cfg(target_env = "sgx"), sgx);
mod_use!(cfg(target_os = "wasi"), wasi);
mod_use!(cfg(target_env = "sgx"), sgx);
mod_use!(cfg(windows), windows);

mod_use!(
cfg(all(
Expand Down Expand Up @@ -231,16 +216,12 @@ mod_use!(
target_os = "openbsd",
target_os = "redox",
target_os = "solaris",
target_os = "wasi",
target_env = "sgx",
windows,
all(
target_arch = "wasm32",
any(
target_os = "emscripten",
target_os = "wasi",
feature = "wasm-bindgen",
feature = "stdweb",
),
any(feature = "wasm-bindgen", feature = "stdweb"),
),
))),
dummy
Expand Down
139 changes: 82 additions & 57 deletions src/linux_android.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,78 +10,103 @@
extern crate std;

use crate::Error;
use crate::utils::use_init;
use std::{thread_local, io::{self, Read}, fs::File};
use core::cell::RefCell;
use core::num::NonZeroU32;
use core::sync::atomic::{AtomicBool, Ordering};
use std::fs::File;
use std::io::Read;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::{io, mem};

// This flag tells getrandom() to return EAGAIN instead of blocking.
// replace with AtomicU8 on stabilization and MSRV bump
static RNG_STATE: AtomicUsize = AtomicUsize::new(0);
// replace with AtomicI32 on stabilization and MSRV bump
static RNG_FD: AtomicIsize = AtomicIsize::new(-1);

const STATE_INIT_ONGOING: usize = 1 << 0;
const STATE_USE_SYSCALL: usize = 1 << 1;
const STATE_USE_FD: usize = 1 << 2;
const GRND_NONBLOCK: libc::c_uint = 0x0001;
static RNG_INIT: AtomicBool = AtomicBool::new(false);

enum RngSource {
GetRandom,
Device(File),
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
let state = RNG_STATE.load(Ordering::Acquire);
Copy link

@HadrienG2 HadrienG2 May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You may be able to reduce synchronization overhead by using a Relaxed load, and only using an Acquire fence after the fact if...

  1. Some state has indeed been initialized
  2. You need to synchronize with extra state besides the atomic value itself (e.g. another atomic value)

But cross-check that this is worthwhile with a benchmark on a relaxed-memory architecture (Arm, Power...), because compilers are less skilled at optimizing fences than they are at optimizing ordered atomic ops.

if state & STATE_USE_SYSCALL != 0 {
use_syscall(dest, true)
} else if state & STATE_USE_FD != 0 {
use_fd(dest)
} else {
init_loop(dest)
}
}

fn init_loop(dest: &mut [u8]) -> Result<(), Error> {
loop {
let state = RNG_STATE.fetch_or(STATE_INIT_ONGOING, Ordering::AcqRel);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Release ordering is about allowing clients who read STATE_INIT_ONGOING with Acquire ordering to synchronize with other shared state that you modified before writing to this variable. I do not think that it is useful here. If this is just a state flag, even Relaxed could be enough, as long as you use an Acquire fence as discussed above where necessary.


if state & STATE_INIT_ONGOING != 0 {
std::thread::yield_now();
continue;
}
return if state & STATE_USE_SYSCALL != 0 {
use_syscall(dest, true)
} else if state & STATE_USE_FD != 0 {
use_fd(dest)
} else {
init(dest)
};
}
}

thread_local!(
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
);
fn init(dest: &mut [u8]) -> Result<(), Error> {
match use_syscall(&mut [], false) {
Ok(()) => {
RNG_STATE.store(STATE_USE_SYSCALL, Ordering::Release);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for Release ordering if you have no writes to other shared memory locations that you want to share with other threads before the atomic store.

use_syscall(dest, true)
},
Err(err) if err.code().get() as i32 == libc::ENOSYS => {
match init_fd() {
Ok(fd) => {
RNG_FD.store(fd as isize, Ordering::SeqCst);
RNG_STATE.store(STATE_USE_FD, Ordering::SeqCst);
Copy link

@HadrienG2 HadrienG2 May 14, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Relaxed followed by Release on this side, and an Acquire fence on the reader's side, are enough to guarantee that the value of RNG_FD loaded by the reader is at least as recent as that of RNG_STATE in this case.

use_fd(dest)
},
Err(err) => {
RNG_STATE.store(0, Ordering::Release);
Err(err.into())
}
}
},
Err(err) => Err(err),
}
}

fn syscall_getrandom(dest: &mut [u8], block: bool) -> Result<(), io::Error> {
fn init_fd() -> io::Result<i32> {
// read one byte from "/dev/random" to ensure that OS RNG has initialized
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?;
let f = File::open("/dev/urandom")?;
let fd = f.as_raw_fd();
mem::forget(f);
Ok(fd)
}

fn use_syscall(dest: &mut [u8], block: bool) -> Result<(), Error> {
let flags = if block { 0 } else { GRND_NONBLOCK };
let ret = unsafe {
libc::syscall(libc::SYS_getrandom, dest.as_mut_ptr(), dest.len(), flags)
};
if ret < 0 || (ret as usize) != dest.len() {
error!("Linux getrandom syscall failed with return value {}", ret);
return Err(io::Error::last_os_error());
return Err(io::Error::last_os_error().into());
newpavlov marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(())
}

pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
RNG_SOURCE.with(|f| {
use_init(f,
|| {
let s = if is_getrandom_available() {
RngSource::GetRandom
} else {
// read one byte from "/dev/random" to ensure that
// OS RNG has initialized
if !RNG_INIT.load(Ordering::Relaxed) {
File::open("/dev/random")?.read_exact(&mut [0u8; 1])?;
RNG_INIT.store(true, Ordering::Relaxed)
}
RngSource::Device(File::open("/dev/urandom")?)
};
Ok(s)
}, |f| {
match f {
RngSource::GetRandom => syscall_getrandom(dest, true),
RngSource::Device(f) => f.read_exact(dest),
}.map_err(From::from)
})
})
}

fn is_getrandom_available() -> bool {
use std::sync::{Once, ONCE_INIT};

static CHECKER: Once = ONCE_INIT;
static AVAILABLE: AtomicBool = AtomicBool::new(false);

CHECKER.call_once(|| {
let mut buf: [u8; 0] = [];
let available = match syscall_getrandom(&mut buf, false) {
Ok(()) => true,
Err(err) => err.raw_os_error() != Some(libc::ENOSYS),
};
AVAILABLE.store(available, Ordering::Relaxed);
});

AVAILABLE.load(Ordering::Relaxed)
fn use_fd(dest: &mut [u8]) -> Result<(), Error> {
unsafe {
let fd = RNG_FD.load(Ordering::Acquire) as i32;
let mut f = File::from_raw_fd(fd);
f.read_exact(dest)?;
mem::forget(f);
}
Ok(())
}

#[inline(always)]
Expand Down
141 changes: 82 additions & 59 deletions src/solaris_illumos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,85 +17,108 @@
//! To make sure we can compile on both Solaris and its derivatives, as well as
//! function, we check for the existance of getrandom(2) in libc by calling
//! libc::dlsym.
extern crate libc;
extern crate std;

use crate::Error;
use crate::utils::use_init;
use std::{thread_local, io::{self, Read}, fs::File};
use core::cell::RefCell;
use core::num::NonZeroU32;
use std::fs::File;
use std::io::Read;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicIsize, AtomicUsize, Ordering};
use std::os::unix::io::{RawFd, AsRawFd, FromRawFd};
use std::{io, mem};

#[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;

enum RngSource {
GetRandom(GetRandomFn),
Device(File),
}
static RNG_FN: AtomicUsize = AtomicUsize::new(0);
// replace with AtomicI32 on stabilization and MSRV bump
static RNG_FD: AtomicIsize = AtomicIsize::new(-1);
// replace with AtomicU8 on stabilization and MSRV bump
static RNG_STATE: AtomicUsize = AtomicUsize::new(0);

thread_local!(
static RNG_SOURCE: RefCell<Option<RngSource>> = RefCell::new(None);
);
const STATE_INIT_ONGOING: usize = 1 << 0;
const STATE_INIT_DONE: usize = 1 << 1;

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 };
pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
let f = RNG_FN.load(Ordering::Acquire);
if f != 0 {
let f: GetRandomFn = unsafe { mem::transmute(f) };
return use_fn(f, dest);
}
let fd = RNG_FD.load(Ordering::Acquire) as i32;
if fd != -1 {
return use_fd(fd, dest);
}

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(())
loop {
let state = RNG_STATE.fetch_or(STATE_INIT_ONGOING, Ordering::AcqRel);
if state & STATE_INIT_DONE != 0 { break; }
if state & STATE_INIT_ONGOING != 0 {
std::thread::yield_now();
continue;
}

let f = fetch_getrandom();
if f == 0 {
let f = match File::open("/dev/random") {
Ok(f) => f,
Err(err) => {
RNG_STATE.store(0, Ordering::Release);
return Err(err.into());
},
};
RNG_FD.store(f.as_raw_fd() as isize, Ordering::SeqCst);
mem::forget(f);
} else {
RNG_FN.store(f, Ordering::SeqCst);
}
RNG_STATE.store(STATE_INIT_DONE, Ordering::SeqCst);
break;
}
}

pub fn getrandom_inner(dest: &mut [u8]) -> Result<(), Error> {
// 256 bytes is the lowest common denominator across all the Solaris
// derived platforms for atomically obtaining random data.
RNG_SOURCE.with(|f| {
use_init(
f,
|| {
let s = match fetch_getrandom() {
Some(fptr) => RngSource::GetRandom(fptr),
None => RngSource::Device(File::open("/dev/random")?),
};
Ok(s)
},
|f| {
match f {
RngSource::GetRandom(rp) => {
for chunk in dest.chunks_mut(256) {
libc_getrandom(*rp, chunk)?
}
}
RngSource::Device(randf) => {
for chunk in dest.chunks_mut(256) {
randf.read_exact(chunk)?
}
}
};
Ok(())
},
)
})
let f = RNG_FN.load(Ordering::Acquire);
if f != 0 {
let f: GetRandomFn = unsafe { mem::transmute(f) };
return use_fn(f, dest);
}
let fd = RNG_FD.load(Ordering::Acquire) as i32;
use_fd(fd, dest)
}

fn fetch_getrandom() -> Option<GetRandomFn> {
use std::mem;
use std::sync::atomic::{AtomicUsize, Ordering};

static FPTR: AtomicUsize = AtomicUsize::new(1);
fn use_fn(f: GetRandomFn, dest: &mut [u8]) -> Result<(), Error> {
for chunk in dest.chunks_mut(256) {
let ret = unsafe {
f(chunk.as_mut_ptr(), chunk.len(), 0) as libc::ssize_t
};

if ret == -1 || ret != chunk.len() as libc::ssize_t {
let err: Error = io::Error::last_os_error().into();
error!("getrandom syscall failed: {}", err);
return Err(err);
}
}
Ok(())
}

if FPTR.load(Ordering::SeqCst) == 1 {
let name = "getrandom\0";
let addr = unsafe { libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize };
FPTR.store(addr, Ordering::SeqCst);
fn use_fd(fd: RawFd, dest: &mut [u8]) -> Result<(), Error> {
let mut f = unsafe { File::from_raw_fd(fd) };
for chunk in dest.chunks_mut(256) {
f.read_exact(chunk)?
}
mem::forget(f);
Ok(())
}

let ptr = FPTR.load(Ordering::SeqCst);
unsafe { mem::transmute::<usize, Option<GetRandomFn>>(ptr) }
/// returns 0 if fetch has failed and function pointer otherwise
fn fetch_getrandom() -> usize {
let name = "getrandom\0";
unsafe {
libc::dlsym(libc::RTLD_DEFAULT, name.as_ptr() as *const _) as usize
}
}

#[inline(always)]
Expand Down
Loading