GROK-0009
low harness-verifiedMJ2 STTS: unbounded samples_count_ drives ~4 billion allocations (decompression bomb / DoS) + num_samples_ overflow
⚠ Harness reproduced — not real-world verified. Reproduce through the public API, a real application, or a platform decoder before treating this as verified.
Classification
| Target | Grok |
|---|---|
| Component | JP2 file format |
| Location | src/lib/core/fileformat/decompress/FileFormatMJ2Decompress.cpp · tts_decompact / read_stts:508-520,529-538 |
| Vuln class | dos |
| CVE | — |
| CVSS | 4 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H) |
| Discovered | 2026-06-16 |
Verification
| Evidence | Harness reproduced (not real-world verified) |
|---|---|
| Harness fired | ✅ yes |
| Protocol | 1.0 |
| Sanitizer | asan |
| Crash type | allocation bomb (ASAN allocation-size-too-big) |
| Repro | clang-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 to | Grok maintainers — support@grokcompression.com (private email; repo has no Private Vulnerability Reporting / SECURITY.md) |
|---|---|
| Reported | 2026-06-23 |
| Vendor ack | — |
| Embargo until | — |
| Public | 2026-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 readHeader → read_stts → tts_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.