Skip to content

Security fixes from the Anvil audit (GHSA-57w8-h6pj-xx45)#411

Merged
boxerab merged 14 commits into
masterfrom
security/anvil-advisory-fixes
Jun 24, 2026
Merged

Security fixes from the Anvil audit (GHSA-57w8-h6pj-xx45)#411
boxerab merged 14 commits into
masterfrom
security/anvil-advisory-fixes

Conversation

@boxerab

@boxerab boxerab commented Jun 24, 2026

Copy link
Copy Markdown
Collaborator

Fixes the 14 issues Ariel Koren (Anvil) reported privately in GHSA-57w8-h6pj-xx45. One commit per finding; each commit body carries the GROK id, the CWE, and a Reported-by: trailer so the mapping back to the advisory is self-contained.

These touch the image-format readers (BMP/JPEG/TIFF), the MJ2 box + sample-table parser, the JP2 asoc parser, the tile-processor MCT lifecycle, the streaming strip composite, the inverse-DWT scratch pool, and the HTJ2K MagSgn SIMD path. None are in the core J2K/T1/T2/wavelet/MQ math.

Highs:

  • GROK-0001 (CWE-787) BMP reader — validate biSize before it sizes the header read into the stack buffer
  • GROK-0004 (CWE-787) JPEG reader — reject 4-component (CMYK/YCCK) JPEGs before the [3]-sized arrays overflow
  • GROK-0007 (CWE-125) MJ2 sample table — bound STCO/STSZ offset+size against the file before using them as pointers
  • GROK-0006 (CWE-416) tile-processor MCT — keep the Mct tile pointer in sync on release/re-decompress (UAF)
  • GROK-0010 (CWE-787) strip composite — size the strip buffer for the tallest tile-row, not the first

Mediums:

  • GROK-0002 (CWE-125) BMP RLE8/RLE4 — bound the input pointer to biSizeImage
  • GROK-0005 (CWE-125) TIFF reader — require numcomps == SamplesPerPixel for contiguous data
  • GROK-0014 (CWE-770) inverse-DWT scratch — add the overflow guard the sibling already had
  • GROK-0011 (CWE-674) JP2 asoc — cap superbox recursion depth

Lows:

  • GROK-0009 (CWE-770) MJ2 STTS — cap sample expansion by file size
  • GROK-0013 (CWE-476) MJ2 read_url/read_urn — null-check the track
  • GROK-0003 (CWE-125) MJ2 read_url/read_urn — guard the headerSize underflow
  • GROK-0008 (CWE-787) PNM strip output — size packed_row_bytes to the packer's byte width
  • GROK-0012 (CWE-125) HTJ2K — widen code-block padding to 16 bytes for the SIMD MagSgn load

Builds clean and the full test suite passes locally. Headed for 20.3.6.

Grok Compression added 14 commits June 24, 2026 11:59
Move the legal-header-size switch ahead of the read() that uses
biSize, and reject lengths exceeding the temp buffer. Previously
biSize<4 underflowed the read length and biSize>124 overran the
124-byte stack buffer, both attacker-controlled stack writes.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0001 (CWE-787)
The RLE decoders walked the compressed buffer with *pixels_ptr++ with
no upper bound, so a large declared height plus a small/zero
biSizeImage and no end-of-bitmap marker ran the pointer off the
allocation. Guard every read against pixels_end.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0002 (CWE-125)
cmptparm[] and planes[] are hard-coded [3], but the fill loop and
grk_image_new used cinfo.output_components, which is 4 for an Adobe
CMYK/YCCK JPEG. The 4th component overran both stack arrays. Reject
output_components > 3 right after the precision gate.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0004 (CWE-787)
The per-row scratch buffer is sized from SamplesPerPixel, but the
chunky de-interleave reads width*numcomps samples (numcomps derived
from photometric + extrasamples). A TIFF declaring numcomps > tiSpp
over-read the row buffer into the output. Reconcile the two for
PLANARCONFIG_CONTIG before reading pixels.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0005 (CWE-125)
Sample offset_ (from STCO) and samples_size_ (from STSZ) were used as
raw offsets into the whole-file buffer with no bounds check, so a
crafted STCO offset (e.g. 0xFFFFFFFF) read far past the input and could
decode adjacent heap into the output. Bound offset+size (and the
box-length probe) by the stream length in both readHeader and the
per-sample decode path.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0007 (CWE-125)
tts_decompact materialised one mj2_sample per declared sample with no
cap on the per-entry samples_count_ (raw uint32), so a single STTS
entry of 0xFFFFFFFF drove ~4.29B push_backs and wrapped num_samples_.
Cap the running total at the file size (each sample needs >=1 byte of
media) and fail the parse on overflow.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0009 (CWE-770)
The box reader recurses into minf/dinf/dref even when no tkhd has been
seen, so a dref->url/urn box with no preceding track header reached
read_url/read_urn with current_track_ == nullptr and dereferenced it.
Reject url/urn boxes outside a track.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0013 (CWE-476)
read_url/read_urn unconditionally did headerSize -= 4 for the
version+flags fullbox header. A box shorter than 4 bytes underflowed
headerSize to a near-UINT32_MAX value, defeating the bounds checks on
the following location reads and over-reading the heap. Reject boxes
too short to hold the fullbox header.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0003 (CWE-125)
Mct captured a non-owning Tile* once at construction. When the tile
cache released a tile (release/releaseForSwath) it freed tile_ but left
mct_->tile_ dangling, and reinitForReDecompress allocated a fresh tile
without updating mct_. On re-decompress of an evicted MCT tile the
inverse MCT pass then read/wrote the freed Tile. Re-point mct_ on
recreate and null it on release.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0006 (CWE-416)
The band-callback strip buffer was allocated for the first tile-row's
reduced height, but the drain loop mutates comp->h to each subsequent
row height without reallocating. With a YTOsiz not aligned to
dy*2^reduce, an interior row is taller than the first and the composite
copy wrote past the allocation. Size the buffer for the tallest reduced
tile-row in the slated region.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0010 (CWE-787)
read_asoc recursed once per nested asoc child with no depth limit,
consuming only the 8-byte child header per level, so a few-MB header of
nested asoc boxes exhausted the stack during header parse. Track depth
and reject nesting beyond maxAsocDepth.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0011 (CWE-674)
WaveletPoolData::alloc multiplied maxDim (the tile dimension) by a
per-thread element size with no overflow check, and the caller ignored
the result. A crafted SIZ tile dimension drove a multi-GB allocation
(or a size_t wrap). Add the overflow guard the sibling dwt_scratch
already has, and abort the inverse transform when the pool cannot be
allocated instead of proceeding with null buffers.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0014 (CWE-770)
The band-callback scratch image sized packed_row_bytes from the raw
(bit-packed) precision, but the PNM streaming packer always emits whole
8- or 16-bit samples. For a precision not in {8,16} this under-sized
the strip buffer and the packer wrote past it. Use the rounded byte
width for PXM output, matching GrkImage::initOutput.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0008 (CWE-787)
The SIMD MagSgn forward reader does 16-byte vector loads that can read
up to 16 bytes past the end of the code-block data, but the buffer was
padded by only 8 bytes, so a small MagSgn segment (scup < 8) over-read
the allocation. Pad by 16 bytes to cover the widest load.

Thanks to Ariel Koren (Anvil security research) for the audit that
discovered this issue and informed the fix.

Reported-by: Ariel Koren <ariel.koren@gmail.com>
Advisory: GROK-0012 (CWE-125)
@boxerab boxerab merged commit 8ff6c3a into master Jun 24, 2026
14 of 20 checks passed
@boxerab boxerab deleted the security/anvil-advisory-fixes branch June 24, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant