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-0007

high harness-verified

MJ2: sample offset/size from STCO/STSZ used as a raw file pointer with no bounds check → out-of-bounds read

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 SEGV / out-of-bounds read (wild, basePtr + 0xFFFFFFFF)
Topmost entry point unknown — establish reachability
Verified through no real consumer named
Real-world impact 7.1 CVSS · high

Classification

TargetGrok
ComponentJP2 file format
Locationsrc/lib/core/fileformat/decompress/FileFormatMJ2Decompress.cpp · readHeader / decompressSampleInternal:724-737,762-785
Vuln classoob-read
CVE
CVSS7.1 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H)
Discovered2026-06-16

Verification

Evidence Harness reproduced (not real-world verified)
Harness fired✅ yes
Protocol1.0
Sanitizerasan
Crash typeSEGV / out-of-bounds read (wild, basePtr + 0xFFFFFFFF)
Reproclang-20 + libc++ ASAN build; grk_decompress -i pocs/grok/GROK-0007/poc.mj2 → ASAN SEGV (READ) in grk_read (StreamIO.h:53) <- FileFormatMJ2Decompress::readHeader (FileFormatMJ2Decompress.cpp:730); rdx=0xffffffff is the bogus STCO offset.

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-0007/poc.mj2

Writeup

Summary

grk_decompress auto-detects MJ2 from file magic and parses its sample tables. Each video sample’s byte offset (sample.offset_, accumulated from the STCO chunk-offset table in stco_decompact) and size (sample.samples_size_, from STSZ) are used as an absolute pointer into the whole-file buffer with no validation that the offset/size lie within the file. A crafted STCO/STSZ makes the parser read arbitrarily far past the input buffer.

Two reachable sites, both with the same root defect:

// readHeader() — runs on the STANDARD parse, before any decompress:
stream_->seek(0);
auto basePtr = stream_->currPtr();
for(const auto& sample : current_track_->samples_) {
  auto ptr = basePtr + sample.offset_;        // :726  offset_ unvalidated (attacker STCO)
  uint32_t len;
  grk_read(ptr, &len);                          // :730  unchecked 4-byte read at basePtr+offset_
  ...
}

// decompressSampleInternal():
auto samplePtr = basePtr + sample.offset_;     // :765
grk_read(samplePtr, &boxLen);                   // :770  unchecked read
...
auto j2kData = samplePtr + boxHeaderSize;
auto j2kLen  = sample.samples_size_ - boxHeaderSize;   // :782  size unbounded vs file
auto subStream = memStreamCreate(j2kData, j2kLen, ...); // :785  J2K substream reads OOB

grk_read(ptr, &val) (the 2-arg form) is an unchecked memcpy (stream/StreamIO.h:124). A grep of the file confirms sample.offset_ is never compared against numBytesLeft() / the stream length anywhere. The only related check (readHeader:731) compares the box length read at the bogus pointer to samples_size_ — which the attacker also controls — so it does not bound the offset.

Reproduction

Craft a .mj2 (magic: JP2_RFC3745_MAGIC at 0 + MJ2_MAGIC at 20 → auto-routed to FileFormatMJ2Decompress) with a valid moov/trak/mdia/minf/stbl chain whose STCO chunk offset (and/or STSZ sample size) points beyond the file (e.g. offset_ = 0xFFFFFFFF). Run grk_decompress -i poc.mj2 -o /tmp/out.png. The readHeader loop (line 726/730) over-reads first; decompressSampleInternal adds an attacker-length J2K substream over OOB memory.

(Runtime ASAN repro not produced in this env — the C++23 codec can’t be built here, see session.)

Root cause

MJ2 sample table offsets/sizes are treated as trusted absolute file positions. There is no “offset_ + samples_size_ <= stream length” validation between the table parse and the pointer arithmetic.

Verification evidence

VERIFIED under AddressSanitizer (2026-06-16). grk_decompress -i pocs/grok/GROK-0007/poc.mj2 (a 256-byte MJ2: ftyp(mjp2) + moov/trak/tkhd + mdia/minf/stbl/{stts,stsz,stsc,stco}, with the STCO chunk offset = 0xFFFFFFFF):

==ERROR: AddressSanitizer: SEGV on unknown address ...  (READ memory access)
  #0 grk::grk_read<unsigned int>(...)                 StreamIO.h:53
  #1 grk::grk_read<unsigned int>(...)                 StreamIO.h:127
  #2 grk::FileFormatMJ2Decompress::readHeader(...)    FileFormatMJ2Decompress.cpp:730
  #3 grk::GrkDecompress::main(...)                     GrkDecompress.cpp:1439
  rdx = 0x00000000ffffffff   (= sample.offset_, the unvalidated STCO offset)

The grk_read(basePtr + sample.offset_, &len) at line 730 reads basePtr + 0xFFFFFFFF → wild OOB read (SEGV here; a smaller in-range-ish offset yields a heap over-read that leaks into the J2K substream). Confirms the missing offset/size validation.

Impact

Heap out-of-bounds read at an attacker-chosen offset, reachable from a single untrusted local .mj2 via the default grk_decompress path (MJ2 auto-detected). Causes a crash/DoS, and the attacker-length J2K substream over out-of-bounds memory can decode adjacent heap into the output image (info-disclosure). Local-untrusted-file reachability. Severity high (CVSS 7.1). Fix: validate sample.offset_ + sample.samples_size_ <= stream length (and offset_ + 4 before the box-length probe) before any pointer use.

References