CVE-2026-128066: CVE-2026-128066: Quadratic Memory Amplification Denial of Service in Pillow TIFF and EXIF IFD Parser
CVE-2026-128066: Quadratic Memory Amplification Denial of Service in Pillow TIFF and EXIF IFD Parser Vulnerability ID: CVE-2026-128066 CVSS Score: 8.7 Published: 2026-11-12 CVE-2026-128066 (GHSA-wqgw-9xfh-qf2g) is a
CVE-2026-128066: Quadratic Memory Amplification Denial of Service in Pillow TIFF and EXIF IFD Parser
Vulnerability ID: CVE-2026-128066
CVSS Score: 8.7
Published: 2026-11-12
CVE-2026-128066 (GHSA-wqgw-9xfh-qf2g) is a resource allocation vulnerability (CWE-770) in the Python Pillow imaging library affecting versions 1.0 through 12.3.0. The flaw resides in the ImageFileDirectory_v2.load() method within src/PIL/TiffImagePlugin.py, which processes Tag Image File Format (TIFF) structures and Exchangeable Image File Format (EXIF) metadata across multiple container formats. By supplying an image with thousands of IFD tag entries that point to overlapping or identical file offsets, an attacker can trigger quadratic memory amplification (O(N * filesize)). Because the vulnerability occurs during metadata parsing rather than pixel decoding, it bypasses Pillow's built-in decompression bomb protections (Image.MAX_IMAGE_PIXELS).
TL;DR
Pillow versions 1.0 through 12.3.0 fail to bound cumulative memory allocations when reading out-of-line IFD tag values in TIFF images and EXIF metadata. A small crafted file (e.g., 72 KB to 786 KB) with aliased tag offsets can force the allocation of gigabytes of RAM upon calling Image.open() or getexif(), resulting in an application crash or kernel Out-Of-Memory (OOM) termination. Fixed in Pillow 13.0.0.
⚠️ Exploit Status: POC
Technical Details
- CVE / Advisory ID: CVE-2026-128066 / GHSA-wqgw-9xfh-qf2g
- Vulnerability Class: CWE-770: Allocation of Resources Without Limits or Throttling
- CVSS v4.0 Score: 8.7 HIGH (AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N)
- Affected Component: ImageFileDirectory_v2.load() in src/PIL/TiffImagePlugin.py
- Affected Formats: TIFF, JPEG (EXIF APP1), PNG (eXIf), WebP (EXIF), MPO
- Affected Versions: Pillow >= 1.0, <= 12.3.0
- Patched Version: Pillow 13.0.0
- Exploit Status: Public Proof-of-Concept Available
Affected Systems
- Pillow (PyPI: pillow) versions 1.0 through 12.3.0
-
Pillow: >= 1.0, <= 12.3.0 (Fixed in:
13.0.0)
Code Analysis
Commit: 4908eda
Bound retained TIFF tag data by stream size
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index 42c1cc96c..c9d506933 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -895,13 +895,17 @@ def _ensure_read(self, fp: IO[bytes], size: int) -> bytes:
def load(self, fp: IO[bytes]) -> None:
self.reset()
self._offset = fp.tell()
+ fp.seek(0, io.SEEK_END)
+ file_size = fp.tell()
+ fp.seek(self._offset)
try:
tag_count = (
self._unpack("Q", self._ensure_read(fp, 8))
if self._bigtiff
else self._unpack("H", self._ensure_read(fp, 2))
)[0]
+ total_size = 0
for i in range(tag_count):
tag, typ, count, data = (
self._unpack("HHQ8s", self._ensure_read(fp, 20))
@@ -928,6 +932,12 @@ def load(self, fp: IO[bytes]) -> None:
if offset >= 2**63:
warnings.warn("Tag offset too large")
continue
+ # Well-formed IFD values should fit within the stream they
+ # are read from, even before accounting for overlaps.
+ total_size += size
+ if total_size > file_size:
+ warnings.warn(f"Tag data larger than file. Skipping tag {tag}")
+ continue
fp.seek(offset)
data = ImageFile._safe_read(fp, size)
fp.seek(here)
Commit: 4b63dff
Share TIFF tag data budget across frames
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index c9d5069..e238123 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -622,6 +622,10 @@ def __init__(
self.tagtype: dict[int, int] = {}
""" Dictionary of tag types """
self.reset()
+ # Maximum total out-of-line tag data that load() may read, as well
+ # as the size of the stream. Set by TiffImageFile so that
+ # all frames of a file share one budget.
+ self._tag_data_limit: int | None = None
self.next = (
self._unpack("Q", ifh[8:])[0]
if self._bigtiff
@@ -896,8 +900,11 @@ def load(self, fp: IO[bytes]) -> None:
self.reset()
self._offset = fp.tell()
fp.seek(0, io.SEEK_END)
- file_size = fp.tell()
+ limit = fp.tell()
fp.seek(self._offset)
+ if self._tag_data_limit is not None:
+ limit = min(limit, self._tag_data_limit)
+ self._tag_data_size = 0
try:
tag_count = (
@@ -935,9 +942,10 @@ def load(self, fp: IO[bytes]) -> None:
# Well-formed IFD values should fit within the stream they
# are read from, even before accounting for overlaps.
total_size += size
- if total_size > file_size:
+ if total_size > limit:
warnings.warn(f"Tag data larger than file. Skipping tag {tag}")
continue
+ self._tag_data_size += size
fp.seek(offset)
data = ImageFile._safe_read(fp, size)
fp.seek(here)
Exploit Details
- GitHub Security Advisory: Official advisory containing a pure-Python reproducer script that generates crafted TIFF and PNG (eXIf) payloads with 6,000 aliased IFD tags.
Mitigation Strategies
- Upgrade Pillow to version 13.0.0 or later to enforce stream-size ceilings on cumulative IFD tag allocations across all frames.
- Restrict enabled image plugins during Image.open() using the formats parameter (e.g., excluding TIFF where not needed).
- Enforce OS-level or container-level memory limits (RLIMIT_AS, cgroups) on worker processes handling untrusted image uploads.
- Avoid invoking getexif() or ImageOps.exif_transpose() on untrusted PNG and WebP files when running vulnerable Pillow versions.
Remediation Steps:
- Audit Python environments and dependency lockfiles (requirements.txt, Pipfile.lock, poetry.lock, uv.lock) for pillow versions < 13.0.0.
- Run
pip install --upgrade 'pillow>=13.0.0'to install the patched release. - Verify that application exception handlers gracefully tolerate or ignore
UserWarning: Tag data larger than fileif warnings are configured to raise exceptions. - Configure process memory limits on media ingestion workers as a defense-in-depth measure against metadata resource exhaustion.
References
- GitHub Security Advisory: GHSA-wqgw-9xfh-qf2g
- Pillow Commit 4908edaf7f416616784c7c03d8c1a60153710ffb: Bound retained TIFF tag data by stream size
- Pillow Commit 4b63dffd8a6d980fa0e21b40832fa6cbf033eacf: Share TIFF tag data budget across frames
- Pillow 13.0.0 Release
- CVE.org Record: CVE-2026-128066
- NVD Vulnerability Detail: CVE-2026-128066
Read the full report for CVE-2026-128066 on our website for more details including interactive diagrams and full exploit analysis.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.