Commit 97ec489a for libheif

commit 97ec489ae983636e7ac7b2ff38607c9eed0c6822
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sat Nov 29 21:46:50 2025 +0100

    [BSD3] impose security limits to the number of samples in stsz and stts boxes

diff --git a/libheif/api/libheif/heif_security.h b/libheif/api/libheif/heif_security.h
index 9dcc286d..d95378a0 100644
--- a/libheif/api/libheif/heif_security.h
+++ b/libheif/api/libheif/heif_security.h
@@ -63,6 +63,11 @@ typedef struct heif_security_limits
   uint64_t max_total_memory;
   uint32_t max_sample_description_box_entries;
   uint32_t max_sample_group_description_box_entries;
+
+  // --- version 3
+
+  uint32_t max_sequence_frames;
+  uint32_t max_number_of_file_brands;
 } heif_security_limits;


diff --git a/libheif/security_limits.cc b/libheif/security_limits.cc
index e7f96f90..11910c46 100644
--- a/libheif/security_limits.cc
+++ b/libheif/security_limits.cc
@@ -25,7 +25,7 @@


 heif_security_limits global_security_limits{
-    .version = 2,
+    .version = 3,

     // --- version 1

@@ -49,12 +49,17 @@ heif_security_limits global_security_limits{

     .max_total_memory = UINT64_C(4) * 1024 * 1024 * 1024,  // 4 GB
     .max_sample_description_box_entries = 1024,
-    .max_sample_group_description_box_entries = 1024
+    .max_sample_group_description_box_entries = 1024,
+
+    // --- version 3
+
+    .max_sequence_frames = 18'000'000,  // 100 hours at 50 fps
+    .max_number_of_file_brands = 1000
 };


 heif_security_limits disabled_security_limits{
-    .version = 2
+    .version = 3
 };


diff --git a/libheif/sequences/seq_boxes.cc b/libheif/sequences/seq_boxes.cc
index b500eba9..fb60be15 100644
--- a/libheif/sequences/seq_boxes.cc
+++ b/libheif/sequences/seq_boxes.cc
@@ -552,6 +552,14 @@ Error Box_stts::parse(BitstreamRange& range, const heif_security_limits* limits)

   uint32_t entry_count = range.read32();

+  if (entry_count > limits->max_sequence_frames) {
+    return {
+      heif_error_Memory_allocation_error,
+      heif_suberror_Security_limit_exceeded,
+      "Security limit for maximum number of sequence frames exceeded"
+    };
+  }
+
   if (auto err = m_memory_handle.alloc(entry_count * sizeof(TimeToSample),
                                        limits, "the 'stts' table")) {
     return err;
@@ -560,6 +568,18 @@ Error Box_stts::parse(BitstreamRange& range, const heif_security_limits* limits)
   m_entries.resize(entry_count);

   for (uint32_t i = 0; i < entry_count; i++) {
+    if (range.eof()) {
+      std::stringstream sstr;
+      sstr << "stts box should contain " << entry_count << " entries, but box only contained "
+          << i << " entries";
+
+      return {
+        heif_error_Invalid_input,
+        heif_suberror_End_of_data,
+        sstr.str()
+      };
+    }
+
     TimeToSample entry{};
     entry.sample_count = range.read32();
     entry.sample_delta = range.read32();
@@ -830,12 +850,32 @@ Error Box_stsz::parse(BitstreamRange& range, const heif_security_limits* limits)
   if (m_fixed_sample_size == 0) {
     // check required memory

+    if (m_sample_count > limits->max_sequence_frames) {
+      return {
+        heif_error_Memory_allocation_error,
+        heif_suberror_Security_limit_exceeded,
+        "Security limit for maximum number of sequence frames exceeded"
+      };
+    }
+
     uint64_t mem_size = m_sample_count * sizeof(uint32_t);
     if (auto err = m_memory_handle.alloc(mem_size, limits, "the 'stsz' table")) {
       return err;
     }

     for (uint32_t i = 0; i < m_sample_count; i++) {
+      if (range.eof()) {
+        std::stringstream sstr;
+        sstr << "stsz box should contain " << m_sample_count << " entries, but box only contained "
+            << i << " entries";
+
+        return {
+          heif_error_Invalid_input,
+          heif_suberror_End_of_data,
+          sstr.str()
+        };
+      }
+
       m_sample_sizes.push_back(range.read32());

       if (range.error()) {