GHSA-p8hx-v2p6-rp84: Medium severity pip/pillow vulnerability

Published Oct 22, 2026
·
Updated

Summary Pillow's decompression-bomb protection (Image.decompressionbombcheck, gated by Image.MAXIMAGEPIXELS) 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() → opencore() runs decompressionbombcheck(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(): calls JpegImagePlugin.JpegImageFile.open(self) (which sets self.size from the frame's JPEG SOF) and rebuilds self.tile, with no Image.decompressionbombcheck(self.size). - PIL/DcxImagePlugin.py, DcxImageFile.seek(): calls PcxImageFile.open(self) (which sets self.size from 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 decompressionbombcheck correctly on seek.

PoC Self-contained; the victim uses default MAXIMAGEPIXELS:

python import io from PIL import Image

attacker builds the file (guard disabled only to CREATE the oversized frame) Image.MAXIMAGEPIXELS = 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", saveall=True, appendimages=[big]) data = buf.getvalue() # ~3 MB Image.MAXIMAGEPIXELS = 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:

python PIL/MpoImagePlugin.py — end of MpoImageFile.seek() self.tile = [ImageFile.Tile("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])] Image.decompressionbombcheck(self.size) # add self.frame = frame

PIL/DcxImagePlugin.py — end of DcxImageFile.seek() PcxImageFile.open(self) Image.decompressionbombcheck(self.size) # add

Affected Software

1 affected componentFixes available
pip/pillow<13.0.0
13.0.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 13.0.0
  2. Compensating control

    In both PIL/DcxImagePlugin.py DcxImageFile.seek() and PIL/MpoImagePlugin.py MpoImageFile.seek(), call Image._decompression_bomb_check(self.size) after re-parsing the target frame and resetting self._size, mirroring the checks performed by GifImagePlugin and TiffImagePlugin.

Event History

Oct 22, 2026
Advisory Published
via GitHub·03:56 PM
Data Sourced
via GitHub·03:56 PM
DescriptionSeverityWeaknessAffected Software

Frequently Asked Questions

1

Which image types and processing paths are affected?

The bypass affects MPO and DCX images when an application seeks to a later frame. The first frame must pass Pillow's decompression-bomb check, while a later frame declares dimensions large enough to exceed the intended limit.

2

What does an attacker need to trigger the issue?

An attacker needs to provide a crafted MPO or DCX file and cause the application to open it and load or otherwise seek to the oversized later frame. No privileges are required, but the supplied severity vector indicates user interaction is required.

3

What is the impact if the bypass succeeds?

Loading the later frame can decode an over-limit image and commit large amounts of memory. This can exhaust memory and cause an out-of-memory denial of service.

4

What can be done if updating is not immediately possible?

Avoid processing untrusted MPO and DCX files, especially seeking to frames beyond the initial frame. If such processing is necessary, ensure later-frame dimensions are checked against the intended pixel limit before decoding.

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