Pillow: Decompression-bomb protection bypass via multi-frame seek() in MPO and DCX (MpoImageFile.seek / DcxImageFile.seek)
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
Software afectado
Haz clic en una tecnología para ver todas sus vulnerabilidades.
| Plataforma | Paquete | Versiones vulnerables | Corregida en |
|---|---|---|---|
| Python (PyPI) | pillow | < 13.0.0 | 13.0.0 |
Referencias
- https://github.com/python-pillow/Pillow/security/advisories/GHSA-p8hx-v2p6-rp84
- https://nvd.nist.gov/vuln/detail/CVE-2026-96636
- https://github.com/python-pillow/Pillow/pull/10011
- https://github.com/python-pillow/Pillow/commit/f1acfc54c53502948d0d14121872061df931db48
- https://github.com/python-pillow/Pillow
- https://github.com/python-pillow/Pillow/releases/tag/13.0.0
- Vector CVSS
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H- Debilidad (CWE)
CWE-400, CWE-409- Plataforma
- Python (PyPI)
- Paquete
pillow- Publicada
- 22.10.2026
- Actualizado
- 22.10.2026 19:17
Fuentes y licencias
OSV / GitHub Advisory Database - GHSA-p8hx-v2p6-rp84 - CC BY 4.0
Historial de vulnerabilidades: pillow
Todas las vulnerabilidades publicadas de este paquete (Python (PyPI)), de la más reciente a la más antigua. Muestra si es un fallo aislado o un paquete que hay que vigilar.
- Entradas en total
- 100
- Primera → última
- 04.2014 10.2026
- Sin corrección
- 0
- Explotada en ataques
- 1
Distribución por gravedad
- Crítica 13
- Alta 58
- Media 27
- Baja 1
- Sin evaluar 1
Divulgaciones por año
- 2017: 1
- 2018: 0
- 2019: 1
- 2020: 10
- 2021: 20
- 2022: 8
- 2023: 3
- 2024: 2
- 2025: 1
- 2026: 42
antes: 12
-
Pillow: Decompression-bomb protection bypass via multi-frame seek() in MPO and DCX (MpoImageFile.seek / DcxImageFile.seek) esta entrada
Identifica las vulnerabilidades de tu aplicación
Con la integración de DockRay y el inventario de software activado, DockRay compara las versiones notificadas de los componentes con las vulnerabilidades conocidas y avisa de las coincidencias confirmadas. La cobertura depende de la tecnología compatible y de los avisos disponibles.