AsciiSet::contains() causes out-of-bounds array access for non-ASCII bytes (byte >= 128)

Open Beginner friendly
#1,132 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
72/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Quiet
Tech stack
rust
Domain
security

Research direction

Start at src/ascii_set.rs lines 47, 58, and 64, and inspect how AsciiSet::contains, add, and remove calculate mask indexes for bytes at or above 128. Add regression coverage for non-ASCII bytes and verify that the public methods no longer access beyond the four-element mask; run the relevant repository tests.

Written by the indexing model from the issue text.

Description

Hi,

I found a potential memory safety issue in the AsciiSet implementation through Kani formal verification.

Location: src/ascii_set.rs:47, 58, 64

Current Code:

const ASCII_RANGE_LEN: usize = 0x80; // 128
const BITS_PER_CHUNK: usize = 32;

pub struct AsciiSet {
mask: [Chunk; ASCII_RANGE_LEN / BITS_PER_CHUNK], // [u32; 4]
}

impl AsciiSet {
pub(crate) const fn contains(&self, byte: u8) -> bool {
let chunk = self.mask[byte as usize / BITS_PER_CHUNK]; // OOB when byte >= 128!
// …
}

pub const fn add(&self, byte: u8) -> Self {
mask[byte as usize / BITS_PER_CHUNK] |= ... // OOB when byte >= 128!
// ...
}

pub const fn remove(&self, byte: u8) -> Self {
mask[byte as usize / BITS_PER_CHUNK] &= ... // OOB when byte >= 128!
// ...
}
}
Analysis:

Array mask has 4 elements (valid indices: 0-3)

Index calculation: byte as usize / 32

For byte >= 128: index >= 4 (out of bounds)

Current Mitigation:
The documentation mentions this is for ASCII only, and should_percent_encode() checks is_ascii() before calling. However, add() and remove() are public API.

Impact:

Potential out-of-bounds memory access

Undefined behavior

Security concern if untrusted input reaches these methods

Suggested Fix:

pub(crate) const fn contains(&self, byte: u8) -> bool {
if byte >= 0x80 { return false; }
let chunk = self.mask[byte as usize / BITS_PER_CHUNK];
let mask = 1 << (byte as usize % BITS_PER_CHUNK);
(chunk & mask) != 0
}

pub const fn add(&self, byte: u8) -> Self {
let mut mask = self.mask;
if byte < 0x80 {
mask[byte as usize / BITS_PER_CHUNK] |= 1 << (byte as usize % BITS_PER_CHUNK);
}
Self { mask }
}
Could you please confirm if this is a valid security concern?

Dominant language
Rust
Stars
1.6k
Forks
406
PR merge metrics
No merged PRs in 30d

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from servo/rust-url

All issues in servo/rust-url

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.