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()) {