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

medium harness-verified

JP2 asoc box: unbounded nesting recursion in read_asoc → stack-exhaustion DoS

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 stack-overflow (DEADLYSIGNAL)
Topmost entry point unknown — establish reachability
Verified through no real consumer named
Real-world impact 5.5 CVSS · medium

Classification

TargetGrok
ComponentJP2 file format
Locationsrc/lib/core/fileformat/FileFormatJP2Family.cpp · FileFormatJP2Family::read_asoc:1282-1347 (recursive call 1331-1332)
Vuln classdos
CVE
CVSS5.5 (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 typestack-overflow (DEADLYSIGNAL)
Reproclang-20 + libc++ ASAN build; grk_decompress -i pocs/grok/GROK-0011/poc.jp2 (400000 nested asoc boxes, 3.2MB) → ASAN: stack-overflow; trace is read_asoc (FileFormatJP2Family.cpp:1332) recursing on itself until exhaustion.

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-0011/poc.jp2

Writeup

Summary

The JP2/JPX association-box parser read_asoc recurses once per nested asoc child (FileFormatJP2Family.cpp:1331-1332, case JP2_ASOC: ... read_asoc(childAsoc, ...)) with no recursion-depth limit anywhere in the parser. Each level consumes only the 8-byte child box header before recursing, so an asoc payload of N bytes nests ~N/8 deep. A few-MB JP2 header with asoc{ asoc{ asoc{ ... }}} exhausts the stack → SIGSEGV during header parse, before any codestream decode.

// read_asoc(AsocBox* parent, ...):
while(asocBytesUsed < asocSize && *header_data_size > 8) {
  ... read childSize/childTag (8 bytes) ...
  case JP2_ASOC:
    auto* childAsoc = new AsocBox();                       // :1292  heap per level
    asocBytesUsed += read_asoc(childAsoc, header_data, header_data_size, childSize); // :1331 recurse
}

No depth counter / MAX_DEPTH exists (grep confirms). The asoc handler is registered unconditionally in the decompress ctor (FileFormatJP2Decompress.cpp:65), and the box body is buffered up to stream_->numBytesLeft(), so depth is bounded only by file size.

Reproduction

Craft a .jp2: signature box + ftyp + an asoc box whose body is asoc{ asoc{ ... }} nested tens of thousands deep (each inner level = 00 00 00 08 'asoc', innermost holds a small valid leaf e.g. lbl /xml to satisfy the asocBytesUsed < asocSize exhaustion check at line 1347). Run grk_decompress -i poc.jp2 -o /tmp/o.png. A multi-MB file reaches a depth that overflows the stack.

Root cause

Recursive descent over attacker-nested superboxes with no depth cap; ~8 input bytes per stack frame.

Verification evidence

VERIFIED under AddressSanitizer (2026-06-16). grk_decompress -i pocs/grok/GROK-0011/poc.jp2 (JP2 with 400000 nested asoc boxes, 3.2 MB):

==ERROR: AddressSanitizer: stack-overflow on address 0x...
  #3 grk::FileFormatJP2Family::read_asoc(...)  FileFormatJP2Family.cpp:1292 (new AsocBox)
  #4 grk::FileFormatJP2Family::read_asoc(...)  FileFormatJP2Family.cpp:1332 (recursive call)
  #5 ... :1332
  #6 ... :1332   (read_asoc recursing on itself to exhaustion)
SUMMARY: AddressSanitizer: stack-overflow

serializeAsoc caps total node COUNT (256) but not depth, and the overflow occurs earlier during the read_asoc parse — confirming the depth, not count, is unbounded.

Impact

Stack-exhaustion denial of service from a small untrusted .jp2 via the default grk_decompress header parse — a decompression-bomb-style amplification (~8 bytes/frame). No memory corruption. Local-untrusted-file reachability. Severity medium (CVSS 5.5). Fix: add a recursion-depth cap to read_asoc (and any other superbox recursion), rejecting files past a sane nesting limit.

References