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";