PUBLIC MIRROR A read-only public view of Anvil. Only publicly-disclosed findings are shown; the Playbook, techniques, sessions and embargoed research are hidden.

← Findings

GROK-0009

low harness-verified

MJ2 STTS: unbounded samples_count_ drives ~4 billion allocations (decompression bomb / DoS) + num_samples_ overflow

Harness reproduced Crashes in a custom/AI-generated harness under a sanitizer. A TEST ARTIFACT — not real-world verification.

⚠ Harness reproduced — not real-world verified. Reproduce through the public API, a real application, or a platform decoder before treating this as verified.

Crash allocation bomb (ASAN allocation-size-too-big)
Topmost entry point unknown — establish reachability
Verified through no real consumer named
Real-world impact 4 CVSS · low

Classification

TargetGrok
ComponentJP2 file format
Locationsrc/lib/core/fileformat/decompress/FileFormatMJ2Decompress.cpp · tts_decompact / read_stts:508-520,529-538
Vuln classdos
CVE
CVSS4 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H)
Discovered2026-06-16

Verification

Evidence Harness reproduced (not real-world verified)
Harness fired✅ yes
Protocol1.0
Sanitizerasan
Crash typeallocation bomb (ASAN allocation-size-too-big)
Reproclang-20 + libc++ ASAN build; ASAN_OPTIONS=max_allocation_size_mb=512 grk_decompress -i pocs/grok/GROK-0009/poc.mj2 → ASAN 'requested allocation size 0x30000000 exceeds maximum' in tts_decompact (FileFormatMJ2Decompress.cpp:517) <- read_stts:538; the std::vector<mj2_sample> grew toward samples_count=0xFFFFFFFF entries from an 8-byte field.

Disclosure

Reported toGrok maintainers — support@grokcompression.com (private email; repo has no Private Vulnerability Reporting / SECURITY.md)
Reported2026-06-23
Vendor ack
Embargo until
Public2026-06-24
Patched in

PoC: pocs/grok/GROK-0009/poc.mj2

Writeup

Summary

The MJ2 time-to-sample (STTS) parser expands each table entry into one mj2_sample per sample with no cap on the per-entry samples_count_, which is a raw uint32 from the file. A single 8-byte STTS entry with samples_count_ = 0xFFFFFFFF drives ~4.29 billion push_backs, exhausting memory. num_samples_ (uint32) is also accumulated without an overflow check, so it can wrap and desynchronise from samples_.size().

void tts_decompact(mj2_tk* tk) {
  for(const auto& tts : tk->tts_) {
    tk->num_samples_ += tts.samples_count_;             // :512  uint32 accumulate, can overflow
    for(uint32_t j = 0; j < tts.samples_count_; j++)    // :513  unbounded attacker count
      tk->samples_.push_back(sample);                   // :517  ~4e9 * sizeof(mj2_sample)
  }
}
// read_stts: num_tts entries each read with checked grk_read (so num_tts is bounded by box size),
// but samples_count_ per entry (line 533) is unbounded.

The outer num_tts loop is bounded by the box length (8 bytes/entry, checked reads), but the inner expansion is not — the amplification factor is enormous (8 input bytes → multi-GB).

Reproduction

Craft a .mj2 (auto-detected) with a video track whose STTS box has one entry with samples_count_ = 0xFFFFFFFF. Run grk_decompress -i poc.mj2 -o /tmp/out.png → OOM-kill / unbounded memory growth during readHeaderread_sttstts_decompact.

Root cause

No bound on samples_count_ relative to the file size before materialising one entry per sample, and no overflow guard on the num_samples_ accumulation.

Verification evidence

VERIFIED under AddressSanitizer (2026-06-16). ASAN_OPTIONS=max_allocation_size_mb=512 grk_decompress -i pocs/grok/GROK-0009/poc.mj2 (188-byte MJ2, STTS samples_count=0xFFFFFFFF):

==ERROR: AddressSanitizer: requested allocation size 0x30000000 (...) exceeds maximum supported size of 0x20000000
  #0 operator new(unsigned long)
  ...
  #11 grk::FileFormatMJ2Decompress::tts_decompact(...)  FileFormatMJ2Decompress.cpp:517 (push_back)
  #12 grk::FileFormatMJ2Decompress::read_stts(...)      FileFormatMJ2Decompress.cpp:538

The std::vector<mj2_sample> grew toward 0xFFFFFFFF entries (here requesting 768 MB before the 512 MB ASAN cap aborted it) from an 8-byte STTS field — confirming the unbounded amplification.

Impact

Memory-exhaustion denial of service from a tiny untrusted .mj2 via the default grk_decompress path (MJ2 auto-detected) — a classic decompression-bomb amplification (8 bytes → GBs). The num_samples_ uint32 overflow could additionally desync the sample count from the materialised vector, feeding later table logic. No memory corruption on its own. Local-untrusted-file reachability. Severity low (CVSS 4.0). Fix: reject samples_count_ (and the running num_samples_) exceeding a sane bound derived from the remaining file size.

References