Commit 02084d74 for libheif
commit 02084d74519d53537f0cd9bd954f3e42f74764ee
Author: Dirk Farin <dirk.farin@gmail.com>
Date: Sat May 16 12:19:38 2026 +0200
register memory allocated on derived limits to the parent (#1801)
diff --git a/libheif/api/libheif/heif_security.h b/libheif/api/libheif/heif_security.h
index f63f86c3..0777baa0 100644
--- a/libheif/api/libheif/heif_security.h
+++ b/libheif/api/libheif/heif_security.h
@@ -73,6 +73,15 @@ typedef struct heif_security_limits
// --- version 4
uint32_t max_bad_pixels;
+
+ // --- version 5
+
+ // Internal: when libheif derives a limits struct from another one (e.g. to
+ // tighten the maximum image size for a specific decode), this points back to
+ // the registered context whose total-memory budget the allocation should be
+ // accounted against. nullptr means "this is a root context" (the registered
+ // one). User code should leave this as nullptr; the field is set internally.
+ const struct heif_security_limits* parent;
} heif_security_limits;
diff --git a/libheif/context.cc b/libheif/context.cc
index 94c08391..408fd930 100644
--- a/libheif/context.cc
+++ b/libheif/context.cc
@@ -202,6 +202,10 @@ static void copy_security_limits(heif_security_limits* dst, const heif_security_
if (src->version >= 4) {
dst->max_bad_pixels = src->max_bad_pixels;
}
+
+ // `parent` is an internal field; user-supplied limits are always treated as
+ // a root context. dst is HeifContext::m_limits, which is registered.
+ dst->parent = nullptr;
}
diff --git a/libheif/security_limits.cc b/libheif/security_limits.cc
index c0074239..765ad2e1 100644
--- a/libheif/security_limits.cc
+++ b/libheif/security_limits.cc
@@ -25,7 +25,7 @@
heif_security_limits global_security_limits{
- .version = 4,
+ .version = 5,
// --- version 1
@@ -55,12 +55,17 @@ heif_security_limits global_security_limits{
.max_sequence_frames = 18'000'000, // 100 hours at 50 fps
.max_number_of_file_brands = 1000,
- .max_bad_pixels = 1000
+ .max_bad_pixels = 1000,
+
+ // --- version 5
+
+ .parent = nullptr
};
heif_security_limits disabled_security_limits{
- .version = 4
+ .version = 5,
+ .parent = nullptr
};
@@ -86,6 +91,13 @@ heif_security_limits tighten_image_size_limit_for_ispe(const heif_security_limit
{
heif_security_limits result = *base;
+ // The returned struct is a stack-local derived copy. Point parent at the
+ // registered context so MemoryHandle::alloc() can still find the entry in
+ // sMemoryUsage for total-memory accounting. If base is itself derived, walk
+ // to the root so we keep the parent chain at one hop.
+ result.parent = (base->version >= 5 && base->parent) ? base->parent : base;
+ result.version = 5;
+
if (ispe_width == 0 || ispe_height == 0) {
return result;
}
@@ -176,12 +188,6 @@ size_t TotalMemoryTracker::get_max_total_memory_used() const
Error MemoryHandle::alloc(size_t memory_amount, const heif_security_limits* limits_context,
const char* reason_description)
{
- // we allow several allocations on the same handle, but they have to be for the same context
- if (m_limits_context) {
- assert(m_limits_context == limits_context);
- }
-
-
// --- check whether limits are exceeded
if (!limits_context) {
@@ -208,15 +214,29 @@ Error MemoryHandle::alloc(size_t memory_amount, const heif_security_limits* limi
sstr.str()};
}
- if (limits_context == &global_security_limits ||
- limits_context == &disabled_security_limits) {
+ // Resolve to the registered (root) context for total-memory accounting.
+ // The passed-in limits may be a stack-local derived copy (e.g. tightened for
+ // ispe) whose `parent` points back to the registered context.
+ const heif_security_limits* root_limits = limits_context;
+ while (root_limits->version >= 5 && root_limits->parent) {
+ root_limits = root_limits->parent;
+ }
+
+ // we allow several allocations on the same handle, but they have to be for the same registered context
+ if (m_limits_context) {
+ assert(m_limits_context == root_limits);
+ }
+
+ if (root_limits == &global_security_limits ||
+ root_limits == &disabled_security_limits) {
return Error::Ok;
}
std::lock_guard<std::mutex> lock(get_memory_usage_mutex());
- auto it = sMemoryUsage.find(limits_context);
+ auto it = sMemoryUsage.find(root_limits);
if (it == sMemoryUsage.end()) {
- assert(false);
+ // Unregistered limits context with no resolvable parent — total-memory
+ // tracking is not available, but the per-block check above still applies.
return Error::Ok;
}
@@ -245,7 +265,7 @@ Error MemoryHandle::alloc(size_t memory_amount, const heif_security_limits* limi
// --- register memory usage
- m_limits_context = limits_context;
+ m_limits_context = root_limits;
m_memory_amount += memory_amount;
it->second.total_memory_usage += memory_amount;