From cd5b9b4c7d45c600d662c26dd47777544f2ee2cf Mon Sep 17 00:00:00 2001 From: patrick-immometrica Date: Sun, 15 Nov 2026 15:34:16 +0100 Subject: [PATCH 1/2] Prevent integer overflow when reducing I images --- Tests/test_image_reduce.py | 7 +++++++ src/libImaging/Reduce.c | 8 ++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index ad2700090cc..9a51d9eb542 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -305,6 +305,13 @@ def test_mode_I(factor: int | tuple[int, int]) -> None: compare_reduce_with_box(im, factor) +@pytest.mark.parametrize("value", (2**31 - 1, -(2**31))) +def test_mode_I_extreme(value: int) -> None: + im = Image.new("I", (5, 5), value) + for factor in (2, 3, (2, 3)): + assert im.reduce(factor).getpixel((0, 0)) == value + + @pytest.mark.parametrize("factor", remarkable_factors) def test_mode_F(factor: int | tuple[int, int]) -> None: im = get_image("F") diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index 16593005757..271d25de02d 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -1290,17 +1290,17 @@ ImagingReduceNxN_32bpc( INT32 *line0 = (INT32 *)imIn->image32[yy]; INT32 *line1 = (INT32 *)imIn->image32[yy + 1]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line0[xx + 0] + line0[xx + 1] + line1[xx + 0] + - line1[xx + 1]; + ss += (double)line0[xx + 0] + line0[xx + 1] + + line1[xx + 0] + line1[xx + 1]; } if (xscale & 0x01) { - ss += line0[xx + 0] + line1[xx + 0]; + ss += (double)line0[xx + 0] + line1[xx + 0]; } } if (yscale & 0x01) { INT32 *line = (INT32 *)imIn->image32[yy]; for (xx = xx_from; xx < xx_from + xscale - 1; xx += 2) { - ss += line[xx + 0] + line[xx + 1]; + ss += (double)line[xx + 0] + line[xx + 1]; } if (xscale & 0x01) { ss += line[xx + 0]; From 36358f642388b94e37613c682ec7dadc2b3969e7 Mon Sep 17 00:00:00 2001 From: patrick-immometrica Date: Sun, 15 Nov 2026 15:40:36 +0100 Subject: [PATCH 2/2] Added release notes --- docs/releasenotes/13.1.0.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/releasenotes/13.1.0.rst b/docs/releasenotes/13.1.0.rst index c459f51f873..93921f15f98 100644 --- a/docs/releasenotes/13.1.0.rst +++ b/docs/releasenotes/13.1.0.rst @@ -138,3 +138,10 @@ not bundled into the wheels, so the system zlib was used at runtime instead, and Pillow 13.0.0 also failed to import on systems where ``libz.so.1`` was not on the default library search path, such as NixOS. Linux wheels no longer require a system zlib, and gain the speed of zlib-ng as macOS and Windows wheels already did. + +Reducing I images with large values +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:py:meth:`~PIL.Image.Image.reduce` could overflow when adding up pixels of I mode +images with values beyond about ±2\ :sup:`29`, giving wrong results. This has been +fixed.