From fed7943c45d73343a4094ea8cc9f339362e011c5 Mon Sep 17 00:00:00 2001 From: Diggory Hardy Date: Mon, 8 Jan 2018 12:35:37 +0000 Subject: [PATCH] OsRng: merge in changes from dhardy/master: better error handling, -nacl This removes support for the NaCl target, which appears to be dead. --- src/impls.rs | 36 +++++ src/lib.rs | 3 +- src/os.rs | 365 +++++++++++++++++++-------------------------------- 3 files changed, 170 insertions(+), 234 deletions(-) diff --git a/src/impls.rs b/src/impls.rs index 34c17786c1f..6e005965391 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -167,4 +167,40 @@ pub fn next_u64_via_fill(rng: &mut R) -> u64 { impl_uint_from_fill!(rng, u64, 8) } +/// Implement `fill_bytes` via `try_fill` with implicit error handling. +pub fn fill_via_try_fill(rng: &mut R, dest: &mut [u8]) { + const WAIT_DUR_MS: u32 = 100; + const MAX_WAIT: u32 = (1 * 60 * 1000) / WAIT_DUR_MS; + const TRANSIENT_STEP: u32 = MAX_WAIT / 8; + let mut err_count = 0; + + loop { + if let Err(e) = rng.try_fill_bytes(dest) { + if e.kind().should_retry() { + if err_count > MAX_WAIT { + // TODO: log details & cause? + panic!("Too many RNG errors or timeout; last error: {}", e.msg()); + } + + if e.kind().should_wait() { + #[cfg(feature="std")]{ + let dur = ::std::time::Duration::from_millis(WAIT_DUR_MS as u64); + ::std::thread::sleep(dur); + } + err_count += 1; + } else { + err_count += TRANSIENT_STEP; + } + + continue; + } + + // TODO: log details & cause? + panic!("Fatal RNG error: {}", e.msg()); + } + + break; + } +} + // TODO: implement tests for the above diff --git a/src/lib.rs b/src/lib.rs index fb272dbe70a..75978e5512d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -253,7 +253,6 @@ use core::marker; use core::mem; #[cfg(feature="std")] use std::cell::RefCell; -#[cfg(feature="std")] use std::io; #[cfg(feature="std")] use std::rc::Rc; // external rngs @@ -817,7 +816,7 @@ impl StdRng { /// Reading the randomness from the OS may fail, and any error is /// propagated via the `io::Result` return value. #[cfg(feature="std")] - pub fn new() -> io::Result { + pub fn new() -> Result { match OsRng::new() { Ok(mut r) => Ok(StdRng { rng: r.gen() }), Err(e1) => { diff --git a/src/os.rs b/src/os.rs index f49f2a70835..5e0c28f3a21 100644 --- a/src/os.rs +++ b/src/os.rs @@ -11,11 +11,15 @@ //! Interfaces to the operating system provided random number //! generators. -use std::{io, mem, fmt}; -use {Rng, Error}; +use std::fmt; +use std::io::Read; + +use {Rng, Error, ErrorKind, impls}; /// A random number generator that retrieves randomness straight from -/// the operating system. Platform sources: +/// the operating system. +/// +/// Platform sources: /// /// - Unix-like systems (Linux, Android, Mac OSX): read directly from /// `/dev/urandom`, or from `getrandom(2)` system call if available. @@ -24,7 +28,6 @@ use {Rng, Error}; /// - Windows: calls `RtlGenRandom`, exported from `advapi32.dll` as /// `SystemFunction036`. /// - iOS: calls SecRandomCopyBytes as /dev/(u)random is sandboxed. -/// - PNaCl: calls into the `nacl-irt-random-0.1` IRT interface. /// /// This usually does not block. On some systems (e.g. FreeBSD, OpenBSD, /// Max OS X, and modern Linux) this may block very early in the init @@ -32,41 +35,53 @@ use {Rng, Error}; /// /// [1] See for a more /// in-depth discussion. +#[allow(unused)] // not used by all targets pub struct OsRng(imp::OsRng); +impl fmt::Debug for OsRng { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + impl OsRng { /// Create a new `OsRng`. - pub fn new() -> io::Result { + pub fn new() -> Result { imp::OsRng::new().map(OsRng) } } impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { self.0.next_u32() } - fn next_u64(&mut self) -> u64 { self.0.next_u64() } - fn fill_bytes(&mut self, v: &mut [u8]) { self.0.fill_bytes(v) } - fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { - // TODO: error handling per variant - Ok(self.0.fill_bytes(dest)) + fn next_u32(&mut self) -> u32 { + impls::next_u32_via_fill(self) } -} -impl fmt::Debug for OsRng { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "OsRng {{}}") + fn next_u64(&mut self) -> u64 { + impls::next_u64_via_fill(self) } -} -fn next_u32(fill_buf: &mut FnMut(&mut [u8])) -> u32 { - let mut buf: [u8; 4] = [0; 4]; - fill_buf(&mut buf); - unsafe { mem::transmute::<[u8; 4], u32>(buf) } + fn fill_bytes(&mut self, dest: &mut [u8]) { + impls::fill_via_try_fill(self, dest) + } + + fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { + self.0.try_fill_bytes(v) + } } -fn next_u64(fill_buf: &mut FnMut(&mut [u8])) -> u64 { - let mut buf: [u8; 8] = [0; 8]; - fill_buf(&mut buf); - unsafe { mem::transmute::<[u8; 8], u64>(buf) } +// Specialisation of `ReadRng` for our purposes +#[derive(Debug)] +struct ReadRng (R); + +impl ReadRng { + #[allow(unused)] // not used by all targets + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> { + if dest.len() == 0 { return Ok(()); } + // Use `std::io::read_exact`, which retries on `ErrorKind::Interrupted`. + self.0.read_exact(dest).map_err(|err| { + Error::with_cause(ErrorKind::Unavailable, "error reading random device", err) + }) + } } #[cfg(all(unix, not(target_os = "ios"), @@ -78,13 +93,12 @@ fn next_u64(fill_buf: &mut FnMut(&mut [u8])) -> u64 { mod imp { extern crate libc; - use super::{next_u32, next_u64}; use self::OsRngInner::*; + use super::ReadRng; + use {Error, ErrorKind}; use std::io; use std::fs::File; - use Rng; - use read::ReadRng; #[cfg(all(target_os = "linux", any(target_arch = "x86_64", @@ -107,9 +121,11 @@ mod imp { const NR_GETRANDOM: libc::c_long = 278; #[cfg(target_arch = "powerpc")] const NR_GETRANDOM: libc::c_long = 384; + + const GRND_NONBLOCK: libc::c_uint = 0x0001; unsafe { - syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), 0) + syscall(NR_GETRANDOM, buf.as_mut_ptr(), buf.len(), GRND_NONBLOCK) } } @@ -121,22 +137,33 @@ mod imp { target_arch = "powerpc"))))] fn getrandom(_buf: &mut [u8]) -> libc::c_long { -1 } - fn getrandom_fill_bytes(v: &mut [u8]) { + fn getrandom_try_fill(v: &mut [u8]) -> Result<(), Error> { let mut read = 0; let len = v.len(); while read < len { let result = getrandom(&mut v[read..]); if result == -1 { let err = io::Error::last_os_error(); - if err.kind() == io::ErrorKind::Interrupted { - continue + let kind = err.kind(); + if kind == io::ErrorKind::Interrupted { + continue; + } else if kind == io::ErrorKind::WouldBlock { + // Potentially this would waste bytes, but since we use + // /dev/urandom blocking only happens if not initialised. + // Also, wasting the bytes in v doesn't matter very much. + return Err(Error::new(ErrorKind::NotReady, "getrandom not ready")); } else { - panic!("unexpected getrandom error: {}", err); + return Err(Error::with_cause( + ErrorKind::Unavailable, + "unexpected getrandom error", + err, + )); } } else { read += result as usize; } } + Ok(()) } #[cfg(all(target_os = "linux", @@ -175,45 +202,35 @@ mod imp { target_arch = "powerpc"))))] fn is_getrandom_available() -> bool { false } + #[derive(Debug)] pub struct OsRng { inner: OsRngInner, } + #[derive(Debug)] enum OsRngInner { OsGetrandomRng, OsReadRng(ReadRng), } impl OsRng { - pub fn new() -> io::Result { + pub fn new() -> Result { if is_getrandom_available() { return Ok(OsRng { inner: OsGetrandomRng }); } - let reader = try!(File::open("/dev/urandom")); - let reader_rng = ReadRng::new(reader); + let reader = File::open("/dev/urandom").map_err(|err| { + Error::with_cause(ErrorKind::Unavailable, "error opening random device", err) + })?; + let reader_rng = ReadRng(reader); Ok(OsRng { inner: OsReadRng(reader_rng) }) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { + + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { match self.inner { - OsGetrandomRng => next_u32(&mut getrandom_fill_bytes), - OsReadRng(ref mut rng) => rng.next_u32(), - } - } - fn next_u64(&mut self) -> u64 { - match self.inner { - OsGetrandomRng => next_u64(&mut getrandom_fill_bytes), - OsReadRng(ref mut rng) => rng.next_u64(), - } - } - fn fill_bytes(&mut self, v: &mut [u8]) { - match self.inner { - OsGetrandomRng => getrandom_fill_bytes(v), - OsReadRng(ref mut rng) => rng.fill_bytes(v) + OsGetrandomRng => getrandom_try_fill(v), + OsReadRng(ref mut rng) => rng.try_fill_bytes(v) } } } @@ -223,10 +240,9 @@ mod imp { mod imp { extern crate libc; - use super::{next_u32, next_u64}; - + use {Error, ErrorKind}; + use std::io; - use Rng; use self::libc::{c_int, size_t}; #[derive(Debug)] @@ -244,24 +260,20 @@ mod imp { } impl OsRng { - pub fn new() -> io::Result { + pub fn new() -> Result { Ok(OsRng) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - next_u32(&mut |v| self.fill_bytes(v)) - } - fn next_u64(&mut self) -> u64 { - next_u64(&mut |v| self.fill_bytes(v)) - } - fn fill_bytes(&mut self, v: &mut [u8]) { + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { let ret = unsafe { SecRandomCopyBytes(kSecRandomDefault, v.len() as size_t, v.as_mut_ptr()) }; if ret == -1 { - panic!("couldn't generate random bytes: {}", io::Error::last_os_error()); + Err(Error::with_cause( + ErrorKind::Unavailable, + "couldn't generate random bytes", + io::Error::last_os_error())) + } else { + Ok(()) } } } @@ -271,28 +283,18 @@ mod imp { mod imp { extern crate libc; - use std::{io, ptr}; - use Rng; - - use super::{next_u32, next_u64}; + use {Error, ErrorKind}; + + use std::ptr; #[derive(Debug)] pub struct OsRng; impl OsRng { - pub fn new() -> io::Result { + pub fn new() -> Result { Ok(OsRng) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - next_u32(&mut |v| self.fill_bytes(v)) - } - fn next_u64(&mut self) -> u64 { - next_u64(&mut |v| self.fill_bytes(v)) - } - fn fill_bytes(&mut self, v: &mut [u8]) { + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { let mib = [libc::CTL_KERN, libc::KERN_ARND]; // kern.arandom permits a maximum buffer size of 256 bytes for s in v.chunks_mut(256) { @@ -303,10 +305,12 @@ mod imp { ptr::null(), 0) }; if ret == -1 || s_len != s.len() { - panic!("kern.arandom sysctl failed! (returned {}, s.len() {}, oldlenp {})", - ret, s.len(), s_len); + return Err(Error::new( + ErrorKind::Unavailable, + "kern.arandom sysctl failed")); } } + Ok(()) } } } @@ -315,48 +319,42 @@ mod imp { mod imp { extern crate libc; + use {Error, ErrorKind}; + use std::io; - use Rng; - - use super::{next_u32, next_u64}; #[derive(Debug)] pub struct OsRng; impl OsRng { - pub fn new() -> io::Result { + pub fn new() -> Result { Ok(OsRng) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - next_u32(&mut |v| self.fill_bytes(v)) - } - fn next_u64(&mut self) -> u64 { - next_u64(&mut |v| self.fill_bytes(v)) - } - fn fill_bytes(&mut self, v: &mut [u8]) { + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { // getentropy(2) permits a maximum buffer size of 256 bytes for s in v.chunks_mut(256) { let ret = unsafe { libc::getentropy(s.as_mut_ptr() as *mut libc::c_void, s.len()) }; if ret == -1 { - let err = io::Error::last_os_error(); - panic!("getentropy failed: {}", err); + return Err(Error::with_cause( + ErrorKind::Unavailable, + "getentropy failed", + io::Error::last_os_error())); } } + Ok(()) } } } #[cfg(target_os = "redox")] mod imp { + use {Error, ErrorKind}; + use std::io; use std::fs::File; - use Rng; - use read::ReadRng; + use super::ReadRng; #[derive(Debug)] pub struct OsRng { @@ -364,23 +362,16 @@ mod imp { } impl OsRng { - pub fn new() -> io::Result { - let reader = try!(File::open("rand:")); - let reader_rng = ReadRng::new(reader); + pub fn new() -> Result { + let reader = File::open("rand:").map_err(|err| { + Error::with_cause(ErrorKind::Unavailable, "error opening random device", err) + })?; + let reader_rng = ReadRng(reader); Ok(OsRng { inner: reader_rng }) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - self.inner.next_u32() - } - fn next_u64(&mut self) -> u64 { - self.inner.next_u64() - } - fn fill_bytes(&mut self, v: &mut [u8]) { - self.inner.fill_bytes(v) + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { + self.inner.try_fill_bytes(v) } } } @@ -389,37 +380,33 @@ mod imp { mod imp { extern crate fuchsia_zircon; + use {Error, ErrorKind}; + use std::io; - use Rng; - - use super::{next_u32, next_u64}; #[derive(Debug)] pub struct OsRng; impl OsRng { - pub fn new() -> io::Result { + pub fn new() -> Result { Ok(OsRng) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - next_u32(&mut |v| self.fill_bytes(v)) - } - fn next_u64(&mut self) -> u64 { - next_u64(&mut |v| self.fill_bytes(v)) - } - fn fill_bytes(&mut self, v: &mut [u8]) { + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { for s in v.chunks_mut(fuchsia_zircon::sys::ZX_CPRNG_DRAW_MAX_LEN) { let mut filled = 0; while filled < s.len() { match fuchsia_zircon::cprng_draw(&mut s[filled..]) { Ok(actual) => filled += actual, - Err(e) => panic!("cprng_draw failed: {:?}", e), + Err(e) => { + return Err(Error::with_cause( + ErrorKind::Unavailable, + "cprng_draw failed", + e)); + } }; } } + Ok(()) } } } @@ -427,11 +414,10 @@ mod imp { #[cfg(windows)] mod imp { extern crate winapi; - + + use {Error, ErrorKind}; + use std::io; - use Rng; - - use super::{next_u32, next_u64}; use self::winapi::shared::minwindef::ULONG; use self::winapi::um::ntsecapi::RtlGenRandom; @@ -441,19 +427,10 @@ mod imp { pub struct OsRng; impl OsRng { - pub fn new() -> io::Result { + pub fn new() -> Result { Ok(OsRng) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - next_u32(&mut |v| self.fill_bytes(v)) - } - fn next_u64(&mut self) -> u64 { - next_u64(&mut |v| self.fill_bytes(v)) - } - fn fill_bytes(&mut self, v: &mut [u8]) { + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { // RtlGenRandom takes an ULONG (u32) for the length so we need to // split up the buffer. for slice in v.chunks_mut(::max_value() as usize) { @@ -461,86 +438,13 @@ mod imp { RtlGenRandom(slice.as_mut_ptr() as PVOID, slice.len() as ULONG) }; if ret == 0 { - panic!("couldn't generate random bytes: {}", - io::Error::last_os_error()); + return Err(Error::with_cause( + ErrorKind::Unavailable, + "couldn't generate random bytes", + io::Error::last_os_error())); } } - } - } -} - -#[cfg(target_os = "nacl")] -mod imp { - extern crate libc; - - use std::io; - use std::mem; - use Rng; - - use super::{next_u32, next_u64}; - - #[derive(Debug)] - pub struct OsRng(extern fn(dest: *mut libc::c_void, - bytes: libc::size_t, - read: *mut libc::size_t) -> libc::c_int); - - extern { - fn nacl_interface_query(name: *const libc::c_char, - table: *mut libc::c_void, - table_size: libc::size_t) -> libc::size_t; - } - - const INTERFACE: &'static [u8] = b"nacl-irt-random-0.1\0"; - - #[repr(C)] - struct NaClIRTRandom { - get_random_bytes: Option libc::c_int>, - } - - impl OsRng { - pub fn new() -> io::Result { - let mut iface = NaClIRTRandom { - get_random_bytes: None, - }; - let result = unsafe { - nacl_interface_query(INTERFACE.as_ptr() as *const _, - mem::transmute(&mut iface), - mem::size_of::() as libc::size_t) - }; - if result != 0 { - assert!(iface.get_random_bytes.is_some()); - let result = OsRng(iface.get_random_bytes.take().unwrap()); - Ok(result) - } else { - let error = io::ErrorKind::NotFound; - let error = io::Error::new(error, "IRT random interface missing"); - Err(error) - } - } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - next_u32(&mut |v| self.fill_bytes(v)) - } - fn next_u64(&mut self) -> u64 { - next_u64(&mut |v| self.fill_bytes(v)) - } - fn fill_bytes(&mut self, v: &mut [u8]) { - let mut read = 0; - loop { - let mut r: libc::size_t = 0; - let len = v.len(); - let error = (self.0)(v[read..].as_mut_ptr() as *mut _, - (len - read) as libc::size_t, - &mut r as *mut _); - assert!(error == 0, "`get_random_bytes` failed!"); - read += r as usize; - - if read >= v.len() { break; } - } + Ok(()) } } } @@ -554,14 +458,11 @@ mod imp { pub struct OsRng; impl OsRng { - pub fn new() -> io::Result { - Err(io::Error::new(io::ErrorKind::Other, "Not supported")) + pub fn new() -> Result { + Err(Error::new(ErrorKind::Unavailable, "not supported on WASM")) } - } - - impl Rng for OsRng { - fn next_u32(&mut self) -> u32 { - panic!("Not supported") + pub fn try_fill_bytes(&mut self, v: &mut [u8]) -> Result<(), Error> { + Err(Error::new(ErrorKind::Unavailable, "not supported on WASM")) } } }