GROK-0011
medium harness-verifiedJP2 asoc box: unbounded nesting recursion in read_asoc → stack-exhaustion DoS
⚠ 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/FileFormatJP2Family.cpp · FileFormatJP2Family::read_asoc:1282-1347 (recursive call 1331-1332) |
| Vuln class | dos |
| CVE | — |
| CVSS | 5.5 (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 | stack-overflow (DEADLYSIGNAL) |
| Repro | clang-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 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-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.