Summary
A crafted HEIF/AVIF file containing a clean aperture (clap) box that reduces image dimensions to zero causes an assertion failure (debug builds) or undefined behavior (release builds) when the tiling API is invoked with process_image_transformations=1.
The root cause is double application of the clap transformation: once during image loading (setting m_width=0) and again in process_image_transformations_on_tiling(), which passes the already-transformed zero width to Box_clap::left_rounded(0), triggering Fraction(0xFFFFFFFF, 2).
Target: libheif 1.23.0 (HEAD of v1.23.0 release tag)
Reporter: Feng Ning feng@innora.ai, Innora Security Research
Weakness
- CWE-190/CWE-191: Integer Overflow/Underflow (
0U - 1U = UINT32_MAX)
- CWE-617: Reachable Assertion (debug builds)
Severity
- CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H — 6.5 (Medium)
- Server-side (no UI): CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H — 7.5 (High)
Technical Detail
Call Chain
heif_image_handle_get_image_tiling(handle, 1, &tiling) [heif_tiling.cc:45]
→ tiling = handle->image->get_heif_image_tiling() [heif_tiling.cc:42]
→ tiling.image_width = m_width [image_item.cc:1325]
// m_width is ALREADY 0 (clap applied during loading)
→ process_image_transformations_on_tiling(tiling) [heif_tiling.cc:45]
→ clap->left_rounded(tiling.image_width) [image_item.cc:1459]
→ Fraction(image_width - 1U, 2U) [box.cc:3732]
// image_width=0 → 0-1U = 0xFFFFFFFF
→ assert(num <= INT32_MAX) [box.cc:85]
// 0xFFFFFFFF > INT32_MAX → SIGABRT
Why Double Application
- During
HeifContext::interpret_heif_file(), image items compute display dimensions by applying all transformation properties (irot, imir, clap). This sets m_width/m_height to post-clap values.
get_heif_image_tiling() at image_item.cc:1325 returns m_width/m_height directly — these are the transformed values.
process_image_transformations_on_tiling() iterates over all properties again and re-applies clap, operating on already-transformed dimensions.
Debug vs Release
| Build |
Behavior |
| Debug (assert on) |
assert(num <= INT32_MAX) → SIGABRT → DoS |
| Release (NDEBUG) |
int32_t(0xFFFFFFFF) = -1 → Fraction(-1, 2) → corrupt crop → tiling.image_width=0 returned silently |
Trigger Role Classification
| Role |
Privilege |
Trigger Method |
Impact |
| Any user (L0) |
None |
Open crafted .heif/.avif/.heic |
App crash (DoS) |
| Server (L0) |
None |
Upload to auto-thumbnail service |
Service crash |
Dynamic Evidence
- Crash artifact: 523-byte crafted HEIF file
- Reproduction: 20/20 deterministic with ASAN-instrumented
tile_fuzzer
- Discovery: libFuzzer with
-fsanitize=address,fuzzer on libheif 1.23.0 static build
- Not triggered by:
file_fuzzer (no tiling API) or box_fuzzer (parsing only)
Cross-Verification
5/5 independent models CONFIRMED (GLM-5.1, Gemini 2.5 Flash, kimi-k2.6, deepseek-v4-pro, Gemini 3.1 Pro). Consensus CVSS: 5.5-6.5 (Medium).
Suggested Fixes
Option A (defense-in-depth): Bounds check in left_rounded/right_rounded/top_rounded/bottom_rounded:
int Box_clap::left_rounded(uint32_t image_width) const {
if (image_width == 0) return 0;
Fraction pcX = m_horizontal_offset + Fraction(image_width - 1U, 2U);
...
}
Option B (architectural fix): get_heif_image_tiling() should return untransformed dimensions from ispe box:
tiling.image_width = m_ispe_width; // raw, not m_width
Option C: Replace assert in Fraction(uint32_t, uint32_t) with runtime check and error return.
Upstream Status
NOT fixed in v1.23.0 (latest). Verified against 10+ related Fraction/clap commits including CVE-2023-29659 and CVE-2026-32738. None address the zero-width tiling path.
Affected Versions
libheif 1.23.0 (confirmed). Likely all versions since process_image_transformations_on_tiling was introduced.
Disclosure Timeline
- 2026-06-16: Vulnerability discovered via libFuzzer
- 2026-06-16: GHSA submitted to strukturag/libheif
- 90-day responsible disclosure window
Summary
A crafted HEIF/AVIF file containing a clean aperture (clap) box that reduces image dimensions to zero causes an assertion failure (debug builds) or undefined behavior (release builds) when the tiling API is invoked with
process_image_transformations=1.The root cause is double application of the clap transformation: once during image loading (setting
m_width=0) and again inprocess_image_transformations_on_tiling(), which passes the already-transformed zero width toBox_clap::left_rounded(0), triggeringFraction(0xFFFFFFFF, 2).Target: libheif 1.23.0 (HEAD of v1.23.0 release tag)
Reporter: Feng Ning feng@innora.ai, Innora Security Research
Weakness
0U - 1U = UINT32_MAX)Severity
Technical Detail
Call Chain
Why Double Application
HeifContext::interpret_heif_file(), image items compute display dimensions by applying all transformation properties (irot, imir, clap). This setsm_width/m_heightto post-clap values.get_heif_image_tiling()atimage_item.cc:1325returnsm_width/m_heightdirectly — these are the transformed values.process_image_transformations_on_tiling()iterates over all properties again and re-applies clap, operating on already-transformed dimensions.Debug vs Release
assert(num <= INT32_MAX)→ SIGABRT → DoSint32_t(0xFFFFFFFF) = -1→ Fraction(-1, 2) → corrupt crop →tiling.image_width=0returned silentlyTrigger Role Classification
Dynamic Evidence
tile_fuzzer-fsanitize=address,fuzzeron libheif 1.23.0 static buildfile_fuzzer(no tiling API) orbox_fuzzer(parsing only)Cross-Verification
5/5 independent models CONFIRMED (GLM-5.1, Gemini 2.5 Flash, kimi-k2.6, deepseek-v4-pro, Gemini 3.1 Pro). Consensus CVSS: 5.5-6.5 (Medium).
Suggested Fixes
Option A (defense-in-depth): Bounds check in
left_rounded/right_rounded/top_rounded/bottom_rounded:Option B (architectural fix):
get_heif_image_tiling()should return untransformed dimensions fromispebox:tiling.image_width = m_ispe_width; // raw, not m_widthOption C: Replace
assertinFraction(uint32_t, uint32_t)with runtime check and error return.Upstream Status
NOT fixed in v1.23.0 (latest). Verified against 10+ related Fraction/clap commits including CVE-2023-29659 and CVE-2026-32738. None address the zero-width tiling path.
Affected Versions
libheif 1.23.0 (confirmed). Likely all versions since
process_image_transformations_on_tilingwas introduced.Disclosure Timeline