Commit 187626ff for libheif

commit 187626ffd8d57505aaf6f3ae8174dca0cf0c8a5d
Author: Dirk Farin <dirk.farin@gmail.com>
Date:   Tue May 19 00:56:12 2026 +0200

    validate data size against file size and maximum memory limit

diff --git a/libheif/codecs/decoder.cc b/libheif/codecs/decoder.cc
index 3e7f0a48..44450e45 100644
--- a/libheif/codecs/decoder.cc
+++ b/libheif/codecs/decoder.cc
@@ -75,11 +75,31 @@ Result<std::vector<uint8_t>*> DataExtent::read_data() const
     if (err) {
       return err;
     }
+
+    // Account the (now-known) buffer size against the file's total-memory budget.
+    // append_data_from_iloc has already enforced max_memory_block_size per extent.
+    if (auto memErr = m_raw_memory_handle.alloc(m_raw.size(), m_file->get_security_limits(),
+                                                "decoder input buffer (iloc)")) {
+      m_raw.clear();
+      m_raw.shrink_to_fit();
+      return memErr;
+    }
   }
   else {
+    assert(m_file);
+
+    // Reserve the buffer in the total-memory tracker before allocating it.
+    // This also enforces max_memory_block_size and rejects sizes that would
+    // exceed max_total_memory across all concurrently-live DataExtents.
+    if (auto memErr = m_raw_memory_handle.alloc(m_size, m_file->get_security_limits(),
+                                                "decoder input buffer (sample)")) {
+      return memErr;
+    }
+
     // file range
     Error err = m_file->append_data_from_file_range(m_raw, m_offset, m_size);
     if (err) {
+      m_raw_memory_handle.free();
       return err;
     }
   }
diff --git a/libheif/codecs/decoder.h b/libheif/codecs/decoder.h
index 16dbf25a..a45d012f 100644
--- a/libheif/codecs/decoder.h
+++ b/libheif/codecs/decoder.h
@@ -25,6 +25,7 @@
 #include "box.h"
 #include "error.h"
 #include "file.h"
+#include "security_limits.h"

 #include <memory>
 #include <optional>
@@ -55,6 +56,10 @@ struct DataExtent
   // --- raw data
   mutable std::vector<uint8_t> m_raw; // also for cached data

+  // Holds m_raw's allocation against the file's max_total_memory budget.
+  // Released when DataExtent is destroyed (or moved-from).
+  mutable MemoryHandle m_raw_memory_handle;
+
   // --- image
   heif_item_id m_item_id = 0;

diff --git a/libheif/file.cc b/libheif/file.cc
index d4b4e350..eb90f61e 100644
--- a/libheif/file.cc
+++ b/libheif/file.cc
@@ -830,17 +830,47 @@ Result<std::vector<uint8_t>> HeifFile::get_uncompressed_item_data(heif_item_id I

 Error HeifFile::append_data_from_file_range(std::vector<uint8_t>& out_data, uint64_t offset, uint32_t size) const
 {
-  bool success = m_input_stream->seek(offset);
-  if (!success) {
-    // TODO: error
+  auto old_size = out_data.size();
+
+  // --- check that the requested range does not exceed the file size
+
+  uint64_t end_pos = offset + size;
+  if (m_input_stream->wait_for_file_size(end_pos) != StreamReader::grow_status::size_reached) {
+    std::stringstream sstr;
+    sstr << "File range " << offset << ".." << end_pos << " is beyond end of file.";
+    return {heif_error_Invalid_input,
+            heif_suberror_End_of_data,
+            sstr.str()};
+  }
+
+  // --- check security limit on resulting buffer size
+
+  if (m_limits) {
+    auto max_memory_block_size = m_limits->max_memory_block_size;
+    if (max_memory_block_size && max_memory_block_size - old_size < size) {
+      std::stringstream sstr;
+      sstr << "Sample data of " << size << " bytes would grow total buffer to "
+           << (old_size + size) << " bytes, exceeding the security limit of "
+           << max_memory_block_size << " bytes.";
+      return {heif_error_Memory_allocation_error,
+              heif_suberror_Security_limit_exceeded,
+              sstr.str()};
+    }
+  }
+
+  if (!m_input_stream->seek(offset)) {
+    return {heif_error_Invalid_input,
+            heif_suberror_End_of_data,
+            "Cannot seek to sample data offset."};
   }

-  auto old_size = out_data.size();
   out_data.resize(old_size + size);

-  success = m_input_stream->read(out_data.data() + old_size, size);
-  if (!success) {
-    // TODO: error
+  if (!m_input_stream->read(out_data.data() + old_size, size)) {
+    out_data.resize(old_size);
+    return {heif_error_Invalid_input,
+            heif_suberror_End_of_data,
+            "Failed to read sample data from file."};
   }

   return {};
diff --git a/libheif/file.h b/libheif/file.h
index a9871884..34dc2502 100644
--- a/libheif/file.h
+++ b/libheif/file.h
@@ -68,6 +68,8 @@ public:
   // You have to make sure that the pointer points to a valid object as long as the HeifFile is used.
   void set_security_limits(const heif_security_limits* limits) { m_limits = limits; }

+  const heif_security_limits* get_security_limits() const { return m_limits; }
+
   Error read(const std::shared_ptr<StreamReader>& reader);

   Error read_from_file(const char* input_filename);
diff --git a/libheif/security_limits.h b/libheif/security_limits.h
index 9daeb7e4..a8abb4d2 100644
--- a/libheif/security_limits.h
+++ b/libheif/security_limits.h
@@ -92,8 +92,27 @@ public:

   const heif_security_limits* get_security_limits() const { return m_limits_context; }

-  void operator=(const MemoryHandle&) = delete;
   MemoryHandle(const MemoryHandle&) = delete;
+  MemoryHandle& operator=(const MemoryHandle&) = delete;
+
+  MemoryHandle(MemoryHandle&& other) noexcept
+      : m_limits_context(other.m_limits_context), m_memory_amount(other.m_memory_amount)
+  {
+    other.m_limits_context = nullptr;
+    other.m_memory_amount = 0;
+  }
+
+  MemoryHandle& operator=(MemoryHandle&& other) noexcept
+  {
+    if (this != &other) {
+      free();
+      m_limits_context = other.m_limits_context;
+      m_memory_amount = other.m_memory_amount;
+      other.m_limits_context = nullptr;
+      other.m_memory_amount = 0;
+    }
+    return *this;
+  }

 private:
   const heif_security_limits* m_limits_context = nullptr;
diff --git a/libheif/sequences/track_visual.cc b/libheif/sequences/track_visual.cc
index a36659f1..1ea25e94 100644
--- a/libheif/sequences/track_visual.cc
+++ b/libheif/sequences/track_visual.cc
@@ -247,7 +247,7 @@ Result<std::shared_ptr<HeifPixelImage> > Track_Visual::decode_next_image_sample(
       // --- Find the data extent that stores the compressed frame data.

       DataExtent extent = chunk->get_data_extent_for_sample(sample_idx_in_chunk);
-      decoder->set_data_extent(extent);
+      decoder->set_data_extent(std::move(extent));

       // std::cout << "PUSH chunk " << chunk_idx << " sample " << sample_idx << " (" << extent.m_size << " bytes)\n";