Skip to content

Harden RLE decompression against untrusted indices and offsets#8953

Open
joseph-isaacs wants to merge 2 commits into
developfrom
claude/vortex-fastlanes-upgrade-pljujw
Open

Harden RLE decompression against untrusted indices and offsets#8953
joseph-isaacs wants to merge 2 commits into
developfrom
claude/vortex-fastlanes-upgrade-pljujw

Conversation

@joseph-isaacs

@joseph-isaacs joseph-isaacs commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Rationale for this change

The RLE decompression code didn't carefully validate indices.
This PR fixes that.

joseph-isaacs and others added 2 commits July 24, 2026 11:10
fastlanes 0.6.0 renames `RLE::{encode,decode}` to `unsafe
{encode,decode}_unchecked`, surfacing that the decoder performs
unchecked indexing into the run-values slice.

The encode call site trivially satisfies the contract (all arguments
are 1024-element arrays). The decode path now proves the contract
before entering the unchecked gather:

- `values_idx_offsets` (read from possibly untrusted storage) is
  validated once per decompress: offsets must be non-decreasing and
  span at most `values.len()`, bounding every per-chunk value slice.
- Chunks referencing zero values are rejected instead of underflowing
  the null-position clamp.
- For all-valid chunks, a single SIMD-friendly max-reduction over the
  1024 chunk indices verifies they are all below the chunk's value
  count before `decode_unchecked`, keeping the bounds check out of the
  per-element decode loop.
- The null-clamping path already bounds every index, so it calls
  `decode_unchecked` directly.

Adds regression tests that corrupt indices and offsets must fail
decompression with an error rather than read out of bounds.

Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L499Tv8CF7Aj7a3cABW9fp
The nullable-indices decode path previously clamped every index into
range, which silently remapped a corrupt out-of-bounds index at a
*valid* position to the last chunk value. Only null positions may
legitimately hold garbage indices (e.g. after further compression of
the indices child), so treat the two cases differently:

- Materialize the indices validity mask once per decompress, then use a
  per-chunk SIMD popcount (`count_range`) to classify chunks.
- Fully-valid chunks take the existing max-reduction bounds check.
- Fully-null chunks skip the gather entirely (all values are masked).
- Mixed chunks zero out the null positions via word-at-a-time
  `for_each_set_index` and then run the same max-reduction check, so an
  out-of-bounds index at a valid position now fails decompression with
  an error rather than being clamped.

Signed-off-by: Joe Isaacs <joe.isaacs@live.co.uk>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L499Tv8CF7Aj7a3cABW9fp
@joseph-isaacs joseph-isaacs added the changelog/fix A bug fix label Jul 24, 2026
@joseph-isaacs
joseph-isaacs marked this pull request as ready for review July 24, 2026 15:00
@joseph-isaacs
joseph-isaacs force-pushed the claude/vortex-fastlanes-upgrade-pljujw branch from b1dfdd4 to c99326c Compare July 27, 2026 10:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changelog/fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant