Skip to content

Reject full-range and wrapping UniformChar samplers on deserialize - #1829

Open
ChrisJr404 wants to merge 1 commit into
rust-random:masterfrom
ChrisJr404:fix-uniformchar-deser-fullrange
Open

Reject full-range and wrapping UniformChar samplers on deserialize#1829
ChrisJr404 wants to merge 1 commit into
rust-random:masterfrom
ChrisJr404:fix-uniformchar-deser-fullrange

Conversation

@ChrisJr404

Copy link
Copy Markdown
  • Added a CHANGELOG.md entry

Summary

Fixes #1827. The Uniform<char> deserialization guard checked UniformInt::max(), which is built from wrapping arithmetic, so a crafted payload could clear it and then panic inside sample. Validate low and range directly with checked arithmetic instead.

Motivation

The guard added in #1790 rejects a sampler whose max() exceeds the char range, but max() is range.wrapping_sub(1).wrapping_add(low). Two cases slip through:

{"sampler":{"low":5,"range":0,"thresh":0}}
{"sampler":{"low":4294967280,"range":32,"thresh":0}}

range == 0 is UniformInt's full-u32-range marker, so max() reads low - 1 (a small number that passes), and at sample time the full-range branch returns an arbitrary u32, almost always above 0x10FFFF. The second payload wraps low + range - 1 down to a small value. Both then hit char::from_u32(x).expect(...) and panic, which is reachable on attacker-influenced serde input.

The check can't move into UniformInt::deserialize because range == 0 is a legitimate state for Uniform<u32> (e.g. new_inclusive(0, u32::MAX)), so it belongs in the char layer where a bounded range is always expected.

Details

  • Compute the inclusive max from low and range with checked_sub/checked_add, rejecting range == 0 and any max that overflows or exceeds the compressed char range.
  • Extended test_char_bad_deser with both payloads above, and added test_char_deser_roundtrip to confirm a normal Uniform<char> still serializes, deserializes, and samples.

Tested with cargo test --features serde (all pass); cargo fmt --check and cargo clippy --all-targets --features serde -- -D warnings are clean.

The UniformChar deserialization guard checked UniformInt::max(), which is
built from wrapping arithmetic, so a payload with range == 0 (the full-range
marker) or one whose low + range - 1 wraps cleared the guard and then panicked
in sample() on an invalid Unicode scalar value. Check low and range directly
with checked arithmetic instead, rejecting range == 0 and any inclusive max
that overflows or leaves the char range.
@ChrisJr404
ChrisJr404 force-pushed the fix-uniformchar-deser-fullrange branch from a66ccb6 to 454f0ba Compare August 24, 2026 23:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Uniform<char> serde guard is bypassable, letting sample() panic

1 participant