Control: tags 1138575 + patch
Control: tags 1138575 + pending
Control: tags 1142476 + patch
Control: tags 1142476 + pending

Dear maintainer,

I've prepared an NMU for jpeg-xl (versioned as 0.11.2-5.1) and uploaded 
it to DELAYED/7. Please feel free to tell me if I should cancel it.

cu
Adrian
diffstat for jpeg-xl-0.11.2 jpeg-xl-0.11.2

 changelog                                                     |   11 +
 patches/0001-Fix-possible-overflow-in-APNG-decoder-4870.patch |   52 ++++
 patches/0002-Further-undisable-tests-4041.patch               |   29 ++
 patches/CVE-2025-70103.patch                                  |  107 ++++++++++
 patches/series                                                |    3 
 5 files changed, 202 insertions(+)

diff -Nru jpeg-xl-0.11.2/debian/changelog jpeg-xl-0.11.2/debian/changelog
--- jpeg-xl-0.11.2/debian/changelog	2026-05-26 06:19:04.000000000 +0300
+++ jpeg-xl-0.11.2/debian/changelog	2026-08-13 16:09:49.000000000 +0300
@@ -1,3 +1,14 @@
+jpeg-xl (0.11.2-5.1) unstable; urgency=medium
+
+  * Non-maintainer upload.
+  * CVE-2025-70103: Buffer overflow in PNM decoder
+    (Closes: #1138575)
+  * CVE-2026-52584: Buffer overflow in APNG decoder
+    (Closes: #1142476)
+  * Backport upstream fix for FTBFS on x32.
+
+ -- Adrian Bunk <[email protected]>  Thu, 13 Aug 2026 16:09:49 +0300
+
 jpeg-xl (0.11.2-5) unstable; urgency=medium
 
   * Team upload.
diff -Nru jpeg-xl-0.11.2/debian/patches/0001-Fix-possible-overflow-in-APNG-decoder-4870.patch jpeg-xl-0.11.2/debian/patches/0001-Fix-possible-overflow-in-APNG-decoder-4870.patch
--- jpeg-xl-0.11.2/debian/patches/0001-Fix-possible-overflow-in-APNG-decoder-4870.patch	1970-01-01 02:00:00.000000000 +0200
+++ jpeg-xl-0.11.2/debian/patches/0001-Fix-possible-overflow-in-APNG-decoder-4870.patch	2026-08-13 16:09:49.000000000 +0300
@@ -0,0 +1,52 @@
+From 2ded3a46db6cf1e60c673181d54276f702074020 Mon Sep 17 00:00:00 2001
+From: Eugene Kliuchnikov <[email protected]>
+Date: Fri, 26 Jun 2026 11:24:49 +0200
+Subject: Fix possible overflow in APNG decoder (#4870)
+
+Fixes #4803.
+
+Root reason: copy-paste in RectT::IsSame
+Drive-by: use a.IsInside(b) instead of a.Intersect(b).IsSame(a)
+Drive-by: harden ysize check before copying
+---
+ lib/extras/dec/apng.cc | 3 ++-
+ lib/jxl/base/rect.h    | 2 +-
+ 2 files changed, 3 insertions(+), 2 deletions(-)
+
+diff --git a/lib/extras/dec/apng.cc b/lib/extras/dec/apng.cc
+index 24b4795d..a8c5c5f3 100644
+--- a/lib/extras/dec/apng.cc
++++ b/lib/extras/dec/apng.cc
+@@ -878,6 +878,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
+     const RectT<uint64_t>& vp = current_frame.viewport;
+     size_t xsize = static_cast<size_t>(vp.xsize());
+     size_t ysize = static_cast<size_t>(vp.ysize());
++    JXL_ENSURE(ctx.frameRaw.rows.size() <= ysize);
+     JXL_ASSIGN_OR_RETURN(PackedImage image,
+                          PackedImage::Create(xsize, ysize, format));
+     for (size_t y = 0; y < ysize; ++y) {
+@@ -962,7 +963,7 @@ Status DecodeImageAPNG(const Span<const uint8_t> bytes,
+             /*delay_den=*/png_get_uint_16(payload.data() + 22), raw_viewport,
+             static_cast<DisposeOp>(dispose_op), static_cast<BlendOp>(blend_op)};
+ 
+-        if (!raw_viewport.Intersection(image_rect).IsSame(raw_viewport)) {
++        if (!raw_viewport.IsInside(image_rect)) {
+           // Cropping happened.
+           return JXL_FAILURE("PNG frame is outside of image rect");
+         }
+diff --git a/lib/jxl/base/rect.h b/lib/jxl/base/rect.h
+index 31fe12df..3f6632ed 100644
+--- a/lib/jxl/base/rect.h
++++ b/lib/jxl/base/rect.h
+@@ -109,7 +109,7 @@ class RectT {
+ 
+   bool IsSame(const RectT& other) const {
+     return x0_ == other.x0_ && xsize_ == other.xsize_ && y0_ == other.y0_ &&
+-           ysize_ <= other.ysize_;
++           ysize_ == other.ysize_;
+   }
+ 
+   // Returns true if this Rect fully resides in the given image. ImageT could be
+-- 
+2.47.3
+
diff -Nru jpeg-xl-0.11.2/debian/patches/0002-Further-undisable-tests-4041.patch jpeg-xl-0.11.2/debian/patches/0002-Further-undisable-tests-4041.patch
--- jpeg-xl-0.11.2/debian/patches/0002-Further-undisable-tests-4041.patch	1970-01-01 02:00:00.000000000 +0200
+++ jpeg-xl-0.11.2/debian/patches/0002-Further-undisable-tests-4041.patch	2026-08-13 16:09:49.000000000 +0300
@@ -0,0 +1,29 @@
+From a1fa160e1677ea214667bac3c155b4ce6989fa90 Mon Sep 17 00:00:00 2001
+From: Eugene Kliuchnikov <[email protected]>
+Date: Tue, 7 Jan 2025 10:24:06 +0100
+Subject: Further undisable tests (#4041)
+
+---
+ lib/jxl/jxl_test.cc | 6 +++++-
+ 1 file changed, 5 insertions(+), 1 deletion(-)
+
+diff --git a/lib/jxl/jxl_test.cc b/lib/jxl/jxl_test.cc
+index ddf98427..e8ae20b2 100644
+--- a/lib/jxl/jxl_test.cc
++++ b/lib/jxl/jxl_test.cc
+@@ -391,7 +391,11 @@ TEST(JxlTest, RoundtripLargeFast) {
+ JXL_X86_64_TEST(JxlTest, RoundtripLargeEmptyModular) {
+   ThreadPoolForTests pool(8);
+   TestImage t;
+-  ASSERT_TRUE(t.SetDimensions(4096, 4096));
++  // On 32-bit machines memory could be limited to 2GiB. Current use factor
++  // is about 20x, that means hat 6MPx image is about the max available size.
++  constexpr size_t kDim = sizeof(size_t) == 4 ? 2400 : 4096;
++  const size_t kLim = std::min<size_t>(kDim, 1024);
++  ASSERT_TRUE(t.SetDimensions(kDim, kDim));
+   t.SetDataType(JXL_TYPE_UINT8);
+   ASSERT_TRUE(t.SetChannels(4));
+   JXL_TEST_ASSIGN_OR_DIE(auto frame, t.AddFrame());
+-- 
+2.47.3
+
diff -Nru jpeg-xl-0.11.2/debian/patches/CVE-2025-70103.patch jpeg-xl-0.11.2/debian/patches/CVE-2025-70103.patch
--- jpeg-xl-0.11.2/debian/patches/CVE-2025-70103.patch	1970-01-01 02:00:00.000000000 +0200
+++ jpeg-xl-0.11.2/debian/patches/CVE-2025-70103.patch	2026-08-13 16:09:49.000000000 +0300
@@ -0,0 +1,107 @@
+From 49fb89f23473e57fa1dac416adce7c7679e5d051 Mon Sep 17 00:00:00 2001
+From: Eugene Kliuchnikov <[email protected]>
+Date: Fri, 8 Aug 2025 12:29:08 +0200
+Subject: [PATCH] Take EC into accound when checking required PNM inmput length
+ (#4380)
+
+--- jpeg-xl-0.11.2.orig/lib/extras/dec/pnm.cc
++++ jpeg-xl-0.11.2/lib/extras/dec/pnm.cc
+@@ -497,13 +497,26 @@ Status DecodeImagePNM(const Span<const u
+     }
+   }
+ 
++  // No align - pixels are tightly packed.
++  constexpr size_t kAlign = 0;
++  size_t twidth = PackedImage::BitsPerChannel(data_type) / 8;
+   const JxlPixelFormat format{
+       /*num_channels=*/num_interleaved_channels,
+       /*data_type=*/data_type,
+       /*endianness=*/header.big_endian ? JXL_BIG_ENDIAN : JXL_LITTLE_ENDIAN,
+-      /*align=*/0,
++      kAlign,
+   };
+-  const JxlPixelFormat ec_format{1, format.data_type, format.endianness, 0};
++  // EC format is same as color, but 1-channel.
++  JxlPixelFormat ec_format = format;
++  ec_format.num_channels = 1;
++  size_t required_pnm_size =
++      header.ysize * header.xsize *
++      (num_interleaved_channels + header.ec_types.size()) * twidth;
++  size_t pnm_remaining_size = bytes.data() + bytes.size() - pos;
++  if (pnm_remaining_size < required_pnm_size) {
++    return JXL_FAILURE("PNM file too small");
++  }
++
+   ppf->frames.clear();
+   {
+     JXL_ASSIGN_OR_RETURN(
+@@ -512,42 +525,47 @@ Status DecodeImagePNM(const Span<const u
+     ppf->frames.emplace_back(std::move(frame));
+   }
+   auto* frame = &ppf->frames.back();
++  uint8_t* out = reinterpret_cast<uint8_t*>(frame->color.pixels());
++  std::vector<uint8_t*> ec_out;
+   for (size_t i = 0; i < header.ec_types.size(); ++i) {
+     JXL_ASSIGN_OR_RETURN(
+         PackedImage ec,
+         PackedImage::Create(header.xsize, header.ysize, ec_format));
+     frame->extra_channels.emplace_back(std::move(ec));
++    ec_out.emplace_back(
++        reinterpret_cast<uint8_t*>(frame->extra_channels.back().pixels()));
++    JXL_DASSERT(frame->extra_channels.back().stride == header.xsize * twidth);
+   }
+-  size_t pnm_remaining_size = bytes.data() + bytes.size() - pos;
+-  if (pnm_remaining_size < frame->color.pixels_size) {
+-    return JXL_FAILURE("PNM file too small");
+-  }
+-
+-  uint8_t* out = reinterpret_cast<uint8_t*>(frame->color.pixels());
+-  std::vector<uint8_t*> ec_out(header.ec_types.size());
+-  for (size_t i = 0; i < ec_out.size(); ++i) {
+-    ec_out[i] = reinterpret_cast<uint8_t*>(frame->extra_channels[i].pixels());
+-  }
++  JXL_DASSERT(frame->color.stride ==
++              header.xsize * num_interleaved_channels * twidth);
+   if (ec_out.empty()) {
+-    const bool flipped_y = header.bits_per_sample == 32;  // PFMs are flipped
+-    for (size_t y = 0; y < header.ysize; ++y) {
+-      size_t y_in = flipped_y ? header.ysize - 1 - y : y;
+-      const uint8_t* row_in = &pos[y_in * frame->color.stride];
+-      uint8_t* row_out = &out[y * frame->color.stride];
+-      memcpy(row_out, row_in, frame->color.stride);
++    const bool flipped_y = (header.bits_per_sample == 32);  // PFMs are flipped
++    if (!flipped_y) {
++    // When there are no EC and input is not flipped we can copy the whole
++    // image at once.
++      memcpy(out, pos, header.ysize * frame->color.stride);
++    } else {
++      // Otherwise copy row-by-row.
++      for (size_t y = 0; y < header.ysize; ++y) {
++        size_t y_out = header.ysize - 1 - y;
++        const uint8_t* row_in = pos + y * frame->color.stride;
++        uint8_t* row_out = out + y_out * frame->color.stride;
++        memcpy(row_out, row_in, frame->color.stride);
++      }
+     }
+   } else {
++    // In case there are EC, we have to deinterleave data pixel-wise.
+     JXL_RETURN_IF_ERROR(PackedImage::ValidateDataType(data_type));
+-    size_t pwidth = PackedImage::BitsPerChannel(data_type) / 8;
++    size_t color_stride = twidth * num_interleaved_channels;
+     for (size_t y = 0; y < header.ysize; ++y) {
+       for (size_t x = 0; x < header.xsize; ++x) {
+         memcpy(out, pos, frame->color.pixel_stride());
+-        out += frame->color.pixel_stride();
+-        pos += frame->color.pixel_stride();
++        out += color_stride;
++        pos += color_stride;
+         for (auto& p : ec_out) {
+-          memcpy(p, pos, pwidth);
+-          pos += pwidth;
+-          p += pwidth;
++          memcpy(p, pos, twidth);
++          pos += twidth;
++          p += twidth;
+         }
+       }
+     }
diff -Nru jpeg-xl-0.11.2/debian/patches/series jpeg-xl-0.11.2/debian/patches/series
--- jpeg-xl-0.11.2/debian/patches/series	2026-05-12 10:00:29.000000000 +0300
+++ jpeg-xl-0.11.2/debian/patches/series	2026-08-13 16:09:49.000000000 +0300
@@ -4,3 +4,6 @@
 Fix-modular-on-big-endian-machines-4095.patch
 0001-Fix-test-on-loong64-4654.patch
 disable_test.patch
+CVE-2025-70103.patch
+0001-Fix-possible-overflow-in-APNG-decoder-4870.patch
+0002-Further-undisable-tests-4041.patch

Reply via email to