Skip to content

Releases: strukturag/libheif

v1.23.7 - security release

Choose a tag to compare

@farindk farindk released this 26 Oct 11:48

v1.23.7 is a security and bugfix release. It is ABI- and API-compatible with v1.23.6 and is a drop-in replacement.

Fourteen of the fixed issues are rated high, so all users are advised to upgrade.

Security fixes

(CVE numbers will be added when assigned.)

  • CVE-2026-XXXXX (GHSA-xwcv-cp39-m2w4) Out-of-bounds read in the OpenH264 decoder plugin. An AVC SPS that libheif's coded-size check could not parse was skipped and still passed to OpenH264, which accepts cropping offsets whose sum wraps around and returns plane pointers far outside of its frame buffer. An SPS that cannot be parsed is now an error, also in subset SPS NAL units. The VVC SPS check had the same pattern and fails closed as well. Introduced with the coded-size check of v1.23.5. (high)
  • CVE-2026-XXXXX (GHSA-jx54-6rm6-2cgj) The AV1 coded-size check of v1.23.5 could be bypassed with libaom through an OBU without a size field. libheif stopped scanning at that OBU, while libaom continued and decoded a later, larger sequence header and frame. The last OBU of a sample is now given an explicit size before the data is scanned and decoded, so that both see the same framing. (high)
  • CVE-2026-XXXXX (GHSA-fpr5-xpfr-vcq3) The AV1 coded-size check of v1.23.5 could be bypassed with libaom. num_ticks_per_picture_minus_1 was read with a UVLC reader that stops after 20 leading zero bits, although AV1 allows 31, and the error was ignored. The sequence header was then skipped, or scanned from the middle of the code where it could yield a small decoy size, and libaom allocated the real, much larger frame. The AV1 UVLC reader now follows the AV1 specification, and a sequence header that cannot be scanned completely is rejected. (high)
  • CVE-2026-XXXXX (GHSA-35mc-3887-rr37) Heap use-after-free when grid tiles are decoded in parallel. Several derived tiles (iovl, iden or grid) can share one coded input image. Their bit-depth queries read the decoder data of that input while another tile thread replaced it during decoding. A crafted file could crash the application or corrupt the heap with default options. The bit-depth queries now lock the decode mutex of the queried item. Only v1.23.6 is affected. (high)
  • CVE-2026-XXXXX (GHSA-hpg4-g2gc-pq42) Heap buffer overflow when padding an image to a size that is larger in one dimension and smaller in the other. heif_image_extend_padding_to_size() could be reached with such a size by re-encoding a sequence whose frames have different sizes with the x265 or x264 sequence encoder. Padding now refuses a size below the image or any of its planes, checks all planes before changing them, and the sequence encoders refuse a frame whose size differs from the first frame. (high)
  • CVE-2026-XXXXX (GHSA-9rpf-2x22-78m9) heif_context_encode_thumbnail() returned success without setting the output handle when the image already fits into the bounding box. Callers that released the handle, among them heif-enc -t and the C++ wrapper, freed an uninitialized or stale pointer (use-after-free, double free). The handle is now set to NULL in that case, as the API documentation says. (high)
  • CVE-2026-XXXXX (GHSA-48p4-gj45-w9v6) CPU exhaustion through derived images that decode a large image again and again and use only a few pixels of it, for example a grid of 1x1 tiles that are each a clap crop of an 8192x8192 overlay. A 2.6 kB file took 26 seconds to decode. Each decode now has a pixel budget of twice the size of the distinct images it uses (at least 2048x2048, at most max_image_size_pixels), single-tile decodes get their share of it, an exceeded security limit in a grid tile is an error also without strict decoding, and grid tiles outside of the image are not decoded. (high)
  • CVE-2026-XXXXX (GHSA-2g6j-vhr6-6wrj) CPU exhaustion through many coded pictures in one image item or sequence sample. The decoder decoded all of them, although only one image is returned: a 16 kB file with 1000 hidden HEVC pictures of 4096x4096 took 14 seconds. More than eight coded pictures per decode are now refused. HEVC, AVC and VVC count picture starts, AV1 counts frame OBUs, and AVC data with embedded start codes is refused. (high)
  • CVE-2026-XXXXX (GHSA-7qm8-hj35-269r) CPU exhaustion through repeated transformations. An item could carry many associations of the same irot, imir or clap property, and iden chains multiplied them: a 28 kB file took 73 seconds to decode. An item may now have at most one association of each of these properties, as HEIF requires, and at most 32 transformations are applied along a decode path. (high)
  • CVE-2026-XXXXX (GHSA-hhg3-fxg7-3gxw) CPU exhaustion while opening a file whose images have many colr associations. Reading the file regenerated and compared a colour profile property for every association. Reading and encoding now store colour profiles separately, so that opening a file does not create properties. (high)
  • CVE-2026-XXXXX (GHSA-73j7-gvm9-x8q6) CPU exhaustion in sequence decoding through samples that produce no output, for example when an edit list repeats a sample that the decoder does not display. One call of heif_track_decode_next_image() could run for minutes, also with ignore_sequence_editlist. The number of samples that are decoded without returning frames is now limited per codec, and ignore_sequence_editlist stops after the original samples. (high)
  • CVE-2026-XXXXX (GHSA-74mj-m37q-h3q9) Quadratic CPU time when opening a file with a moov box. The stsc entry of every chunk was looked up by a linear scan, so a file with many chunks and stsc entries took seconds to minutes to open, also when the application only reads the still image. The lookup is now a binary search, and an stsc table whose first_chunk values decrease is refused. (high)
  • CVE-2026-XXXXX (GHSA-rcqc-842v-m26h) Deadlock when decoding a grid image with parallel tile decoding and non-strict decoding. A tile that was skipped because it is not valid left an empty entry in the job queue, which was dispatched as a decode of item ID 0 and could deadlock with the other tile threads. Skipped tiles no longer create decode jobs. (high)
  • CVE-2026-XXXXX (GHSA-pfcm-ffqg-84hx) CPU exhaustion through cyclic Exif IFD pointers in the heifio helper of heif-dec, heif-thumbnailer and heif-enc. The tag search followed IFD pointers in loops. The traversal is now bounded by the size of the Exif data. Applications that use only the library, and the gdk-pixbuf loader, are not affected. (high)
  • CVE-2026-XXXXX (GHSA-gff5-3vgr-p8h2) The gdk-pixbuf loader ignored the error of heif_image_scale_image() and used an uninitialized image pointer, which gdk-pixbuf later released. A small valid file made the scaling fail when an application loaded it with a fixed width or height and kept the aspect ratio (a 1x65536 AVIF loaded at a width of 3840 requests a 2.9 TB image), which crashed the application. The loader now checks the result and refuses output sizes above max_image_size_pixels. (medium)
  • (GHSA-r9cg-w542-g7hj) heif-dec -S and heif-info printed and released an uninitialized URI pointer when a urim sample entry has no uri box. Depending on the build, they crashed, or read and freed the URI of a previous track a second time. The library is not affected. (low)

All of these issues were found by Anthropic's OSS Scanner. Thanks also to @iPlayForSG and @SounLabs, who reported GHSA-74mj-m37q-h3q9 independently.

Hardening

  • heif_image_scale_image() and heif_track_get_urim_sample_entry_uri_of_first_cluster() set their output pointer to NULL on error, so that a caller that misses the error check does not use an uninitialized pointer
  • A JPEG stream whose SOF marker cannot be parsed is refused instead of being passed to the decoder without a size check
  • The libde265 plugin refuses NAL units of 2 GB or more. libde265 takes the size as int, and versions 1.1.0 to 1.1.2 did not reject the negative value
  • Reading sample auxiliary information with a constant size from saiz is bounded by the sample count of the saiz box, as it already was for variable sizes
  • The chroma bit depth query of a grid image whose first tile is missing returns -1 instead of dereferencing a null pointer
  • heif-enc: the WebVMT parser was rewritten without std::regex, whose recursion overflowed the stack for long sync data (about 30,000 characters). Timestamps are checked as the WebVMT specification defines them (minutes and seconds up to 59, three fraction digits, hours that fit the 32-bit millisecond counter), invalid hex and base64 data and payloads that are not JSON are reported, and a missing file or a file without WEBVMT signature is an error (#1941)
  • heif-enc: the PNG loader read past the end of the row for 16-bit grayscale images with alpha

Bug fixes

  • Image sequences:
    • The colour metadata of the sample entry (colr, clli, mdcv, amve, ndwt, pasp) is attached to the decoded frames, and an NCLX profile in the sample entry takes precedence over the bitstream, with the same full-range correction and mismatch warnings as for still images (#1938, @dlemstra)
    • A decoder is drained before the decoding switches to a chunk with another sample description, and at the end of the input the last active decoder is flushed. Frames at the end of a chunk were lost before (#1939, @dlemstra)
    • Repeated playback through an edit list works with all decoders, and the FFmpeg plugin recognizes the end of the stream
    • The data_reference_index of the urim sample entry in URI metadata tracks was written uninitialized; it is now 1
    • The stsc table that libheif writes for a track with several chunks numbers its entries instead of letting all of them start at chunk 1
  • libde265: images with NAL units larger than 16 MiB could not be decoded, not even with the security limits disabled. The NAL size limit is now taken from max_memory_block_size, and an error of libde265 for a NAL unit ...
Read more

v1.23.6 - security release

Choose a tag to compare

@farindk farindk released this 05 Oct 16:35

v1.23.6 is a security and bugfix release. It is ABI- and API-compatible with v1.23.5 and is a drop-in replacement.

Four of the fixed issues are rated high, so all users are advised to upgrade.

Security fixes

(CVE numbers will be added when assigned.)

  • CVE-2026-XXXXX (GHSA-h4h8-qgvc-m7r2) Memory exhaustion in the OpenJPEG decoder plugin through the tile and component counts of a JPEG 2000 codestream. OpenJPEG allocates its coding parameters for every tile, and for every component of every tile, while it reads the SIZ marker segment. This happened before the plugin could check anything, and OpenJPEG has no limit of its own (uclouvain/openjpeg#1111). A 1357-byte file declaring 65535 tiles of one pixel made it allocate 1.8 GB with 24 components and 4.7 GB with 121 components, whatever the security limits were. The plugin now reads the SIZ marker segment itself before it calls OpenJPEG and bounds the number of tiles and components by max_number_of_tiles, max_components, max_memory_block_size and the size of the input. Found by OSS-Fuzz. (high)
  • CVE-2026-XXXXX (GHSA-fcmw-5764-7rq8) Memory exhaustion in the uncompressed (unci) decoder with generic compression. For an image with one compressed unit per tile and tile-component interleave, the unit was read and decompressed again for every component and the results were concatenated, none of it charged to the memory budget. A 22 kB file with 256 components allocated more than 5 GiB with the default security limits. A per-tile unit is now fetched once, decompression stops as soon as more data than the tile size is produced, a unit that decompresses to less is rejected, and the tile data is charged to the memory limits. (high)
  • CVE-2026-XXXXX (GHSA-6fqc-p7r8-2g36) CPU exhaustion in the unci decoder with generic compression. When a compressed unit spans several tiles (the whole item, or the whole image of a component), the complete item was decompressed again for every tile, so decoding took time proportional to the number of tiles times the item size. A 1 kB file with a 512x512 image of 1x1 tiles kept a CPU busy for several minutes, and a valid 4096x4096 image with 64x64 tiles decoded 700 times slower than the same image without tiles. Such an item is now decompressed once and the image is exposed as a single tile. Images with row or pixel units keep their tiling, and decoding a tile only reads and decompresses the units of this tile. (high)
  • CVE-2026-XXXXX (GHSA-q7mw-2fmm-5q94) Heap out-of-bounds read in libsharpyuv through sample values above the bit depth of an image. The sharp YUV conversion passed 10 and 12 bit RGB planes to libsharpyuv, which uses each sample as an index into a gamma table that is sized for the bit depth. Out-of-range samples could come from a crafted file when it is decoded to YCbCr 4:2:0 (through the unci mixed-interleave decoder or signed JPEG 2000 components, see GHSA-v5rj-g4wv-j5w3 below), or from an application that encodes an image with such values. The result was a crash, or unrelated memory influencing the output pixels. The sample range is now checked before the samples are handed to libsharpyuv. (high)
  • CVE-2026-XXXXX (GHSA-vv35-6hxg-95x8) The bit depth that an image handle reports could differ from the bit depth of the decoded image, so that an application reading the planes with the bit depth of the handle (e.g. 16-bit samples from a plane of 8-bit samples) read out of bounds. There were three ways to get there: a configuration box (hvcC, av1C) that contradicts the bitstream; a grid whose tiles have different bit depths, where the tile that did not match was silently left out since v1.19.0 and the bit depth of the result depended on which decoding thread finished first; and overlay (iovl) images with more than 8 bits, which reported the bit depth of their first input image but were composed on an 8-bit canvas. The last one needs no crafted file. A decoded image that contradicts its handle is now refused, all tiles of a grid have to have the same format, and overlays are composed with the bit depth of their first input image. (medium)
  • CVE-2026-XXXXX (GHSA-8p2c-9wv4-rx4p) Null pointer dereference when decoding an image sequence in which a chunk of a visual track references a non-visual sample entry (for example an mp4a entry in the stsd box of a pict, vide or auxv track). The decoder of such a chunk was only guarded by an assert(). The track is now rejected when it is loaded. (medium)
  • CVE-2026-XXXXX (GHSA-86ch-j429-fpr2) With the FFmpeg decoder plugin, the image size limit was not reliably applied before an HEVC bitstream reached FFmpeg, so a file could make the decoder allocate buffers for a much larger picture than its ispe property declares. The SPS parser skipped 56 instead of 88 bits for each sub-layer profile and failed on valid headers with sub-layer profiles, the size scan skipped every SPS that it could not parse, and the plugin did not pass the limit on to FFmpeg. An SPS that cannot be parsed is now an error, the plugin refuses NAL units that contain a start code prefix (so that FFmpeg sees the same NAL units that libheif checked), and the limit is given to FFmpeg as max_pixels. Builds that decode HEVC with libde265 are not affected. (medium)
  • CVE-2026-XXXXX (GHSA-3gwx-cjv4-jr74) Reachable assertion when decoding a grid image whose tiles have an alpha plane of more than 16 bits, which unci tiles can have since v1.22.0. The opaque fill value was computed with a shift that was only guarded by assert(alpha_bpp <= 16): builds with assertions aborted, builds without shifted by up to 64 bits (undefined behaviour). Unsigned alpha planes of up to 64 bits are now filled with their own sample width. For other kinds of alpha planes (signed, float, complex) no opaque value is defined and the decoded image carries a warning. (medium)
  • (GHSA-hqpw-h8p6-22q9) Heap out-of-bounds read of up to 6 bytes in the FFmpeg decoder plugin. The compressed data was passed to FFmpeg's parser without the AV_INPUT_BUFFER_PADDING_SIZE bytes of padding that FFmpeg requires for its input buffers. This happened with ordinary files. The bytes were only read and not returned to the caller. The input is now padded, and the parser is no longer used (#1921). (low)
  • (GHSA-hvwh-xpj6-ph5x) Use after free of an error message in the WebP (since v1.22.1) and raw (since v1.22.0) input loaders of heif-enc. They released their image and returned its error, whose message heif-enc then printed. The lifetime of heif_error.message is now documented in the public header: the string belongs to the object on which the failing function was called and is only valid until the next call on that object, or until the object is released. (low)
  • (GHSA-v5rj-g4wv-j5w3) Decoded images could contain sample values above their declared bit depth. The unci mixed-interleave decoder read each chroma sample with the width of the storage word instead of the bit depth of its component, so that the bits of the next sample or the padding bits ended up in the value. Well-formed files with a chroma bit depth other than 8 or 16 were decoded with wrong chroma. The OpenJPEG plugin stored the negative values of signed JPEG 2000 components into unsigned samples. Code that relies on the declared bit depth can be made to read out of bounds by such samples, as libsharpyuv was (GHSA-q7mw-2fmm-5q94). Mixed-interleave chroma is now read with its component bit depth, including alignment padding and row_align_size, and the OpenJPEG plugin refuses signed components. (low)

Thanks to @hackerman70000 and @john-preston for reporting these issues, and to @moo-fahmii and @overtimepog for the reports behind the unci row size and MIAF items under Hardening.

Hardening

  • Encoding refuses an image whose samples exceed its declared bit depth, with a usage error that names the plane. With out-of-range 10-bit samples, x264 aborted on an internal assertion, OpenJPEG failed to encode, and the other encoders encoded the image without complaint
  • The result of every colour conversion step is checked against the state that the operation declared: colorspace, chroma format, planes, bit depths and plane sizes. An operation that contradicts its declaration made the next one read planes that do not exist or that have another sample size. The RGB to YCbCr operation declines targets that are not planar YCbCr, and the alpha operations decline filter-array images
  • heif_image_add_plane() checks the bit depth of interleaved formats: 8 bits for interleaved RGB and RGBA, 9 to 16 bits for the RRGGBB formats. An interleaved plane with 64-bit or 128-bit components ran into an assertion, and without assertions heif_image_get_bits_per_pixel() returned a truncated value
  • Overlay composition checks the plane layout of every input image. With colour planes that are larger than the image, the blend loop read past the end of the alpha plane (only reachable through the internal classes)
  • unci pixel interleave: the row size is computed in closed form instead of with a loop over all pixel columns (GHSA-vr5j-9r89-725x). The loop took about 0.02 s for a 16.7 million pixel wide row in a release build, and far longer in a sanitizer build
  • JPEG 2000: a codestream whose SIZ marker segment does not directly follow the SOC marker is not passed to a decoder anymore. OpenJPEG and FFmpeg skip data in between and would use a SIZ marker segment that libheif did not check
  • The MIAF derivation constraints (ISO/IEC 23000-22, clause 7.3.11) are also checked for the auxiliary images of coded images. The alpha image of a plain coded primary image was never validated (#1929)
  • The sdtp box bounds its sample count by max_sequence_frames and charges its table to the memory limits, like the other sample tables
  • Box dumps are written into a single stream instead of copying the strings of all child boxes at every nesting level, and the sample tables print at mos...
Read more

v1.23.5 - security release

Choose a tag to compare

@farindk farindk released this 21 Sep 18:23

v1.23.5 is a security and bugfix release. It is ABI- and API-compatible with v1.23.4 and is a drop-in replacement.

One of the fixed issues is rated high, so all users are advised to upgrade.

Security fixes

(CVE numbers will be added when assigned.)

  • CVE-2026-XXXXX (GHSA-v8qw-hwjv-44hw) Memory exhaustion through a mismatch between the container and the bitstream image size. A crafted image can declare a small size in its ispe property while the bitstream declares a much larger coded frame. The container-level checks used the ispe size, so the oversized bitstream reached the decoder, which allocated a frame buffer for the in-band size before libheif rejected the mismatch. The advisory demonstrated this for AV1 with the libaom backend (a 351-byte AVIF declaring 64x64 but coding up to 27648x27648, allocating hundreds of MB to more than 10 GB), but the same class affects every codec whose real frame size lives in the bitstream. The coded size is now checked against max_image_size_pixels in the codec-independent decode path, before any bytes reach a decoder plugin: all AV1 sequence headers, all HEVC/AVC/VVC SPS NAL units (including those carried in the item data, not only the ones in the configuration record), the JPEG SOF marker and the JPEG 2000 SIZ reference grid are scanned for the largest coded size. (high)
  • CVE-2026-XXXXX (GHSA-qwpf-5wf7-r996) Heap use-after-free and double free when encoding an image that carries a TAI timestamp, including transcoding a file with an itai property. ImageDescription shallow-copied its raw heif_tai_timestamp_packet pointer, and a temporary in ImageItem::encode_to_bitstream_and_boxes() freed the packet while the item and the source image still held it. The timestamp is now stored by value. (medium)
  • CVE-2026-XXXXX (GHSA-9c75-9g8r-4728) Memory amplification through a JPEG 2000 pclr box declaring zero palette columns. The entry-count bound was skipped for zero columns, so an 11-byte box allocated 65,535 empty palette entries, and nested j2kH containers could repeat this within the child and nesting limits: a 3 KB file reached about 330 MB RSS, none of it charged to max_total_memory. Zero columns are rejected (ISO/IEC 15444-1 requires 1 to 255), the byte bound is unconditional, and the palette storage is charged to the memory limits. (medium)
  • CVE-2026-XXXXX (GHSA-r7gr-2xm2-23wf) Heap out-of-bounds read in alpha compositing for uncompressed (unci) images whose colour planes have different bit depths. Op_flatten_alpha_plane read every plane through the sample type of the first colour plane, so an 8-bit blue plane next to 16-bit red and green planes was read with a halved stride past its end, and the bytes ended up in the composited output. ColorState now tracks one bit depth per plane, and the operator declines mixed sample widths at planning time. (medium)
  • CVE-2026-XXXXX (GHSA-q492-cfcm-895h) The OpenJPEG decoder plugin's pre-decode size check bounded the JPEG 2000 window span (x1-x0)*(y1-y0) but not the absolute reference-grid coordinates, so a codestream with a 17-pixel window on a grid near the 32-bit boundary reached opj_decode(). Against OpenJPEG 2.3.1 this produced a heap-buffer-overflow write inside OpenJPEG (the class of CVE-2020-6851); OpenJPEG 2.5.4 rejects the input. The reference-grid area is now bounded as well. (low)
  • (GHSA-qfj5-c4pq-q998) Heap out-of-bounds read in the uncompressed encoder when an application attached a separate alpha plane to an image with an interleaved chroma format. The interleaved encoders took their component list from the chroma format (three entries) but decided whether to write alpha from the presence of an alpha plane, and indexed the list at [3]. heif_image_add_plane() now rejects a separate alpha plane on interleaved images, and the encoders derive both decisions from the chroma format. Only reachable through the public API; decoding never produces such an image. (low)
  • (GHSA-7pwf-qh74-p35w) The caller's heif_security_limits were not applied when parsing a mini box (the MIAF minimized image format) or the av1C/hvcC blob embedded in it; the built-in defaults were used instead. An application that tightened the limits got no enforcement of its max_memory_block_size or max_total_memory on such files. The allocations are bounded by the bytes present in the box, so this could not amplify memory use. (low)

Thanks to @homm, @jitxie (Yunding Lab, Tencent Security), @peter-hendy, @joelczk, @adamyordan, @k3mlol, @bruhdev1290 and @hyunjungdoh for reporting these issues, and to @SomnathDas, @sil3ntwizard and @iceray00 for the reports behind the API-contract and UBSan items under Hardening.

Hardening

  • Colour conversion tracks one bit depth per plane instead of one image-wide value, and every operator declares the sample width and the per-plane depths it can read, so images with mixed plane depths (which unci allows) are declined at planning time instead of being caught, or not, by runtime checks. The blanket check that refused every mixed-depth YCbCr conversion (GHSA-w7mc-p8jc-p853) is replaced by per-operator constraints
  • The colour-conversion and encoder entry points verify the plane layout: the planes that an image's colorspace and chroma format imply must be present exactly once, at the implied sizes. Missing, duplicate or foreign planes previously failed deep inside an operator, passed through as a no-op, or hit an assert() in the x265 plugin that release builds compile out
  • heif_image_add_plane() rejects a Cb or Cr plane whose size does not match the chroma subsampling of the image. An oversized plane built this way fooled the row fill in heif_image_extend_to_size_fill_with_zero() into a 4 GB out-of-bounds write (GHSA-j2rv-58fh-w8pw), an undersized one caused out-of-bounds reads in the RGB conversion (#1796). Only reachable by an application constructing an inconsistent image through the public API
  • heif_image_extend_to_size_fill_with_zero() rejects a target smaller than the current image with a usage error instead of underflowing the fill length and writing past the plane (GHSA-hqc2-cx5m-g6ff, only reachable by out-of-contract API use)
  • memcpy() is never called with a NULL pointer, even for zero-length copies (empty box payloads, zero-length iloc extents, empty ICC profiles or masks). This is undefined behaviour in C17 and C++ and was reported by UBSan (GHSA-2764-mqj2-c458, GHSA-x8qp-vqp7-mm4r)
  • JPEG 2000 pclr: the palette precision field is read and written as the specification defines it (the low 7 bits hold the precision minus one), and the entry count is returned as the 16-bit value it is

Bug fixes

  • Overlay (iovl) images with negative 16-bit offsets lost the affected layer: the sign extension was wrong for the 2-byte offset form, so the layer was placed far outside the canvas (since v1.19.2, conformance files C019 and C021)
  • Overlay layers with a negative offset were clipped wrongly: a layer whose negative offset was at least half its size was not drawn at all, smaller negative offsets drew a truncated part, and the alpha path wrote to the wrong column (since v1.20.0)
  • An item may be referenced repeatedly within one iref entry. An overlay that places the same input image at two positions references it twice in its dimg entry, and such files (conformance file C021) were rejected since v1.17.0; heif_context_add_overlay_image() could not write them either. The same applies to a repeated track ID within one tref reference type box
  • Visual sequences: each decoded frame received the duration of the next pushed sample instead of its own, so a variable frame rate sequence with the durations {100, 250, 400} decoded as {250, 400, 100} (#1914)
  • Sequence encoding with x265 aborted on the header-only first drain in builds with the libstdc++ assertions enabled (Fedora's default), because the pending output was moved out of a std::optional instead of being extracted (#1907)
  • kvazaar: monochrome and alpha auxiliary images crashed with kvazaar git master, which keeps the chroma format in new config fields that only the input-format parser updates. The input format is now set through config_parse(), and chroma formats a kvazaar build does not support return an error (#1915)
  • Uncompressed Bayer (filter array) images could not be decoded to a different bit depth: 8-bit to 16-bit interleaved RGB and 12-bit to 8-bit RGB failed with "Invalid bit depth", and a native decode of a 12-bit Bayer image with convert_hdr_to_8bit returned an image without planes
  • Compositing the alpha of a monochrome image failed with "unsupported color conversion" for monochrome and RGB targets; luma-only images are now composited directly on their Y plane
  • Decoding uncompressed images whose colour planes have different depths to 8 bits depended on the order of the planes (RGB 8/8/16 was refused while 16/8/8 worked), and mixed-depth YCbCr images (16/16/8) could not be decoded to 8-bit RGB at all
  • FFmpeg decoder plugin: fixed a link failure with MSVC when built as a dynamic plugin (#1854)

Behavior changes

  • The coded image size in the bitstream is checked against max_image_size_pixels before decoding, for all codecs. Files whose bitstream declares a frame larger than the limit are rejected even when their ispe property is small
  • heif_image_add_plane() rejects a separate alpha plane on an image with an interleaved chroma format, and a Cb/Cr plane with a size that does not match the chroma subsampling
  • heif_image_extend_to_size_fill_with_zero() returns a usage error for a target smaller than the current image
  • Encoding an image whose planes do not match its colorspace and chroma format returns a usage error; converting such an image returns Unsupported_image_type
  • Repeated references within one iref entry or one tref reference type box are accepted
  • JPEG 2000 pclr boxes with zero columns are rejected

v1.23.4 - security release

Choose a tag to compare

@farindk farindk released this 06 Sep 15:00

v1.23.4 is a security and bugfix release. It is ABI- and API-compatible with v1.23.3 and is a drop-in replacement.

Three of the fixed issues are rated high, so all users are advised to upgrade.

Security fixes

(CVE numbers will be added when assigned.)

  • CVE-2026-XXXXX (GHSA-vg7w-rp49-4fc2) The max_items security limit was not enforced for the child boxes of iinf, so a file could declare an unbounded number of items. Parsing then cost quadratic time in the per-item iref reference scan. The limit is now checked up front, the declared count is bounded by the remaining input, and a short read is reported instead of silently yielding fewer children. The iref queries are indexed by source item ID rather than scanning the whole reference list, which removes the residual quadratic cost (on a file with 1000 items and 160000 dimg references, read time drops from 1.83 s to 0.26 s). In Emscripten builds, the same item count sized an alloca() in the JavaScript item-ID helpers, overflowing the 64 KB WebAssembly shadow stack into the static data segment and permanently trapping the module instance. (high)
  • CVE-2026-XXXXX (GHSA-xrp2-63fq-jm8q) Unbounded recursion in the reference-cycle check crashed the parser on a long chain of derived items, without bound when the item-count limit is disabled. The check no longer runs at file load, and the decode-time walk that replaces it uses an explicit heap worklist instead of recursion. (high)
  • CVE-2026-XXXXX (GHSA-prgh-72vc-3xmc) Permanent decoder deadlock through a lock-order inversion in parallel grid tile decoding (enabled by default). ImageItem::decode_image() holds a per-item non-recursive mutex across its nested decodes, so two workers whose items reference each other took the two mutexes in opposite order. Cyclic decode reference graphs, following both the dimg and the alpha auxl edges, are now rejected before decoding starts. (high)
  • CVE-2026-XXXXX (GHSA-fqpw-fj22-78w4) Heap out-of-bounds read in the encoder plugins for images whose luma and chroma bit depths differ. The aom and x265 plugins took the sample width from the luma channel and applied it to the chroma planes, reading each chroma row at twice its allocated width and encoding those bytes into the output. Every encoder plugin now validates its input. (medium)
  • CVE-2026-XXXXX (GHSA-4rv4-953r-p24q) Unreclaimable memory leak in heif_track_get_next_raw_sequence_sample(). Four error paths in the sample auxiliary-information stage dropped the already-copied sample payload, and the error return could not carry it back to the caller. Since nothing requires distinct trak boxes to reference distinct media bytes, a small file with many tracks aliasing one payload leaked it once per track and per call, uncharged to max_total_memory. The entry point also lacked the exception guard, so a failing allocation unwound std::bad_alloc across the extern "C" boundary and aborted the host process. (medium)
  • CVE-2026-XXXXX (GHSA-rhgw-q5g8-xjh2) Heap out-of-bounds read in the WebCodecs decoder plugin. The pixel buffer was sized for the frame's visible rectangle but the copy loops walked it at the coded width and height, and the gap between the two is the HEVC conformance window that the decoded file controls. Only Emscripten builds with WITH_WEBCODECS=ON (off by default, experimental) that do not also link libde265 are affected. (medium)

Thanks to @hackerman70000 for reporting these issues.

Hardening

  • Color conversion rejects images with components wider than 16 bits. Every conversion operator accesses planes through uint8_t* or uint16_t* and derives shifts from the bit depth, so a monochrome unci track with a 64-bit component reached Op_mono_to_YCbCr420 and shifted an int by 56 (undefined behavior, found by OSS-Fuzz). Each operator now declares its own bit-depth limit instead of only bounding the depth from below
  • The sharp-yuv RGB to YCbCr 4:2:0 operator declines input whose alpha plane has a different bit depth than the color channels. It derived the alpha sample width from heif_channel_R, so 10-bit planar RGB next to an 8-bit alpha plane walked a one-byte-per-sample plane with a step of two (heap-buffer-overflow read, found by OSS-Fuzz)
  • The number of iref reference entries is capped against max_items, matching the caps already applied in iloc, iinf and ipma. Only the per-entry reference count was bounded before
  • heif_image_handle_get_depth_image_handle(), _get_thumbnail() and _get_auxiliary_image_handle() return the item's error at handle-creation time instead of handing out a handle for an item that failed to parse
  • MIAF's derived-image dependency constraints (ISO/IEC 23000-22, clause 7.3.11) are validated before decoding for files that declare the miaf brand

Bug fixes

  • heif_item_add_*_property() returned the index of the property box in the file-wide ipco container, while the property getters address the properties of a single item. The two numberings coincide only in a file with a single item, so reading a property back with the returned id indexed past the end of the item's property list, and the out-of-range check built an Error object without returning it. The id returned is now the position within the item's own property list (GHSA-5w8x-856j-7x7c, reported as a vulnerability but caused by an unsupported use of the API; it is a real crash in supported multi-item contexts)
  • heif_track_encode_sequence_image() documents its options parameter as optional, but passing NULL dereferenced a null pointer, and a second dereference lurked on the same path in the color conversion options whenever the input actually needed converting
  • A file containing a reference cycle no longer fails to load as a whole. The parse-time check rejected the file whenever the primary item's dimg subtree contained a cycle, which also made the file's other, independently valid items undecodable. Such a file now opens, its valid items decode, and only the cyclic item fails, at decode
  • aom: lossless encoding (-L) failed with "Only --enable_chroma_deltaq=0 can be used with --lossless=1". libaom enables chroma delta-q for AOM_TUNE_IQ and then refuses to combine it with lossless coding, and libheif auto-selects that tune for still images. Lossless images keep AOM_TUNE_SSIM, and an explicit request for both is reported as the unsupported combination it is
  • x264: 10-bit input was encoded as an 8-bit stream built from misread bytes. The plugin applied the profile names "main10-intra" and "main12-intra", which are x265 names that x264_param_apply_profile() rejects, and never set param.i_bitdepth or X264_CSP_HIGH_DEPTH. 10-bit input now produces a High 10 stream; 12 bits, which x264 does not support, is refused
  • uvg266: images with a bit depth the build does not support are refused instead of silently producing a corrupted image (#1859)

Behavior changes

  • Files with a reference cycle now load instead of being rejected at parse time; the cyclic item fails when it is decoded
  • For files declaring the miaf brand, derived-image chains that violate ISO/IEC 23000-22 clause 7.3.11 are rejected before decoding

Other

  • Updated the libaom build helper to v3.15.0
  • The clap and grid regression tests no longer require a HEVC decoder, so they are skipped rather than failed on distributions that build libheif without the HEVC codecs (#1900)

v1.23.3 - security release

Choose a tag to compare

@farindk farindk released this 01 Sep 21:38

v1.23.3 is a security and bugfix release. It is ABI- and API-compatible with v1.23.2 and is a drop-in replacement.

One of the fixed issues is rated critical, so all users are strongly advised to upgrade.

Security fixes

(CVE numbers will be added when assigned.)

  • CVE-2026-XXXXX (GHSA-x8r2-mggj-j6wr) Heap buffer overflow (write) in the uncompressed (unci) mixed-interleave decoder when the two chroma components declare different bit depths. Both the written bytes and the overflow length are controlled by the file. (critical)
  • CVE-2026-XXXXX (GHSA-8fmq-r4pf-7m57) Permanent decoder deadlock through a reference cycle between an image and its alpha auxiliary image. The alpha edge was not covered by the cycle guard and re-entered a held mutex. (high)
  • CVE-2026-XXXXX (GHSA-w7mc-p8jc-p853) Heap out-of-bounds read in the YCbCr 4:2:0 to 16-bit interleaved RGB conversion when the chroma planes have a lower bit depth than luma. Heap memory could end up in the decoded image. YCbCr conversions with mismatched luma and chroma bit depths are now rejected. (high)
  • CVE-2026-XXXXX (GHSA-4jqm-2x34-6f6r) Heap buffer overflow in the SVT-AV1 encoder plugin when encoding a high-bit-depth alpha channel, and a double free on its send-picture error path. (high)
  • CVE-2026-84451 (GHSA-hh47-fhqr-cj2r) Incomplete fix for GHSA-73p7-m7gg-w2jv: the tile range check of the unci decoder (without icef) could still overflow, allowing an out-of-bounds read. (medium)
  • CVE-2026-XXXXX (GHSA-4h82-g446-83fm) Heap out-of-bounds read when converting odd-height 4:2:0 frames of an uncompressed (uncv) image sequence to RGB. (medium)
  • CVE-2026-XXXXX (GHSA-9rj8-5mp5-26c9) Out-of-bounds read in the RGB to YCbCr identity-matrix color conversion when the R, G, and B planes have different bit depths. (medium)
  • CVE-2026-84450 (GHSA-gh5q-69gg-c964) A clap property combined with an oversized ispe reached an assert() in the Fraction arithmetic and aborted the process (incomplete fix for GHSA-jc8f-p23p-5hjg). An error is returned instead. (medium)
  • (GHSA-mw6f-29j3-76f4) Several smaller findings: heif_image_handle_get_depth_image_handle() and heif_image_handle_get_depth_image_representation_info() dereferenced a null pointer on files without a depth image; the TIFF input decoder of the example tools had an unbounded EXIF tag allocation and a division by zero on zero YCbCr subsampling; assert()s in the PNG input decoder are now error returns; integer overflow in the Go binding's ImageAccess.GetPlane(); heif-view now verifies the decoded frame size before display. (medium)
  • (GHSA-8857-r8x5-7499) Undefined behavior (negative shift) in the HDR bit-depth up-conversion for target bit depths above 16. Such conversions are now rejected. (low)

Thanks to @hrjeon2, @alextwordfence, @Nora-Qiu, @ikkyu3, @Namron2000, @followboy1999, @the-vibe-dev, @hillalee, and @sonicnew for reporting these issues.

Hardening

  • unci: fixed a pointer wrap on 32-bit builds in the tile decoders that walk the data with a raw pointer (bytealign and block component-interleave, block pixel-interleave). A huge row_align_size wrapped the pointer past the bounds check and read out of bounds (found by OSS-Fuzz)
  • unci: the per-tile decode path (heif_image_handle_decode_image_tile()) now runs the same uncC layout checks as the full-image path; a block-pixel layout with oversized component bit depths caused an undefined shift
  • unci: the sequence decode path rejects an oversized row_align_size like the still-image path
  • AVC SPS: variable-length fields are validated
  • Color conversion: fixed a negative shift when up-converting an alpha channel to more than twice its bit depth (e.g. 2-bit alpha with 12-bit color in an unci image), a signed overflow when compositing alpha on 16-bit images, and a missing chroma bit-depth check in the YCbCr 4:2:0 to 16-bit RGB conversion
  • Pixel plane allocation: the size computation is guarded against 64-bit overflow (reachable only with the image-size security limit disabled)
  • heif_region_item_add_region_polygon() checks its parameters (NULL pointer, negative point count)
  • Fixed a data race (broken double-checked locking) when pasting decoded grid tiles into the output image (#1888)
  • ID allocation: avoid an unsigned wrap when the maximum ID is reserved (fuzzer with -fsanitize=integer)

Bug fixes

  • Regression since v1.22.0: images with a clap crop, and small images whose coded frame is padded (hardware encoders), failed with "Security limit exceeded" even with limits disabled. The tightened decode size limit introduced with #1798 is now derived from the ispe size with a floor of 256x256 (#1856)
  • Regression since v1.21.0: YCbCr images with more than 14 bits per pixel could not be converted to RGB. The 14-bit limit now applies only to the YCgCo-Re integer path (#1881)
  • ftyp box parse errors are reported as such instead of "No 'ftyp' box", and a brands security limit of 0 (LIBHEIF_SECURITY_LIMITS=off) is treated as no limit (#1872)
  • When no tile of a grid image could be decoded, the tile decoding error is returned instead of an unspecific error, and the missing-decoder error now names the compression format and an example plugin (#1876)
  • AVC: fixed the SPS conformance window crop unit scaling. Encoded AVC images with dimensions that are not a multiple of 16 got an oversized ispe and a spurious clap property, and then failed to decode (#1866)
  • iloc: reading a sub-range of an extent stored in idat failed with "Not enough data present in 'iloc'", so multi-tile or icef-compressed uncompressed images stored in idat could not be decoded
  • Files with a mime item whose content encoding is unknown or whose decompressor is not compiled in are no longer rejected as a whole. The item's raw data stays accessible through heif_item_get_item_data(). The "identity" content encoding is treated as no encoding
  • heif_item_get_item_data() crashed on an item that was just added to a context (in-memory extents are now served directly)
  • heif_context_add_item(), heif_context_add_mime_item(), heif_context_add_precompressed_mime_item(), and heif_context_add_uri_item() aborted on NULL strings or data and on a negative size; they return a usage error now. heif_context_add_item() wrote a file with a 'pict' handler and a bogus pitm that no reader accepted
  • Encoders write the pixel aspect ratio into the bitstream where the codec supports it (Extended_SAR in the HEVC/AVC/VVC VUI, JFIF density fields), so the bitstream agrees with the pasp property (#1860)
  • x264: nclx color signalling was silently not written into the SPS VUI, producing a warning about disagreeing colr box and bitstream on decode
  • SVT-AV1 (>= 4.0.0): the QP derived from the quality setting is no longer rounded to an integer (quarter-QP steps). The value is written directly into the encoder configuration, avoiding a locale-dependent string round trip and an unintended switch from CRF to CQP rate control (#1867)
  • aom: wrong chroma plane height for 4:2:2 input
  • rav1e: the encoder was configured with the storage bit depth instead of the actual bit depth
  • OpenJPH: high-bit-depth source samples were read as 8-bit
  • uvg266: the high-bit-depth plane copy dropped the bytes-per-sample factor
  • OpenJPEG: lossless encoding was still rate-limited, producing truncated codestreams with visible errors
  • FFmpeg decoder: 12, 14, and 16-bit monochrome images (including alpha planes of such images) are decoded; high-bit-depth JPEG and JPEG 2000 are returned at the coded bit depth instead of as scaled 16-bit; an nclx profile is only attached for codecs that carry CICP signalling (AVC, HEVC, VVC, AV1), fixing wrong colors of JPEG 2000 images; big-endian hosts are supported
  • gdk-pixbuf: GError is set when stop_load() fails
  • Box_udes: fixed the dump output of the 'tags' field
  • CMake: HAVE_BROTLI and HAVE_LIBSHARPYUV are no longer propagated in the public interface; fixed the Brotli configuration

Behavior changes

  • heif_context_write() / heif_context_write_to_file() refuse a context that was read from a file (Unsupported_feature). Item and sample data were never copied from the input, so the written file was truncated. Writing a context that contains only metadata items (no image, no sequence) returns a usage error instead of writing an invalid ftyp box
  • YCbCr to RGB conversions where the chroma planes have a different bit depth than luma return heif_suberror_Unsupported_color_conversion
  • Alpha compositing (flattening) rejects images with more than 16 bits per sample; HDR up-conversion to more than 16 bits is rejected
  • The x265 encoder returns an error for images with more than 142,606,336 luma samples (the HEVC Level 7.2 maximum, e.g. 16384x8704) instead of crashing inside x265 (GHSA-2c3g-p585-8rpq)

Other

  • New heif_item_type_* and heif_entity_group_* fourcc constants, and documentation of the entity group types, in the API headers (#1864)
  • The uncompressed codec is no longer labeled experimental in heif-enc and the CMake options (#1869)
  • CI: Linux jobs moved to Ubuntu 24.04, GCC 16 added to the compiler matrix, clang-tidy performance checks enabled, all GitHub Actions pinned to commit SHAs, Coverity scan re-enabled

v1.23.2 - security release

Choose a tag to compare

@farindk farindk released this 25 Aug 11:14

v1.23.2 is a security and bugfix release. It is ABI- and API-compatible with v1.23.1 and is a drop-in replacement.

Two of the fixed issues are rated critical, so all users are strongly advised to upgrade.

Security fixes

(CVE numbers will be added when assigned.)

  • CVE-2026-84383 (GHSA-g89c-p67h-r497) Heap buffer overflow in scale_nearest_neighbor() via duplicate alpha planes from nested iden/auxl items. (critical)
  • (GHSA-2jg2-4ch7-h545) Out-of-bounds read and write in derived-item and pixel-plane handling. Through iden and auxl item chains, a crafted file could attach pixel planes whose size differs from the image geometry; crop, scale, and plane-extraction code then indexed those planes with the wrong size. A working code-execution exploit was confirmed. Plane sizes are now validated wherever they are consumed. (critical)
  • CVE-2026-84384 (GHSA-24wx-9w62-c96w) brotli/zlib decompression of mime metadata and unci image data had no effective output-size limit, so a decompression bomb could exhaust memory. Decompressed output is now bounded by the security limits. (high)
  • CVE-2026-84447 (GHSA-x8xm-cm2c-cfc8) Chains of derived-image references (grid, iovl, iden) bypassed decode caching and memory limits, causing CPU and memory amplification. (high)
  • CVE-2026-84446 (GHSA-xw34-mjcp-jqh8) Sequence sample-timing initialization could produce non-terminating decode loops and unbounded memory, bypassing max_sequence_frames. (high)
  • CVE-2026-84444 (GHSA-j264-xvrp-5v7q) Out-of-bounds write in the unci encoder when heif_context_add_image_tile() is given a tile whose planes do not match its declared size. (high)
  • CVE-2026-84448 (GHSA-p58j-h3vm-3fp5) Heap out-of-bounds read in the inline-mask region API when mask_data_len does not match the region geometry. (medium)

Thanks to David Schutz (Meta Product Security), @rootxharsh, @KarimPwnz, @hacktronai-research, @Nora-Qiu, @holimart, @buraks1998, and @zenniskayy2k4 for reporting these issues.

Hardening

  • C++ exceptions such as std::bad_alloc can no longer escape the C API read/decode entry points; they are returned as a heif_error instead of aborting the process
  • assert()s in the pixel-image plane allocation were replaced by runtime errors
  • stts/ctts tables describing more samples than the track can have are rejected
  • pclr (JPEG 2000 palette) box: the number of palette entries is bounded by the box size
  • BitReader::skip_bytes() is now constant time (fixes a fuzzer timeout on bogus alignment values)
  • iden items now validate the decoded image size like all other items
  • The uncompressed (unci) encoder rejects images without pixel planes

Bug fixes

  • meta, mini, and moov boxes with size 0 (extending to the end of the file) are now parsed correctly (#1857)
  • Fixed an integer overflow when probing the file size (#1857)
  • Fixed undefined behavior (signed shift) when reading the NAL unit length in the OpenH264 decoder

Behavior changes

  • heif_region_item_add_region_inline_mask_data() now requires non-zero width and height and the mask_data_len must equal the expected (width * height + 7) / 8; otherwise it returns an error instead of storing the mask
  • heif_image_add_plane() returns an error instead of aborting for bit depths outside 1..128 or interleaved component counts outside 1..255

v1.23.1

Choose a tag to compare

@farindk farindk released this 26 Jun 08:36

This is primarily a bugfix release with a few new decoder/encoder capabilities.

New features

  • FFmpeg decoder plugin gains AV1, VVC, JPEG, and JPEG 2000/HTJ2K decoding (thanks to @torusrxxx)
  • SVT-AV1 encoder: new tune=iq and ms-ssim tune parameters (thanks to @pszemus)
  • C++ API: added getters/setters for the CLLI and MDCV HDR metadata boxes (#1825)
  • Sequence decoder now scales the alpha auxiliary track to the main image size

Bug fixes

  • Fixed pixi box writing for multi-channel images
  • Corrected the placement of the TAI clock_type field into the top 2 bits (thanks to @ocrete)
  • Empty/unset plugin directory is no longer scanned

Security fixes

  • CVE-2026-62289 (GHSA-jc8f-p23p-5hjg) Integer underflow in Fraction constructor via double clap transform application
  • CVE-2026-62291 (GHSA-xpw3-9rhw-482x) Heap out of bounds write in libheif uncompressed encoder when writing images with mismatched auxiliary alpha dimensions
  • CVE-2026-62292 (GHSA-73p7-m7gg-w2jv) Out-of-bounds read in uncompressed unci tile range slicing
  • CVE-2026-62377 (GHSA-9ww4-9v47-m7pj) Reachable assertion in HeifContext::get_track() aborts on a valid-but-empty HEIF sequence file
  • (GHSA-46rp-pcq2-rpmr) Heap out-of-bounds write in the uncompressed encoder for RRGGBB images with interleaved bit-depth â‰Ī 8

v1.23.0

Choose a tag to compare

@farindk farindk released this 29 May 16:00

This is a smaller release that adds API functions to read and write metadata:

  • ambient viewing environment
  • nominal diffuse white luminance

It also adds a output_image_nclx_profile_passthrough option to heif_decoding_options to pass through the input image NCLX without doing any internal color conversion.

Security

  • CVE-2026-50142 (GHSA-jvmp-j3cw-84mh) - unbounded heap allocation in HEIF sequence parser (stsz fixed-size mode missing bound check)

v1.22.2

Choose a tag to compare

@farindk farindk released this 25 May 19:14

Fixes missing heif_image_get_bayer_pattern_size() function.

v1.22.1

Choose a tag to compare

@farindk farindk released this 25 May 16:10

Fixes

  • build issues with OpenJPEG plugin (#1813)
  • non-plain C in header (#1812)

Security

  • CVE-2026-49271 (GHSA-r7qj-cg5r-r6vf) - Wrapped icef compressed-unit range check causes out-of-bounds read in uncompressed HEIF decoder
  • (GHSA-5hqq-636x-r3cr) - Out-of-bounds write in inline mask region API when source mask exceeds declared region