GROK-0001
high harness-verifiedBMP reader: stack buffer overflow in readInfoHeader (biSize read into fixed buffer before validation)
⚠ 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/BMPFormat.h · BMPFormat<T>::readInfoHeader:537-541 |
| 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 of size 512) |
| Repro | clang-20 + libc++ ASAN build (-DGRK_ENABLE_ASAN=ON); grk_compress -i pocs/grok/GROK-0001/poc.bmp -o /tmp/o.jp2 → ASAN: stack-buffer-overflow WRITE size 512 in fread <- FileStandardIO::read (FileStandardIO.cpp:83) <- BMPFormat<int>::readInfoHeader (BMPFormat.h:540), overflowing 'temp' (line 538). |
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-0001/poc.bmp
Writeup
Summary
grk_compress accepts an untrusted input image and auto-dispatches by extension/magic
to the BMP reader. BMPFormat<T>::readInfoHeader (src/lib/codec/formats/BMPFormat.h:537)
uses the attacker-controlled DIB-header-size field biSize to size a read() into a
fixed 124-byte stack buffer before validating biSize, giving an attacker-controlled
stack buffer overflow with attacker-controlled length and content.
// readFileHeader() — biSize read raw from file offset 14, NO validation:
get_int(&temp_ptr, &infoHeader->biSize); // BMPFormat.h:528
// readInfoHeader():
const size_t len_initial = infoHeader->biSize - sizeof(uint32_t); // :537 (size_t)
uint8_t temp[sizeof(GRK_BITMAPINFOHEADER)]; // :538 124 bytes
if(!read(temp, len_initial)) // :540 fread(temp,1,len_initial,fh)
return false;
switch(infoHeader->biSize) { ...valid sizes... default: return false; } // :543 — TOO LATE
read() resolves to ImageFormat::read → FileStandardIO::read → fread(buf, 1, len, fh)
(fileio/FileStandardIO.cpp:83) with no clamp of len to the destination buffer.
The switch that rejects unknown biSize values runs only after the read.
Two trigger classes:
biSize ∈ {0,1,2,3}→len_initial = biSize - 4underflows thesize_tto ~0xFFFF…FFFC→freadpours the entire remaining file intotemp[124](unbounded, fully attacker-controlled overflow).biSize > 124but not a known header constant (e.g.200) →len_initial = 196 > 124→ a 72-byte stack smash before theswitchever rejects the value.
Reproduction
Reachability (all auto, no special flags): GrkCompress → loadInputImage<int32_t> →
BMPFormat<int32_t>::readImage (BMPFormat.h:800) → readFileHeader (:825) →
readInfoHeader (:827).
Craft a .bmp:
- Bytes 0-1:
"BM"(bfType == 19778, required byreadFileHeader). - Bytes 2-13: any
bfSize/reserved/bfOffBits. - Bytes 14-17 (
biSize):00 00 00 00(value 0) — orC8 00 00 00(200). - Then ≥ ~200 bytes of attacker-controlled padding (for the 124-byte buffer to be overrun;
for the
biSize==0case, as many bytes as desired — the whole tail lands on the stack).
Run grk_compress -i poc.bmp -o /tmp/out.jp2 (build with -fsanitize=address to observe
the stack-buffer-overflow WRITE).
Root cause
The DIB header size is consumed as a read length before being validated against the set of
legal header sizes, and the underlying read() performs an unclamped fread into a
fixed-size stack buffer. Both the underflow (small biSize) and the over-length
(biSize > sizeof(GRK_BITMAPINFOHEADER)) cases escape the only sanity check, which is
ordered after the copy.
Verification evidence
VERIFIED under AddressSanitizer (2026-06-16, clang-20 + libc++ ASAN build,
-DGRK_ENABLE_ASAN=ON). grk_compress -i pocs/grok/GROK-0001/poc.bmp -o /tmp/o.jp2:
==ERROR: AddressSanitizer: stack-buffer-overflow ... WRITE of size 512
#0 fread
#1 FileStandardIO::read(unsigned char*, unsigned long) FileStandardIO.cpp:83
#2 BMPFormat<int>::readInfoHeader(...) BMPFormat.h:540
#3 BMPFormat<int>::readImage(...) BMPFormat.h:827
#4 grk::loadInputImage<int>(...) GrkCompress.cpp:296
...
This frame has 3 object(s):
[160, 284) 'temp' (line 538) <== Memory access at offset 284 overflows this variable
The 512-byte fread (attacker-controlled length & content, here from biSize=0) overflows the
124-byte stack temp exactly as predicted. sizeof(GRK_BITMAPINFOHEADER) == 124.
Impact
Stack buffer overflow writable with attacker-controlled length and content, reachable from a
single untrusted local .bmp opened by grk_compress (no non-default config). This is a
classic return-address-overwrite primitive (stack canaries/ASLR notwithstanding) with
RCE potential, and a guaranteed crash/DoS at minimum. Reachability is local-untrusted-file
(user converts a received image). Severity high (CVSS 7.8); treat as the top-priority fix
on this target. Same bug family as historical OpenJPEG BMP-reader CVEs.