github strukturag/libheif v1.23.6
v1.23.6 - security release

4 hours ago

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 most 100 entries. A 1 MB sdtp box nested in j2kH containers produced a 200 MB dump and timed out the box fuzzer (found by OSS-Fuzz)
  • A tile position outside of the tiling is also rejected when the image transformations are ignored. It was only checked as part of the conversion of the tile position
  • A data extent that holds no data does not fall through to reading from a file that it does not have (null pointer dereference)
  • HDR RGB to YCbCr 4:2:0 conversion: the row stride is no longer truncated to int

Bug fixes

  • Decoding single tiles (heif_image_handle_decode_image_tile()) of images that are combined from several image items:
    • Regression since v1.22.0: decoding the single tile of an image with a clap crop, or with a rotation by 90 degrees of a non-square image, failed with "Decoded image does not have the size signaled in the file"
    • With a rotation or mirroring, the alpha channel of a tile came from a different tile, because the tile position was converted twice
    • The tiles of a grid whose tiles have alpha images of their own (as heif-enc writes them) were returned without alpha channel
    • An iden image over a tiled image decoded only the first tile, which was then rejected for its size
  • Uncompressed image sequences (uncv) with tiles: all tiles of a frame showed the content of the first tile, and the sample was read from the file once for each tile
  • The bilinear 4:2:0 chroma upsampling took the Cb and Cr samples of the outermost rows and columns from the wrong position (#1930). This affected decoding with bilinear upsampling, decoding a 4:2:0 image to YCbCr 4:4:4, encoders that need 4:4:4 input (JPEG 2000), and 4:2:0 images with a crop at an odd position
  • Overlay (iovl) images with more than 8 bits per sample were composed byte by byte and decoded to a wrong picture. An alpha plane with another bit depth than the colour planes is weighted with its own range
  • A grid, overlay or iden image that comes before its input images in the file got no component descriptions (an overlay was described with 8 bits)
  • These functions crashed with a null pointer dereference when they were called on the handle returned by heif_context_encode_image(): heif_decode_image(), heif_image_handle_decode_image_tile(), heif_image_handle_get_luma_bits_per_pixel(), heif_image_handle_get_chroma_bits_per_pixel(), heif_image_handle_get_preferred_decoding_colorspace() and heif_image_handle_has_alpha_channel(). Decoding an image from the context it was encoded into is not supported; the functions now return an error, -1 or 0
  • HEVC encoding: the 48 constraint flags in the hvcC box were always zero. The SPS parser skipped them, and the writer shifted each byte once too often. They now match the SPS, as ISO/IEC 14496-15 demands
  • HEVC encoding: heix or hevx was written for profiles that have no HEVC brand (high throughput and screen content coding profiles), and miaf was written for every HEVC image, also outside of the MIAF profiles
  • heif_context_write_to_file() always returned success. When the file could not be opened or written (missing directory, full disk), heif-enc ended without a message, without an output file and with exit code 0
  • heif-enc ignored unknown options and options without their argument, so a mistyped option was encoded with the default value
  • heif-enc did not release the encoder when it was called without an input file, and neither the encoder nor the encoding options after a write error
  • FFmpeg decoder plugin:
    • HEVC and AVC streams with matrix_coefficients 0 (planar GBR, up to 14 bits for AVC) and 9-bit streams are decoded (#1920)
    • Full-range 8-bit 4:2:2 and 4:4:4 output (AVC, and every JPEG with this chroma format) is accepted
    • A JPEG that is coded without colour transform is decoded with the right colours when the file has no colr box
    • Images with a left conformance window crop were rejected with "Decoded image does not have the size signaled in the file", because FFmpeg rounded the crop down (#1922)
    • Complete access units are sent to the decoder without running the parser over them first (#1921)

Behavior changes

  • A decoded image whose bit depth contradicts the image handle is refused with "Decoded image does not have the bit depth signaled in the file"
  • All tiles of a grid image need the same colorspace, chroma format and bit depths, and the same components. A grid in which, for example, only some of the tiles have an alpha channel is refused; whether its decoded image had an alpha plane depended on timing before. heif_context_add_image_tile() and heif_context_encode_grid() do not check this yet when the tiles are added; the requirement is now documented
  • Overlay (iovl) images are composed with the bit depth of their first input image instead of always 8 bits. An input image with more bits than the first one, and a canvas with more than 16 bits, are refused
  • heif_image_tiling reports a single tile for an image whose alpha image has a different tiling, and for an unci image whose compressed units span several tiles
  • A tile position outside of the tiling returns a usage error, also with ignore_transformations
  • unci with generic compression: a compressed unit has to decompress to exactly the size that libheif computes for it. Surplus data was ignored and missing data was read as zeros before. This also refuses 4:2:0 and 4:2:2 images with an odd width (or an odd height for 4:2:0) that heif-enc wrote with compression; they were decoded with wrong chroma before
  • An uncv sample that is smaller than the frame data is an error
  • Encoding an image with sample values above its declared bit depth returns a usage error
  • heif_image_add_plane() refuses bit depths other than 8 for interleaved RGB and RGBA (the legacy values 24 and 32 are still accepted) and other than 9 to 16 for the RRGGBB formats
  • HEVC files written by libheif carry the constraint flags of the SPS in hvcC. The miaf brand is not written for HEVC images with more than 10 bits or in a high throughput or screen content coding profile, and those two profile families get no HEVC brand
  • Encoding a 16-bit image as HEVC returns heif_error_Unsupported_feature with heif_suberror_Unsupported_bit_depth instead of an encoder plugin error. The hvcC box cannot signal 16 bits
  • JPEG 2000: the OpenJPEG plugin refuses codestreams with signed components, codestreams that do not start with an SOC marker and an SIZ marker segment, and codestreams with more tiles or components than max_number_of_tiles, max_components and max_memory_block_size allow
  • An image sequence track with a chunk that references a non-visual sample entry is rejected when the file is read
  • An HEVC SPS that cannot be parsed is an error instead of being skipped. The FFmpeg plugin refuses NAL units that contain 00 00 00, 00 00 01 or 00 00 02
  • heif_context_write_to_file() returns heif_error_Encoding_error with heif_suberror_Cannot_write_output_data when the file cannot be written
  • heif-enc exits with code 5 for an unknown option or an option without its argument. The option --even-size, deprecated since v1.10.0 and without function since v1.19.0, is gone
  • heif-info -d and heif_context_debug_dump_boxes_to_file() print at most 100 entries per sample table, followed by a note about how many were omitted
  • With bilinear chroma upsampling, the pixels in the outermost rows and columns of a 4:2:0 image change

Don't miss a new libheif release

NewReleases is sending notifications on new releases.