Commit 69a0ca1b for libheif

commit 69a0ca1bed228e5766c298db8ee86b5569bba71a
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Fri May 15 20:07:26 2026 +0200

    pass a tightened heif_security_limits to the decoder plugins with a maximum image size just above the ispe size (#1798)

diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc
index be374c40..2a8b0346 100644
--- a/libheif/image-items/image_item.cc
+++ b/libheif/image-items/image_item.cc
@@ -1142,8 +1142,15 @@ Result<std::shared_ptr<HeifPixelImage>> ImageItem::decode_compressed_image(const

   decoder->set_data_extent(std::move(extent));

-  return decoder->decode_single_frame_from_compressed_data(options,
-                                                           get_context()->get_security_limits());
+  // Tighten max_image_size_pixels for this decode so a decoder plugin (e.g.
+  // dav1d) cannot allocate buffers far larger than the ispe-declared size
+  // when the codec bitstream lies about its dimensions.
+  heif_security_limits tightened = tighten_image_size_limit_for_ispe(
+      get_context()->get_security_limits(),
+      get_width(), get_height(),
+      max_coding_unit_size_for_codec(get_compression_format()));
+
+  return decoder->decode_single_frame_from_compressed_data(options, &tightened);
 }


diff --git a/libheif/image-items/tiled.cc b/libheif/image-items/tiled.cc
index eb04b06f..67768264 100644
--- a/libheif/image-items/tiled.cc
+++ b/libheif/image-items/tiled.cc
@@ -1011,8 +1011,13 @@ ImageItem_Tiled::decode_grid_tile(const heif_decoding_options& options, uint32_t

   m_tile_decoder->set_data_extent(std::move(*extentResult));

-  return m_tile_decoder->decode_single_frame_from_compressed_data(options,
-                                                                  get_context()->get_security_limits());
+  uint32_t tw = 0, th = 0;
+  get_tile_size(tw, th);
+  heif_security_limits tightened = tighten_image_size_limit_for_ispe(
+      get_context()->get_security_limits(), tw, th,
+      max_coding_unit_size_for_codec(m_tile_decoder->get_compression_format()));
+
+  return m_tile_decoder->decode_single_frame_from_compressed_data(options, &tightened);
 }


diff --git a/libheif/security_limits.cc b/libheif/security_limits.cc
index ed8241c6..c0074239 100644
--- a/libheif/security_limits.cc
+++ b/libheif/security_limits.cc
@@ -64,6 +64,42 @@ heif_security_limits disabled_security_limits{
 };


+uint32_t max_coding_unit_size_for_codec(heif_compression_format format)
+{
+  switch (format) {
+    case heif_compression_AV1:      return 128;  // AV1 max superblock
+    case heif_compression_VVC:      return 128;  // VVC max CTU
+    case heif_compression_HEVC:     return 64;   // HEVC max CTU
+    case heif_compression_AVC:      return 16;   // H.264 macroblock
+    case heif_compression_JPEG:     return 16;   // JPEG MCU (4:2:0)
+    case heif_compression_JPEG2000: return 64;
+    case heif_compression_HTJ2K:    return 64;
+    default:                        return 0;
+  }
+}
+
+
+heif_security_limits tighten_image_size_limit_for_ispe(const heif_security_limits* base,
+                                                       uint32_t ispe_width,
+                                                       uint32_t ispe_height,
+                                                       uint32_t coding_unit_size)
+{
+  heif_security_limits result = *base;
+
+  if (ispe_width == 0 || ispe_height == 0) {
+    return result;
+  }
+
+  uint64_t allowed = (static_cast<uint64_t>(ispe_width)  + coding_unit_size) *
+                     (static_cast<uint64_t>(ispe_height) + coding_unit_size);
+
+  if (result.max_image_size_pixels == 0 || allowed < result.max_image_size_pixels) {
+    result.max_image_size_pixels = allowed;
+  }
+  return result;
+}
+
+
 Error check_for_valid_image_size(const heif_security_limits* limits, uint32_t width, uint32_t height)
 {
   uint64_t maximum_image_size_limit = limits->max_image_size_pixels;
diff --git a/libheif/security_limits.h b/libheif/security_limits.h
index 6845008b..9daeb7e4 100644
--- a/libheif/security_limits.h
+++ b/libheif/security_limits.h
@@ -41,6 +41,26 @@ static const int MAX_FRACTION_VALUE = 0x10000;

 Error check_for_valid_image_size(const heif_security_limits* limits, uint32_t width, uint32_t height);

+// Maximum coding-unit size (in pixels) that the given codec may pad a coded
+// frame up to. Used as the margin for tighten_image_size_limit_for_ispe.
+// Returns 0 for codecs without coding-unit padding (e.g. uncompressed).
+uint32_t max_coding_unit_size_for_codec(heif_compression_format format);
+
+// Return a copy of `base` with max_image_size_pixels lowered to a value
+// just above the declared image size. This is used to bound how much memory
+// a codec plugin may allocate for an image whose internal (codec-declared)
+// dimensions exceed the file-declared (ispe) dimensions — without us having
+// to parse the codec bitstream ourselves.
+//
+// `coding_unit_size` is the maximum coding-unit size of the target codec
+// (e.g. 128 for AV1/VVC, 64 for HEVC, 16 for AVC). The allowed coded
+// dimensions are (ispe + coding_unit_size) in each axis, since a codec may
+// pad the coded frame up to a coding-unit boundary.
+heif_security_limits tighten_image_size_limit_for_ispe(const heif_security_limits* base,
+                                                       uint32_t ispe_width,
+                                                       uint32_t ispe_height,
+                                                       uint32_t coding_unit_size);
+

 class TotalMemoryTracker
 {