diff --git a/src/lib.rs b/src/lib.rs index 5df58e93311..e1987a825e6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -251,7 +251,7 @@ extern crate rand_core; #[cfg(feature = "log")] #[macro_use] extern crate log; #[cfg(not(feature = "log"))] macro_rules! trace { ($($x:tt)*) => () } #[cfg(not(feature = "log"))] macro_rules! debug { ($($x:tt)*) => () } -#[cfg(all(feature="std", not(feature = "log")))] macro_rules! info { ($($x:tt)*) => () } +#[cfg(not(feature = "log"))] macro_rules! info { ($($x:tt)*) => () } #[cfg(not(feature = "log"))] macro_rules! warn { ($($x:tt)*) => () } #[cfg(all(feature="std", not(feature = "log")))] macro_rules! error { ($($x:tt)*) => () } diff --git a/src/rngs/adapter/reseeding.rs b/src/rngs/adapter/reseeding.rs index 7ec8de51675..e8dca1d4230 100644 --- a/src/rngs/adapter/reseeding.rs +++ b/src/rngs/adapter/reseeding.rs @@ -16,44 +16,71 @@ use core::mem::size_of; use rand_core::{RngCore, CryptoRng, SeedableRng, Error, ErrorKind}; use rand_core::block::{BlockRngCore, BlockRng}; -/// A wrapper around any PRNG which reseeds the underlying PRNG after it has -/// generated a certain number of random bytes. +/// A wrapper around any PRNG that implements [`BlockRngCore`], that adds the +/// ability to reseed it. /// -/// When the RNG gets cloned, the clone is reseeded on first use. +/// `ReseedingRng` reseeds the underlying PRNG in the following cases: /// -/// Reseeding is never strictly *necessary*. Cryptographic PRNGs don't have a -/// limited number of bytes they can output, or at least not a limit reachable -/// in any practical way. There is no such thing as 'running out of entropy'. +/// - On a manual call to [`reseed()`]. +/// - After `clone()`, the clone will be reseeded on first use. +/// - After a process is forked, the RNG in the child process is reseeded within +/// the next few generated values, depending on the block size of the +/// underlying PRNG. For [`ChaChaCore`] and [`Hc128Core`] this is a maximum of +/// 15 `u32` values before reseeding. +/// - After the PRNG has generated a configurable number of random bytes. /// -/// Some small non-cryptographic PRNGs can have very small periods, for -/// example less than 264. Would reseeding help to ensure that you do -/// not wrap around at the end of the period? A period of 264 still -/// takes several centuries of CPU-years on current hardware. Reseeding will -/// actually make things worse, because the reseeded PRNG will just continue -/// somewhere else *in the same period*, with a high chance of overlapping with -/// previously used parts of it. +/// # When should reseeding after a fixed number of generated bytes be used? /// -/// # When should you use `ReseedingRng`? +/// Reseeding after a fixed number of generated bytes is never strictly +/// *necessary*. Cryptographic PRNGs don't have a limited number of bytes they +/// can output, or at least not a limit reachable in any practical way. There is +/// no such thing as 'running out of entropy'. /// -/// - Reseeding can be seen as some form of 'security in depth'. Even if in the -/// future a cryptographic weakness is found in the CSPRNG being used, -/// occasionally reseeding should make exploiting it much more difficult or -/// even impossible. -/// - It can be used as a poor man's cryptography (not recommended, just use a -/// good CSPRNG). Previous implementations of `thread_rng` for example used -/// `ReseedingRng` with the ISAAC RNG. That algorithm, although apparently -/// strong and with no known attack, does not come with any proof of security -/// and does not meet the current standards for a cryptographically secure -/// PRNG. By reseeding it frequently (every 32 kiB) it seems safe to assume -/// there is no attack that can operate on the tiny window between reseeds. +/// Occasionally reseeding can be seen as some form of 'security in depth'. Even +/// if in the future a cryptographic weakness is found in the CSPRNG being used, +/// or a flaw in the implementation, occasionally reseeding should make +/// exploiting it much more difficult or even impossible. +/// +/// Use [`ReseedingRng::new`] with a `threshold` of `0` to disable reseeding +/// after a fixed number of generated bytes. /// /// # Error handling /// -/// Although extremely unlikely, reseeding the wrapped PRNG can fail. -/// `ReseedingRng` will never panic but try to handle the error intelligently -/// through some combination of retrying and delaying reseeding until later. +/// Although unlikely, reseeding the wrapped PRNG can fail. `ReseedingRng` will +/// never panic but try to handle the error intelligently through some +/// combination of retrying and delaying reseeding until later. /// If handling the source error fails `ReseedingRng` will continue generating /// data from the wrapped PRNG without reseeding. +/// +/// Manually calling [`reseed()`] will not have this retry or delay logic, but +/// reports the error. +/// +/// # Example +/// +/// ``` +/// use rand::prelude::*; +/// use rand::prng::chacha::ChaChaCore; // Internal part of ChaChaRng that +/// // implements BlockRngCore +/// use rand::rngs::OsRng; +/// use rand::rngs::adapter::ReseedingRng; +/// +/// let prng = ChaChaCore::from_entropy(); +// FIXME: it is better to use EntropyRng as reseeder, but that doesn't implement +// clone yet. +/// let reseeder = OsRng::new().unwrap(); +/// let mut reseeding_rng = ReseedingRng::new(prng, 0, reseeder); +/// +/// println!("{}", reseeding_rng.gen::()); +/// +/// let mut cloned_rng = reseeding_rng.clone(); +/// assert!(reseeding_rng.gen::() != cloned_rng.gen::()); +/// ``` +/// +/// [`ChaChaCore`]: ../../prng/chacha/struct.ChaChaCore.html +/// [`Hc128Core`]: ../../prng/hc128/struct.Hc128Core.html +/// [`BlockRngCore`]: ../../../rand_core/block/trait.BlockRngCore.html +/// [`ReseedingRng::new`]: struct.ReseedingRng.html#method.new +/// [`reseed()`]: struct.ReseedingRng.html#method.reseed #[derive(Debug)] pub struct ReseedingRng(BlockRng>) where R: BlockRngCore + SeedableRng, @@ -63,13 +90,12 @@ impl ReseedingRng where R: BlockRngCore + SeedableRng, Rsdr: RngCore { - /// Create a new `ReseedingRng` with the given parameters. + /// Create a new `ReseedingRng` from an existing PRNG, combined with a RNG + /// to use as reseeder. /// - /// # Arguments - /// - /// * `rng`: the random number generator to use. - /// * `threshold`: the number of generated bytes after which to reseed the RNG. - /// * `reseeder`: the RNG to use for reseeding. + /// `threshold` sets the number of generated bytes after which to reseed the + /// PRNG. Set it to zero to never reseed based on the number of generated + /// values. pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { ReseedingRng(BlockRng::new(ReseedingCore::new(rng, threshold, reseeder))) } @@ -126,6 +152,7 @@ struct ReseedingCore { reseeder: Rsdr, threshold: i64, bytes_until_reseed: i64, + fork_counter: usize, } impl BlockRngCore for ReseedingCore @@ -136,11 +163,13 @@ where R: BlockRngCore + SeedableRng, type Results = ::Results; fn generate(&mut self, results: &mut Self::Results) { - if self.bytes_until_reseed <= 0 { - // We get better performance by not calling only `auto_reseed` here + let global_fork_counter = fork::get_fork_counter(); + if self.bytes_until_reseed <= 0 || + self.is_forked(global_fork_counter) { + // We get better performance by not calling only `reseed` here // and continuing with the rest of the function, but by directly // returning from a non-inlined function. - return self.reseed_and_generate(results); + return self.reseed_and_generate(results, global_fork_counter); } let num_bytes = results.as_ref().len() * size_of::(); self.bytes_until_reseed -= num_bytes as i64; @@ -152,20 +181,26 @@ impl ReseedingCore where R: BlockRngCore + SeedableRng, Rsdr: RngCore { - /// Create a new `ReseedingCore` with the given parameters. - /// - /// # Arguments - /// - /// * `rng`: the random number generator to use. - /// * `threshold`: the number of generated bytes after which to reseed the RNG. - /// * `reseeder`: the RNG to use for reseeding. - pub fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { - assert!(threshold <= ::core::i64::MAX as u64); + /// Create a new `ReseedingCore`. + fn new(rng: R, threshold: u64, reseeder: Rsdr) -> Self { + use ::core::i64::MAX; + fork::register_fork_handler(); + + // Because generating more values than `i64::MAX` takes centuries on + // current hardware, we just clamp to that value. + // Also we set a threshold of 0, which indicates no limit, to that + // value. + let threshold = + if threshold == 0 { MAX } + else if threshold <= MAX as u64 { threshold as i64 } + else { MAX }; + ReseedingCore { inner: rng, reseeder, threshold: threshold as i64, bytes_until_reseed: threshold as i64, + fork_counter: 0, } } @@ -177,26 +212,48 @@ where R: BlockRngCore + SeedableRng, }) } + fn is_forked(&self, global_fork_counter: usize) -> bool { + // In theory, on 32-bit platforms, it is possible for + // `global_fork_counter` to wrap around after ~4e9 forks. + // + // This check will detect a fork in the normal case where + // `fork_counter < global_fork_counter`, and also when the difference + // between both is greater than `isize::MAX` (wrapped around). + // + // It will still fail to detect a fork if there have been more than + // `isize::MAX` forks, without any reseed in between. Seems unlikely + // enough. + (self.fork_counter.wrapping_sub(global_fork_counter) as isize) < 0 + } + #[inline(never)] fn reseed_and_generate(&mut self, - results: &mut ::Results) + results: &mut ::Results, + global_fork_counter: usize) { - trace!("Reseeding RNG after {} generated bytes", - self.threshold - self.bytes_until_reseed); - let threshold = if let Err(e) = self.reseed() { + if self.is_forked(global_fork_counter) { + info!("Fork detected, reseeding RNG"); + } else { + trace!("Reseeding RNG (periodic reseed)"); + } + + let num_bytes = + results.as_ref().len() * size_of::<::Item>(); + + let threshold = if let Err(e) = self.reseed() { let delay = match e.kind { - ErrorKind::Transient => 0, + ErrorKind::Transient => num_bytes as i64, kind @ _ if kind.should_retry() => self.threshold >> 8, _ => self.threshold, }; warn!("Reseeding RNG delayed reseeding by {} bytes due to \ - error from source: {}", delay, e); + error from source: {}", delay, e); delay } else { + self.fork_counter = global_fork_counter; self.threshold }; - - let num_bytes = results.as_ref().len() * size_of::<::Item>(); + self.bytes_until_reseed = threshold - num_bytes as i64; self.inner.generate(results); } @@ -212,6 +269,7 @@ where R: BlockRngCore + SeedableRng + Clone, reseeder: self.reseeder.clone(), threshold: self.threshold, bytes_until_reseed: 0, // reseed clone on first use + fork_counter: self.fork_counter, } } } @@ -220,6 +278,55 @@ impl CryptoRng for ReseedingCore where R: BlockRngCore + SeedableRng + CryptoRng, Rsdr: RngCore + CryptoRng {} + +#[cfg(all(feature="std", unix, not(target_os="emscripten")))] +mod fork { + extern crate libc; + + use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; + use std::sync::atomic::{AtomicBool, ATOMIC_BOOL_INIT}; + + // Fork protection + // + // We implement fork protection on Unix using `pthread_atfork`. + // When the process is forked, we increment `RESEEDING_RNG_FORK_COUNTER`. + // Every `ReseedingRng` stores the last known value of the static in + // `fork_counter`. If the cached `fork_counter` is less than + // `RESEEDING_RNG_FORK_COUNTER`, it is time to reseed this RNG. + // + // If reseeding fails, we don't deal with this by setting a delay, but just + // don't update `fork_counter`, so a reseed is attempted as soon as + // possible. + + static RESEEDING_RNG_FORK_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT; + + pub fn get_fork_counter() -> usize { + RESEEDING_RNG_FORK_COUNTER.load(Ordering::Relaxed) + } + + static FORK_HANDLER_REGISTERED: AtomicBool = ATOMIC_BOOL_INIT; + + extern fn fork_handler() { + // Note: fetch_add is defined to wrap on overflow + // (which is what we want). + RESEEDING_RNG_FORK_COUNTER.fetch_add(1, Ordering::Relaxed); + } + + pub fn register_fork_handler() { + if FORK_HANDLER_REGISTERED.load(Ordering::Relaxed) == false { + unsafe { libc::pthread_atfork(None, None, Some(fork_handler)) }; + FORK_HANDLER_REGISTERED.store(true, Ordering::Relaxed); + } + } +} + +#[cfg(not(all(feature="std", unix, not(target_os="emscripten"))))] +mod fork { + pub fn get_fork_counter() -> usize { 0 } + pub fn register_fork_handler() {} +} + + #[cfg(test)] mod test { use {Rng, SeedableRng}; diff --git a/src/rngs/mod.rs b/src/rngs/mod.rs index 29546eb1360..bcc5ea5f9a4 100644 --- a/src/rngs/mod.rs +++ b/src/rngs/mod.rs @@ -15,6 +15,7 @@ //! - [`EntropyRng`], [`OsRng`] and [`JitterRng`] as entropy sources //! - [`mock::StepRng`] as a simple counter for tests //! - [`adapter::ReadRng`] to read from a file/stream +//! - [`adapter::ReseedingRng`] to reseed a PRNG on clone / process fork etc. //! //! # Background — Random number generators (RNGs) //! @@ -161,6 +162,7 @@ //! [`thread_rng`]: ../fn.thread_rng.html //! [`mock::StepRng`]: mock/struct.StepRng.html //! [`adapter::ReadRng`]: adapter/struct.ReadRng.html +//! [`adapter::ReseedingRng`]: adapter/struct.ReseedingRng.html //! [`ChaChaRng`]: ../prng/chacha/struct.ChaChaRng.html pub mod adapter;