Skip to content

Bound sequence parser allocations to the input size - #14

Open
fordred wants to merge 1 commit into
gen2brain:mainfrom
fordred:fix/bound-sequence-parser-allocations
Open

Bound sequence parser allocations to the input size#14
fordred wants to merge 1 commit into
gen2brain:mainfrom
fordred:fix/bound-sequence-parser-allocations

Conversation

@fordred

@fordred fordred commented Aug 11, 2026

Copy link
Copy Markdown

Summary

The HEIC sequence sample-table parsers (parseStsz, parseOffsets, parseStsc, parseStts in sequence.go) size their slices directly from 32-bit counts read from the file, without checking that the input can back them.

Reproduction: a 124-byte ftypmsf1 file with a pict track whose stsz box claims sample_count = 0x20000000 forces a 4.0 GiB allocation inside heic.DecodeConfig (measured via runtime.MemStats), before any decode or dimension check. A 10 MB body cap doesn't help — forged counts need no backing data. parseStts has the same amplification via an unbounded inner loop over a per-entry count.

Fix

Allocations are now bounded by data actually present in the input:

  • parseStsz — per-entry sizes must fit the box payload ((len(p)-12)/4); a uniform table implies one byte of media data per sample, so its count is capped by the input size.
  • parseOffsets / parseStsc — entry counts capped by the box payload length.
  • parseStts — entry count capped by the payload; the expanded duration total capped by the input size.

parseTrak already has the full input in hand (data), so the input-size bound is passed down with no signature churn to the public API. Worst-case allocation is now proportional to the input (~30x, from the tables themselves), instead of unbounded.

Tests

  • Same-package bounds tests for each parser with forged counts.
  • TestDecodeConfigDoesNotAmplifyForgedSequenceCounts — the 124-byte forged container must fail fast allocating < 64 MiB (pre-fix: ~4 GiB).
  • Legit sequences unaffected: full suite incl. TestDecodeAll (17-frame anim.heic) passes; benches and the wasm2go build are clean.

Found via KopiChange's review of a v0.7.1 consumer; the fix there guards at the boundary, this bounds the parser for every consumer.

The sequence sample-table parsers (parseStsz, parseOffsets, parseStsc,
parseStts) sized slices directly from 32-bit counts read from the file,
without checking that the input could back them. A tiny forged HEIF
container (e.g. a 124-byte ftypmsf1 file with a pict track whose stsz
box claims 2^29 samples) forced a ~4 GiB allocation inside
DecodeConfig/Decode - an OOM vector on untrusted uploads.

Allocations are now bounded by data actually present:
- parseStsz: per-entry sizes must fit the box payload; a uniform table
  implies one byte of media data per sample, so its count is capped by
  the input size.
- parseOffsets/parseStsc: entry counts capped by the box payload length.
- parseStts: entry count capped by the payload; the expanded duration
  total capped by the input size.

Adds same-package bounds tests for each parser plus a DecodeConfig
regression test asserting the forged container allocates under 64 MiB.
Legit sequences are unaffected (verified against the 17-frame
anim.heic fixture).
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.

1 participant