GitHub Advisoryadvisory · PyPI

GHSA-254f-3936-288h Pillow: PdfParser can recurse without limit in get_value() and linearize_page_tree()

MEDIUM Published 2026-10-21 Modified 2026-10-21 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)

Weakness · CWE (1)

  • CWE-835 Loop with Unreachable Exit Condition weakness

References