From 5312ee4f0e1bdca7ce4a5c133a14a42692d4cc5b Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 25 Aug 2026 13:05:00 +0500 Subject: [PATCH 1/3] Reject NaN and out-of-range blur radii --- Tests/test_image_filter.py | 9 +++++++++ src/libImaging/BoxBlur.c | 5 ++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 4fa24e2f9c5..e2277f8ec19 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -235,6 +235,15 @@ def test_invalid_box_blur_filter(radius: int | tuple[int, int]) -> None: im.filter(box_blur_filter) +@pytest.mark.parametrize("radius", (float("nan"), float("inf"), 2**31)) +def test_out_of_range_blur_filter_radius(radius: float) -> None: + im = hopper() + with pytest.raises(ValueError): + im.filter(ImageFilter.BoxBlur(radius)) + with pytest.raises(ValueError): + im.filter(ImageFilter.GaussianBlur(radius)) + + def test_rankfilter_size_1() -> None: im = Image.new("L", (3, 3), 128) diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index 4fea4fe44b8..cadc0f89cf4 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -244,9 +244,12 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) if (n < 1) { return ImagingError_ValueError("number of passes must be greater than zero"); } - if (xradius < 0 || yradius < 0) { + if (!(xradius >= 0) || !(yradius >= 0)) { return ImagingError_ValueError("radius must be >= 0"); } + if (xradius >= INT_MAX || yradius >= INT_MAX) { + return ImagingError_ValueError("radius is too large"); + } if (imIn->mode != imOut->mode || imIn->type != imOut->type || imIn->bands != imOut->bands || imIn->xsize != imOut->xsize || From ed5a26fb0caa987f95b539494ae6b26fc5f6c551 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Tue, 25 Aug 2026 13:13:37 +0500 Subject: [PATCH 2/3] Avoid comparing a float radius against INT_MAX --- Tests/test_image_filter.py | 4 ++-- src/libImaging/BoxBlur.c | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index e2277f8ec19..731d7b3ae4a 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -238,9 +238,9 @@ def test_invalid_box_blur_filter(radius: int | tuple[int, int]) -> None: @pytest.mark.parametrize("radius", (float("nan"), float("inf"), 2**31)) def test_out_of_range_blur_filter_radius(radius: float) -> None: im = hopper() - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="radius"): im.filter(ImageFilter.BoxBlur(radius)) - with pytest.raises(ValueError): + with pytest.raises(ValueError, match="radius"): im.filter(ImageFilter.GaussianBlur(radius)) diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index cadc0f89cf4..f1dea723ea1 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -244,11 +244,13 @@ ImagingBoxBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int n) if (n < 1) { return ImagingError_ValueError("number of passes must be greater than zero"); } + /* Negated comparisons, so that NaN is rejected as well. */ if (!(xradius >= 0) || !(yradius >= 0)) { return ImagingError_ValueError("radius must be >= 0"); } - if (xradius >= INT_MAX || yradius >= INT_MAX) { - return ImagingError_ValueError("radius is too large"); + /* 2**31 and above cannot be converted to an int. */ + if (xradius >= 2147483648.0f || yradius >= 2147483648.0f) { + return ImagingError_ValueError("radius too large"); } if (imIn->mode != imOut->mode || imIn->type != imOut->type || From ac5f69b73c6086d2dc0865389d5afed322c73dc1 Mon Sep 17 00:00:00 2001 From: Lazizbek Ergashev Date: Sun, 30 Aug 2026 13:22:50 +0500 Subject: [PATCH 3/3] Avoid signed overflow for large blur radii --- Tests/test_image_filter.py | 10 ++++++++++ src/libImaging/BoxBlur.c | 22 +++++++++++----------- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 731d7b3ae4a..e0124dd6f55 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -244,6 +244,16 @@ def test_out_of_range_blur_filter_radius(radius: float) -> None: im.filter(ImageFilter.GaussianBlur(radius)) +@pytest.mark.parametrize("radius", (2**24, 2**30)) +def test_large_blur_filter_radius(radius: int) -> None: + # A radius this large overflows the accumulators unless they stay unsigned + im = Image.new("L", (3, 3), 128) + assert im.filter(ImageFilter.BoxBlur(radius)).getpixel((1, 1)) == 128 + + im = Image.new("RGB", (3, 3), (128, 128, 128)) + assert im.filter(ImageFilter.BoxBlur(radius)).getpixel((1, 1)) == (128, 128, 128) + + def test_rankfilter_size_1() -> None: im = Image.new("L", (3, 3), 128) diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index f1dea723ea1..1d70c6ee8c3 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -40,10 +40,10 @@ void static inline ImagingLineBoxBlur32( /* Compute acc for -1 pixel (outside of image): From "-radius-1" to "-1" get first pixel, then from "0" to "radius-1". */ - acc[0] = lineIn[0][0] * (radius + 1); - acc[1] = lineIn[0][1] * (radius + 1); - acc[2] = lineIn[0][2] * (radius + 1); - acc[3] = lineIn[0][3] * (radius + 1); + acc[0] = lineIn[0][0] * (UINT32)(radius + 1); + acc[1] = lineIn[0][1] * (UINT32)(radius + 1); + acc[2] = lineIn[0][2] * (UINT32)(radius + 1); + acc[3] = lineIn[0][3] * (UINT32)(radius + 1); /* As radius can be bigger than xsize, iterate to edgeA -1. */ for (x = 0; x < edgeA - 1; x++) { acc[0] += lineIn[x][0]; @@ -52,10 +52,10 @@ void static inline ImagingLineBoxBlur32( acc[3] += lineIn[x][3]; } /* Then multiply remainder to last x. */ - acc[0] += lineIn[lastx][0] * (radius - edgeA + 1); - acc[1] += lineIn[lastx][1] * (radius - edgeA + 1); - acc[2] += lineIn[lastx][2] * (radius - edgeA + 1); - acc[3] += lineIn[lastx][3] * (radius - edgeA + 1); + acc[0] += lineIn[lastx][0] * (UINT32)(radius - edgeA + 1); + acc[1] += lineIn[lastx][1] * (UINT32)(radius - edgeA + 1); + acc[2] += lineIn[lastx][2] * (UINT32)(radius - edgeA + 1); + acc[3] += lineIn[lastx][3] * (UINT32)(radius - edgeA + 1); if (edgeA <= edgeB) { /* Subtract pixel from left ("0"). @@ -123,11 +123,11 @@ void static inline ImagingLineBoxBlur8( #define SAVE(x, bulk) lineOut[x] = (UINT8)((bulk + (1 << 23)) >> 24) - acc = lineIn[0] * (radius + 1); + acc = lineIn[0] * (UINT32)(radius + 1); for (x = 0; x < edgeA - 1; x++) { acc += lineIn[x]; } - acc += lineIn[lastx] * (radius - edgeA + 1); + acc += lineIn[lastx] * (UINT32)(radius - edgeA + 1); if (edgeA <= edgeB) { for (x = 0; x < edgeA; x++) { @@ -176,7 +176,7 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) { int radius = (int)floatRadius; UINT32 ww = (UINT32)(1 << 24) / (floatRadius * 2 + 1); - UINT32 fw = ((1 << 24) - (radius * 2 + 1) * ww) / 2; + UINT32 fw = ((1 << 24) - ((UINT32)radius * 2 + 1) * ww) / 2; int edgeA = MIN(radius + 1, imIn->xsize); int edgeB = MAX(imIn->xsize - radius - 1, 0);