Описание
Pillow: Heap out-of-bounds write in ImageFilter.RankFilter via integer overflow in ImagingExpand
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:
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:
The expand() call is made before image.rankfilter(...).
Current src/libImaging/Filter.c:ImagingExpand() does not check output-size
overflow:
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:
src/libImaging/RankFilter.c does contain checks that would reject this size:
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:
Observed on local Pillow 12.3.0.dev0 ASAN target:
4-byte write variant with source pixel loaded from normal image bytes:
Observed ASAN signature:
Version checks:
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:
Ссылки
- https://github.com/python-pillow/Pillow/security/advisories/GHSA-xj96-63gp-2gmr
- https://nvd.nist.gov/vuln/detail/CVE-2026-59197
- https://github.com/python-pillow/Pillow/pull/9695
- https://github.com/python-pillow/Pillow/commit/cce3bdb867c77a3420261ed1bfdb6b0787ec8fc1
- https://github.com/python-pillow/Pillow/releases/tag/12.3.0
Пакеты
Pillow
< 12.3.0
12.3.0
Связанные уязвимости
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.
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.
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.
Pillow is a Python imaging library. Prior to 12.3.0, Pillow's public r ...