GROK-0004
high harness-verifiedJPEG reader: stack buffer overflow on CMYK/YCCK (4-component) JPEG — fixed [3] arrays indexed by output_components
⚠ 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 | Codec / image-format I/O |
| Location | src/lib/codec/formats/JPEGFormat.h · JPEGFormat<T>::jpegtoimage:133,139,235-244,300-303,343-347 |
| Vuln class | oob-write |
| CVE | — |
| CVSS | 7.8 (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/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-buffer-overflow (WRITE) |
| Repro | clang-20 + libc++ ASAN build (system libjpeg auto-linked via find_package); grk_compress -i pocs/grok/GROK-0004/poc.jpg -o /tmp/o.jp2 → ASAN stack-buffer-overflow WRITE at JPEGFormat<int>::jpegtoimage (JPEGFormat.h:237) — the cmptparm[j] loop with a 4-component CMYK JPEG (output_components==4) overruns the [3] stack array. |
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-0004/poc.jpg
Writeup
Summary
grk_compress decodes an untrusted JPEG via libjpeg and copies its planes into a grk_image.
JPEGFormat<T>::jpegtoimage declares fixed 3-element stack arrays for the component params
and plane pointers, but then loops up to cinfo.output_components, which is 4 for a
CMYK/YCCK JPEG (libjpeg’s default out_color_space for an Adobe 4-channel file). The only
header gate is bps != 8; nothing rejects >3 components. The 4th iteration writes one element
past each stack array, and the de-interleave then writes through the clobbered/garbage plane
pointer for every pixel.
T* planes[3]; // :133 3-element stack array
grk_image_comp cmptparm[3]; /* mono or RGB */ // :139 3-element stack array
...
bps = cinfo.data_precision;
if(bps != 8) { ...error... } // :219 ONLY guard; no component-count check
decompress_num_comps = (uint16_t)cinfo.output_components; // :226 (=4 for CMYK)
...
for(int j = 0; j < cinfo.output_components; j++) { // :235 j reaches 3
cmptparm[j].prec = (uint8_t)bps; ... cmptparm[j].w = w; // :237-243 cmptparm[3] OOB write
}
image_ = grk_image_new(decompress_num_comps, &cmptparm[0], ...); // :246 reads cmptparm[3] OOB
...
for(int j=0;j<decompress_num_comps;j++) planes[j] = ...; // :300-303 planes[3] OOB write
interleave(buffer32s, planes, w, decompress_num_comps); // :343-347 wild write via planes[3]
libjpeg never has its out_color_space overridden here (Step 4 is a no-op, :205-209), so a
CMYK/YCCK input yields output_components == 4 and cmptparm/planes are overrun.
Reproduction
Reachability: GrkCompress → loadInputImage<int32_t> → JPEGFormat<int32_t>::jpegtoimage
(requires the codec built with JPEG support — GRK_BUILD_JPEG defaults ON).
Create a CMYK (or YCCK) baseline 8-bit JPEG (e.g. ImageMagick convert in.png -colorspace CMYK cmyk.jpg, which embeds an Adobe APP14 marker → libjpeg reports 4 components). Run
grk_compress -i cmyk.jpg -o /tmp/out.jp2.
PoC build note (this environment could not build the C++23 codec for a runtime ASAN repro —
clang-20 here lacks <concepts>/libc++; needs libc++-20-dev or gcc ≥ 13). Build with
-fsanitize=address to observe the stack-buffer-overflow WRITE at the cmptparm[j]/planes[j]
loop.
Root cause
Component-param and plane-pointer arrays are hard-coded [3] (“mono or RGB”) while the loop
bound and grk_image_new count come straight from libjpeg’s output_components, which is 4 for
4-channel JPEGs. No output_components <= 3 guard, and out_color_space is left at libjpeg’s
default instead of being forced to RGB/grayscale.
Verification evidence
VERIFIED under AddressSanitizer (2026-06-16, system libjpeg auto-linked via find_package(JPEG)).
grk_compress -i pocs/grok/GROK-0004/poc.jpg -o /tmp/o.jp2 (a 32×32 CMYK JPEG, output_components==4):
==ERROR: AddressSanitizer: stack-buffer-overflow ... WRITE of size 1
#0 JPEGFormat<int>::jpegtoimage(...) JPEGFormat.h:237 (cmptparm[j].prec = ...)
#1 JPEGFormat<int>::readImage(...) JPEGFormat.h:687
#2 grk::loadInputImage<int>(...) GrkCompress.cpp:326
Address ... in stack of frame JPEGFormat<int>::jpegtoimage (JPEGFormat.h:132)
The 4th component (j==3) writes past the 3-element cmptparm/planes stack arrays exactly as
predicted.
Impact
Stack buffer overflow (write) plus a wild write through an attacker-influenced plane pointer,
reachable from a single untrusted local CMYK/YCCK .jpg opened by grk_compress (default codec
config). Controllable stack corruption with RCE potential; guaranteed crash at minimum.
Local-untrusted-file reachability. Severity high (CVSS 7.8). Fix: reject
output_components > 3, or force cinfo.out_color_space = JCS_RGB/JCS_GRAYSCALE before
jpeg_start_decompress.