Skip to content

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.

REPRO-2026-00142 libheif · c DoS May 22, 2026 CVE entry ↗ .txt
Severity
MEDIUM
CVSS
6.5
Reproduced in
50m 30s
Tool calls
313
Spend
$2.85
01 · Overview

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).

02 · Severity & CVSS

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 threat level
6.5 / 10 CVSS base
Weakness CWE-191 (Integer Underflow) / CWE-125 (Out-of-bounds Read)

Medium — meaningful risk under specific conditions. Schedule a fix in the normal cycle.

03 · Affected Versions

Affected libheif Versions

libheif · c versions <= 1.21.2 are affected.

How to Reproduce CVE-2026-32738

$ pruva-verify REPRO-2026-00142
or curl -O https://pruva.dev/api/v1/reproductions/REPRO-2026-00142/artifacts/reproduction_steps.sh && chmod +x reproduction_steps.sh && ./reproduction_steps.sh
Run in a VM or disposable container. This exploits a real vulnerability.
06 · Proof of Reproduction

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 1,118 events · 313 tool calls · 51 min
51 minDuration
313Tool calls
267Reasoning steps
1,118Events
1Dead-ends
Agent activity over 51 min
Support
29
Repro
606
Variant
478
0:0050:30

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:

  1. Missing validation in Box_stsc::parse(): The samples_per_chunk field is read as a raw uint32_t from the file with no rejection of the value 0, even though ISO 14496-12 requires this field to be ≥ 1.

  2. Unsigned integer underflow in Chunk::Chunk() (line ~82):

    m_last_sample = first_sample + num_samples - 1;
    

    When num_samples = 0 and first_sample = 0, this evaluates to 0 + 0 - 1 = UINT32_MAX due to unsigned underflow. The m_sample_ranges vector remains empty because the for (i < 0) loop never executes.

  3. 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_ranges is empty, accessing index 0 dereferences a null pointer (empty vector begin()), causing a SEGV.

  4. All samples mapped to the empty chunk: In Track::init_sample_timing_table(), the loop condition i > m_chunks[current_chunk]->last_sample_number() is always false because last_sample_number() returns UINT32_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:

  1. Installs build dependencies (cmake, build-essential, git, libjpeg-dev).
  2. Clones libheif from GitHub.
  3. Builds the vulnerable version v1.21.2 and the fixed version v1.22.0 with minimal codecs (only JPEG decoder and uncompressed codec enabled).
  4. Creates two tiny JPEG images and uses heif-enc to generate a valid 2-frame HEIF sequence.
  5. Binary-patches the stsc box in the sequence file, changing samples_per_chunk from 2 to 0.
  6. Compiles a small C harness that opens the file and calls heif_track_decode_next_image().
  7. 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 in Chunk::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: 139
    

    Exit 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: 1
    

    The 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

  1. Upgrade to libheif 1.22.0 or later — the fix adds explicit validation of stsc entries against stsz/stts sample counts and rejects zero samples_per_chunk values.
  2. Add regression tests — the reproduction file and harness can be incorporated into CI to prevent reintroduction of the bug.
  3. Harden parsing — consider adding more defensive checks in Box_stsc::parse() and Chunk::Chunk() to reject invalid zero values immediately, rather than relying on downstream validation.

Additional Notes

  • Idempotency: repro/reproduction_steps.sh was 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 one stsc entry 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.

Event 1/40
0:001:14
0:00
session startedaccounts/fireworks/models/kimi-k2p6 · cve-2026-32738 · cve-2026
0:04
0:05
0:06
web search
0:11
0:12
web search
0:16
0:17
web search
0:29
0:30
web search
0:40
0:42
web search
1:09
1:09
extract_facts
no facts extracted
1:10
1:10
1:10
supportrepro
1:13
1:14
1:14
1:14

Artifacts and Evidence for CVE-2026-32738

Scripts, logs, diffs, and output captured during the reproduction.

No artifacts available

08 · How to Fix

How to Fix CVE-2026-32738

Upgrade libheif · c to 1.22.0 or later.

Coming soon

Step-by-step mitigation and hardening guidance for CVE-2026-32738 — configuration checks, workarounds where no patch exists, and how to verify you're protected — is on the way.

10 · FAQ

FAQ: CVE-2026-32738

How does the CVE-2026-32738 crash occur?

With the sample-range vector left empty due to the underflow, subsequent access to any sample indexes into index 0 of that empty std::vector, dereferencing a null page — an out-of-bounds read that crashes the process with SIGSEGV. Any application decoding an attacker-supplied HEIF sequence file is exposed.

Which libheif versions are affected by CVE-2026-32738, and where is it fixed?

libheif <= 1.21.2 is affected. It is fixed in 1.22.0 — upgrade to 1.22.0 or later.

How severe is CVE-2026-32738?

The record rates it medium severity; the root-cause analysis cites a CVSS 3.1 base score of 6.5. The impact is a denial-of-service crash, not code execution.

How can I reproduce CVE-2026-32738?

Download the verified script from this page and run it in an isolated environment against libheif <= 1.21.2. It feeds a crafted HEIF sequence file whose stsc box sets samples_per_chunk = 0 to the decoder and shows the resulting SIGSEGV crash, then confirms 1.22.0 rejects the malformed box instead of crashing.
11 · References

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.