GHSA-x8xm-cm2c-cfc8
CVE Information
Summary
libheif supports HEIF derived image types (
grid,iovloverlay, and the experimentaltilitiled image). The decode path forgridandiovldoes not cache the result of decoding a referenced base image, so when many derived items reference the same underlying encoded image throughiden(identity) indirection, the base image is fully decoded once per reference. Because the cycle-detectionprocessed_idsset is passed by value through the decode call chain andBox_iref::check_for_double_references()only deduplicates references within a single iref entry, the cross-entry indirect reference structure is legal and the base image can be decoded up to ~996x (grid) or ~998x (overlay). Separately, the experimentaltilitiled image type eagerly pre-allocates astd::vectorof tile offsets inTiledHeader::set_parameters()without routing the allocation throughMemoryHandle, somax_total_memoryis never enforced and a single small file can allocate hundreds of MB at file-open time.Three variants share this cluster. All three are confirmed with POC:
Variant Type CVSS V1: grid + iden indirect reference (no decode cache, CPU) grid/iden 6.5 V2: overlay + iden indirect reference (no decode cache, CPU) iovl/iden 7.5 V3: tiled m_offsets pre-allocation bypasses MemoryHandle (memory) tili 7.5 We assess the cluster at roughly CVSS 3.1 7.5 (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). This score is an automated estimate from our analysis and is provided for reference; the final score is for the maintainer to confirm. V1 carries UI:R because triggering it requires the consumer to request the grid image, whereas V2 triggers during overlay decode and V3 triggers at file-open.
Tested versions
Version / revision Build Result v1.23.1 (v1.23.1-7-g1a3583bc) ASan affected. Amplification confirmed via POC. v1.23.1 non-ASan affected. CPU exhaustion / large allocation confirmed. Source was not modified. V3 additionally requires
ENABLE_EXPERIMENTAL_FEATURES=ON(non-default) for thetilipath.Root cause
Two related root causes:
No decode-result caching across derived items (V1, V2).
ImageItem::decode_image()and the derived-image decode paths (ImageItem_Grid::decode_and_paste_tile_image,ImageItem_Overlay::decode_overlay_image,ImageItem_iden::decode_compressed_image) receive theprocessed_idscycle-detection set by value. The base image ID therefore never enters the shared set, so repeated references to the same base image are each treated as a fresh decode.Box_iref::check_for_double_references()only deduplicates references within a single iref entry, not across entries, so a grid/overlay item referencing up to ~996/998 distinctidenitems that each point at the same base image is a legal structure. The overlay path additionally performs a YUV 4:2:0 to RGB 4:4:4 colorspace conversion per decodediden, stacking extra CPU cost on top of the decode amplification.Tiled offsets allocation not routed through MemoryHandle (V3).
TiledHeader::set_parameters()callsm_offsets.resize(nTiles)wherenTilescan reach 16,777,216. The allocation uses a standardstd::vectorallocator that bypassesMemoryHandletracking entirely, somax_total_memory(default 4 GB) is never enforced. The allocation is eager, performed at file-open viaImageItem_Tiled::initialize_decoder()(called fromHeifContext::interpret_heif_file_images), rather than at decode time, and uses global security limits instead of context limits.Affected files and functions: -
libheif/image-items/grid.cc—ImageItem_Grid::decode_and_paste_tile_image,decode_full_grid_image-libheif/image-items/overlay.cc—ImageItem_Overlay::decode_overlay_image,read_overlay_spec-libheif/image-items/tiled.cc—TiledHeader::set_parameters,ImageItem_Tiled::initialize_decoder-libheif/image-items/iden.cc—ImageItem_iden::decode_compressed_image-libheif/image-items/image_item.cc—ImageItem::decode_image-libheif/box.cc—Box_iref::check_for_double_references,Box_iref::parse-libheif/security_limits.cc—global_security_limitsSecurity boundary and prerequisites
- Attacker: remote, unauthenticated. Sends a crafted HEIF/AVIF file.
- No authentication required (file parsing requires none).
- Attack complexity: Low. A single malicious file triggers the issue.
- V3 requires the
tilifeature compiled in (ENABLE_EXPERIMENTAL_FEATURES=ON), which is non-default; this lowers its real-world exploitability.PoC
V2: iovl + iden CPU amplification (representative, highest impact)
Construct a HEIF file with: - Item 1:
uncibase image (e.g., 256×256 RGB, 196KB) - Items 2..999: 998idenitems, each referencing Item 1 viadimg- Item 1000:iovl(overlay) item, referencing all 998 iden items viadimg- Total: 1000 items (withinmax_items=1000limit)When
heif_decode_image()is called on the iovl item,decode_overlay_image()iterates all 998 iden items. Each iden triggersdecode_image(base), which fully decodes the 256×256 base image from scratch. No decode result cache exists. The base image is decoded 998 times.CPU amplification results:
Base image File size Decode time Amplification 64×64 80 KB 376 ms 668× 256×256 265 KB 5,694 ms 917× 512×512 835 KB 22,786 ms 941× 1024×1024 3.1 MB 91,300 ms 940× 2048×2048 12.1 MB 366,600 ms (6.1 min) 940× Official tools affected:
heif-thumbnailer(265KB file → 5.8s),heif-dec(265KB file → 13.2s).heif-infois not affected (only reads metadata, does not decode).Security limits bypassed:
max_items=1000(exactly 1000 items, within limit),check_for_double_references()(no duplicates within single iref entry),processed_ids(only prevents circular refs, not repeated base decoding).V1: grid + iden (same mechanism, grid instead of iovl)
Same file structure but using
griditem instead ofiovl.check_for_double_references()correctly rejects a grid that directly references the same base image multiple times, but does NOT reject the indirect chaingrid → iden → base. GDB verification confirms base image is decoded N times.V3: tiled m_offsets memory amplification
Construct a HEIF file with a
tiliitem containingtilCbox withtile_width=1, tile_height=1andispebox withwidth=4096, height=4096. This results innTiles = 4096 × 4096 / (1 × 1) = 16,777,216.When libheif opens this file,
TiledHeader::set_parameters()callsm_offsets.resize(16,777,216), allocating ~256MB without MemoryHandle tracking.Memory amplification results:
Items File size RSS Amplification 1 245 bytes 262 MB 1,121,485:1 5 445 bytes 1,285 MB 3,030,083:1 10 696 bytes 2,566 MB 3,866,053:1 Security limit bypass:
max_total_memory=1byte does not prevent allocation —m_offsets.resize()is not tracked byMemoryHandle.V3 requires
ENABLE_EXPERIMENTAL_FEATURES=ONat build time. Thetiliitem type is not registered in default builds.Impact
- Availability (V1, V2): CPU exhaustion. V1 achieves up to ~996x decode amplification; V2 achieves ~940x amplification, with a 265 KB file exceeding a common 5 s API timeout and a 12.1 MB file consuming 366.6 s CPU. A:H.
- Availability (V3): memory exhaustion. Up to 256 MB per item is allocated untracked; with
max_items=1000, up to ~250 GB of untracked memory can be requested from a 245-byte file (1.12Mx amplification). A:H.- Remote: exploitable by sending a crafted file. V2 and V3 require no user interaction beyond opening the file.
Suggested fix
- For V1 and V2: introduce a decode-result cache keyed by item ID (shared across a single top-level decode operation), or pass
processed_idsby reference and treat a second visit as a cache hit rather than a cycle. Also enforce an upper bound on the number of derived-image references (dimg) a single item may declare.- For V3: route the
m_offsetsallocation throughMemoryHandleso it is counted againstmax_total_memory, defer the allocation to decode time, and use the context security limits rather than the global limits. Add amax_tiles_per_imagesecurity-limit field.Reporter and coordination
- Reporter(s): Yuqi Qiu & Xiang Li
- Affiliation: Nankai University, AOSP Lab