LIBHEIF-0002
high patchedGrid uint32 underflow → heap out-of-bounds read in decode_grid_tile
Classification
| Target | libheif |
|---|---|
| Component | Grid image item |
| Location | libheif/image-items/grid.cc · ImageItem_Grid::decode_grid_tile:586-588 |
| Vuln class | oob-read |
| CVE | CVE-2026-48029 |
| CVSS | 7.1 (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:H) |
| Discovered | 2026-05-03 |
Verification
| Evidence | ✓ Disclosure ready (real-world verified) |
|---|---|
| Harness fired | ✅ yes |
| Protocol | 1.0 |
| Sanitizer | asan |
| Crash type | SEGV (heap OOB READ) — unchecked std::vector::operator[] in decode_grid_tile at grid.cc:588 (NDEBUG) |
| Repro | Release-NDEBUG ASAN+UBSAN build of 1.21.2, decode_grid_overlay_runner on F2-clusterfuzz-irot270-16389b.bin → 'AddressSanitizer: SEGV ... READ' at grid.cc:588 (debug build: assert at grid.cc:586). v1.22.0: rc=0, no signal. |
Disclosure
| Reported to | GitHub private security advisory — strukturag/libheif |
|---|---|
| Reported | 2026-05-03 |
| Vendor ack | 2026-05-18 |
| Embargo until | — |
| Public | 2026-05-19 |
| Patched in | 1.22.0 |
PoC: project/blogpost/findings/F2-grid-oob-read/F2-clusterfuzz-irot270-16389b.bin (16,389-byte HEIF, irot=270, in /home/ariel/Projects/libheif)
Writeup
Summary
ImageItem_Grid::decode_grid_tile computes a tile index
idx = ty * cols + tx and guards it only with a debug assert before
m_grid_tile_ids[idx]. Two cooperating defects make idx attacker-influenced:
a uint32 underflow in the inverse-rotation tile arithmetic (CWE-191) feeds an
unchecked std::vector::operator[] (CWE-125). In release/NDEBUG builds — the way
distros ship libheif — the assert is compiled out and the access is a real heap
out-of-bounds read of 4 bytes at an attacker-influenced offset.
Reproduction
16,389-byte HEIF with a grid item carrying an irot=270 rotation property,
decoded via heif_image_handle_decode_image_tile (or the full-grid path):
==ERROR: AddressSanitizer: SEGV on unknown address 0x610400000e00 (READ)
#0 ImageItem_Grid::decode_grid_tile grid.cc:588:26
#1 ImageItem_Grid::decode_compressed_image grid.cc:224
...
(Debug build instead hits assert(idx < m_grid_tile_ids.size()) at
grid.cc:586.) The release-NDEBUG re-run via the build-flavor matrix is what
proved this is memory-safety, not a robustness assert.
Root cause
Two sites (the sibling-class audit localized both):
- Source —
image_item.cc:604+(transform_requested_tile_position_to_original_tile_position): forirot90°/270° withrows ≠ columns, the inverse-rotation subtractions use pre-rotationtiling.num_columns/num_rowsagainst post-rotation caller coordinates, underflowing to ~UINT32_MAX. - Sink —
grid.cc:586-588: theidxbounds check is a debug-onlyassert; release builds index the vector unchecked.
Fixed upstream in v1.22.0: commit 518bd95f replaces the assert with a
runtime bounds check (sink); commit e523ec0b validates (tile_x,tile_y)
against post-rotation dims before any subtraction (source). Sibling
overlay.cc:265 already parse-time count-checks; grid did not.
Verification evidence
Verified per the verification protocol
v1.0: deterministic ASAN SEGV (READ) at grid.cc:588 on a release-NDEBUG 1.21.2
build; debug build aborts on the grid.cc:586 assert; re-tested against a fresh
v1.22.0 release+debug build → both return cleanly
(heif_error_Invalid_input / heif_suberror_Missing_grid_images). Reachable with
no codec back-end installed.
Impact
Per the impact rubric: high (CWE-191 → CWE-125). Heap OOB read with an
attacker-influenced offset, reachable from public decode APIs
(heif_image_handle_decode_image_tile, heif_image_handle_get_grid_image_tile_id,
and the full-grid heif_decode_image path) on a crafted HEIF/HEIC/AVIF before any
codec runs. Deterministic SIGSEGV when the offset overshoots into unmapped memory;
where it lands in mapped heap, the 4 bytes read are used as a heif_item_id
lookup key (not a trivially-leaking primitive). Disclosed via GitHub private
security advisory (2026-05-03), fixed in v1.22.0 (2026-05-19),
CVE-2026-48029.