GHSA-p8hx-v2p6-rp84 BIT-pillow-2026-96636 CVE-2026-96636 PYSEC-2026-4337
Pillow: Decompression-bomb protection bypass via multi-frame seek() in MPO and DCX (MpoImageFile.seek / DcxImageFile.seek)
- Severity
- 6.5 / 10
- Medium · CVSS 3.1
- Exploit chance (EPSS)
- 0.34%
- Chance of exploitation in the next 30 days. Higher than 28.02% of scored vulnerabilities.
- Known exploited
- No
- Not in CISA's catalog
- Weakness
- CWE-400 Uncontrolled Resource Consumption
- CWE-409 Improper Handling of Highly Compressed Data (Data Amplification)
- Fix
13.0.0- Upgrade pillow to this version or later
Description
Summary
Pillow's decompression-bomb protection (Image._decompression_bomb_check, gated by Image.MAX_IMAGE_PIXELS) is enforced only on the first frame, inside Image.open(). For multi-frame formats, seek() re-parses the target frame's header and resets self._size. GifImagePlugin and TiffImagePlugin re-run the bomb check on seek(), but MpoImageFile.seek() and DcxImageFile.seek() do not. A crafted MPO or DCX whose first frame is tiny (passing the check) but whose later frame declares enormous dimensions therefore bypasses the protection: loading that frame decodes an over-limit image and commits large amounts of memory — a denial of service (memory exhaustion / OOM).
Same class as the recently assigned CVE-2026-55379 (BdfFontFile), CVE-2026-55380 (GdImageFile) and CVE-2026-40192 (FITS GZIP).
Details
Image.open() → _open_core() runs _decompression_bomb_check(im.size) once, on the frame present at open time. Both vulnerable seek() methods re-parse a new frame and set a new self._size without re-checking:
PIL/MpoImagePlugin.py,MpoImageFile.seek(): callsJpegImagePlugin.JpegImageFile._open(self)(which setsself._sizefrom the frame's JPEG SOF) and rebuildsself.tile, with noImage._decompression_bomb_check(self.size).PIL/DcxImagePlugin.py,DcxImageFile.seek(): callsPcxImageFile._open(self)(which setsself._sizefrom the page's PCX header), with no check.
Confirmed present in 12.3.0 (latest release) and on main (13.0.0.dev0). Compare GifImagePlugin._seek and TiffImagePlugin, which call _decompression_bomb_check correctly on seek.
PoC
Self-contained; the victim uses default MAX_IMAGE_PIXELS:
import io
from PIL import Image
# attacker builds the file (guard disabled only to CREATE the oversized frame)
Image.MAX_IMAGE_PIXELS = None
small = Image.new("RGB", (8, 8))
big = Image.new("RGB", (14000, 14000), (120, 60, 30)) # 196,000,000 px > error threshold
buf = io.BytesIO()
small.save(buf, format="MPO", save_all=True, append_images=[big])
data = buf.getvalue() # ~3 MB
Image.MAX_IMAGE_PIXELS = int(1024 * 1024 * 1024 // 4 // 3) # restore default
# victim (default limits)
im = Image.open(io.BytesIO(data)) # frame 0 (8x8) passes the bomb check
im.seek(1) # frame 1 (14000x14000) -> NOT re-checked <-- bug
im.load() # decodes the over-limit image, commits memory
print("loaded over-limit frame:", im.size)
Measured on Pillow 12.3.0 (default limit 89,478,485 px; error threshold 178,956,970 px):
| Vector | File size | Frame 1 | Peak RSS |
|---|---|---|---|
| MPO (JPEG) | ~3.0 MB | 14000×14000 (196 Mpx) | ~763 MB |
| DCX (PCX) | ~6.2 MB | 14000×14000 (196 Mpx) | ~200 MB |
| Control: same image standalone | — | 196 Mpx | DecompressionBombError (correctly blocked) |
The DCX vector (0x3ADE68B1 magic + offset directory + two PCX pages) behaves identically via DcxImageFile.seek(). Scaling frame 1 toward JPEG's ~65500×65500 maximum drives multi-GB commitments from a small file, reliably OOM-killing the process.
Impact
Denial of service (memory exhaustion / OOM) — CWE-400 / CWE-1284, a bypass of Pillow's documented decompression-bomb mitigation. seek() + load() is the normal way to iterate frames (e.g. PIL.ImageSequence.Iterator, thumbnailers, converters, 3D-photo handlers). Because MPO is auto-detected from a JPEG carrying MPF metadata, a file served with a .jpg extension can be opened as MPO, so any application that iterates frames of untrusted images is exposed.
Suggested fix
Re-run the check after seek() re-parses a frame, mirroring GIF/TIFF:
# PIL/MpoImagePlugin.py — end of MpoImageFile.seek()
self.tile = [ImageFile._Tile("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])]
Image._decompression_bomb_check(self.size) # add
self.__frame = frame
# PIL/DcxImagePlugin.py — end of DcxImageFile.seek()
PcxImageFile._open(self)
Image._decompression_bomb_check(self.size) # add
Affected versions
PyPI
| Package | Vulnerable | Fixed in |
|---|---|---|
| pillow | < 13.0.0 |
13.0.0 |
How to fix
Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.
pillow PyPI → 13.0.0
- pip
pip install --upgrade "pillow>=13.0.0"- poetry
poetry add "pillow>=13.0.0"- uv
uv add "pillow>=13.0.0"
Severity breakdown
CVSS 3.1 6.5
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H
- Attack vector
- Network
- Attack complexity
- Low
- Privileges required
- None
- User interaction
- Required
- Scope
- Unchanged
- Confidentiality
- None
- Integrity
- None
- Availability
- High
Weakness
CWE-400: Uncontrolled Resource Consumption
The product does not properly control the allocation and maintenance of a limited resource.
How to prevent it
- Architecture and Design: Design throttling mechanisms into the system architecture. The best protection is to limit the amount of resources that an unauthorized user can cause to be expended. A strong authentication and access control model will help prevent such attacks from occurring in the first place. The login application should be protected against DoS attacks as much as possible. Limiting the database access, perhaps by caching result sets, can help minimize the resources expended. To further limit the potential for a DoS attack, consider tracking the rate of requests received from users and blocking requests that exceed a defined rate threshold.
- Architecture and Design: Mitigation of resource exhaustion attacks requires that the target system either: recognizes the attack and denies that user further access for a given amount of time, or uniformly throttles all requests in order to make it more difficult to consume resources more quickly than they can again be freed. The first of these solutions is an issue in itself though, since it may allow attackers to prevent the use of the system by a particular valid user. If the attacker impersonates the valid user, they may be able to prevent the user from accessing the server in question. The second solution is simply difficult to effectively institute -- and even when properly done, it does not provide a full solution. It simply makes the attack require more resources on the part of the attacker.
- Architecture and Design: Ensure that protocols have specific limits of scale placed on them.
- Implementation: Ensure that all failures in resource allocation place the system into a safe posture.
CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)
The product does not handle or incorrectly handles a compressed input with a very high compression ratio that produces a large output.
References
Advisories
Packages
Other links
Timeline
- Published
- Last updated by the source
- Latest EPSS score
Related advisories
| Advisory | Severity | Why it's related |
|---|---|---|
| PYSEC-2023-175 Pillow versions before v10.0.1 bundled libwebp binaries in wheels that are vulnerable to CVE-2023-5129 (previously CVE-2023-4863). | High Known exploited | Also affects pillow |
| GHSA-j7hp-h8jx-5ppr libwebp: OOB write in BuildHuffmanTable | High Known exploited | Also affects pillow |
| GHSA-78g4-xqwv-q3g8 Pillow: Bundled libopenjp2 2.5.4 in all wheels since 11.1.0 carries OSV-2025-219 heap-buffer-overflow write reachable from Image.open().load() | High | Also affects pillow |
| GHSA-654x-cwwv-5j9c Pillow: Arrow export leaks stale heap memory when PILLOW_ALIGNMENT > 1 | Medium | Also affects pillow |
| GHSA-xvh5-g6gv-559v Pillow: Memory disclosure and segfault in libtiff encoder via crafted TIFF tags | High | Also affects pillow |
| GHSA-g7cr-3p24-7wf7 Pillow: Heap buffer overflow writing JPEG comment after ICC profile fills buffer | High | Also affects pillow |
| GHSA-9v9v-9qvf-h2hj Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds) | High | Also affects pillow |
| GHSA-g4qv-2jxx-769h Pillow: PNG eXIf chunk makes EXIF parsing run in quadratic time | Medium | Also affects pillow |
MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.
Check your repos with MergeHarbor