Skip to content

Commit db14664

Browse files
authored
Check UniformChar validity on deser (#1790)
Prevent memory safety violation in `UniformChar` via deserialization.
1 parent bea8620 commit db14664

4 files changed

Lines changed: 49 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,15 @@ You may also find the [Upgrade Guide](https://rust-random.github.io/book/update.
1010

1111
## [Unreleased]
1212

13+
### Fixes
14+
- Fix possible memory safety violation due to deserialization of `UniformChar` from bad source ([#1790])
15+
1316
### Changes
1417
- Document required output order of fn `partial_shuffle` and apply `#[must_use]` ([#1769])
1518
- Avoid usage of `unsafe` in contexts where non-local memory corruption could invalidate contract ([#1791])
1619

1720
[#1769]: https://github.com/rust-random/rand/pull/1769
21+
[#1790]: https://github.com/rust-random/rand/pull/1790
1822
[#1791]: https://github.com/rust-random/rand/pull/1791
1923

2024
## [0.10.1] — 2026-02-11

src/distr/uniform_int.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,15 @@ pub struct UniformInt<X> {
7474

7575
macro_rules! uniform_int_impl {
7676
($ty:ty, $uty:ty, $sample_ty:ident) => {
77+
impl UniformInt<$ty> {
78+
/// Get the maximum possible value
79+
#[allow(unused)]
80+
#[inline]
81+
pub(crate) fn max(&self) -> $ty {
82+
self.range.wrapping_sub(1).wrapping_add(self.low)
83+
}
84+
}
85+
7786
impl SampleUniform for $ty {
7887
type Sampler = UniformInt<$ty>;
7988
}
@@ -693,6 +702,7 @@ mod tests {
693702
let r = Uniform::try_from(2u32..7).unwrap();
694703
assert_eq!(r.0.low, 2);
695704
assert_eq!(r.0.range, 5);
705+
assert_eq!(r.0.max(), 6);
696706
}
697707

698708
#[test]
@@ -707,6 +717,7 @@ mod tests {
707717
let r = Uniform::try_from(2u32..=6).unwrap();
708718
assert_eq!(r.0.low, 2);
709719
assert_eq!(r.0.range, 5);
720+
assert_eq!(r.0.max(), 6);
710721
}
711722

712723
#[test]

src/distr/uniform_other.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,24 @@ impl SampleUniform for char {
3333
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3434
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
3535
pub struct UniformChar {
36+
#[cfg_attr(feature = "serde", serde(deserialize_with = "deser_sampler"))]
3637
sampler: UniformInt<u32>,
3738
}
3839

40+
#[cfg(feature = "serde")]
41+
fn deser_sampler<'de, D>(d: D) -> Result<UniformInt<u32>, D::Error>
42+
where
43+
D: serde::Deserializer<'de>,
44+
{
45+
let sampler = <UniformInt<u32> as serde::Deserialize>::deserialize(d)?;
46+
if sampler.max() > char::MAX as u32 - CHAR_SURROGATE_LEN {
47+
return Err(serde::de::Error::custom(
48+
"bad sampler range for UniformChar",
49+
));
50+
}
51+
Ok(sampler)
52+
}
53+
3954
/// UTF-16 surrogate range start
4055
const CHAR_SURROGATE_START: u32 = 0xD800;
4156
/// UTF-16 surrogate range size
@@ -298,6 +313,24 @@ mod tests {
298313
}
299314
}
300315

316+
#[test]
317+
#[cfg(feature = "serde")]
318+
fn test_char_bad_deser() {
319+
let json = r#"{"sampler":{"low":4294967200,"range":0,"thresh":0}}"#;
320+
let result = serde_json::from_str::<Uniform<char>>(json);
321+
assert!(result.is_err());
322+
let err = result.unwrap_err();
323+
assert_eq!(err.classify(), serde_json::error::Category::Data);
324+
325+
#[cfg(feature = "alloc")]
326+
{
327+
assert_eq!(
328+
alloc::string::ToString::to_string(&err),
329+
"bad sampler range for UniformChar at line 1 column 51"
330+
);
331+
}
332+
}
333+
301334
#[test]
302335
#[cfg_attr(miri, ignore)] // Miri is too slow
303336
fn test_durations() {

src/seq/slice.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,7 @@ mod test {
823823

824824
#[test]
825825
#[cfg(feature = "std")]
826+
#[cfg_attr(miri, ignore)] // Miri is too slow
826827
fn test_multiple_weighted_distributions() {
827828
use super::*;
828829

0 commit comments

Comments
 (0)