Coverage Report

Created: 2026-09-14 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libheif/libheif/security_limits.h
Line
Count
Source
1
/*
2
 * HEIF codec.
3
 * Copyright (c) 2018 Dirk Farin <dirk.farin@gmail.com>
4
 *
5
 * This file is part of libheif.
6
 *
7
 * libheif is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU Lesser General Public License as
9
 * published by the Free Software Foundation, either version 3 of
10
 * the License, or (at your option) any later version.
11
 *
12
 * libheif is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU Lesser General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Lesser General Public License
18
 * along with libheif.  If not, see <http://www.gnu.org/licenses/>.
19
 */
20
#ifndef LIBHEIF_SECURITY_LIMITS_H
21
#define LIBHEIF_SECURITY_LIMITS_H
22
23
#include "libheif/heif.h"
24
#include <cinttypes>
25
#include <cstddef>
26
#include <set>
27
#include <atomic>
28
#include <memory>
29
#include "error.h"
30
31
32
extern heif_security_limits global_security_limits;
33
extern heif_security_limits disabled_security_limits;
34
35
// Maximum nesting level of boxes in input files.
36
// We put a limit on this to avoid unlimited stack usage by malicious input files.
37
static const int MAX_BOX_NESTING_LEVEL = 20;
38
39
static const int MAX_BOX_SIZE = 0x7FFFFFFF; // 2 GB
40
static const int64_t MAX_LARGE_BOX_SIZE = 0x0FFFFFFFFFFFFFFF;
41
static const int64_t MAX_FILE_POS = 0x007FFFFFFFFFFFFFLL; // maximum file position
42
static const int MAX_FRACTION_VALUE = 0x10000;
43
44
// Maximum number of 'iovl' overlay images that may be nested inside one another
45
// along a single decode path. Real files use at most one overlay per decode
46
// chain, so this is a structural sanity limit, not a common-case constraint.
47
static const uint32_t MAX_OVERLAY_NESTING_LEVEL = 3;
48
49
// Maximum number of input images that a single 'iovl' overlay may composite.
50
// This is a hardcoded stopgap until a configurable security-limit field can be
51
// added to heif_security_limits in the next major release (that is an API/ABI
52
// change and cannot go into a point release).
53
static const uint32_t MAX_OVERLAY_IMAGES = 5;
54
55
// Factor applied to max_items to bound the total number of sub-image decode
56
// operations triggered by a single top-level decode. Derived images (grid,
57
// iovl, iden) can reference the same base image through indirection, and the
58
// cycle-detection set is per-path (it forks at every branch), so a shared
59
// subtree can be re-decoded once per path that reaches it. Nested sharing makes
60
// the number of decode operations grow as branch^depth, which is bounded only
61
// by the recursion depth (<= number of items). Because a well-formed file
62
// decodes each of its (<= max_items) items a small number of times, capping the
63
// total at a modest multiple of max_items stops the exponential blow-up while
64
// leaving every realistic file untouched.  (GHSA-x8xm-cm2c-cfc8, variants V1/V2.)
65
static const uint32_t MAX_DERIVED_IMAGE_DECODE_FACTOR = 2;
66
67
68
// Traversal state threaded through the derived-image decode recursion for one
69
// top-level decode. It is copied by value at every hop, which gives the two
70
// members opposite (and intended) sharing semantics:
71
//
72
//   - processed_ids and overlay_nesting are plain values, so each recursion
73
//     branch gets its own copy. processed_ids is a per-path cycle guard;
74
//     overlay_nesting counts the overlays nested along the current path.
75
//
76
//   - decode_count is a shared_ptr to an atomic, so every copy (including the
77
//     copies handed to parallel tile-decode threads) shares one global counter
78
//     that bounds the total number of decode operations.
79
//
80
// A default-constructed DecodeTraversalState (max_decodes == 0) imposes no limit; the
81
// budget is seeded once at the top level in HeifContext::decode_image().
82
struct DecodeTraversalState
83
{
84
  std::set<heif_item_id> processed_ids;
85
86
  uint32_t overlay_nesting = 0;         // number of overlays on the path to here
87
  uint32_t max_overlay_nesting = 0;     // 0 == unlimited
88
89
  std::shared_ptr<std::atomic<uint32_t>> decode_count;
90
  uint32_t max_decodes = 0;             // 0 == unlimited
91
92
  // Count one sub-image decode against the shared budget.
93
  // Returns false when the budget has been exhausted.
94
  bool count_decode()
95
135k
  {
96
135k
    if (max_decodes == 0 || !decode_count) {
97
0
      return true;
98
0
    }
99
135k
    return decode_count->fetch_add(1, std::memory_order_relaxed) < max_decodes;
100
135k
  }
101
};
102
103
104
Error check_for_valid_image_size(const heif_security_limits* limits, uint32_t width, uint32_t height);
105
106
// Maximum coding-unit size (in pixels) that the given codec may pad a coded
107
// frame up to. Used as the margin for tighten_image_size_limit_for_ispe.
108
// Returns 0 for codecs without coding-unit padding (e.g. uncompressed).
109
uint32_t max_coding_unit_size_for_codec(heif_compression_format format);
110
111
// Return a copy of `base` with max_image_size_pixels lowered to a value
112
// just above the declared image size. This is used to bound how much memory
113
// a codec plugin may allocate for an image whose internal (codec-declared)
114
// dimensions exceed the file-declared (ispe) dimensions — without us having
115
// to parse the codec bitstream ourselves.
116
//
117
// `coding_unit_size` is the maximum coding-unit size of the target codec
118
// (e.g. 128 for AV1/VVC, 64 for HEVC, 16 for AVC). The allowed coded
119
// dimensions are (ispe + coding_unit_size) in each axis, since a codec may
120
// pad the coded frame up to a coding-unit boundary.
121
//
122
// The allowed size never drops below MIN_TIGHTENED_CODED_IMAGE_PIXELS: the
123
// HEVC/AVC conformance window may crop arbitrarily much, and hardware
124
// encoders use minimum surface sizes far above tiny image dimensions (issue
125
// #1856 has a conformant 2x2 HEIC coded as a 160x64 frame). The floor keeps
126
// the worst-case allocation for a lying bitstream small while accepting such
127
// padded coded frames; check_decoded_image_size() still validates the
128
// decoded output against 'ispe' afterwards.
129
static const uint64_t MIN_TIGHTENED_CODED_IMAGE_PIXELS = 65536;  // 256x256
130
131
heif_security_limits tighten_image_size_limit_for_ispe(const heif_security_limits* base,
132
                                                       uint32_t ispe_width,
133
                                                       uint32_t ispe_height,
134
                                                       uint32_t coding_unit_size);
135
136
137
class TotalMemoryTracker
138
{
139
public:
140
  explicit TotalMemoryTracker(const heif_security_limits* limits_context);
141
  ~TotalMemoryTracker();
142
143
  size_t get_max_total_memory_used() const;
144
145
  void operator=(const TotalMemoryTracker&) = delete;
146
  TotalMemoryTracker(const TotalMemoryTracker&) = delete;
147
148
private:
149
  const heif_security_limits* m_limits_context = nullptr;
150
};
151
152
153
class MemoryHandle
154
{
155
public:
156
330k
  MemoryHandle() = default;
157
464k
  ~MemoryHandle() { free(); }
158
159
  Error alloc(size_t memory_amount, const heif_security_limits* limits_context, const char* reason_description);
160
161
  // calloc-style overload: checks `count * element_size` for size_t overflow before allocating.
162
  // Use this when allocating an array whose total size is count*element_size, to avoid silent
163
  // truncation on 32-bit builds when count is near UINT32_MAX.
164
  Error alloc(size_t count, size_t element_size,
165
              const heif_security_limits* limits_context, const char* reason_description);
166
167
  void free();
168
169
  void free(size_t memory_amount);
170
171
102
  const heif_security_limits* get_security_limits() const { return m_limits_context; }
172
173
  MemoryHandle(const MemoryHandle&) = delete;
174
  MemoryHandle& operator=(const MemoryHandle&) = delete;
175
176
  MemoryHandle(MemoryHandle&& other) noexcept
177
133k
      : m_limits_context(other.m_limits_context), m_memory_amount(other.m_memory_amount)
178
133k
  {
179
133k
    other.m_limits_context = nullptr;
180
133k
    other.m_memory_amount = 0;
181
133k
  }
182
183
  MemoryHandle& operator=(MemoryHandle&& other) noexcept
184
133k
  {
185
133k
    if (this != &other) {
186
133k
      free();
187
133k
      m_limits_context = other.m_limits_context;
188
133k
      m_memory_amount = other.m_memory_amount;
189
133k
      other.m_limits_context = nullptr;
190
133k
      other.m_memory_amount = 0;
191
133k
    }
192
133k
    return *this;
193
133k
  }
194
195
private:
196
  const heif_security_limits* m_limits_context = nullptr;
197
  size_t m_memory_amount = 0;
198
};
199
200
201
#endif  // LIBHEIF_SECURITY_LIMITS_H