Skip to content

Commit 050c1af

Browse files
authored
ThreadRng: fix use-after-free in TL-dtor; doc for sized iterators (#1035)
ThreadRng: fix use-after-free in TL-dtor + doc * Fixes #986 * Fixes #1022
1 parent 60d31c8 commit 050c1af

File tree

5 files changed

+50
-31
lines changed

5 files changed

+50
-31
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
1616
- Implement weighted sampling without replacement (#976, #1013)
1717

1818
### Changes
19+
- `ThreadRng` is no longer `Copy` to enable safe usage within thread-local destructors (see #968)
1920
- `gen_range(a, b)` was replaced with `gen_range(a..b)`, and `gen_range(a..=b)`
2021
is supported (#744, #1003). Note that `a` and `b` can no longer be references or SIMD types.
2122
- Replace `AsByteSliceMut` with `Fill` (#940)

src/distributions/mod.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -162,17 +162,21 @@ pub trait Distribution<T> {
162162
/// use rand::thread_rng;
163163
/// use rand::distributions::{Distribution, Alphanumeric, Uniform, Standard};
164164
///
165-
/// let rng = thread_rng();
165+
/// let mut rng = thread_rng();
166166
///
167167
/// // Vec of 16 x f32:
168-
/// let v: Vec<f32> = Standard.sample_iter(rng).take(16).collect();
168+
/// let v: Vec<f32> = Standard.sample_iter(&mut rng).take(16).collect();
169169
///
170170
/// // String:
171-
/// let s: String = Alphanumeric.sample_iter(rng).take(7).map(char::from).collect();
171+
/// let s: String = Alphanumeric
172+
/// .sample_iter(&mut rng)
173+
/// .take(7)
174+
/// .map(char::from)
175+
/// .collect();
172176
///
173177
/// // Dice-rolling:
174178
/// let die_range = Uniform::new_inclusive(1, 6);
175-
/// let mut roll_die = die_range.sample_iter(rng);
179+
/// let mut roll_die = die_range.sample_iter(&mut rng);
176180
/// while roll_die.next().unwrap() != 6 {
177181
/// println!("Not a 6; rolling again!");
178182
/// }

src/rng.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -165,21 +165,24 @@ pub trait Rng: RngCore {
165165
/// use rand::{thread_rng, Rng};
166166
/// use rand::distributions::{Alphanumeric, Uniform, Standard};
167167
///
168-
/// let rng = thread_rng();
168+
/// let mut rng = thread_rng();
169169
///
170170
/// // Vec of 16 x f32:
171-
/// let v: Vec<f32> = rng.sample_iter(Standard).take(16).collect();
171+
/// let v: Vec<f32> = (&mut rng).sample_iter(Standard).take(16).collect();
172172
///
173173
/// // String:
174-
/// let s: String = rng.sample_iter(Alphanumeric).take(7).map(char::from).collect();
174+
/// let s: String = (&mut rng).sample_iter(Alphanumeric)
175+
/// .take(7)
176+
/// .map(char::from)
177+
/// .collect();
175178
///
176179
/// // Combined values
177-
/// println!("{:?}", rng.sample_iter(Standard).take(5)
180+
/// println!("{:?}", (&mut rng).sample_iter(Standard).take(5)
178181
/// .collect::<Vec<(f64, bool)>>());
179182
///
180183
/// // Dice-rolling:
181184
/// let die_range = Uniform::new_inclusive(1, 6);
182-
/// let mut roll_die = rng.sample_iter(die_range);
185+
/// let mut roll_die = (&mut rng).sample_iter(die_range);
183186
/// while roll_die.next().unwrap() != 6 {
184187
/// println!("Not a 6; rolling again!");
185188
/// }

src/rngs/thread.rs

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
//! Thread-local random number generator
1010
1111
use core::cell::UnsafeCell;
12-
use core::ptr::NonNull;
12+
use std::rc::Rc;
1313
use std::thread_local;
1414

1515
use super::std::Core;
@@ -37,38 +37,42 @@ use crate::{CryptoRng, Error, RngCore, SeedableRng};
3737
// of 32 kB and less. We choose 64 kB to avoid significant overhead.
3838
const THREAD_RNG_RESEED_THRESHOLD: u64 = 1024 * 64;
3939

40-
/// The type returned by [`thread_rng`], essentially just a reference to the
41-
/// PRNG in thread-local memory.
40+
/// A reference to the thread-local generator
4241
///
43-
/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance.
44-
/// As hinted by the name, the generator is thread-local. `ThreadRng` is a
45-
/// handle to this generator and thus supports `Copy`, but not `Send` or `Sync`.
42+
/// An instance can be obtained via [`thread_rng`] or via `ThreadRng::default()`.
43+
/// This handle is safe to use everywhere (including thread-local destructors)
44+
/// but cannot be passed between threads (is not `Send` or `Sync`).
4645
///
47-
/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed
48-
/// the PRNG from fresh entropy every 64 kiB of random data.
49-
/// [`OsRng`] is used to provide seed data.
46+
/// `ThreadRng` uses the same PRNG as [`StdRng`] for security and performance
47+
/// and is automatically seeded from [`OsRng`].
5048
///
49+
/// Unlike `StdRng`, `ThreadRng` uses the [`ReseedingRng`] wrapper to reseed
50+
/// the PRNG from fresh entropy every 64 kiB of random data as well as after a
51+
/// fork on Unix (though not quite immediately; see documentation of
52+
/// [`ReseedingRng`]).
5153
/// Note that the reseeding is done as an extra precaution against side-channel
5254
/// attacks and mis-use (e.g. if somehow weak entropy were supplied initially).
5355
/// The PRNG algorithms used are assumed to be secure.
5456
///
5557
/// [`ReseedingRng`]: crate::rngs::adapter::ReseedingRng
5658
/// [`StdRng`]: crate::rngs::StdRng
5759
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))]
58-
#[derive(Copy, Clone, Debug)]
60+
#[derive(Clone, Debug)]
5961
pub struct ThreadRng {
60-
// inner raw pointer implies type is neither Send nor Sync
61-
rng: NonNull<ReseedingRng<Core, OsRng>>,
62+
// Rc is explictly !Send and !Sync
63+
rng: Rc<UnsafeCell<ReseedingRng<Core, OsRng>>>,
6264
}
6365

6466
thread_local!(
65-
static THREAD_RNG_KEY: UnsafeCell<ReseedingRng<Core, OsRng>> = {
67+
// We require Rc<..> to avoid premature freeing when thread_rng is used
68+
// within thread-local destructors. See #968.
69+
static THREAD_RNG_KEY: Rc<UnsafeCell<ReseedingRng<Core, OsRng>>> = {
6670
let r = Core::from_rng(OsRng).unwrap_or_else(|err|
6771
panic!("could not initialize thread_rng: {}", err));
6872
let rng = ReseedingRng::new(r,
6973
THREAD_RNG_RESEED_THRESHOLD,
7074
OsRng);
71-
UnsafeCell::new(rng)
75+
Rc::new(UnsafeCell::new(rng))
7276
}
7377
);
7478

@@ -81,9 +85,8 @@ thread_local!(
8185
/// For more information see [`ThreadRng`].
8286
#[cfg_attr(doc_cfg, doc(cfg(all(feature = "std", feature = "std_rng"))))]
8387
pub fn thread_rng() -> ThreadRng {
84-
let raw = THREAD_RNG_KEY.with(|t| t.get());
85-
let nn = NonNull::new(raw).unwrap();
86-
ThreadRng { rng: nn }
88+
let rng = THREAD_RNG_KEY.with(|t| t.clone());
89+
ThreadRng { rng }
8790
}
8891

8992
impl Default for ThreadRng {
@@ -92,23 +95,30 @@ impl Default for ThreadRng {
9295
}
9396
}
9497

98+
impl ThreadRng {
99+
#[inline(always)]
100+
fn rng(&mut self) -> &mut ReseedingRng<Core, OsRng> {
101+
unsafe { &mut *self.rng.get() }
102+
}
103+
}
104+
95105
impl RngCore for ThreadRng {
96106
#[inline(always)]
97107
fn next_u32(&mut self) -> u32 {
98-
unsafe { self.rng.as_mut().next_u32() }
108+
self.rng().next_u32()
99109
}
100110

101111
#[inline(always)]
102112
fn next_u64(&mut self) -> u64 {
103-
unsafe { self.rng.as_mut().next_u64() }
113+
self.rng().next_u64()
104114
}
105115

106116
fn fill_bytes(&mut self, dest: &mut [u8]) {
107-
unsafe { self.rng.as_mut().fill_bytes(dest) }
117+
self.rng().fill_bytes(dest)
108118
}
109119

110120
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
111-
unsafe { self.rng.as_mut().try_fill_bytes(dest) }
121+
self.rng().try_fill_bytes(dest)
112122
}
113123
}
114124

src/seq/mod.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,8 @@ pub trait SliceRandom {
265265

266266
/// Extension trait on iterators, providing random sampling methods.
267267
///
268-
/// This trait is implemented on all sized iterators, providing methods for
268+
/// This trait is implemented on all iterators `I` where `I: Iterator + Sized`
269+
/// and provides methods for
269270
/// choosing one or more elements. You must `use` this trait:
270271
///
271272
/// ```

0 commit comments

Comments
 (0)