Skip to content

Reduce number of syscalls in rand #53725

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 3 commits into from
Sep 2, 2018
Merged
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
66 changes: 26 additions & 40 deletions src/libstd/sys/unix/rand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ pub fn hashmap_random_keys() -> (u64, u64) {
mod imp {
use fs::File;
use io::Read;
#[cfg(any(target_os = "linux", target_os = "android"))]
use libc;
use sys::os::errno;

#[cfg(any(target_os = "linux", target_os = "android"))]
fn getrandom(buf: &mut [u8]) -> libc::c_long {
Expand All @@ -40,71 +40,57 @@ mod imp {
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
fn getrandom_fill_bytes(_buf: &mut [u8]) -> bool { false }

#[cfg(any(target_os = "linux", target_os = "android"))]
fn getrandom_fill_bytes(v: &mut [u8]) -> bool {
use sync::atomic::{AtomicBool, Ordering};
use sys::os::errno;

static GETRANDOM_UNAVAILABLE: AtomicBool = AtomicBool::new(false);
if GETRANDOM_UNAVAILABLE.load(Ordering::Relaxed) {
return false;
}

let mut read = 0;
while read < v.len() {
let result = getrandom(&mut v[read..]);
if result == -1 {
let err = errno() as libc::c_int;
if err == libc::EINTR {
continue;
} else if err == libc::ENOSYS {
GETRANDOM_UNAVAILABLE.store(true, Ordering::Relaxed);
Copy link
Contributor

Choose a reason for hiding this comment

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

Would this not loop infinitely in the ENOSYS case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh my...

return false;
} else if err == libc::EAGAIN {
return false
return false;
} else {
panic!("unexpected getrandom error: {}", err);
}
} else {
read += result as usize;
}
}

return true
true
}

#[cfg(any(target_os = "linux", target_os = "android"))]
fn is_getrandom_available() -> bool {
use io;
use sync::atomic::{AtomicBool, Ordering};
use sync::Once;

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

CHECKER.call_once(|| {
let mut buf: [u8; 0] = [];
let result = getrandom(&mut buf);
let available = if result == -1 {
let err = io::Error::last_os_error().raw_os_error();
err != Some(libc::ENOSYS)
} else {
true
};
AVAILABLE.store(available, Ordering::Relaxed);
});

AVAILABLE.load(Ordering::Relaxed)
}

#[cfg(not(any(target_os = "linux", target_os = "android")))]
fn is_getrandom_available() -> bool { false }

pub fn fill_bytes(v: &mut [u8]) {
// getrandom_fill_bytes here can fail if getrandom() returns EAGAIN,
// meaning it would have blocked because the non-blocking pool (urandom)
// has not initialized in the kernel yet due to a lack of entropy the
// has not initialized in the kernel yet due to a lack of entropy. The
// fallback we do here is to avoid blocking applications which could
// depend on this call without ever knowing they do and don't have a
// work around. The PRNG of /dev/urandom will still be used but not
// over a completely full entropy pool
if is_getrandom_available() && getrandom_fill_bytes(v) {
return
// work around. The PRNG of /dev/urandom will still be used but over a
// possibly predictable entropy pool.
if getrandom_fill_bytes(v) {
return;
}

let mut file = File::open("/dev/urandom")
.expect("failed to open /dev/urandom");
file.read_exact(v).expect("failed to read /dev/urandom");
// getrandom failed because it is permanently or temporarily (because
// of missing entropy) unavailable. Open /dev/urandom, read from it,
// and close it again.
let mut file = File::open("/dev/urandom").expect("failed to open /dev/urandom");
file.read_exact(v).expect("failed to read /dev/urandom")
}
}

Expand Down