std::rand::OsRng: Use getrandom syscall on Linux

`getrandom(2)` system call [1] has been added on Linux 3.17.
This patch makes `OsRng` use `getrandom` if available, and use
traditional `/dev/urandom` fallback if not.

[1]:
https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=c6e9d6f38894798696f23c8084ca7edbf16ee895
This commit is contained in:
klutzy 2014-11-06 03:53:27 +09:00
parent 14cd5c590e
commit fdf5195f58
2 changed files with 120 additions and 12 deletions

View File

@ -45,8 +45,12 @@
//! so the "quality" of `/dev/random` is not better than `/dev/urandom` in most cases.
//! However, this means that `/dev/urandom` can yield somewhat predictable randomness
//! if the entropy pool is very small, such as immediately after first booting.
//! If an application likely to be run soon after first booting, or on a system with very
//! few entropy sources, one should consider using `/dev/random` via `ReaderRng`.
//! Linux 3,17 added `getrandom(2)` system call which solves the issue: it blocks if entropy
//! pool is not initialized yet, but it does not block once initialized.
//! `OsRng` tries to use `getrandom(2)` if available, and use `/dev/urandom` fallback if not.
//! If an application does not have `getrandom` and likely to be run soon after first booting,
//! or on a system with very few entropy sources, one should consider using `/dev/random` via
//! `ReaderRng`.
//! - On some systems (e.g. FreeBSD, OpenBSD and Mac OS X) there is no difference
//! between the two sources. (Also note that, on some systems e.g. FreeBSD, both `/dev/random`
//! and `/dev/urandom` may block once if the CSPRNG has not seeded yet.)

View File

@ -15,45 +15,149 @@ pub use self::imp::OsRng;
#[cfg(all(unix, not(target_os = "ios")))]
mod imp {
extern crate libc;
use io::{IoResult, File};
use path::Path;
use rand::Rng;
use rand::reader::ReaderRng;
use result::{Ok, Err};
use slice::{ImmutableSlice, MutableSlice};
use mem;
use os::errno;
#[cfg(all(target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
fn getrandom(buf: &mut [u8]) -> libc::c_long {
extern "C" {
fn syscall(number: libc::c_long, ...) -> libc::c_long;
}
#[cfg(target_arch = "x86_64")]
const NR_GETRANDOM: libc::c_long = 318;
#[cfg(target_arch = "x86")]
const NR_GETRANDOM: libc::c_long = 355;
#[cfg(target_arch = "arm")]
const NR_GETRANDOM: libc::c_long = 384;
unsafe {
syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0u)
}
}
#[cfg(not(all(target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 }
fn getrandom_fill_bytes(v: &mut [u8]) {
let mut read = 0;
let len = v.len();
while read < len {
let result = getrandom(v[mut read..]);
if result == -1 {
let err = errno() as libc::c_int;
if err == libc::EINTR {
continue;
} else {
panic!("unexpected getrandom error: {}", err);
}
} else {
read += result as uint;
}
}
}
fn getrandom_next_u32() -> u32 {
let mut buf: [u8, ..4] = [0u8, ..4];
getrandom_fill_bytes(&mut buf);
unsafe { mem::transmute::<[u8, ..4], u32>(buf) }
}
fn getrandom_next_u64() -> u64 {
let mut buf: [u8, ..8] = [0u8, ..8];
getrandom_fill_bytes(&mut buf);
unsafe { mem::transmute::<[u8, ..8], u64>(buf) }
}
#[cfg(all(target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm")))]
fn is_getrandom_available() -> bool {
use sync::atomic::{AtomicBool, INIT_ATOMIC_BOOL, Relaxed};
static GETRANDOM_CHECKED: AtomicBool = INIT_ATOMIC_BOOL;
static GETRANDOM_AVAILABLE: AtomicBool = INIT_ATOMIC_BOOL;
if !GETRANDOM_CHECKED.load(Relaxed) {
let mut buf: [u8, ..0] = [];
let result = getrandom(&mut buf);
let available = if result == -1 {
let err = errno() as libc::c_int;
err != libc::ENOSYS
} else {
true
};
GETRANDOM_AVAILABLE.store(available, Relaxed);
GETRANDOM_CHECKED.store(true, Relaxed);
available
} else {
GETRANDOM_AVAILABLE.load(Relaxed)
}
}
#[cfg(not(all(target_os = "linux",
any(target_arch = "x86_64", target_arch = "x86", target_arch = "arm"))))]
fn is_getrandom_available() -> bool { false }
/// A random number generator that retrieves randomness straight from
/// the operating system. Platform sources:
///
/// - Unix-like systems (Linux, Android, Mac OSX): read directly from
/// `/dev/urandom`.
/// `/dev/urandom`, or from `getrandom(2)` system call if available.
/// - Windows: calls `CryptGenRandom`, using the default cryptographic
/// service provider with the `PROV_RSA_FULL` type.
/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
/// This does not block.
#[cfg(unix)]
pub struct OsRng {
inner: ReaderRng<File>
inner: OsRngInner,
}
enum OsRngInner {
OsGetrandomRng,
OsReaderRng(ReaderRng<File>),
}
impl OsRng {
/// Create a new `OsRng`.
pub fn new() -> IoResult<OsRng> {
if is_getrandom_available() {
return Ok(OsRng { inner: OsGetrandomRng });
}
let reader = try!(File::open(&Path::new("/dev/urandom")));
let reader_rng = ReaderRng::new(reader);
Ok(OsRng { inner: reader_rng })
Ok(OsRng { inner: OsReaderRng(reader_rng) })
}
}
impl Rng for OsRng {
fn next_u32(&mut self) -> u32 {
self.inner.next_u32()
match self.inner {
OsGetrandomRng => getrandom_next_u32(),
OsReaderRng(ref mut rng) => rng.next_u32(),
}
}
fn next_u64(&mut self) -> u64 {
self.inner.next_u64()
match self.inner {
OsGetrandomRng => getrandom_next_u64(),
OsReaderRng(ref mut rng) => rng.next_u64(),
}
}
fn fill_bytes(&mut self, v: &mut [u8]) {
self.inner.fill_bytes(v)
match self.inner {
OsGetrandomRng => getrandom_fill_bytes(v),
OsReaderRng(ref mut rng) => rng.fill_bytes(v)
}
}
}
}
@ -75,7 +179,7 @@ mod imp {
/// the operating system. Platform sources:
///
/// - Unix-like systems (Linux, Android, Mac OSX): read directly from
/// `/dev/urandom`.
/// `/dev/urandom`, or from `getrandom(2)` system call if available.
/// - Windows: calls `CryptGenRandom`, using the default cryptographic
/// service provider with the `PROV_RSA_FULL` type.
/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
@ -145,10 +249,10 @@ mod imp {
/// the operating system. Platform sources:
///
/// - Unix-like systems (Linux, Android, Mac OSX): read directly from
/// `/dev/urandom`.
/// `/dev/urandom`, or from `getrandom(2)` system call if available.
/// - Windows: calls `CryptGenRandom`, using the default cryptographic
/// service provider with the `PROV_RSA_FULL` type.
///
/// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed
/// This does not block.
pub struct OsRng {
hcryptprov: HCRYPTPROV