/
reseeding.rs
342 lines (300 loc) · 12.3 KB
/
reseeding.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// https://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//! A wrapper around another PRNG that reseeds it after it
//! generates a certain number of random bytes.
use core::mem::size_of;
use rand_core::{RngCore, CryptoRng, SeedableRng, Error, ErrorKind};
use rand_core::block::{BlockRngCore, BlockRng};
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
extern crate libc;
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT,
AtomicBool, ATOMIC_BOOL_INIT, Ordering};
/// A wrapper around any PRNG which reseeds the underlying PRNG after it has
/// generated a certain number of random bytes.
///
/// When the RNG gets cloned, the clone is reseeded on first use.
///
/// 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'.
///
/// Some small non-cryptographic PRNGs can have very small periods, for
/// example less than 2<sup>64</sup>. Would reseeding help to ensure that you do
/// not wrap around at the end of the period? A period of 2<sup>64</sup> 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 you use `ReseedingRng`?
///
/// - 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.
///
/// # 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.
/// If handling the source error fails `ReseedingRng` will continue generating
/// data from the wrapped PRNG without reseeding.
#[derive(Debug)]
pub struct ReseedingRng<R, Rsdr>(BlockRng<ReseedingCore<R, Rsdr>>)
where R: BlockRngCore + SeedableRng,
Rsdr: RngCore;
impl<R, Rsdr> ReseedingRng<R, Rsdr>
where R: BlockRngCore + SeedableRng,
Rsdr: RngCore
{
/// Create a new `ReseedingRng` 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 {
ReseedingRng(BlockRng::new(ReseedingCore::new(rng, threshold, reseeder)))
}
/// Reseed the internal PRNG.
pub fn reseed(&mut self) -> Result<(), Error> {
self.0.core.reseed()
}
}
// TODO: this should be implemented for any type where the inner type
// implements RngCore, but we can't specify that because ReseedingCore is private
impl<R, Rsdr: RngCore> RngCore for ReseedingRng<R, Rsdr>
where R: BlockRngCore<Item = u32> + SeedableRng,
<R as BlockRngCore>::Results: AsRef<[u32]> + AsMut<[u32]>
{
#[inline(always)]
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
#[inline(always)]
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
fn fill_bytes(&mut self, dest: &mut [u8]) {
self.0.fill_bytes(dest)
}
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
self.0.try_fill_bytes(dest)
}
}
impl<R, Rsdr> Clone for ReseedingRng<R, Rsdr>
where R: BlockRngCore + SeedableRng + Clone,
Rsdr: RngCore + Clone
{
fn clone(&self) -> ReseedingRng<R, Rsdr> {
// Recreating `BlockRng` seems easier than cloning it and resetting
// the index.
ReseedingRng(BlockRng::new(self.0.core.clone()))
}
}
impl<R, Rsdr> CryptoRng for ReseedingRng<R, Rsdr>
where R: BlockRngCore + SeedableRng + CryptoRng,
Rsdr: RngCore + CryptoRng {}
#[derive(Debug)]
struct ReseedingCore<R, Rsdr> {
inner: R,
reseeder: Rsdr,
threshold: i64,
bytes_until_reseed: i64,
fork_counter: usize,
}
impl<R, Rsdr> BlockRngCore for ReseedingCore<R, Rsdr>
where R: BlockRngCore + SeedableRng,
Rsdr: RngCore
{
type Item = <R as BlockRngCore>::Item;
type Results = <R as BlockRngCore>::Results;
fn generate(&mut self, results: &mut Self::Results) {
let global_fork_counter = 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, global_fork_counter);
}
let num_bytes = results.as_ref().len() * size_of::<Self::Item>();
self.bytes_until_reseed -= num_bytes as i64;
self.inner.generate(results);
}
}
impl<R, Rsdr> ReseedingCore<R, Rsdr>
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);
register_fork_handler();
ReseedingCore {
inner: rng,
reseeder,
threshold: threshold as i64,
bytes_until_reseed: threshold as i64,
fork_counter: 0,
}
}
/// Reseed the internal PRNG.
fn reseed(&mut self) -> Result<(), Error> {
R::from_rng(&mut self.reseeder).map(|result| {
self.bytes_until_reseed = self.threshold;
self.inner = result
})
}
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 <Self as BlockRngCore>::Results,
global_fork_counter: usize)
{
if self.is_forked(global_fork_counter) {
info!("Fork detected, reseeding RNG");
} else {
// FIXME: the amount logged here may not be correct if the intial
// `bytes_until_reseed` was set by a delay instead of `threshold`.
trace!("Reseeding RNG after {} generated bytes",
self.threshold - self.bytes_until_reseed);
}
let num_bytes =
results.as_ref().len() * size_of::<<R as BlockRngCore>::Item>();
let threshold = if let Err(e) = self.reseed() {
let delay = match e.kind {
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);
delay
} else {
self.fork_counter = global_fork_counter;
self.threshold
};
self.bytes_until_reseed = threshold - num_bytes as i64;
self.inner.generate(results);
}
}
impl<R, Rsdr> Clone for ReseedingCore<R, Rsdr>
where R: BlockRngCore + SeedableRng + Clone,
Rsdr: RngCore + Clone
{
fn clone(&self) -> ReseedingCore<R, Rsdr> {
ReseedingCore {
inner: self.inner.clone(),
reseeder: self.reseeder.clone(),
threshold: self.threshold,
bytes_until_reseed: 0, // reseed clone on first use
fork_counter: self.fork_counter,
}
}
}
impl<R, Rsdr> CryptoRng for ReseedingCore<R, Rsdr>
where R: BlockRngCore + SeedableRng + CryptoRng,
Rsdr: RngCore + CryptoRng {}
// 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 a soon as possible.
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
static RESEEDING_RNG_FORK_COUNTER: AtomicUsize = ATOMIC_USIZE_INIT;
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
fn get_fork_counter() -> usize {
RESEEDING_RNG_FORK_COUNTER.load(Ordering::Relaxed)
}
#[cfg(not(all(feature="std", unix, not(target_os="emscripten"))))]
fn get_fork_counter() -> usize { 0 }
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
static FORK_HANDLER_REGISTERED: AtomicBool = ATOMIC_BOOL_INIT;
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
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);
}
#[cfg(all(feature="std", unix, not(target_os="emscripten")))]
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"))))]
fn register_fork_handler() {}
#[cfg(test)]
mod test {
use {Rng, SeedableRng};
use prng::chacha::ChaChaCore;
use rngs::mock::StepRng;
use super::ReseedingRng;
#[test]
fn test_reseeding() {
let mut zero = StepRng::new(0, 0);
let rng = ChaChaCore::from_rng(&mut zero).unwrap();
let mut reseeding = ReseedingRng::new(rng, 32*4, zero);
// Currently we only support for arrays up to length 32.
// TODO: cannot generate seq via Rng::gen because it uses different alg
let mut buf = [0u32; 32]; // Needs to be a multiple of the RNGs result
// size to test exactly.
reseeding.fill(&mut buf);
let seq = buf;
for _ in 0..10 {
reseeding.fill(&mut buf);
assert_eq!(buf, seq);
}
}
#[test]
fn test_clone_reseeding() {
let mut zero = StepRng::new(0, 0);
let rng = ChaChaCore::from_rng(&mut zero).unwrap();
let mut rng1 = ReseedingRng::new(rng, 32*4, zero);
let first: u32 = rng1.gen();
for _ in 0..10 { let _ = rng1.gen::<u32>(); }
let mut rng2 = rng1.clone();
assert_eq!(first, rng2.gen::<u32>());
}
}