From 5220c53594387c4fc6934ed95d1168eb751c4dd7 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 25 Aug 2026 13:25:41 +0500 Subject: [PATCH 1/3] Avoid integer overflow when calculating reduced image size --- Tests/test_image_reduce.py | 15 +++++++++++++++ src/libImaging/Reduce.c | 2 +- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index 6771b46b051..cb69a880ebf 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -53,6 +53,21 @@ def test_args_factor(size: int | tuple[int, int], expected: tuple[int, int]) -> assert expected == im.reduce(size).size +@pytest.mark.parametrize( + "size, expected", + ( + (2**31 - 1, (1, 1)), + ((2**31 - 1, 1), (1, 10)), + ((1, 2**31 - 1), (10, 1)), + ), +) +def test_args_factor_large( + size: int | tuple[int, int], expected: tuple[int, int] +) -> None: + im = Image.new("L", (10, 10)) + assert expected == im.reduce(size).size + + @pytest.mark.parametrize( "size, expected_error", ((0, ValueError), (2.0, TypeError), ((0, 10), ValueError)) ) diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index a4e58ced81b..0c46d86fa7a 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -1461,7 +1461,7 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) { } imOut = ImagingNewDirty( - imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale + imIn->mode, (box[2] - 1) / xscale + 1, (box[3] - 1) / yscale + 1 ); if (!imOut) { return NULL; From bf60f3c865d120619fc942e050f01f380b4dab92 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 25 Aug 2026 13:51:42 +0500 Subject: [PATCH 2/3] Explain how the reduced size is rounded up --- src/libImaging/Reduce.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index 0c46d86fa7a..c4cb4bc76be 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -1460,6 +1460,9 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) { return (Imaging)ImagingError_ModeError(); } + /* Round the size up. Dividing before adding avoids overflowing for a + large scale, and guarantees a size of at least 1x1. + */ imOut = ImagingNewDirty( imIn->mode, (box[2] - 1) / xscale + 1, (box[3] - 1) / yscale + 1 ); From 4d40d5d53fe2340df3cac8a3ebc4b53ca442d283 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 25 Aug 2026 13:51:42 +0500 Subject: [PATCH 3/3] Test reducing I and F images by a large factor --- Tests/test_image_reduce.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index cb69a880ebf..1f28f330e77 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -53,6 +53,7 @@ def test_args_factor(size: int | tuple[int, int], expected: tuple[int, int]) -> assert expected == im.reduce(size).size +@pytest.mark.parametrize("mode", ("L", "I", "F")) @pytest.mark.parametrize( "size, expected", ( @@ -62,10 +63,10 @@ def test_args_factor(size: int | tuple[int, int], expected: tuple[int, int]) -> ), ) def test_args_factor_large( - size: int | tuple[int, int], expected: tuple[int, int] + size: int | tuple[int, int], expected: tuple[int, int], mode: str ) -> None: - im = Image.new("L", (10, 10)) - assert expected == im.reduce(size).size + im = Image.new(mode, (10, 10)) + assert im.reduce(size).size == expected @pytest.mark.parametrize(