Version in base suite: 1.19.8-1 Base version: libheif_1.19.8-1 Target version: libheif_1.19.8-1+deb13u1 Base file: /srv/ftp-master.debian.org/ftp/pool/main/libh/libheif/libheif_1.19.8-1.dsc Target file: /srv/ftp-master.debian.org/policy/pool/main/libh/libheif/libheif_1.19.8-1+deb13u1.dsc changelog | 12 patches/CVE-2025-68431.patch | 21 patches/CVE-2026-32740.patch | 59 ++ patches/CVE-2026-32741.patch | 24 + patches/CVE-2026-32882.patch | 21 patches/CVE-2026-47178.patch | 60 ++ patches/CVE-2026-47247.patch | 51 ++ patches/CVE-2026-47709-1_uncC-tile-count-overflow.patch | 29 + patches/CVE-2026-47709-2_missing-ispe-null-deref.patch | 36 + patches/CVE-2026-47714.patch | 222 ++++++++++ patches/CVE-2026-48029.patch | 120 +++++ patches/CVE-2026-49271.patch | 26 + patches/CVE-2026-62289-clap-zero-size-tiling-underflow.patch | 97 ++++ patches/CVE-2026-GHSA-2h34-fcv6-jqvh-tili-offset-oob-read.patch | 52 ++ patches/GHSA-5hqq-636x-r3cr.patch | 138 ++++++ patches/GHSA-73p7-m7gg-w2jv.patch | 52 ++ patches/overlay-simplify-overlap-area-computation.patch | 133 +++++ patches/series | 16 18 files changed, 1169 insertions(+) dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpyltcf5ac/libheif_1.19.8-1.dsc: no acceptable signature found dpkg-source: warning: cannot verify inline signature for /srv/release.debian.org/tmp/tmpyltcf5ac/libheif_1.19.8-1+deb13u1.dsc: no acceptable signature found diff -Nru libheif-1.19.8/debian/changelog libheif-1.19.8/debian/changelog --- libheif-1.19.8/debian/changelog 2025-04-29 08:32:58.000000000 +0000 +++ libheif-1.19.8/debian/changelog 2026-08-06 10:48:46.000000000 +0000 @@ -1,3 +1,15 @@ +libheif (1.19.8-1+deb13u1) trixie-security; urgency=high + + * Non-maintainer upload by the Security Team. + * Fixed issues: + CVE-2025-68431, CVE-2026-32882, CVE-2026-32740, CVE-2026-47247, + CVE-2026-32741, CVE-2026-47709, CVE-2026-49271, CVE-2026-62292, + CVE-2026-47714, CVE-2026-48029, CVE-2026-62289 and GHSA-2h34-fcv6-jqvh + * Mitigated CVE-2026-47178: rejected files affected by the issue with + heif_error_Unsupported_feature instead of being decoded. + + -- Aron Xu Thu, 06 Aug 2026 18:48:46 +0800 + libheif (1.19.8-1) unstable; urgency=medium * New upstream version 1.19.8 diff -Nru libheif-1.19.8/debian/patches/CVE-2025-68431.patch libheif-1.19.8/debian/patches/CVE-2025-68431.patch --- libheif-1.19.8/debian/patches/CVE-2025-68431.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2025-68431.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,21 @@ +Description: CVE-2025-68431: fix wrong copy width in HeifPixelImage::overlay() + The non-alpha memcpy path subtracted in_x0 from the copy length a second + time, although in_x0 has already been subtracted from in_w when the + overlay was clipped against the left border. Upstream reports this as a + heap buffer over-read (the subtraction can underflow to a huge length). +Origin: upstream, https://github.com/strukturag/libheif/commit/b8c12a7b70f46c9516711a988483bed377b78d46 +Last-Update: 2026-08-06 + +diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc +index 7425ea11..960ec99d 100644 +--- a/libheif/pixelimage.cc ++++ b/libheif/pixelimage.cc +@@ -1324,7 +1324,7 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int32_t + if (!has_alpha) { + memcpy(out_p + out_x0 + (out_y0 + y - in_y0) * out_stride, + in_p + in_x0 + y * in_stride, +- in_w - in_x0); ++ in_w); + } + else { + for (uint32_t x = in_x0; x < in_w; x++) { diff -Nru libheif-1.19.8/debian/patches/CVE-2026-32740.patch libheif-1.19.8/debian/patches/CVE-2026-32740.patch --- libheif-1.19.8/debian/patches/CVE-2026-32740.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-32740.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,59 @@ +Description: CVE-2026-32740: fix heap buffer overflow from chroma rounding mismatch in tile/area copy + copy_image_to() and extract_image_area() computed the copy size by + rounding (w - x0) / (h - y0) into chroma channel dimensions, which + for 4:2:0 subsampled planes and odd offsets/sizes can round up to a + larger value than channel_width(w)/channel_height(h) minus the + already-rounded offset xs/ys, causing the copy to run past the end + of the destination (copy_image_to) or source (extract_image_area) + plane. Clamp the copy size to the actual plane bounds instead. +Origin: upstream, https://github.com/strukturag/libheif/commit/23903961e1237ecdb0779a65b423cb75f524d254 +Last-Update: 2026-08-06 + +diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc +index c984bd69..09ac476c 100644 +--- a/libheif/pixelimage.cc ++++ b/libheif/pixelimage.cc +@@ -776,13 +776,16 @@ Error HeifPixelImage::copy_image_to(const std::shared_ptr& + uint32_t src_width = source->get_width(channel); + uint32_t src_height = source->get_height(channel); + +- uint32_t copy_width = std::min(src_width, channel_width(w - x0, chroma, channel)); +- uint32_t copy_height = std::min(src_height, channel_height(h - y0, chroma, channel)); +- +- copy_width *= source->get_storage_bits_per_pixel(channel) / 8; +- + uint32_t xs = channel_width(x0, chroma, channel); + uint32_t ys = channel_height(y0, chroma, channel); ++ ++ // Compute copy size from actual plane bounds to avoid chroma rounding mismatch. ++ // channel_height(y0) + channel_height(h - y0) can exceed channel_height(h) with 4:2:0 ++ // due to ceiling division, so we use (plane_size - offset) instead. ++ uint32_t copy_width = std::min(src_width, channel_width(w, chroma, channel) - xs); ++ uint32_t copy_height = std::min(src_height, channel_height(h, chroma, channel) - ys); ++ ++ copy_width *= source->get_storage_bits_per_pixel(channel) / 8; + xs *= source->get_storage_bits_per_pixel(channel) / 8; + + for (uint32_t py = 0; py < copy_height; py++) { +@@ -1595,13 +1598,16 @@ HeifPixelImage::extract_image_area(uint32_t x0, uint32_t y0, uint32_t w, uint32_ + }; + } + +- uint32_t copy_width = channel_width(minW, chroma, channel); +- uint32_t copy_height = channel_height(minH, chroma, channel); +- +- copy_width *= get_storage_bits_per_pixel(channel) / 8; +- + uint32_t xs = channel_width(x0, chroma, channel); + uint32_t ys = channel_height(y0, chroma, channel); ++ ++ // Clamp copy size to source plane bounds to avoid chroma rounding mismatch OOB read. ++ uint32_t src_plane_h = channel_height(get_height(), chroma, channel); ++ uint32_t src_plane_w = channel_width(get_width(), chroma, channel); ++ uint32_t copy_width = std::min(channel_width(minW, chroma, channel), src_plane_w - xs); ++ uint32_t copy_height = std::min(channel_height(minH, chroma, channel), src_plane_h - ys); ++ ++ copy_width *= get_storage_bits_per_pixel(channel) / 8; + xs *= get_storage_bits_per_pixel(channel) / 8; + + for (uint32_t py = 0; py < copy_height; py++) { diff -Nru libheif-1.19.8/debian/patches/CVE-2026-32741.patch libheif-1.19.8/debian/patches/CVE-2026-32741.patch --- libheif-1.19.8/debian/patches/CVE-2026-32741.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-32741.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,24 @@ +Description: CVE-2026-32741: fix heap buffer overflow in decode_mask_image() + The fast-path memcpy used data.size() (the size of the encoded input + buffer) as the copy length instead of width * height (the size of + the destination plane), and the stride comparison had a truncating + cast that could mask a mismatch. If the compressed payload is larger + than the destination plane, this overflows the plane buffer. +Origin: upstream, https://github.com/strukturag/libheif/commit/123694271ac02f2de68a3ccdc5d483eb8a2ae593 +Last-Update: 2026-08-06 + +diff --git a/libheif/image-items/mask_image.cc b/libheif/image-items/mask_image.cc +index 3670cc3a..e99a5916 100644 +--- a/libheif/image-items/mask_image.cc ++++ b/libheif/image-items/mask_image.cc +@@ -113,8 +113,8 @@ Error MaskImageCodec::decode_mask_image(const HeifContext* context, + + size_t stride; + uint8_t* dst = img->get_plane(heif_channel_Y, &stride); +- if (((uint32_t)stride) == width) { +- memcpy(dst, data.data(), data.size()); ++ if (stride == static_cast(width)) { ++ memcpy(dst, data.data(), static_cast(width) * height); + } + else + { diff -Nru libheif-1.19.8/debian/patches/CVE-2026-32882.patch libheif-1.19.8/debian/patches/CVE-2026-32882.patch --- libheif-1.19.8/debian/patches/CVE-2026-32882.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-32882.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,21 @@ +Description: CVE-2026-32882: fix overlay compositing OOB read when alpha plane stride differs from color plane stride + The alpha-blending path in HeifPixelImage::overlay() indexed the + alpha plane using the color channel's stride (in_stride) instead of + the alpha plane's own stride, causing an out-of-bounds read whenever + the two planes are not identically strided. +Origin: upstream, https://github.com/strukturag/libheif/commit/22b6266bf03a9f016f84a9b57b80e1dd7c79d64a +Last-Update: 2026-08-06 + +diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc +index 960ec99d..ed314740 100644 +--- a/libheif/pixelimage.cc ++++ b/libheif/pixelimage.cc +@@ -1330,7 +1330,7 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int32_t + for (uint32_t x = in_x0; x < in_w; x++) { + uint8_t* outptr = &out_p[out_x0 + (out_y0 + y - in_y0) * out_stride + x]; + uint8_t in_val = in_p[in_x0 + y * in_stride + x]; +- uint8_t alpha_val = alpha_p[in_x0 + y * in_stride + x]; ++ uint8_t alpha_val = alpha_p[in_x0 + y * alpha_stride + x]; + + *outptr = (uint8_t) ((in_val * alpha_val + *outptr * (255 - alpha_val)) / 255); + } diff -Nru libheif-1.19.8/debian/patches/CVE-2026-47178.patch libheif-1.19.8/debian/patches/CVE-2026-47178.patch --- libheif-1.19.8/debian/patches/CVE-2026-47178.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-47178.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,60 @@ +Description: CVE-2026-47178: reject uncompressed configurations whose decoders write out of bounds + This is not a port of the upstream arithmetic fix but a mitigation. It + rejects the affected files at format-validation time because the upstream + fix does not fit naturally into the five decoder classes in current version. +Forwarded: not-needed +Author: Aron Xu +Last-Update: 2026-08-06 + +diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc +index b3ec1fbb..c07a6d81 100644 +--- a/libheif/codecs/uncompressed/unc_codec.cc ++++ b/libheif/codecs/uncompressed/unc_codec.cc +@@ -194,6 +194,32 @@ static Error uncompressed_image_type_is_supported(const std::shared_ptrget_sampling_type() != sampling_mode_no_subsampling ++ && (uncC->get_interleave_type() == interleave_mode_row ++ || uncC->get_interleave_type() == interleave_mode_pixel)) { ++ return Error(heif_error_Unsupported_feature, ++ heif_suberror_Unsupported_data_version, ++ "Chroma subsampling is not supported with row or pixel interleave"); ++ } ++ ++ if ((uncC->get_sampling_type() == sampling_mode_420 || uncC->get_sampling_type() == sampling_mode_422) ++ && (uncC->get_number_of_tile_columns() > 1 || uncC->get_number_of_tile_rows() > 1) ++ && (uncC->get_interleave_type() == interleave_mode_component ++ || uncC->get_interleave_type() == interleave_mode_mixed ++ || uncC->get_interleave_type() == interleave_mode_tile_component)) { ++ return Error(heif_error_Unsupported_feature, ++ heif_suberror_Unsupported_data_version, ++ "Tiled YCbCr 4:2:0/4:2:2 uncompressed images with component, mixed, or tile-component interleave are not supported"); ++ } ++ + if (uncC->get_block_size() != 0) { + std::stringstream sstr; + sstr << "Uncompressed block_size of " << ((int) uncC->get_block_size()) << " is not implemented yet"; +@@ -530,6 +556,14 @@ Error UncompressedImageCodec::decode_uncompressed_image_tile(const HeifContext* + return error; + } + ++ // Same check as in decode_uncompressed_image(). Without it, the per-tile ++ // decode API would bypass every uncC/cmpd configuration check, including the ++ // ones above that reject configurations whose decoders write out of bounds. ++ error = uncompressed_image_type_is_supported(uncC, cmpd); ++ if (error) { ++ return error; ++ } ++ + uint32_t tile_width = ispe->get_width() / uncC->get_number_of_tile_columns(); + uint32_t tile_height = ispe->get_height() / uncC->get_number_of_tile_rows(); + diff -Nru libheif-1.19.8/debian/patches/CVE-2026-47247.patch libheif-1.19.8/debian/patches/CVE-2026-47247.patch --- libheif-1.19.8/debian/patches/CVE-2026-47247.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-47247.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,51 @@ +Description: CVE-2026-47247: fix grid tile coverage check and zero-initialize pixel plane allocation + (a) ImageItem_Grid::decode_full_grid_image() checked tile coverage + with integer division (src_width < grid_width / columns), which + under-detects gaps: e.g. 9 tiles of 11px covering a 107px-wide grid + pass the check (107/9 == 11) even though 9*11 = 99 < 107, leaving an + 8-pixel-wide strip of the canvas never written by any tile. Compare + with a multiplication instead so any gap is rejected. + . + (b) HeifPixelImage::ImagePlane::alloc() used a plain `new uint8_t[]`, + leaving stride padding and alignment slack uninitialized; combined + with (a), a grid gap left uninitialized heap bytes in the padding + region readable via the decoded image, leaking heap contents. Zero- + initialize the allocation so no uninitialized memory can be exposed + through any codepath that doesn't fully overwrite the plane + (including the grid gap from (a), and other partial-write paths). +Origin: upstream, https://github.com/strukturag/libheif/commit/6aca89d76a5118bd47adcb5b83e7b01a32134985 +Reviewed-by: Aron Xu +Last-Update: 2026-08-06 + +diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc +index 7243f93b..565c70be 100644 +--- a/libheif/image-items/grid.cc ++++ b/libheif/image-items/grid.cc +@@ -313,8 +313,10 @@ Result> ImageItem_Grid::decode_full_grid_image(c + return err; + } + +- if (src_width < grid.get_width() / grid.get_columns() || +- src_height < grid.get_height() / grid.get_rows()) { ++ // Integer division would let e.g. 9 tiles of 11px each "cover" a 107px canvas ++ // (107/9 == 11), leaving an 8-pixel gap inside the visible image area. ++ if (static_cast(src_width) * grid.get_columns() < grid.get_width() || ++ static_cast(src_height) * grid.get_rows() < grid.get_height()) { + return Error{heif_error_Invalid_input, + heif_suberror_Invalid_grid_data, + "Grid tiles do not cover whole image"}; +diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc +index 09ac476c..df25cefd 100644 +--- a/libheif/pixelimage.cc ++++ b/libheif/pixelimage.cc +@@ -276,7 +276,9 @@ Error HeifPixelImage::ImagePlane::alloc(uint32_t width, uint32_t height, heif_ch + } + + try { +- allocated_mem = new uint8_t[static_cast(m_mem_height) * stride + alignment - 1]; ++ // Must zero-initialize: padding regions (stride, alignment slack) are not written by ++ // decoders, so uninitialized contents would leak across decoded images. ++ allocated_mem = new uint8_t[static_cast(m_mem_height) * stride + alignment - 1](); + uint8_t* mem_8 = allocated_mem; + + // shift beginning of image data to aligned memory position diff -Nru libheif-1.19.8/debian/patches/CVE-2026-47709-1_uncC-tile-count-overflow.patch libheif-1.19.8/debian/patches/CVE-2026-47709-1_uncC-tile-count-overflow.patch --- libheif-1.19.8/debian/patches/CVE-2026-47709-1_uncC-tile-count-overflow.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-47709-1_uncC-tile-count-overflow.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,29 @@ +Description: CVE-2026-47709: integer overflow when parsing uncC tile counts + num_tile_cols_minus_one / num_tile_rows_minus_one are stored as (count - 1); + a stored value of 0xFFFFFFFF represents 2^32 tiles, which cannot be + represented in the uint32_t m_num_tile_cols/m_num_tile_rows fields and + overflows the security-limit division below it. Reject this value + unconditionally before it reaches the (otherwise policy-only, + user-disableable) max_number_of_tiles check. +Origin: upstream, https://github.com/strukturag/libheif/commit/f36bd8cddd0883dbe2d793f48f62d7d5be2ad678 +Last-Update: 2026-08-06 + +--- a/libheif/codecs/uncompressed/unc_boxes.cc ++++ b/libheif/codecs/uncompressed/unc_boxes.cc +@@ -301,6 +301,16 @@ Error Box_uncC::parse(BitstreamRange& range, const heif_security_limits* limits) + uint32_t num_tile_cols_minus_one = range.read32(); + uint32_t num_tile_rows_minus_one = range.read32(); + ++ // The field is stored as `count - 1`, so 0xFFFFFFFF would mean 2^32 tiles, ++ // which we cannot represent in our uint32 m_num_tile_cols/rows. Reject this ++ // unconditionally; the security-limit check below is policy and may be ++ // disabled by the user, but this representation limit must always hold. ++ if (num_tile_cols_minus_one == 0xFFFFFFFF || num_tile_rows_minus_one == 0xFFFFFFFF) { ++ return {heif_error_Unsupported_feature, ++ heif_suberror_Invalid_parameter_value, ++ "uncC num_tile_cols/rows_minus_one of 0xFFFFFFFF (2^32 tiles) exceeds the supported range"}; ++ } ++ + if (limits->max_number_of_tiles && + static_cast(num_tile_cols_minus_one) + 1 > limits->max_number_of_tiles / (static_cast(num_tile_rows_minus_one) + 1)) { + std::stringstream sstr; diff -Nru libheif-1.19.8/debian/patches/CVE-2026-47709-2_missing-ispe-null-deref.patch libheif-1.19.8/debian/patches/CVE-2026-47709-2_missing-ispe-null-deref.patch --- libheif-1.19.8/debian/patches/CVE-2026-47709-2_missing-ispe-null-deref.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-47709-2_missing-ispe-null-deref.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,36 @@ +Description: CVE-2026-47709: reject unci images with no ispe box + Without an 'ispe' box, get_heif_image_tiling() and get_tile_size() would + later dereference a null Box_ispe pointer (get_heif_image_tiling() only + has an assert(), which is compiled out in release/NDEBUG builds). Reject + such images while loading the item, mirroring the existing "No 'uncC' box + found" check just above it. +Origin: upstream, https://github.com/strukturag/libheif/commit/294d9c09db838aba9a615a6b6aba4c7019b7bbfd +Reviewed-by: Aron Xu +Last-Update: 2026-08-06 +Comment: Upstream's fix targets ImageItem_uncompressed::initialize_decoder(), + which does not exist at 1.19.8, this patch adds the equivalent check + directly in on_load_file(). + +--- a/libheif/image-items/unc_image.cc ++++ b/libheif/image-items/unc_image.cc +@@ -514,6 +514,7 @@ Error ImageItem_uncompressed::on_load_file() + { + std::shared_ptr cmpd = get_property(); + std::shared_ptr uncC = get_property(); ++ std::shared_ptr ispe = get_property(); + + if (!uncC) { + return Error{heif_error_Invalid_input, +@@ -521,6 +522,12 @@ Error ImageItem_uncompressed::on_load_file() + "No 'uncC' box found."}; + } + ++ if (!ispe) { ++ return Error{heif_error_Invalid_input, ++ heif_suberror_Unspecified, ++ "No 'ispe' box found for uncompressed image item."}; ++ } ++ + m_decoder = std::make_shared(uncC, cmpd); + + DataExtent extent; diff -Nru libheif-1.19.8/debian/patches/CVE-2026-47714.patch libheif-1.19.8/debian/patches/CVE-2026-47714.patch --- libheif-1.19.8/debian/patches/CVE-2026-47714.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-47714.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,222 @@ +Description: CVE-2026-47714: check inline region mask allocation against integer overflow and security limits + Box_iref-referenced region items (rgan) with an inline mask geometry (heif_region_type_inline_mask) + computed the mask buffer size as "width * height / 8" using 32-bit unsigned arithmetic with no + overflow or zero-size check before resizing/copying into mask_data, allowing a crafted region item + to trigger an integer overflow (undersized allocation followed by an out-of-bounds copy) or a + divide-by-zero-adjacent zero-size mask. Ported from upstream's fix, which introduced a MemoryHandle + allocation-tracking class that does not exist in this version (see Comment below): plumb the + heif_security_limits pointer through RegionItem::parse()/RegionGeometry::parse() down to + RegionGeometry_InlineMask::parse(), reject width==0 || height==0, compute the mask size with 64-bit + arithmetic as (uint64_t(width) * height + 7) / 8, and reject sizes exceeding + limits->max_memory_block_size before touching mask_data. +Origin: upstream, https://github.com/strukturag/libheif/commit/e561521f2382cf8f49b581a6044882390b8cc7a5 + , https://github.com/strukturag/libheif/commit/b1bd1c000e596f09ecd7bae87f95b88c21a6dc4a + , https://github.com/strukturag/libheif/commit/271d6ca590c7e1f00cd2fd0414d91b68f83dd4b6 +Reviewed-by: Aron Xu +Last-Update: 2026-08-06 + +--- a/libheif/context.cc ++++ b/libheif/context.cc +@@ -870,7 +870,7 @@ Error HeifContext::interpret_heif_file() + if (err) { + return err; + } +- region_item->parse(region_data); ++ region_item->parse(region_data, get_security_limits()); + if (iref_box) { + std::vector references = iref_box->get_references_from(id); + for (const auto& ref : references) { +diff --git a/libheif/region.cc b/libheif/region.cc +index afbf7c37..eb25a4b8 100644 +--- a/libheif/region.cc ++++ b/libheif/region.cc +@@ -27,7 +27,7 @@ + #include + + +-Error RegionItem::parse(const std::vector& data) ++Error RegionItem::parse(const std::vector& data, const heif_security_limits* limits) + { + if (data.size() < 8) { + return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, +@@ -105,7 +105,7 @@ Error RegionItem::parse(const std::vector& data) + continue; + } + +- Error error = region->parse(data, field_size, &dataOffset); ++ Error error = region->parse(data, field_size, &dataOffset, limits); + if (error) { + return error; + } +@@ -202,7 +202,8 @@ int32_t RegionGeometry::parse_signed(const std::vector& data, + + Error RegionGeometry_Point::parse(const std::vector& data, + int field_size, +- unsigned int* dataOffset) ++ unsigned int* dataOffset, ++ const heif_security_limits* limits) + { + unsigned int bytesRequired = (field_size / 8) * 2; + if (data.size() - *dataOffset < bytesRequired) { +@@ -243,7 +244,8 @@ void RegionGeometry_Point::encode(StreamWriter& writer, int field_size_bytes) co + + Error RegionGeometry_Rectangle::parse(const std::vector& data, + int field_size, +- unsigned int* dataOffset) ++ unsigned int* dataOffset, ++ const heif_security_limits* limits) + { + unsigned int bytesRequired = (field_size / 8) * 4; + if (data.size() - *dataOffset < bytesRequired) { +@@ -275,7 +277,8 @@ void RegionGeometry_Rectangle::encode(StreamWriter& writer, int field_size_bytes + + Error RegionGeometry_Ellipse::parse(const std::vector& data, + int field_size, +- unsigned int* dataOffset) ++ unsigned int* dataOffset, ++ const heif_security_limits* limits) + { + unsigned int bytesRequired = (field_size / 8) * 4; + if (data.size() - *dataOffset < bytesRequired) { +@@ -308,7 +311,8 @@ void RegionGeometry_Ellipse::encode(StreamWriter& writer, int field_size_bytes) + + Error RegionGeometry_Polygon::parse(const std::vector& data, + int field_size, +- unsigned int* dataOffset) ++ unsigned int* dataOffset, ++ const heif_security_limits* limits) + { + uint32_t bytesRequired1 = (field_size / 8) * 1; + if (data.size() - *dataOffset < bytesRequired1) { +@@ -339,7 +343,8 @@ Error RegionGeometry_Polygon::parse(const std::vector& data, + + Error RegionGeometry_ReferencedMask::parse(const std::vector& data, + int field_size, +- unsigned int* dataOffset) ++ unsigned int* dataOffset, ++ const heif_security_limits* limits) + { + unsigned int bytesRequired = (field_size / 8) * 4; + if (data.size() - *dataOffset < bytesRequired) { +@@ -394,7 +399,8 @@ void RegionGeometry_Polygon::encode(StreamWriter& writer, int field_size_bytes) + + Error RegionGeometry_InlineMask::parse(const std::vector& data, + int field_size, +- unsigned int* dataOffset) ++ unsigned int* dataOffset, ++ const heif_security_limits* limits) + { + unsigned int bytesRequired = (field_size / 8) * 4 + 1; + if (data.size() - *dataOffset < bytesRequired) { +@@ -411,13 +417,24 @@ Error RegionGeometry_InlineMask::parse(const std::vector& data, + return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, + "Deflate compressed inline mask is not yet supported"); + } +- unsigned int additionalBytesRequired = width * height / 8; +- if (data.size() - *dataOffset < additionalBytesRequired) { ++ ++ if (width == 0 || height == 0) { ++ return Error(heif_error_Invalid_input, heif_suberror_Unspecified, ++ "Zero size mask image."); ++ } ++ ++ uint64_t bytesForMask = (uint64_t(width) * height + 7) / 8; ++ if (limits->max_memory_block_size && bytesForMask > limits->max_memory_block_size) { ++ return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, ++ "Inline mask exceeds maximum memory block size."); ++ } ++ ++ if (data.size() - *dataOffset < bytesForMask) { + return Error(heif_error_Invalid_input, heif_suberror_Invalid_region_data, + "Insufficient data remaining for inline mask region data[]"); + } +- mask_data.resize(additionalBytesRequired); +- std::copy(data.begin() + *dataOffset, data.begin() + *dataOffset + additionalBytesRequired, mask_data.begin()); ++ mask_data.resize(bytesForMask); ++ std::copy(data.begin() + *dataOffset, data.begin() + *dataOffset + static_cast(bytesForMask), mask_data.begin()); + return Error::Ok; + } + +diff --git a/libheif/region.h b/libheif/region.h +index 7509c656..e29d747e 100644 +--- a/libheif/region.h ++++ b/libheif/region.h +@@ -38,7 +38,7 @@ public: + RegionItem(heif_item_id itemId, uint32_t ref_width, uint32_t ref_height) + : item_id(itemId), reference_width(ref_width), reference_height(ref_height) {} + +- Error parse(const std::vector& data); ++ Error parse(const std::vector& data, const heif_security_limits* limits); + + Error encode(std::vector& result) const; + +@@ -67,7 +67,8 @@ public: + + virtual heif_region_type getRegionType() = 0; + +- virtual Error parse(const std::vector& data, int field_size, unsigned int* dataOffset) = 0; ++ virtual Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) = 0; + + virtual bool encode_needs_32bit() const { return false; } + +@@ -82,7 +83,8 @@ protected: + class RegionGeometry_Point : public RegionGeometry + { + public: +- Error parse(const std::vector& data, int field_size, unsigned int* dataOffset) override; ++ Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) override; + + bool encode_needs_32bit() const override; + +@@ -96,7 +98,8 @@ public: + class RegionGeometry_Rectangle : public RegionGeometry + { + public: +- Error parse(const std::vector& data, int field_size, unsigned int* dataOffset) override; ++ Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) override; + + bool encode_needs_32bit() const override; + +@@ -111,7 +114,8 @@ public: + class RegionGeometry_Ellipse : public RegionGeometry + { + public: +- Error parse(const std::vector& data, int field_size, unsigned int* dataOffset) override; ++ Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) override; + + bool encode_needs_32bit() const override; + +@@ -126,7 +130,8 @@ public: + class RegionGeometry_Polygon : public RegionGeometry + { + public: +- Error parse(const std::vector& data, int field_size, unsigned int* dataOffset) override; ++ Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) override; + + bool encode_needs_32bit() const override; + +@@ -149,7 +154,8 @@ public: + class RegionGeometry_ReferencedMask : public RegionGeometry + { + public: +- Error parse(const std::vector& data, int field_size, unsigned int *dataOffset) override; ++ Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) override; + + void encode(StreamWriter&, int field_size_bytes) const override; + +@@ -163,7 +169,8 @@ public: + class RegionGeometry_InlineMask : public RegionGeometry + { + public: +- Error parse(const std::vector& data, int field_size, unsigned int *dataOffset) override; ++ Error parse(const std::vector& data, int field_size, unsigned int* dataOffset, ++ const heif_security_limits* limits) override; + + void encode(StreamWriter&, int field_size_bytes) const override; + diff -Nru libheif-1.19.8/debian/patches/CVE-2026-48029.patch libheif-1.19.8/debian/patches/CVE-2026-48029.patch --- libheif-1.19.8/debian/patches/CVE-2026-48029.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-48029.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,120 @@ +Description: CVE-2026-48029: fix tile coordinates validation in rotated grid images + ImageItem::transform_requested_tile_position_to_original_tile_position() validated the + caller-supplied (tile_x, tile_y) against the wrong (in-file) tile-grid dimensions, and + heif_image_handle_get_grid_image_tile_id() performed its own bounds check against the + in-file grid before applying the transform at all. For a grid image with a 90/270 degree + irot rotation, the displayed tile grid has its columns and rows swapped relative to the + file; a tile coordinate that was in-range for the file's grid but out-of-range for the + displayed grid was accepted, and the inverse-rotation arithmetic (e.g. + `num_rows - 1 - tile_x` with `tile_x >= num_rows`) underflowed, leading to an + out-of-bounds read via the grid tile array. Validate the requested tile position against + the displayed dimensions (via process_image_transformations_on_tiling()) before doing any + arithmetic, track the current tile-grid extent through the reversed property walk so each + inverse rotation uses the correct (possibly already-swapped) dimensions, and call the + transform before the bounds check in heif_image_handle_get_grid_image_tile_id() so its + Error is propagated instead of being bypassed. +Origin: upstream, https://github.com/strukturag/libheif/commit/e523ec0bf379110b7c33d4c159f8b1202d332157 +Reviewed-by: Aron Xu +Last-Update: 2026-08-06 + +--- a/libheif/api/libheif/heif.cc ++++ b/libheif/api/libheif/heif.cc +@@ -976,16 +976,19 @@ struct heif_error heif_image_handle_get_grid_image_tile_id(const struct heif_ima + } + + const ImageGrid& gridspec = gridItem->get_grid_spec(); +- if (tile_x >= gridspec.get_columns() || tile_y >= gridspec.get_rows()) { ++ ++ if (process_image_transformations) { ++ Error err = gridItem->transform_requested_tile_position_to_original_tile_position(tile_x, tile_y); ++ if (err) { ++ return err.error_struct(handle->context.get()); ++ } ++ } ++ else if (tile_x >= gridspec.get_columns() || tile_y >= gridspec.get_rows()) { + return { heif_error_Usage_error, + heif_suberror_Unspecified, + "Grid tile index out of range" }; + } + +- if (process_image_transformations) { +- gridItem->transform_requested_tile_position_to_original_tile_position(tile_x, tile_y); +- } +- + *tile_item_id = gridItem->get_grid_tiles()[tile_y * gridspec.get_columns() + tile_x]; + + return heif_error_ok; +diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc +index 77ae43ed..cbcd5519 100644 +--- a/libheif/image-items/image_item.cc ++++ b/libheif/image-items/image_item.cc +@@ -716,29 +716,50 @@ Error ImageItem::transform_requested_tile_position_to_original_tile_position(uin + return propertiesResult.error; + } + ++ // The caller's (tile_x, tile_y) are in the *displayed* tile grid, so they ++ // must be validated against the displayed dimensions, not the in-file ones. ++ // For rotations of 90/270 degrees the displayed grid has its columns and ++ // rows swapped relative to the file. + heif_image_tiling tiling = get_heif_image_tiling(); ++ if (Error err = process_image_transformations_on_tiling(tiling)) { ++ return err; ++ } ++ ++ if (tile_x >= tiling.num_columns || tile_y >= tiling.num_rows) { ++ return {heif_error_Usage_error, ++ heif_suberror_Unspecified, ++ "Tile coordinate out of range for displayed image"}; ++ } ++ ++ // Walk the property chain in reverse, undoing each transformation as we go. ++ // Track the current (intermediate) tile-grid dimensions so each inverse uses ++ // the right extent and so 90/270 degree rotations swap dims for subsequent steps. ++ uint32_t cur_cols = tiling.num_columns; ++ uint32_t cur_rows = tiling.num_rows; + + //for (auto& prop : std::ranges::reverse_view(propertiesResult.value)) { + for (auto propIter = propertiesResult.value.rbegin(); propIter != propertiesResult.value.rend(); propIter++) { + if (auto irot = std::dynamic_pointer_cast(*propIter)) { + switch (irot->get_rotation_ccw()) { + case 90: { +- uint32_t tx0 = tiling.num_columns - 1 - tile_y; ++ uint32_t tx0 = cur_rows - 1 - tile_y; + uint32_t ty0 = tile_x; +- tile_y = ty0; + tile_x = tx0; ++ tile_y = ty0; ++ std::swap(cur_cols, cur_rows); + break; + } + case 270: { + uint32_t tx0 = tile_y; +- uint32_t ty0 = tiling.num_rows - 1 - tile_x; +- tile_y = ty0; ++ uint32_t ty0 = cur_cols - 1 - tile_x; + tile_x = tx0; ++ tile_y = ty0; ++ std::swap(cur_cols, cur_rows); + break; + } + case 180: { +- tile_x = tiling.num_columns - 1 - tile_x; +- tile_y = tiling.num_rows - 1 - tile_y; ++ tile_x = cur_cols - 1 - tile_x; ++ tile_y = cur_rows - 1 - tile_y; + break; + } + case 0: +@@ -752,10 +773,10 @@ Error ImageItem::transform_requested_tile_position_to_original_tile_position(uin + if (auto imir = std::dynamic_pointer_cast(*propIter)) { + switch (imir->get_mirror_direction()) { + case heif_transform_mirror_direction_horizontal: +- tile_x = tiling.num_columns - 1 - tile_x; ++ tile_x = cur_cols - 1 - tile_x; + break; + case heif_transform_mirror_direction_vertical: +- tile_y = tiling.num_rows - 1 - tile_y; ++ tile_y = cur_rows - 1 - tile_y; + break; + default: + assert(false); diff -Nru libheif-1.19.8/debian/patches/CVE-2026-49271.patch libheif-1.19.8/debian/patches/CVE-2026-49271.patch --- libheif-1.19.8/debian/patches/CVE-2026-49271.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-49271.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,26 @@ +Description: CVE-2026-49271: prevent integer overflow when reading unci compressed data units + icef per-unit unit_offset/unit_size values (attacker-controlled) were used + directly to construct iterators into compressed_bytes with no bounds + check, allowing an out-of-bounds read. Add a subtraction-form bounds + check (which avoids a uint64_t wraparound that an addition-form check + would be vulnerable to) before constructing the iterators. +Origin: upstream, https://github.com/strukturag/libheif/commit/5782bca04a70ebc01c59397205a3cfff22841311 +Reviewed-by: Aron Xu +Last-Update: 2026-08-06 + +--- a/libheif/codecs/uncompressed/decoder_abstract.cc ++++ b/libheif/codecs/uncompressed/decoder_abstract.cc +@@ -219,6 +219,13 @@ const Error AbstractDecoder::get_compressed_image_data_uncompressed(const HeifCo + } + + for (Box_icef::CompressedUnitInfo unit_info : icef_box->get_units()) { ++ if (unit_info.unit_offset > compressed_bytes.size() || ++ unit_info.unit_size > compressed_bytes.size() - unit_info.unit_offset) { ++ return {heif_error_Invalid_input, ++ heif_suberror_Unspecified, ++ "compressed data range out of bounds"}; ++ } ++ + auto unit_start = compressed_bytes.begin() + unit_info.unit_offset; + auto unit_end = unit_start + unit_info.unit_size; + std::vector compressed_unit_data = std::vector(unit_start, unit_end); diff -Nru libheif-1.19.8/debian/patches/CVE-2026-62289-clap-zero-size-tiling-underflow.patch libheif-1.19.8/debian/patches/CVE-2026-62289-clap-zero-size-tiling-underflow.patch --- libheif-1.19.8/debian/patches/CVE-2026-62289-clap-zero-size-tiling-underflow.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-62289-clap-zero-size-tiling-underflow.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,97 @@ +Description: CVE-2026-62289: fix clap transform double-application in image tiling (GHSA-jc8f-p23p-5hjg) + The base ImageItem::get_heif_image_tiling() returned the already + clap/irot/imir-transformed m_width/m_height, but + process_image_transformations_on_tiling() applies those same transformative + properties itself, so they were applied twice. For a 'clap' (clean + aperture) box that rounds a dimension down to zero, the second application + passed image_width==0 into Box_clap::left_rounded()/top_rounded(), where + `image_width - 1U` underflows to UINT32_MAX and violates the + Fraction(uint32_t,uint32_t) constructor's `assert(num <= INT32_MAX)` + (reachable-assertion abort in debug builds; undefined/corrupt crop + rectangle in NDEBUG release builds). + . + Three layers, as upstream: + - image_item.cc: base get_heif_image_tiling() now reports the coded + (ispe) dimensions when available, matching the grid/unc/tiled overrides, + so the transformative properties are applied exactly once. + - context.cc: reject a clap that rounds a dimension to zero or less at + parse time, mirroring the existing ispe zero-size rejection in + check_for_valid_image_size(). + - box.cc: guard left_rounded()/top_rounded() against a zero image + dimension as defense in depth. +Origin: upstream, https://github.com/strukturag/libheif/commit/f01870c1d7323a3003796d58eba7fff502be994c +Last-Update: 2026-08-06 + +--- a/libheif/box.cc ++++ b/libheif/box.cc +@@ -3410,6 +3410,10 @@ int Box_clap::left_rounded(uint32_t image_width) const + + // left = horizOff + (width-1)/2 - (clapWidth-1)/2 + ++ if (image_width == 0) { ++ return 0; ++ } ++ + Fraction pcX = m_horizontal_offset + Fraction(image_width - 1U, 2U); + Fraction left = pcX - (m_clean_aperture_width - 1) / 2; + +@@ -3425,6 +3429,10 @@ int Box_clap::right_rounded(uint32_t image_width) const + + int Box_clap::top_rounded(uint32_t image_height) const + { ++ if (image_height == 0) { ++ return 0; ++ } ++ + Fraction pcY = m_vertical_offset + Fraction(image_height - 1U, 2U); + Fraction top = pcY - (m_clean_aperture_height - 1) / 2; + +--- a/libheif/context.cc ++++ b/libheif/context.cc +@@ -491,8 +491,16 @@ Error HeifContext::interpret_heif_file() + for (const auto& prop : properties) { + auto clap = std::dynamic_pointer_cast(prop); + if (clap) { +- image->set_resolution(clap->get_width_rounded(), +- clap->get_height_rounded()); ++ int clap_width = clap->get_width_rounded(); ++ int clap_height = clap->get_height_rounded(); ++ if (clap_width <= 0 || clap_height <= 0) { ++ return {heif_error_Invalid_input, ++ heif_suberror_Invalid_clean_aperture, ++ "Clean aperture (clap) reduces image to zero size"}; ++ } ++ ++ image->set_resolution(static_cast(clap_width), ++ static_cast(clap_height)); + + if (image->has_intrinsic_matrix()) { + image->get_intrinsic_matrix().apply_clap(clap.get(), image->get_width(), image->get_height()); +--- a/libheif/image-items/image_item.cc ++++ b/libheif/image-items/image_item.cc +@@ -1070,10 +1070,21 @@ heif_image_tiling ImageItem::get_heif_image_tiling() const + tiling.num_columns = 1; + tiling.num_rows = 1; + +- tiling.tile_width = m_width; +- tiling.tile_height = m_height; +- tiling.image_width = m_width; +- tiling.image_height = m_height; ++ // Report the coded (pre-transformation) dimensions here. The caller applies ++ // the transformative properties (irot, imir, clap) via ++ // process_image_transformations_on_tiling(), so handing it the already ++ // transformed m_width/m_height would apply them a second time. ++ uint32_t coded_width = m_width; ++ uint32_t coded_height = m_height; ++ if (has_ispe_resolution()) { ++ coded_width = get_ispe_width(); ++ coded_height = get_ispe_height(); ++ } ++ ++ tiling.tile_width = coded_width; ++ tiling.tile_height = coded_height; ++ tiling.image_width = coded_width; ++ tiling.image_height = coded_height; + + tiling.top_offset = 0; + tiling.left_offset = 0; diff -Nru libheif-1.19.8/debian/patches/CVE-2026-GHSA-2h34-fcv6-jqvh-tili-offset-oob-read.patch libheif-1.19.8/debian/patches/CVE-2026-GHSA-2h34-fcv6-jqvh-tili-offset-oob-read.patch --- libheif-1.19.8/debian/patches/CVE-2026-GHSA-2h34-fcv6-jqvh-tili-offset-oob-read.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/CVE-2026-GHSA-2h34-fcv6-jqvh-tili-offset-oob-read.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,52 @@ +Description: Fix heap OOB read in tili offset table parsing (GHSA-2h34-fcv6-jqvh) + Box_iloc::read_data() discarded the Error returned by Box_idat::read_data() + for construction_method=1 (idat) extents, then unconditionally decremented + the requested size by the (possibly unfulfilled) extent length. By combining + one valid and one out-of-range extent whose lengths sum to the requested + size, the post-loop "limited_size && size > 0" shortfall guard could be + bypassed and read_data() would return Ok with a shorter-than-expected + buffer. TiledHeader::read_offset_table_range() then trusted that buffer's + length and indexed into it via readvec() without a bounds check, producing + a heap out-of-bounds read while parsing the per-tile offset/size table of a + tiled ('tili') image item. + . + Fixes the root cause (propagate the idat read error, matching the + construction_method 0 branch already a few lines above) and adds a + defense-in-depth bounds check before the offset-table parsing loop. +Origin: upstream, https://github.com/strukturag/libheif/commit/5e8fcffc16a3a2cf17584a2400f7386146f830f0 +Reviewed-by: Aron Xu +Last-Update: 2026-08-06 + +--- a/libheif/box.cc ++++ b/libheif/box.cc +@@ -1701,10 +1701,13 @@ Error Box_iloc::read_data(heif_item_id item_id, + "idat box referenced in iref box is not present in file"}; + } + +- idat->read_data(istr, +- extent.offset + item->base_offset, +- extent.length, +- *dest, limits); ++ Error err = idat->read_data(istr, ++ extent.offset + item->base_offset, ++ extent.length, ++ *dest, limits); ++ if (err) { ++ return err; ++ } + + size -= extent.length; + } +--- a/libheif/image-items/tiled.cc ++++ b/libheif/image-items/tiled.cc +@@ -361,6 +361,10 @@ Error TiledHeader::read_offset_table_range(const std::shared_ptr& file + return err; + } + ++ if (data.size() < size_to_read) { ++ return eofError; ++ } ++ + size_t idx = 0; + for (uint64_t i = start; i < end; i++) { + m_offsets[i].offset = readvec(data, idx, m_parameters.offset_field_length / 8); diff -Nru libheif-1.19.8/debian/patches/GHSA-5hqq-636x-r3cr.patch libheif-1.19.8/debian/patches/GHSA-5hqq-636x-r3cr.patch --- libheif-1.19.8/debian/patches/GHSA-5hqq-636x-r3cr.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/GHSA-5hqq-636x-r3cr.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,138 @@ +Description: heif_region_item_add_region_inline_mask(): clamp copy to declared region size + heif_region_item_add_region_inline_mask() sized the destination mask buffer from the + caller-declared region (width x height) but copied pixels from the source mask heif_image + using that image's own (possibly larger) width/height and an unbounded running pixel_index, + so a source mask image larger than the declared region wrote past the end of the + heap-allocated mask_data buffer. Clamp the copy loop to min(declared, source) in each + dimension and derive the destination bit index from the declared width, so an oversized + source image is cropped to the region instead of overflowing it. +Origin: upstream, https://github.com/strukturag/libheif/commit/40f361a1e1c3b8f1a90d4bace1d93227c06448de +Bug: https://github.com/strukturag/libheif/security/advisories/GHSA-5hqq-636x-r3cr +Last-Update: 2026-08-06 +Comment: Context-adapted, not a clean cherry-pick. + Upstream's fix (written against a later tree) uses heif_image_get_plane2()/size_t stride; + v1.19.8 only has heif_image_get_plane()/int stride at this call site, so that accessor was + left unchanged and only the copy-bounds logic fix was applied (std::min-clamped copy_width/ + copy_height, and pixel_index recomputed per-pixel from the declared width instead of an + unbounded running counter). The regression test from upstream's tests/region.cc was ported + verbatim (no API differences needed). Note: in this minimal build configuration (no AV1 + encoder plugin enabled), the new test - like the three pre-existing "create ... mask region" + tests in this file - fails at heif_context_get_encoder_for_format(ctx, heif_compression_AV1, ...) + with err.code==3 before it ever reaches the patched code path; this is the same pre-existing, + unrelated missing-encoder artifact recorded in the ctest baseline for the "region" binary, not + a new regression introduced by this patch. The fix was independently verified against a + standalone AddressSanitizer driver using the JPEG encoder path, which confirmed a heap-buffer- + overflow before this patch and a clean run after. + +--- a/libheif/api/libheif/heif_regions.cc ++++ b/libheif/api/libheif/heif_regions.cc +@@ -338,14 +338,15 @@ struct heif_error heif_region_item_add_region_inline_mask(struct heif_region_ite + uint32_t mask_width = mask_image->image->get_width(); + int stride; + uint8_t* p = heif_image_get_plane(mask_image, heif_channel_Y, &stride); +- uint64_t pixel_index = 0; + +- for (uint32_t y = 0; y < mask_height; y++) { +- for (uint32_t x = 0; x < mask_width; x++) { ++ uint32_t copy_width = std::min(width, mask_width); ++ uint32_t copy_height = std::min(height, mask_height); ++ ++ for (uint32_t y = 0; y < copy_height; y++) { ++ for (uint32_t x = 0; x < copy_width; x++) { + uint8_t mask_bit = p[y * stride + x] & 0x80; // use high-order bit of the 8-bit mask value as binary mask value ++ uint64_t pixel_index = static_cast(y) * width + x; + region->mask_data.data()[pixel_index/8] |= uint8_t(mask_bit >> (pixel_index % 8)); +- +- pixel_index++; + } + } + +diff --git a/tests/region.cc b/tests/region.cc +index fb7ebca4..ebb635ba 100644 +--- a/tests/region.cc ++++ b/tests/region.cc +@@ -586,3 +586,84 @@ TEST_CASE("create inline mask region from image") { + heif_image_handle_release(readbackHandle); + heif_context_free(readbackCtx); + } ++ ++ ++TEST_CASE("inline mask region from oversized image is cropped") { ++ // Regression test for GHSA-5hqq-636x-r3cr: when the source mask image is ++ // larger than the declared region, the old code sized the destination mask ++ // buffer from the region dimensions but indexed writes by the source image ++ // dimensions, causing a heap out-of-bounds write. The source image is now ++ // cropped to the region instead. ++ struct heif_error err; ++ ++ heif_context* ctx = heif_context_alloc(); ++ heif_encoder* enc; ++ err = heif_context_get_encoder_for_format(ctx, heif_compression_AV1, &enc); ++ REQUIRE(err.code == heif_error_Ok); ++ ++ uint32_t input_width = 64; ++ uint32_t input_height = 64; ++ heif_image* img; ++ heif_image_create(input_width, input_height, heif_colorspace_YCbCr, ++ heif_chroma_420, &img); ++ fill_new_plane(img, heif_channel_Y, input_width, input_height); ++ fill_new_plane(img, heif_channel_Cb, (input_width + 1) / 2, (input_height + 1) / 2); ++ fill_new_plane(img, heif_channel_Cr, (input_width + 1) / 2, (input_height + 1) / 2); ++ ++ heif_image_handle* handle; ++ err = heif_context_encode_image(ctx, img, enc, nullptr, &handle); ++ REQUIRE(err.code == heif_error_Ok); ++ ++ struct heif_region_item* region_item; ++ err = heif_image_handle_add_region_item(handle, input_width, input_height, ®ion_item); ++ REQUIRE(err.code == heif_error_Ok); ++ ++ // Source mask image (64x64) much larger than the declared region (8x2). ++ // With the bug this writes far past the end of a 2-byte destination buffer. ++ heif_image* mask_image; ++ heif_image_create(64, 64, heif_colorspace_monochrome, heif_chroma_monochrome, &mask_image); ++ err = heif_image_add_plane(mask_image, heif_channel_Y, 64, 64, 8); ++ REQUIRE(err.code == heif_error_Ok); ++ int stride; ++ uint8_t* p = heif_image_get_plane(mask_image, heif_channel_Y, &stride); ++ memset(p, 0, 64 * stride); ++ p[0] = 0x80; // region pixel (0,0) -> set ++ p[7] = 0x80; // region pixel (7,0) -> set ++ p[stride + 0] = 0x80; // region pixel (0,1) -> set ++ // Pixels outside the 8x2 region must be cropped away. With the old code these ++ // would corrupt the in-bounds result or write out of bounds: ++ p[10] = 0x80; // (10,0) beyond declared width ++ p[2 * stride + 0] = 0x80; // (0,2) beyond declared height ++ ++ heif_region* out_region = nullptr; ++ err = heif_region_item_add_region_inline_mask(region_item, 20, 50, 8, 2, mask_image, &out_region); ++ REQUIRE(err.code == heif_error_Ok); ++ REQUIRE(out_region != nullptr); ++ ++ size_t data_len = heif_region_get_inline_mask_data_len(out_region); ++ REQUIRE(data_len == 2); // (8*2+7)/8 = 2 bytes ++ ++ std::vector mask_data_in(data_len); ++ int32_t x, y; ++ uint32_t width, height; ++ err = heif_region_get_inline_mask_data(out_region, &x, &y, &width, &height, mask_data_in.data()); ++ REQUIRE(err.code == heif_error_Ok); ++ REQUIRE(x == 20); ++ REQUIRE(y == 50); ++ REQUIRE(width == 8); ++ REQUIRE(height == 2); ++ // Destination bits, row-major over the 8x2 region: ++ // (0,0)=index0 -> 0x80, (7,0)=index7 -> 0x01 => byte0 = 0x81 ++ // (0,1)=index8 -> 0x80 => byte1 = 0x80 ++ // The cropped-away pixels (10,0) and (0,2) must not appear. ++ REQUIRE(mask_data_in[0] == 0x81); ++ REQUIRE(mask_data_in[1] == 0x80); ++ ++ heif_region_release(out_region); ++ heif_image_release(mask_image); ++ heif_region_item_release(region_item); ++ heif_image_handle_release(handle); ++ heif_encoder_release(enc); ++ heif_context_free(ctx); ++ heif_image_release(img); ++} diff -Nru libheif-1.19.8/debian/patches/GHSA-73p7-m7gg-w2jv.patch libheif-1.19.8/debian/patches/GHSA-73p7-m7gg-w2jv.patch --- libheif-1.19.8/debian/patches/GHSA-73p7-m7gg-w2jv.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/GHSA-73p7-m7gg-w2jv.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,52 @@ +Description: GHSA-73p7-m7gg-w2jv: bounds-check unci tile-range extraction before memcpy + The final "cut out the range that we actually need" memcpy() calls in the + generic-compression / uncompressed decode path used attacker-controlled + range_start_offset/range_size with no bounds validation at all, allowing a + read past the end of the decompressed data buffer. Add a subtraction-form + bounds check (avoiding a uint64_t addition overflow) before each memcpy. +Origin: upstream, https://github.com/strukturag/libheif/commit/089a809bf6bed1abae102d5e97b6bb8c4f53b515 +Bug: https://github.com/strukturag/libheif/security/advisories/GHSA-73p7-m7gg-w2jv +Last-Update: 2026-08-06 +Comment: No Bug-Debian field: no CVE has been assigned to this advisory and + it therefore has no entry in the Debian security tracker yet. + . + Upstream's fix is against libheif/codecs/uncompressed/unc_decoder.cc + (function unc_decoder::get_compressed_image_data_uncompressed), which does + not exist at 1.19.8 (created later by the >=1.20 uncompressed codec + reorganization). v1.19.8's equivalent code, + AbstractDecoder::get_compressed_image_data_uncompressed() in + libheif/codecs/uncompressed/decoder_abstract.cc, has no range check at all + before either of its two memcpy() call sites (it predates even the + overflow-prone addition-form check that upstream commit d7a43f98 first + introduced in September 2025, which itself post-dates v1.19.8). This patch + hand-adds the non-overflowing subtraction-form check + (range_start_offset > size || range_size > size - range_start_offset) + immediately before each of the two memcpy() calls, using the same error + code/suberror/message as upstream's fix. + +--- a/libheif/codecs/uncompressed/decoder_abstract.cc ++++ b/libheif/codecs/uncompressed/decoder_abstract.cc +@@ -238,6 +238,11 @@ const Error AbstractDecoder::get_compressed_image_data_uncompressed(const HeifCo + } + + // cut out the range that we actually need ++ if (range_start_offset > data->size() || range_size > data->size() - range_start_offset) { ++ return {heif_error_Invalid_input, ++ heif_suberror_Unspecified, ++ "Data range out of existing range"}; ++ } + memcpy(data->data(), data->data() + range_start_offset, range_size); + data->resize(range_size); + } +@@ -256,6 +261,11 @@ const Error AbstractDecoder::get_compressed_image_data_uncompressed(const HeifCo + } + + // cut out the range that we actually need ++ if (range_start_offset > data->size() || range_size > data->size() - range_start_offset) { ++ return {heif_error_Invalid_input, ++ heif_suberror_Unspecified, ++ "Data range out of existing range"}; ++ } + memcpy(data->data(), data->data() + range_start_offset, range_size); + data->resize(range_size); + } diff -Nru libheif-1.19.8/debian/patches/overlay-simplify-overlap-area-computation.patch libheif-1.19.8/debian/patches/overlay-simplify-overlap-area-computation.patch --- libheif-1.19.8/debian/patches/overlay-simplify-overlap-area-computation.patch 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/overlay-simplify-overlap-area-computation.patch 2026-08-06 10:48:46.000000000 +0000 @@ -0,0 +1,133 @@ +Description: Fix out-of-bounds accesses in HeifPixelImage::overlay() for negative offsets + This patch is upstream commit 85e21ad4, which restructures the + computation so that the right/bottom clip is evaluated in signed 64-bit + arithmetic BEFORE the left/top clip mutates in_w/in_h. After it, + in_w/in_h hold the true visible extent for both signs of dx/dy and all + four accesses above are in bounds. + . + It is also a hard prerequisite for the CVE-2025-68431 one-line fix that + follows it in this series: that fix replaces the memcpy length + `in_w - in_x0` with `in_w`, which is only correct on top of this + reordering. Applied to unreordered 1.19.8 code it would instead turn the + existing over-read into an over-WRITE of in_x0 bytes per row. +Origin: upstream, https://github.com/strukturag/libheif/commit/85e21ad44eba931314337300a2376b8d28f085ae +Last-Update: 2026-08-06 + +diff --git a/libheif/image-items/overlay.cc b/libheif/image-items/overlay.cc +index c010fee8..64d233de 100644 +--- a/libheif/image-items/overlay.cc ++++ b/libheif/image-items/overlay.cc +@@ -38,7 +38,7 @@ void writevec(uint8_t* data, size_t& idx, I value, int len) + + static int32_t readvec_signed(const std::vector& data, int& ptr, int len) + { +- const uint32_t high_bit = 0x80 << ((len - 1) * 8); ++ const uint32_t high_bit = UINT32_C(0x80) << ((len - 1) * 8); + + uint32_t val = 0; + while (len--) { +diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc +index 04e81fe2..7425ea11 100644 +--- a/libheif/pixelimage.cc ++++ b/libheif/pixelimage.cc +@@ -1250,11 +1250,8 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int32_t + uint32_t out_w = get_width(channel); + uint32_t out_h = get_height(channel); + +- // top-left points where to start copying in source and destination +- uint32_t in_x0; +- uint32_t in_y0; +- uint32_t out_x0; +- uint32_t out_y0; ++ ++ // --- check whether overlay image overlaps with current image + + if (dx > 0 && static_cast(dx) >= out_w) { + // the overlay image is completely outside the right border -> skip overlaying +@@ -1265,37 +1262,50 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int32_t + return Error::Ok; + } + +- if (dx < 0) { +- // overlay image started partially outside of left border +- +- in_x0 = negate_negative_int32(dx); +- out_x0 = 0; +- in_w = in_w - in_x0; // in_x0 < in_w because in_w > -dx = in_x0 ++ if (dy > 0 && static_cast(dy) >= out_h) { ++ // the overlay image is completely outside the bottom border -> skip overlaying ++ return Error::Ok; + } +- else { +- in_x0 = 0; +- out_x0 = static_cast(dx); ++ else if (dy < 0 && in_h <= negate_negative_int32(dy)) { ++ // the overlay image is completely outside the top border -> skip overlaying ++ return Error::Ok; + } + +- // we know that dx >= 0 && dx < out_w + +- if (static_cast(dx) > UINT32_MAX - in_w || +- dx + in_w > out_w) { ++ // --- compute overlapping area ++ ++ // top-left points where to start copying in source and destination ++ uint32_t in_x0; ++ uint32_t in_y0; ++ uint32_t out_x0; ++ uint32_t out_y0; ++ ++ // right border ++ if (dx + static_cast(in_w) > out_w) { + // overlay image extends partially outside of right border ++ in_w = static_cast(static_cast(out_w) - dx); ++ } + +- in_w = out_w - static_cast(dx); // we know that dx < out_w from first condition ++ // bottom border ++ if (dy + static_cast(in_h) > out_h) { ++ // overlay image extends partially outside of bottom border ++ in_h = static_cast(static_cast(out_h) - dy); + } + ++ // left border ++ if (dx < 0) { ++ // overlay image starts partially outside of left border + +- if (dy > 0 && static_cast(dy) >= out_h) { +- // the overlay image is completely outside the bottom border -> skip overlaying +- return Error::Ok; ++ in_x0 = negate_negative_int32(dx); ++ out_x0 = 0; ++ in_w = in_w - in_x0; // in_x0 < in_w because in_w > -dx = in_x0 + } +- else if (dy < 0 && in_h <= negate_negative_int32(dy)) { +- // the overlay image is completely outside the top border -> skip overlaying +- return Error::Ok; ++ else { ++ in_x0 = 0; ++ out_x0 = static_cast(dx); + } + ++ // top border + if (dy < 0) { + // overlay image started partially outside of top border + +@@ -1308,15 +1318,7 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int32_t + out_y0 = static_cast(dy); + } + +- // we know that dy >= 0 && dy < out_h +- +- if (static_cast(dy) > UINT32_MAX - in_h || +- dy + in_h > out_h) { +- // overlay image extends partially outside of bottom border +- +- in_h = out_h - static_cast(dy); // we know that dy < out_h from first condition +- } +- ++ // --- computer overlay in overlapping area + + for (uint32_t y = in_y0; y < in_h; y++) { + if (!has_alpha) { diff -Nru libheif-1.19.8/debian/patches/series libheif-1.19.8/debian/patches/series --- libheif-1.19.8/debian/patches/series 1970-01-01 00:00:00.000000000 +0000 +++ libheif-1.19.8/debian/patches/series 2026-08-06 10:45:38.000000000 +0000 @@ -0,0 +1,16 @@ +overlay-simplify-overlap-area-computation.patch +CVE-2025-68431.patch +CVE-2026-32882.patch +CVE-2026-32740.patch +CVE-2026-47247.patch +CVE-2026-32741.patch +CVE-2026-47709-1_uncC-tile-count-overflow.patch +CVE-2026-47709-2_missing-ispe-null-deref.patch +CVE-2026-49271.patch +GHSA-73p7-m7gg-w2jv.patch +CVE-2026-47178.patch +CVE-2026-47714.patch +GHSA-5hqq-636x-r3cr.patch +CVE-2026-48029.patch +CVE-2026-GHSA-2h34-fcv6-jqvh-tili-offset-oob-read.patch +CVE-2026-62289-clap-zero-size-tiling-underflow.patch