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);
+ }
+}