Commit 5219a32f for libheif

commit 5219a32f00030a4b9f1cf7b9095051a51bc443c5
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Sun Jan 11 16:00:41 2026 +0100

    replace 'sample' to 'chunk' in names of 'saio' box and add check for valid input (#1661)

diff --git a/libheif/sequences/seq_boxes.cc b/libheif/sequences/seq_boxes.cc
index 5f3ff1ac..91a050b8 100644
--- a/libheif/sequences/seq_boxes.cc
+++ b/libheif/sequences/seq_boxes.cc
@@ -1963,24 +1963,24 @@ void Box_saio::set_aux_info_type(uint32_t aux_info_type, uint32_t aux_info_type_
 }


-void Box_saio::add_sample_offset(uint64_t s)
+void Box_saio::add_chunk_offset(uint64_t s)
 {
   if (s > 0xFFFFFFFF) {
     m_need_64bit = true;
     set_version(1);
   }

-  m_sample_offset.push_back(s);
+  m_chunk_offset.push_back(s);
 }


-uint64_t Box_saio::get_sample_offset(uint32_t idx) const
+uint64_t Box_saio::get_chunk_offset(uint32_t idx) const
 {
-  if (idx >= m_sample_offset.size()) {
+  if (idx >= m_chunk_offset.size()) {
     return 0;
   }
   else {
-    return m_sample_offset[idx];
+    return m_chunk_offset[idx];
   }
 }

@@ -2006,8 +2006,8 @@ std::string Box_saio::dump(Indent& indent) const
     sstr << fourcc_to_string(m_aux_info_type_parameter) << "\n";
   }

-  for (size_t i = 0; i < m_sample_offset.size(); i++) {
-    sstr << indent << "[" << i << "] : 0x" << std::hex << m_sample_offset[i] << "\n";
+  for (size_t i = 0; i < m_chunk_offset.size(); i++) {
+    sstr << indent << "[" << i << "] : 0x" << std::hex << m_chunk_offset[i] << "\n";
   }

   return sstr.str();
@@ -2020,7 +2020,7 @@ void Box_saio::patch_file_pointers(StreamWriter& writer, size_t offset)

   writer.set_position(m_offset_start_pos);

-  for (uint64_t ptr : m_sample_offset) {
+  for (uint64_t ptr : m_chunk_offset) {
     if (get_version() == 0 && ptr + offset > std::numeric_limits<uint32_t>::max()) {
       writer.write32(0); // TODO: error
     } else if (get_version() == 0) {
@@ -2043,16 +2043,16 @@ Error Box_saio::write(StreamWriter& writer) const
     writer.write32(m_aux_info_type_parameter);
   }

-  if (m_sample_offset.size() > std::numeric_limits<uint32_t>::max()) {
+  if (m_chunk_offset.size() > std::numeric_limits<uint32_t>::max()) {
     return Error{heif_error_Unsupported_feature,
                  heif_suberror_Unspecified,
-                 "Maximum number of samples exceeded"};
+                 "Maximum number of chunks exceeded"};
   }
-  writer.write32(static_cast<uint32_t>(m_sample_offset.size()));
+  writer.write32(static_cast<uint32_t>(m_chunk_offset.size()));

   m_offset_start_pos = writer.get_position();

-  for (uint64_t size : m_sample_offset) {
+  for (uint64_t size : m_chunk_offset) {
     if (m_need_64bit) {
       writer.write64(size);
     } else {
@@ -2075,26 +2075,28 @@ Error Box_saio::parse(BitstreamRange& range, const heif_security_limits* limits)
     m_aux_info_type_parameter = range.read32();
   }

-  uint32_t num_samples = range.read32();
+  uint32_t num_chunks = range.read32();

-  if (limits && num_samples > limits->max_sequence_frames) {
+  // We have no explicit maximum on the number of chunks.
+  // Use the maximum number of frames as an upper limit.
+  if (limits && num_chunks > limits->max_sequence_frames) {
     return {
       heif_error_Memory_allocation_error,
       heif_suberror_Security_limit_exceeded,
-      "Number of 'saio' samples exceeds the maximum number of sequence frames."
+      "Number of 'saio' chunks exceeds the maximum number of sequence frames."
     };
   }

   // check required memory
-  uint64_t mem_size = num_samples * sizeof(uint64_t);
+  uint64_t mem_size = num_chunks * sizeof(uint64_t);

   if (auto err = m_memory_handle.alloc(mem_size, limits, "the 'saio' table")) {
     return err;
   }

-  m_sample_offset.resize(num_samples);
+  m_chunk_offset.resize(num_chunks);

-  for (uint32_t i = 0; i < num_samples; i++) {
+  for (uint32_t i = 0; i < num_chunks; i++) {
     uint64_t offset;
     if (get_version() == 1) {
       offset = range.read64();
@@ -2103,7 +2105,7 @@ Error Box_saio::parse(BitstreamRange& range, const heif_security_limits* limits)
       offset = range.read32();
     }

-    m_sample_offset[i] = offset;
+    m_chunk_offset[i] = offset;

     if (range.error()) {
       return range.get_error();
diff --git a/libheif/sequences/seq_boxes.h b/libheif/sequences/seq_boxes.h
index b7154d80..7c29ac4c 100644
--- a/libheif/sequences/seq_boxes.h
+++ b/libheif/sequences/seq_boxes.h
@@ -466,6 +466,8 @@ public:

   const std::vector<uint32_t>& get_offsets() const { return m_offsets; }

+  size_t get_number_of_chunks() const { return m_offsets.size(); }
+
   void patch_file_pointers(StreamWriter&, size_t offset) override;

 protected:
@@ -876,12 +878,12 @@ public:

   uint32_t get_aux_info_type_parameter() const { return m_aux_info_type_parameter; }

-  void add_sample_offset(uint64_t offset);
+  void add_chunk_offset(uint64_t offset);

-  // This will be 1 if all infos are written contiguously
-  size_t get_num_samples() const { return m_sample_offset.size(); }
+  // If this is 1, the SAI data of all samples is written contiguously in the file.
+  size_t get_num_chunks() const { return m_chunk_offset.size(); }

-  uint64_t get_sample_offset(uint32_t idx) const;
+  uint64_t get_chunk_offset(uint32_t idx) const;

   std::string dump(Indent&) const override;

@@ -901,8 +903,8 @@ private:
   bool m_need_64bit = false;
   mutable uint64_t m_offset_start_pos;

-  // If sample_offset==1, all samples are stored contiguous in the file
-  std::vector<uint64_t> m_sample_offset;
+  // If |chunk_offset|==1, the SAI data of all samples is stored contiguously in the file
+  std::vector<uint64_t> m_chunk_offset;

   MemoryHandle m_memory_handle;
 };
diff --git a/libheif/sequences/track.cc b/libheif/sequences/track.cc
index 669ae298..35759678 100644
--- a/libheif/sequences/track.cc
+++ b/libheif/sequences/track.cc
@@ -96,7 +96,7 @@ void SampleAuxInfoHelper::write_interleaved(const std::shared_ptr<HeifFile>& fil
   if (m_interleaved && !m_data.empty()) {
     // TODO: I think this does not work because the image data does not know that there is SAI in-between
     uint64_t pos = file->append_mdat_data(m_data);
-    m_saio->add_sample_offset(pos);
+    m_saio->add_chunk_offset(pos);

     m_data.clear();
   }
@@ -109,7 +109,7 @@ void SampleAuxInfoHelper::write_all(const std::shared_ptr<Box>& parent, const st

   if (!m_data.empty()) {
     uint64_t pos = file->append_mdat_data(m_data);
-    m_saio->add_sample_offset(pos);
+    m_saio->add_chunk_offset(pos);
   }
 }

@@ -120,9 +120,9 @@ SampleAuxInfoReader::SampleAuxInfoReader(std::shared_ptr<Box_saiz> saiz,
   m_saiz = saiz;
   m_saio = saio;

-  m_contiguous = (saio->get_num_samples() == 1);
+  m_contiguous = (saio->get_num_chunks() == 1);
   if (m_contiguous) {
-    uint64_t offset = saio->get_sample_offset(0);
+    uint64_t offset = saio->get_chunk_offset(0);
     auto nSamples = saiz->get_num_samples();

     for (uint32_t i = 0; i < nSamples; i++) {
@@ -151,7 +151,7 @@ Result<std::vector<uint8_t> > SampleAuxInfoReader::get_sample_info(const HeifFil
     offset = m_contiguous_offsets[idx];
   }
   else {
-    offset = m_saio->get_sample_offset(idx);
+    offset = m_saio->get_chunk_offset(idx);
   }

   uint8_t size = m_saiz->get_sample_size(idx);
@@ -409,6 +409,15 @@ Error Track::load(const std::shared_ptr<Box_trak>& trak_box)
     }

     if (saio) {
+      if (saio->get_num_chunks() != 1 &&
+          saio->get_num_chunks() != m_stco->get_number_of_chunks()) {
+        return Error{
+          heif_error_Invalid_input,
+          heif_suberror_Unspecified,
+          "Invalid number of chunks in 'saio' box."
+        };
+      }
+
       if (aux_info_type == fourcc("suid")) {
         m_aux_reader_content_ids = std::make_unique<SampleAuxInfoReader>(saiz, saio);
       }