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;