Commit 097e29b2 for libheif

commit 097e29b2343ece41f4ee5d3898e801997ce9e98b
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue Aug 25 02:35:31 2026 +0200

    fuzzing: limit image size to 16 Mpixel to bound codec-internal frame buffers

    The CIFuzz MemorySanitizer job hit a libFuzzer out-of-memory in
    file_fuzzer on an 857-byte corpus file. Its AV1 bitstream declares a
    6279x8996 4:4:4 frame and dav1d allocates two 349 MB pictures inside
    dav1d_submit_frame before the tile decode fails. libheif's
    max_total_memory and max_memory_block_size never see these
    codec-internal buffers. The only limit that reaches them is
    max_image_size_pixels (passed on as dav1d's frame_size_limit), which
    the fuzzers left at the 1 Gpixel default. Under MSan every allocation
    costs about three times its size in RSS, so 724 MB of real memory
    became 2617 MB and exceeded the 2560 MB limit.

    Set max_image_size_pixels to 16 Mpixel in file_fuzzer, tile_fuzzer and
    sequence_fuzzer, and add the input as a regression seed.

diff --git a/fuzzing/data/corpus/av1-huge-frame-header-oom.heic b/fuzzing/data/corpus/av1-huge-frame-header-oom.heic
new file mode 100644
index 00000000..0f843647
Binary files /dev/null and b/fuzzing/data/corpus/av1-huge-frame-header-oom.heic differ
diff --git a/fuzzing/file_fuzzer.cc b/fuzzing/file_fuzzer.cc
index 13fefb60..b447e0fd 100644
--- a/fuzzing/file_fuzzer.cc
+++ b/fuzzing/file_fuzzer.cc
@@ -102,12 +102,19 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
   heif_get_file_mime_type(data, clip_int(size));

   ctx = heif_context_alloc();
-  heif_context_get_security_limits(ctx)->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024;
   assert(ctx);

   auto* limits = heif_context_get_security_limits(ctx);
+  limits->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024;
   limits->max_memory_block_size = 128 * 1024 * 1024; // 128 MB

+  // Also bound the image size itself. libheif's memory accounting only covers its own
+  // allocations, but the codec libraries allocate their frame buffers before libheif
+  // sees anything (dav1d needs ~12.5 bytes/pixel for a frame header alone). Under MSan,
+  // where every allocation costs about three times its size in RSS, a 56 Mpixel AV1 frame
+  // header was enough to exceed libFuzzer's 2560 MB limit.
+  limits->max_image_size_pixels = 16 * 1024 * 1024; // 16 Mpixel
+
   err = heif_context_read_from_memory(ctx, data, size, nullptr);
   if (err.code != heif_error_Ok) {
     // Not a valid HEIF file passed (which is most likely while fuzzing).
diff --git a/fuzzing/sequence_fuzzer.cc b/fuzzing/sequence_fuzzer.cc
index 4a4b3a9d..bfbc07f7 100644
--- a/fuzzing/sequence_fuzzer.cc
+++ b/fuzzing/sequence_fuzzer.cc
@@ -36,6 +36,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
   auto* limits = heif_context_get_security_limits(ctx);
   limits->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024;
   limits->max_memory_block_size = 128 * 1024 * 1024;
+  // Also bound the image size itself. libheif's memory accounting only covers its own
+  // allocations, but the codec libraries allocate their frame buffers before libheif
+  // sees anything (dav1d needs ~12.5 bytes/pixel for a frame header alone). Under MSan,
+  // where every allocation costs about three times its size in RSS, a 56 Mpixel AV1 frame
+  // header was enough to exceed libFuzzer's 2560 MB limit.
+  limits->max_image_size_pixels = 16 * 1024 * 1024; // 16 Mpixel

   heif_error err = heif_context_read_from_memory(ctx, data, size, nullptr);
   if (err.code != heif_error_Ok) {
diff --git a/fuzzing/tile_fuzzer.cc b/fuzzing/tile_fuzzer.cc
index 4caf16c0..f530bb76 100644
--- a/fuzzing/tile_fuzzer.cc
+++ b/fuzzing/tile_fuzzer.cc
@@ -104,6 +104,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
   auto* limits = heif_context_get_security_limits(ctx);
   limits->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024;
   limits->max_memory_block_size = 128 * 1024 * 1024;
+  // Also bound the image size itself. libheif's memory accounting only covers its own
+  // allocations, but the codec libraries allocate their frame buffers before libheif
+  // sees anything (dav1d needs ~12.5 bytes/pixel for a frame header alone). Under MSan,
+  // where every allocation costs about three times its size in RSS, a 56 Mpixel AV1 frame
+  // header was enough to exceed libFuzzer's 2560 MB limit.
+  limits->max_image_size_pixels = 16 * 1024 * 1024; // 16 Mpixel

   err = heif_context_read_from_memory(ctx, data, size, nullptr);
   if (err.code != heif_error_Ok) {