CVE-2026-32738: Verified Repro With Script Download
CVE-2026-32738: libheif: integer underflow out-of-bounds read crash via crafted HEIF stsc box
CVE-2026-32738 is verified against libheif · c. Affected versions: <= 1.21.2. Fixed in 1.22.0. Vulnerability class: DoS. This medium reproduction includes runnable sandbox proof, artifacts, and a plain-text agent view under REPRO-2026-00142.
What Is CVE-2026-32738?
CVE-2026-32738 (CWE-191 Integer Underflow / CWE-125 Out-of-bounds Read) is a medium-severity denial-of-service vulnerability in libheif, where a crafted HEIF/HEIC sequence file causes an integer underflow leading to an out-of-bounds read and process crash. Pruva reproduced it (reproduction REPRO-2026-00142).
CVE-2026-32738 Severity & CVSS Score
CVE-2026-32738 is rated medium severity, with a CVSS base score of 6.5 out of 10.
Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.
Affected libheif Versions
libheif · c versions <= 1.21.2 are affected.
How to Reproduce CVE-2026-32738
pruva-verify REPRO-2026-00142 curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00142/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh Proof of Reproduction for CVE-2026-32738
Reproduced by Pruva's autonomous agents — 313 tool calls over 51 min. Full root-cause analysis and the complete transcript are below.
How the agent worked
Root Cause and Exploit Chain for CVE-2026-32738
Summary
libheif versions 1.21.2 and below contain an integer underflow vulnerability in the Chunk constructor during HEIF/HEIC sequence file parsing. When a crafted file's stsc (sample-to-chunk) box specifies samples_per_chunk = 0, the unsigned 32-bit expression first_sample + num_samples - 1 underflows to UINT32_MAX. This causes all sequence samples to be mapped to an empty chunk. Subsequent sample access attempts to read index 0 of an empty std::vector, resulting in a guaranteed null-page read / SIGSEGV crash — a denial of service against any application that decodes attacker-supplied HEIF sequence files.
Impact
- Package:
libheif(C++ library for HEIF/HEIC/AVIF) - Affected versions:
<= 1.21.2 - Fixed version:
1.22.0 - Risk level: Medium (CVSS 3.1 base 6.5)
- Consequences: Denial of service via process crash (SIGSEGV). Any application using libheif to decode HEIF sequences is exposed.
Root Cause
The vulnerability occurs in two locations in libheif/sequences/chunk.cc:
Missing validation in
Box_stsc::parse(): Thesamples_per_chunkfield is read as a rawuint32_tfrom the file with no rejection of the value0, even though ISO 14496-12 requires this field to be ≥ 1.Unsigned integer underflow in
Chunk::Chunk()(line ~82):m_last_sample = first_sample + num_samples - 1;When
num_samples = 0andfirst_sample = 0, this evaluates to0 + 0 - 1 = UINT32_MAXdue to unsigned underflow. Them_sample_rangesvector remains empty because thefor (i < 0)loop never executes.Out-of-bounds read in
Chunk::get_data_extent_for_sample()(line ~112):extent.set_file_range(m_ctx->get_heif_file(), m_sample_ranges[n - m_first_sample].offset, m_sample_ranges[n - m_first_sample].size);Since
m_sample_rangesis empty, accessing index0dereferences a null pointer (empty vectorbegin()), causing a SEGV.All samples mapped to the empty chunk: In
Track::init_sample_timing_table(), the loop conditioni > m_chunks[current_chunk]->last_sample_number()is always false becauselast_sample_number()returnsUINT32_MAX. Consequently, every sample is assigned to chunk 0, ensuring the crash is triggered on the first frame access.
The fix commit is edc12502 ("Validate stsc sample coverage against stsz/stts") in the v1.21.2..v1.22.0 history, which adds a validation check in Track::load() ensuring the total samples covered by stsc entries match the stsz/stts sample count. Additionally, Box_stsc::parse() or related logic in 1.22.0 explicitly rejects samples_per_chunk = 0 with the controlled error message: 'stsc' box with zero samples per chunk entry.
Reproduction Steps
The reproduction script is at repro/reproduction_steps.sh.
What the script does:
- Installs build dependencies (
cmake,build-essential,git,libjpeg-dev). - Clones
libheiffrom GitHub. - Builds the vulnerable version
v1.21.2and the fixed versionv1.22.0with minimal codecs (only JPEG decoder and uncompressed codec enabled). - Creates two tiny JPEG images and uses
heif-encto generate a valid 2-frame HEIF sequence. - Binary-patches the
stscbox in the sequence file, changingsamples_per_chunkfrom2to0. - Compiles a small C harness that opens the file and calls
heif_track_decode_next_image(). - Runs the harness against both library versions and captures logs.
Expected evidence:
- Vulnerable (1.21.2): The harness crashes with
SIGSEGV(exit code 139 / signal 11). AddressSanitizer builds show a clear null-page read inChunk::get_data_extent_for_sample(). - Fixed (1.22.0): The harness returns a controlled decode error:
Invalid input: Unspecified: 'stsc' box with zero samples per chunk entry.No crash occurs.
Evidence
Log files generated by the reproduction script:
logs/vulnerable.log:=== Testing vulnerable libheif v1.21.2 === Track handler: pict Vulnerable exit code: 139Exit code 139 = 128 + 11, confirming a
SIGSEGV(signal 11) crash.logs/fixed.log:=== Testing fixed libheif v1.22.0 === Error reading file: Invalid input: Unspecified: 'stsc' box with zero samples per chunk entry. Fixed exit code: 1The fixed version rejects the malformed file with a clean, controlled error instead of crashing.
AddressSanitizer output from the ASAN-instrumented vulnerable build (captured during verification):
DEBUG: get_data_extent_for_sample called with n=0, m_first_sample=0, m_last_sample=4294967295, vector_size=0
AddressSanitizer:DEADLYSIGNAL
==12262==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008
#0 Chunk::get_data_extent_for_sample(unsigned int) const
#1 Track_Visual::decode_next_image_sample(heif_decoding_options const&)
#2 heif_track_decode_next_image
Recommendations / Next Steps
- Upgrade to libheif 1.22.0 or later — the fix adds explicit validation of
stscentries againststsz/sttssample counts and rejects zerosamples_per_chunkvalues. - Add regression tests — the reproduction file and harness can be incorporated into CI to prevent reintroduction of the bug.
- Harden parsing — consider adding more defensive checks in
Box_stsc::parse()andChunk::Chunk()to reject invalid zero values immediately, rather than relying on downstream validation.
Additional Notes
- Idempotency:
repro/reproduction_steps.shwas run twice consecutively; both runs produced identical results (vulnerable crash, fixed clean rejection) and exited with code 0. - Edge cases: The crash is guaranteed as long as any accessed sample maps to a chunk with
samples_per_chunk = 0. The reproduction uses a minimal 2-frame sequence, but the vulnerability scales to any sequence file where at least onestscentry has a zero sample count.
CVE-2026-32738 Reproduction Transcript
The agent's step-by-step process — every tool call, every handoff, the moment the exploit fired.
Full session Replay every step — scrub the timeline or play it back.
Artifacts and Evidence for CVE-2026-32738
Scripts, logs, diffs, and output captured during the reproduction.
No artifacts available
How to Fix CVE-2026-32738
Upgrade libheif · c to 1.22.0 or later.
FAQ: CVE-2026-32738
How does the CVE-2026-32738 crash occur?
Which libheif versions are affected by CVE-2026-32738, and where is it fixed?
How severe is CVE-2026-32738?
How can I reproduce CVE-2026-32738?
References for CVE-2026-32738
Authoritative sources for CVE-2026-32738 — official vulnerability databases and the upstream advisory. Pruva's reproduction verifies the issue firsthand; these are the primary records to corroborate it.