PILLOW · GHSA-wqgw-9xfh-qf2g · Package
FIX → 13.0.0
Pillow's TIFF and EXIF directory parser reads each out-of-line tag value into memory without a total cap, so overlapping entries let a small file allocate thousands of times its size. An attacker who can submit an image can crash or stall the process that opens it, a denial of service with no code execution.
first public Nov 2, 2026 · 1 source
Could this affect your app?
Free first check from your website. Connect your repo for the full answer.
Any service that calls Image.open() on an uploaded TIFF or JPEG, or getexif() or ImageOps.exif_transpose() on uploaded PNG, JPEG or WebP, is exposed, since the parser runs before any pixel decoding and before the decompression-bomb check. Services that only process trusted images or never open user uploads can skip it.
Not affected if no-untrusted-images, pillow-fixed-13.0.0
Who cares image-upload, pillow, python, thumbnailers
python -c "import PIL; print(PIL.__version__)", affected up to and including 12.3.0pip show pillowImage.open() on uploads or getexif() / ImageOps.exif_transpose() on uploaded PNG, JPEG or WebP============================================================================
ANT-2026-D7STJUWL [medium] CWE-770 dos
Memory DoS in TIFF and EXIF IFD parsing via overlapping tag data
Location: src/PIL/TiffImagePlugin.py:927 in ImageFileDirectory_v2.load()
Commit: a707c6b21c313880d0771e54965d142dd42358b6
============================================================================
A small, crafted image file can make Pillow use thousands of times its own size in memory while the file is only being opened, which lets anyone who can submit an image crash or stall the process handling it. A 144 KB TIFF makes Image.open() return normally while holding 1.6 GiB of memory. The largest such standard TIFF, about 786 KB, asks for roughly 48 GiB, which in practice gets the worker killed by the operating system. The file declares a 1×1 image, so the decompression-bomb check never triggers and no warning is raised. There is no memory corruption; the impact is denial of service.
The bug is in ImageFileDirectory_v2.load() in src/PIL/TiffImagePlugin.py (around line 927). For every directory entry whose value is stored elsewhere in the file, it reads that value into a new bytes object with ImageFile._safe_read(fp, count * unit_size) and keeps it in _tagdata. _safe_read() only checks that the bytes exist. Nothing limits the total read across entries, and nothing stops every entry from pointing at the same bytes. An attacker can therefore give each of N distinct private tags an offset and count that covers the whole file, at a cost of 12 bytes per entry. Memory grows as N × file size, which is quadratic in the file size, and BigTIFF removes the 65,535-entry ceiling.
Check it with your coding agent
It reads this page and tells you if your code is affected.
Are you affected?
Ask from here. InfraStack checks the affected versions against your stack and answers in your inbox.
InfraStack checks the affected versions against your stack and answers in Slack, with the evidence and the fix.
Upgrade to 13.0.0
13.0.0The same loop parses EXIF data, so the problem reaches beyond TIFF:
Image.open() itself, via _read_dpi_from_exif().eXIf chunk costs 754 MiB on getexif() or ImageOps.exif_transpose().The attached reproducer builds a 72 KB TIFF and a 72 KB PNG. Using only Image.open() and getexif(), each causes 412 MiB of tag data to be retained, about 6,000 times the file size. The loop dates to the original PIL 1.1.7 import, so every Pillow release since 1.0 is affected.
TiffImagePlugin.ImageFileDirectory_v2.load() reads the out-of-line value of every IFD entry into its own bytes object and keeps it in self._tagdata. Nothing bounds the sum of those reads, and nothing prevents every entry from pointing at the same region of the file. An IFD with N entries whose (offset, count) each cover the whole file therefore costs N × filesize bytes of memory, while the IFD itself costs only 12 bytes per entry. This one loop is the IFD parser for TIFF frames and for Image.Exif alike, so it is reachable from Image.open() on a TIFF or JPEG and from getexif() / ImageOps.exif_transpose() on PNG, JPEG, WebP and MPO.
Observed on the stock build at this commit: a valid 144,110-byte 1×1 greyscale TIFF with 12,000 such entries makes Image.open() return normally (no exception, no warning, 1 declared pixel so MAX_IMAGE_PIXELS is irrelevant) after 1.5 s, with the process RSS grown from 15 MiB to 1,671 MiB and im.tag_v2 holding 1,649 MiB of tag data for as long as the image object lives. The same IFD placed in the eXIf chunk of a 96 KB PNG makes im.getexif() retain 754 MiB. A 65,870-byte JPEG whose APP1 Exif segment is such an IFD (5,400 entries × 65,519 bytes) allocates ~340 MiB inside Image.open() itself, with no further call.
# src/PIL/TiffImagePlugin.py:902-946 (ImageFileDirectory_v2.load)
for i in range(tag_count):
tag, typ, count, data = ( … self._unpack("HHL4s", self._ensure_read(fp, 12)))
…
size = count * unit_size # 918: attacker-chosen
if size > (8 if self._bigtiff else 4):
here = fp.tell()
(offset,) = self._unpack("Q" if self._bigtiff else "L", data)
…
fp.seek(offset) # 926: attacker-chosen
data = ImageFile._safe_read(fp, size) # 927: new bytes object of `size`
fp.seek(here)
…
self._tagdata[tag] = data # 945: kept
self.tagtype[tag] = typ
ImageFile._safe_read() only guarantees that size bytes are actually present in the stream (it reads in SAFEBLOCK chunks and raises on truncation); it is a guard against allocating for data that does not exist, not against reading the same existing data many times. For a well-formed file each entry points at its own disjoint value, so the total read is at most the file size. For the crafted file every one of the N entries has a distinct tag number (so none overwrites another in _tagdata), type BYTE, count = filesize - 1 and offset = 1; each iteration allocates and fills a fresh filesize - 1-byte object and stores it. load() does not care whether the tag number is known or whether the type makes sense for it; any 16-bit tag number and any type in _load_dispatch is accepted. Memory is N × filesize, and since filesize ≈ 12 N, it is quadratic in the file size: 12,000 entries / 144 KB → 1.6 GiB (measured); the classic-TIFF maximum of 65,535 entries / 786 KB → ~48 GiB; BigTIFF (tag_count is a Q) has no 16-bit entry limit at all.
Reachability:
TiffImageFile._open() (TiffImagePlugin.py:1187) → self._seek(0) (:1210) → self.tag_v2.load(self.fp) (:1258, and again at :1271). This runs inside Image.open(), before load() and before any size check (_decompression_bomb_check only looks at width × height and is satisfied by the 1×1 image), and the resulting directory stays referenced as im.tag_v2. _seek() parses the IFD twice, but reset() drops the first copy, so the peak does not double; it is paid again for every frame on seek()/n_frames.Image.Exif.load() / load_from_fp() / _get_ifd_dict() (Image.py:4146-4196, :4119-4133) wrap the same ImageFileDirectory_v2.load() around the EXIF blob. PngImagePlugin.chunk_eXIf (PngImagePlugin.py:697) stores the whole chunk as info["exif"], JpegImagePlugin concatenates APP1 Exif segments into info["exif"] (JpegImagePlugin.py:114-117), and WebP exposes its EXIF chunk the same way; im.getexif(), ImageOps.exif_transpose() and _getexif() then parse it and keep the values in im._exif. For JPEG no explicit call is needed: JpegImageFile._open() ends with self._read_dpi_from_exif() (JpegImagePlugin.py:413), which calls getexif() whenever no JFIF DPI is present, so the APP1 IFD is parsed during Image.open(). Note that an EXIF blob is parsed into several IFDs (main, EXIF, GPS, Interop via _get_ifd_dict), each through its own load() call.The only failure path in load() is except OSError → warning; running out of memory is not an OSError, so on Linux the realistic outcome for a larger N is the kernel OOM killer terminating the process rather than a Python exception.
The loop predates Pillow: it came in with the PIL 1.1.7 import in 9a640e315715 ("Forking PIL", 2010-07-30) and was carried over unchanged into ImageFileDirectory_v2, so every release since 1.0 is affected. The per-tag _safe_read bounding added in the CVE-2019-16865 era does not address the sum.
Any service that calls Image.open() on an uploaded TIFF or JPEG — or getexif() / ImageOps.exif_transpose() on an uploaded PNG, JPEG or WebP, which thumbnailers routinely do to honour orientation — can be made to allocate and retain memory roughly entries × filesize, i.e. thousands of times the upload size, from a file of a few hundred KB that declares a 1×1 image and passes the decompression-bomb check. A ~800 KB TIFF requests ~48 GiB; in practice the worker (or its container) is OOM-killed within seconds, taking down all requests it was serving, and repeated uploads keep it down. No pixel decoding, `load