GHSA-254f-3936-288h: Pip/Pillow vulnerability
Summary PdfParser's getvalue() can recurse without any limit and crash with an uncaught RecursionError. It is reachable through Image.save(im, fp=<pdf>, format="PDF", append=True) when the existing PDF file is attacker-controlled. This is the same class of bug as https://github.com/advisories/GHSA-r73j-pqj5-w3x7 (the /Prev trailer chain loop, fixed earlier this year), in the same file, but in two different functions that fix did not touch.
Details getvalue() in src/PIL/PdfParser.py
This is a classmethod that takes maxnesting: int = -1 and has a guard:
python if maxnesting == 0: return None, None
Every recursive call (nested arrays, dictionaries, indirect object definitions) passes maxnesting=maxnesting - 1. Since the default and every call site start at -1, this only ever counts down further negative: -1, -2, -3, and so on. It never equals exactly 0, so the guard never runs. A PDF object with a value nested a few thousand levels deep is enough to hit Python's recursion limit. It does not even need a valid /Pages tree, since readpdfinfo() parses the /Root object's own dictionary first, before checking anything else.
This advisory reports a single vulnerability: an ineffective recursion-depth guard in PdfParser.getvalue() (the sentinel -1 default never reaches the == 0 check). The mention of GHSA-r73j-pqj5-w3x7 is comparative context for an already-fixed, already-CVE'd bug in a different function — it is not a second vulnerability being reported. The three trigger paths (nested arrays/dicts/indirect objects) are not independently fixable; they share one root cause and one fix location in getvalue() .
Steps to reproduce Both PDFs below are plain text, built directly in Python, no PDF library needed.
python from PIL.PdfParser import PdfParser
def buildpdf(root): out = bytearray(b"%PDF-1.4\n") offset = len(out) out += f"1 0 obj\n".encode() out += root out += b"\nendobj\n" out += b"xref\n" out += f"0 2\n".encode() out += b"0000000000 65535 f \n" out += f"{offset:010d} 00000 ".encode() + b"n \n" xrefoffset = len(out) out += b"xref\n" out += f"0 2\n".encode() out += b"0000000000 65535 f \n" out += f"{offset:010d} 00000 ".encode() + b"n \n" out += b"trailer\n" out += f"<< /Size 2 /Root 1 0 R >>\n".encode() out += b"startxref\n" out += f"{xrefoffset}\n".encode() out += b"%%EOF" return bytes(out)
bomb = b"[" 3000 + b"]" 3000 root = b"<< /Type /Catalog /Pages 2 0 R /Bomb " + bomb + b" >>" with open("nestingbomb.pdf", "wb") as f: f.write(buildpdf(root))
PdfParser("nestingbomb.pdf") # raises RecursionError
Expected: a clean PdfFormatError.
Actual: raises a raw RecursionError: maximum recursion depth exceeded.
Impact - Availability: an attacker who controls the content of a PDF file that an application later appends an image page to (Image.save(..., format="PDF", append=True)) can crash that request with a RecursionError, using a file of a few hundred bytes. - Confidentiality: none. - Integrity: none. - Pillow cannot open PDFs for reading through Image.open(). PdfParser is only reached through the append-mode save path, so the app has to be doing that specific thing (for example, stamping or watermarking an existing PDF) for this to matter.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/Pillowto a version that resolves this vulnerability.Fixed in 13.0.0
Event History
Frequently Asked Questions
Which workflows are exposed?
The issue is reachable when Pillow saves an image to an existing PDF with Image.save(im, fp=<pdf>, format="PDF", append=True) and that PDF is attacker-controlled. Processing a PDF supplied by an untrusted party in this append workflow is the relevant exposure condition.
What must an attacker provide to trigger the crash?
An attacker needs to control the existing PDF and include an object value nested deeply enough to exceed Python's recursion limit. The PDF does not need a valid /Pages tree, because the parser reads the /Root object's dictionary before validating that structure.
Does the nesting limit protect the default behavior?
No. The default max_nesting value is -1, and recursive calls decrement it further, so it never reaches the zero value that would stop recursion. As a result, deeply nested arrays, dictionaries, or indirect object definitions can recurse until an uncaught RecursionError occurs.