Commit 3f41d712 for libheif

commit 3f41d712a434b21dbc8bb5cb9b1160478dcc2ec5
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Mon Oct 5 14:02:07 2026 +0200

    Bound the 'sdtp' sample count by max_sequence_frames

    Box_sdtp::parse() read one sample-dependency byte for every remaining
    byte of the box and resized its table to that count without any limit,
    unlike the other sample tables. Cap the count by max_sequence_frames and
    account the allocation through a MemoryHandle, mirroring Box_saiz.

diff --git a/libheif/sequences/seq_boxes.cc b/libheif/sequences/seq_boxes.cc
index add0f703..e6cc03f2 100644
--- a/libheif/sequences/seq_boxes.cc
+++ b/libheif/sequences/seq_boxes.cc
@@ -2263,10 +2263,22 @@ Error Box_sdtp::parse(BitstreamRange& range, const heif_security_limits* limits)
   // in the standard. Instead, we read until the end of the box.
   size_t nSamples = range.get_remaining_bytes();

+  if (limits && limits->max_sequence_frames > 0 && nSamples > limits->max_sequence_frames) {
+    return {
+      heif_error_Memory_allocation_error,
+      heif_suberror_Security_limit_exceeded,
+      "Number of 'sdtp' samples exceeds the maximum number of sequence frames."
+    };
+  }
+
+  if (auto err = m_memory_handle.alloc(nSamples, limits, "the 'sdtp' table")) {
+    return err;
+  }
+
   m_sample_information.resize(nSamples);
   range.read(m_sample_information.data(), nSamples);

-  return Error::Ok;
+  return range.get_error();
 }


diff --git a/libheif/sequences/seq_boxes.h b/libheif/sequences/seq_boxes.h
index cf4761fc..462a44f9 100644
--- a/libheif/sequences/seq_boxes.h
+++ b/libheif/sequences/seq_boxes.h
@@ -961,6 +961,7 @@ protected:

 private:
   std::vector<uint8_t> m_sample_information;
+  MemoryHandle m_memory_handle;
 };


diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 38c877ec..616a0b8d 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -55,6 +55,7 @@ else()
     add_libheif_test(duplicate_alpha_channel)
     add_libheif_test(idat)
 add_libheif_test(box_dump_limit)
+add_libheif_test(sdtp_sample_limit)
     add_libheif_test(scale_plane_checks)
     add_libheif_test(crop_plane_checks)
     add_libheif_test(extract_area_plane_checks)
diff --git a/tests/sdtp_sample_limit.cc b/tests/sdtp_sample_limit.cc
new file mode 100644
index 00000000..e9d261c3
--- /dev/null
+++ b/tests/sdtp_sample_limit.cc
@@ -0,0 +1,88 @@
+/*
+  libheif unit tests
+
+  MIT License
+
+  Copyright (c) 2026 Dirk Farin <dirk.farin@gmail.com>
+
+  Permission is hereby granted, free of charge, to any person obtaining a copy
+  of this software and associated documentation files (the "Software"), to deal
+  in the Software without restriction, including without limitation the rights
+  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+  copies of the Software, and to permit persons to whom the Software is
+  furnished to do so, subject to the following conditions:
+
+  The above copyright notice and this permission notice shall be included in all
+  copies or substantial portions of the Software.
+
+  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+  SOFTWARE.
+*/
+
+// 'sdtp' stores one byte per sample and reads to the end of the box, so its
+// sample count scales with the file size. Like the other sample tables it is
+// now bounded by max_sequence_frames at parse time.
+
+#include "catch_amalgamated.hpp"
+#include "box.h"
+#include "security_limits.h"
+
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+namespace {
+
+std::vector<uint8_t> sdtp_box(uint32_t num_samples)
+{
+  std::vector<uint8_t> v;
+  uint32_t size = 8 + 4 + num_samples;
+  v.push_back(uint8_t(size >> 24));
+  v.push_back(uint8_t(size >> 16));
+  v.push_back(uint8_t(size >> 8));
+  v.push_back(uint8_t(size));
+  for (char c : std::string("sdtp")) v.push_back(uint8_t(c));
+  v.push_back(0); v.push_back(0); v.push_back(0); v.push_back(0); // version + flags
+  v.insert(v.end(), num_samples, 0);
+  return v;
+}
+
+Error parse_sdtp(uint32_t num_samples, uint32_t max_sequence_frames)
+{
+  std::vector<uint8_t> data = sdtp_box(num_samples);
+  auto reader = std::make_shared<StreamReader_memory>(data.data(), data.size(), false);
+  BitstreamRange range(reader, data.size());
+
+  heif_security_limits limits = *heif_get_global_security_limits();
+  limits.max_sequence_frames = max_sequence_frames;
+
+  std::shared_ptr<Box> box;
+  return Box::read(range, &box, &limits);
+}
+
+} // namespace
+
+
+TEST_CASE("sdtp sample count is bounded by max_sequence_frames")
+{
+  SECTION("more samples than the limit allows is rejected") {
+    Error err = parse_sdtp(1000, 100);
+    REQUIRE(err.error_code == heif_error_Memory_allocation_error);
+    REQUIRE(err.sub_error_code == heif_suberror_Security_limit_exceeded);
+  }
+
+  SECTION("a table within the limit is accepted") {
+    Error err = parse_sdtp(50, 100);
+    REQUIRE(err.error_code == heif_error_Ok);
+  }
+
+  SECTION("the limit disabled (0) accepts any count") {
+    Error err = parse_sdtp(1000, 0);
+    REQUIRE(err.error_code == heif_error_Ok);
+  }
+}