{"id":"CVE-2026-112896","aliases":["BIT-pillow-2026-112896","CLSA-2026-1792576513","CLSA-2026-1792580209","CLSA-2026-1792580364","CLSA-2026-1792631285","CLSA-2026-1792648426","CLSA-2026-1792648427","DEBIAN-CVE-2026-112896","EUVD-2026-104658","GHSA-pchr-8j38-3x4p","PYSEC-2026-4334","ROOT-APP-PYPI-CVE-2026-112896","SUSE-SU-2026:5038-1","UBUNTU-CVE-2026-112896","openSUSE-SU-2026:12230-1"],"status":"published","title":"Pillow: TIFF encoder ColorMap plane-size mismatch causes a heap out-of-bounds read","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","severity":{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-09","score":0.00475,"percentile":0.41305,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-125"],"published":"2026-10-19T21:59:17Z","modified":"2026-11-06T11:15: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"]},{"ecosystem":"Root:PyPI","package":"pillow","fixed_in":["12.3.0+aikido.1","10.3.0+aikido.16","10.4.0+aikido.14","12.3.0+aikido.2","10.2.0+aikido.1","12.2.0+aikido.8","9.0.1+aikido.1","10.4.0+aikido.15","12.1.0+aikido.15","11.3.0+aikido.11","9.0.1+aikido.2","10.3.0+aikido.17","8.0.1+aikido.1","8.1.2+aikido.1","8.0.1+aikido.2","10.4.0+aikido.16","10.1.0+aikido.1","12.3.0+aikido.3","10.3.0+aikido.18","10.2.0+aikido.2","10.3.0+aikido.19","12.3.0+aikido.4","10.4.0+aikido.17","10.3.0+aikido.20","9.4.0+aikido.1","9.0.0+aikido.1","9.0.0+aikido.2","11.0.0+aikido.14","7.0.0+aikido.2","7.0.0+aikido.3","12.2.0+aikido.9","9.5.0+aikido.19","10.4.0+aikido.18"],"sources":["osv"]}],"affected":[{"source":"ghsa","ecosystem":"PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"last_affected":"12.3.0"},{"fixed":"13.0.0"}]}]},{"source":"osv","ecosystem":"Bitnami","package":"pillow","ranges":[{"type":"SEMVER","events":[{"introduced":"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":"0"},{"fixed":"13.0.0"}]}],"versions":["1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7.0","1.7.1","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","1.7.8","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","2.0.0","2.1.0","2.2.0","2.2.1","2.2.2","2.3.0","2.3.1","2.3.2","2.4.0","2.5.0","2.5.1","2.5.2","2.5.3","2.6.0","2.6.1","2.6.2","2.7.0","2.8.0","2.8.1","2.8.2","2.9.0","3.0.0","3.1.0","3.1.0.rc1","3.1.0rc1","3.1.1","3.1.2","3.2.0","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","4.0.0","4.1.0","4.1.1","4.2.0","4.2.1","4.3.0","5.0.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":"PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"13.0.0"}]}],"versions":["1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7.0","1.7.1","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","1.7.8","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","2.0.0","2.1.0","2.2.0","2.2.1","2.2.2","2.3.0","2.3.1","2.3.2","2.4.0","2.5.0","2.5.1","2.5.2","2.5.3","2.6.0","2.6.1","2.6.2","2.7.0","2.8.0","2.8.1","2.8.2","2.9.0","3.0.0","3.1.0","3.1.0.rc1","3.1.0rc1","3.1.1","3.1.2","3.2.0","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","4.0.0","4.1.0","4.1.1","4.2.0","4.2.1","4.3.0","5.0.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":"Root:PyPI","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"12.3.0+aikido.1"},{"fixed":"10.3.0+aikido.16"},{"fixed":"10.4.0+aikido.14"},{"fixed":"12.3.0+aikido.2"},{"fixed":"10.2.0+aikido.1"},{"fixed":"12.2.0+aikido.8"},{"fixed":"9.0.1+aikido.1"},{"fixed":"10.4.0+aikido.15"},{"fixed":"12.1.0+aikido.15"},{"fixed":"11.3.0+aikido.11"},{"fixed":"9.0.1+aikido.2"},{"fixed":"10.3.0+aikido.17"},{"fixed":"8.0.1+aikido.1"},{"fixed":"8.1.2+aikido.1"},{"fixed":"8.0.1+aikido.2"},{"fixed":"10.4.0+aikido.16"},{"fixed":"10.1.0+aikido.1"},{"fixed":"12.3.0+aikido.3"},{"fixed":"10.3.0+aikido.18"},{"fixed":"10.2.0+aikido.2"},{"fixed":"10.3.0+aikido.19"},{"fixed":"12.3.0+aikido.4"},{"fixed":"10.4.0+aikido.17"},{"fixed":"10.3.0+aikido.20"},{"fixed":"9.4.0+aikido.1"},{"fixed":"9.0.0+aikido.1"},{"fixed":"9.0.0+aikido.2"},{"fixed":"11.0.0+aikido.14"},{"fixed":"7.0.0+aikido.2"},{"fixed":"7.0.0+aikido.3"},{"fixed":"12.2.0+aikido.9"},{"fixed":"9.5.0+aikido.19"},{"fixed":"10.4.0+aikido.18"}]}]},{"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:14.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.2.1-1ubuntu2","2.2.1-2ubuntu1","2.2.1-3ubuntu2","2.2.1-3ubuntu3","2.2.1-3ubuntu4","2.2.1-3ubuntu6","2.3.0-1ubuntu1","2.3.0-1ubuntu2","2.3.0-1ubuntu3","2.3.0-1ubuntu3.2","2.3.0-1ubuntu3.3","2.3.0-1ubuntu3.4","2.3.0-1ubuntu3.4+esm1","2.3.0-1ubuntu3.4+esm2","2.3.0-1ubuntu3.4+esm3","2.3.0-1ubuntu3.4+esm4","2.3.0-1ubuntu3.4+esm5"]},{"source":"osv","ecosystem":"Ubuntu:Pro:16.04:LTS","package":"pillow","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"}]}],"versions":["2.9.0-1","3.0.0-1","3.0.0-1build1","3.1.0-1","3.1.1-1","3.1.2-0ubuntu1","3.1.2-0ubuntu1.1","3.1.2-0ubuntu1.3","3.1.2-0ubuntu1.4","3.1.2-0ubuntu1.5","3.1.2-0ubuntu1.6","3.1.2-0ubuntu1.6+esm1","3.1.2-0ubuntu1.6+esm2","3.1.2-0ubuntu1.6+esm3"]},{"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-1.1"}]}],"bundled":14}],"products":[{"vendor":"python","product":"pillow"},{"vendor":"python-pillow","product":"pillow"}],"references":[{"url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-pchr-8j38-3x4p","tags":["x_refsource_CONFIRM","exploit","Exploit","Vendor Advisory","advisory","web","evidence","report"]},{"url":"https://github.com/python-pillow/Pillow/commit/d03dc3c587a9b0bdc9e4304d70d59a635a87b742","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","advisory"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-112896","tags":["web","advisory"]},{"url":"https://github.com/advisories/GHSA-pchr-8j38-3x4p"},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/112xxx/CVE-2026-112896.json","tags":["advisory"]},{"url":"https://security-tracker.debian.org/tracker/CVE-2026-112896","tags":["advisory"]},{"url":"https://github.com/python-pillow/Pillow","tags":["package"]},{"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-112896","tags":["report"]},{"url":"https://www.cve.org/CVERecord?id=CVE-2026-112896","tags":["report"]},{"url":"","tags":["advisory"]},{"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-112898","tags":["web"]}],"sources":{"cve":[{"id":"CVE-2026-112896","title":"Pillow: TIFF encoder ColorMap plane-size mismatch causes a heap out-of-bounds read","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T21:59:17.743Z","modified":"2026-10-20T13:55:36.949Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high","assessor":"cna"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T13:44:40.64332Z"}],"euvd":[{"id":"EUVD-2026-104658","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T21:59:17Z","modified":"2026-10-20T13:55:36Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"updated_at":"2026-11-02T16:59:44.419021Z"}],"ghsa":[{"id":"GHSA-pchr-8j38-3x4p","title":"Pillow: TIFF encoder ColorMap plane-size mismatch causes a heap out-of-bounds read","description":"## Summary\n\nA small attacker-supplied TIFF can be opened and loaded normally, then reach an out-of-bounds read when an application saves it back to TIFF with retained metadata. The fixed reproducer is a 1×1, 16-bit grayscale, PackBits-compressed TIFF containing tag 320 (ColorMap) with **768 SHORT values**: three zero-filled planes of 256 entries each. No Python object is forged and no metadata is injected after loading.\n\nPillow's TIFF encoder allocates **768 × 2 = 1536 bytes**, then passes three plane pointers separated by 256 SHORT entries to libtiff. With `BitsPerSample = 16`, libtiff instead expects **65536 SHORT values per plane** and attempts a **131072-byte read** from a plane pointer. Existing ASAN observations identify a heap-buffer-overflow READ crossing the 1536-byte allocation made in `PyImaging_LibTiffEncoderNew`.\n\nThe malformed ColorMap need not be a valid palette for this grayscale image. The issue is that accepted metadata is forwarded on save without reconciling the fixed plane extent with the effective sample depth. Opening/loading alone is not the demonstrated fault; the encoder path is required.\n\n## Impact and prerequisites\n\nAn application must accept the input, retain the relevant TIFF metadata, and invoke the libtiff-backed TIFF save path. The observed consequence is an out-of-bounds read and ASAN termination. Process-memory exposure during encoding is a potential consequence, **not demonstrated by extracting arbitrary heap contents in this report**. No arbitrary read primitive, full memory-disclosure chain, code execution, address-space bypass, or cross-process effect is claimed.\n\nThe 131072-byte attempted copy is not the number of bytes proven disclosed. This report does not run a synthetic-canary serializer or collect an unsanitized encoded output.\n\n## Tested environment and limits of the evidence\n\nThe existing observations were recorded on August 30, 2026, with **CPython 3.11.8 on macOS ARM64**, using the unmodified official **Pillow 12.3.0** wheel and its bundled **libtiff 4.7.1**:\n\n- Wheel: `pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl`\n- Wheel size: `4785266` bytes\n- Wheel SHA-256: `37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6`\n- Pillow source revision: `bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d`\n- Bundled libtiff SHA-256: `e8988717b7470394a47d5ccd343130796b24ee70b6d885d3818e0e0b53238be1`\n\nApple's ASAN runtime was injected at process startup. This is **not an ASAN-rebuilt Pillow wheel**; allocation/memcpy interception detects the reported boundary violation, but the wheel's native instructions are not comprehensively instrumented.\n\nOnly this concrete full-package environment is established here. No affected-version range, current development-branch result, or patched release is asserted. Severity is **Unknown**; no CVSS score is proposed.\n\nSeparate earlier evidence reached the original Pillow 8.0.1 encoder constructor compiled as a component against a modern Python/libtiff environment. **That was not a complete Pillow 8.0.1 installation** and does not establish the historical package's full runtime behavior. It is not part of this reproducer or an affected-version range.\n\n## Observed evidence and controls\n\nThe earlier integration harness performed the natural sequence `Image.open(BytesIO(input))`, `image.load()`, then `image.save(BytesIO(), format=\"TIFF\")`, without replacing the image's metadata. Each case was recorded three times:\n\n| Original fixture | Bytes | Recorded result |\n| --- | ---: | --- |\n| `colormap16` | 1685 | Loaded as `I;16`; save terminated with ASAN exit 86, READ of size 131072 crossing a 1536-byte heap region (3/3). |\n| `colormap8` | 1684 | Same fixed ColorMap, but 8-bit samples; save succeeded and retained SHORT/count 768 (3/3). |\n| `colormap_absent` | 137 | Same 16-bit geometry without ColorMap; save succeeded (3/3). |\n\nAddress-free summary of the recorded native diagnostic:\n\n```text\nAddressSanitizer: heap-buffer-overflow\nREAD of size 131072\n0 bytes after 1536-byte region\nCopy path: memcpy -\u003e _TIFFmemcpy -\u003e setByteArray -\u003e _TIFFsetShortArrayExt\n           -\u003e _TIFFVSetField -\u003e TIFFVSetField -\u003e ImagingLibTiffSetField\n           -\u003e PyImaging_LibTiffEncoderNew\nAllocation: calloc -\u003e PyImaging_LibTiffEncoderNew\n```\n\nInput SHA-256 values (also enforced inside the script):\n\n- `colormap16`: `5f38e0ead90b97949fb0c98c63f9c68d00ba9181db6631ecb8927376dcc75f4d`\n- `colormap8`: `fdb26d9135549ed44824901985086c5ee4f06515699ca3291e498c523935405c`\n- `colormap_absent`: `bd54979624f3ffb713342e0458caff9e17c477f940cda2f0960feebfabbc9556`\n\n## Root cause and source references\n\n- [Pillow `src/encode.c`, lines 870–899, pinned revision](https://github.com/python-pillow/Pillow/blob/bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d/src/encode.c#L870): the ColorMap tuple must contain exactly 768 entries; a 1536-byte allocation is passed with a fixed stride of 256. The check does not reconcile that extent with `BitsPerSample`.\n- [Pillow `src/PIL/TiffImagePlugin.py`, lines 1944–1991, pinned revision](https://github.com/python-pillow/Pillow/blob/bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d/src/PIL/TiffImagePlugin.py#L1944): save carries supplied tags into the libtiff encoder and sorts the tags, so sample depth is established before ColorMap.\n- [libtiff `tif_dir.c`, lines 441–448, pinned revision](https://gitlab.com/libtiff/libtiff/-/blob/5fe20d0e9aba49a6a350ed533459d1505203838f/libtiff/tif_dir.c#L441): `TIFFTAG_COLORMAP` computes the per-plane count as `1L \u003c\u003c td-\u003etd_bitspersample` and copies that many SHORTs from each pointer.\n\nThe reported boundary is Pillow's construction of a fixed-sized array for a native API whose required size depends on metadata; this does not establish a standalone libtiff defect for a correctly sized caller buffer.\n\n## Prior art and disclosure scope\n\n[Pillow PR #5148](https://github.com/python-pillow/Pillow/pull/5148), merged December 28, 2020, is related TIFF metadata prior art, but its archived diff replaces a short-lived stack field-name pointer with the persistent literal `\"CustomField\"`. That is a **different field-name-lifetime fix**, not this plane-size mismatch. The reviewed material does not establish an exact prior public report or fix for this root, and **novelty is not asserted**. This specific report remains NOT_SENT; association with other observations is not a prior disclosure of it.\n\n## Reproduction instructions\n\nUse an **already reviewed, existing** installation of the exact wheel above, an existing compatible macOS ARM64 CPython 3.11 interpreter, and an existing compatible Apple ASAN runtime. `--package-path` is the absolute directory containing `PIL`, not the `PIL` directory itself. The script does not install, download, build, patch, or submit anything. It checks selected key package files against the original installation's hashes and checks the loaded package/version; these checks are not a full installation attestation.\n\nSave the complete Python block below as `reproduce.py` and run:\n\n```sh\npython3.11 -I -S -B reproduce.py \\\n  --package-path /absolute/path/to/reviewed/site-packages \\\n  --asan-runtime /absolute/path/to/reviewed/libclang_rt.asan_osx_dynamic.dylib\n```\n\nBoth arguments are required. The runtime must be loaded at child startup, not by late `dlopen`. Each child resolves `__asan_init`, `__asan_address_is_poisoned`, and `__asan_region_is_poisoned` in its actual process symbol table **before importing Pillow or opening a fixture**. Merely setting `DYLD_INSERT_LIBRARIES` is not accepted as proof of ASAN availability. A missing symbol or package mismatch prevents the operation.\n\nEach of the three cases runs once in a separate child with a **30-second wall limit, 10-second hard CPU limit, core dumps disabled, and 1 MiB regular-file-size limit**. The parent supplies a clean allowlisted environment, isolated Python startup (`-I -S -B`), an empty temporary working directory, no stdin, and a 128 KiB combined pipe-output cap. Timeout/output-cap handling kills the child's process group. These limits are containment measures, not a security sandbox. No TIFF is written to disk. Temporary directories are removed; raw diagnostics are never forwarded or persisted.\n\nThe compressed/base64 constants below are the **unchanged original benign TIFF fixtures**, with decompressed size and SHA-256 checks. There are no external fixture dependencies. The standalone script supplied with this description is byte-for-byte the same as this block.\n\n**Separate August 31, 2026 inline validation:** the exact embedded standalone script passed all three fixed cases: one ASan READ of 131,072 bytes crossing a 1,536-byte allocation and two passing controls. Child guards and limits passed, with no timeout. This is additional bounded evidence, separate from earlier runs; no new build, install, full historical chain or older-version integration replay was performed.\n\n## Expected results from the standalone script\n\nCases run in order: `colormap8`, `colormap_absent`, `colormap16`. Every printed line should have `guard_passed: true`, `within_limits: true`, and `expected_observation: true`. The first two require successful saves and the expected retained sample-depth/ColorMap fields. The last requires ASAN exit 86 **and** the specific READ/extent/stack signatures, not merely any crash. Only the two controls inspect encoded metadata; the unsafe case never exports an encoded TIFF.\n\nThe parent exits 0 only if all three expectations match; 1 means a case did not match (including guard failure or a changed ASAN diagnostic), and 2 means parent setup failed. A failed guard, timeout, unrelated crash, or missing diagnostic is **inconclusive**, not evidence that the defect is absent. Do not bypass the ASAN guard to obtain an output TIFF.\n\n## Complete standalone reproducer\n\n```python\nimport argparse\nimport base64\nimport ctypes\nimport hashlib\nimport io\nimport json\nimport os\nfrom pathlib import Path\nimport platform\nimport resource\nimport selectors\nimport struct\nimport subprocess\nimport sys\nimport tempfile\nimport time\nimport zlib\n\n\nFIXTURES = {'colormap8': [1684,\n               'fdb26d9135549ed44824901985086c5ee4f06515699ca3291e498c523935405c',\n               'eNrz9NRi4GBgYOBmYGBkYWAEssAYic3EyAymQaqYoWzWBgYGNigbhAWh6iexMTCIIomLIZkjDmUzAbEMkhoHIJsBiCYxjIJRMApGwSgY2QAAPu0DiA=='],\n 'colormap_absent': [137,\n                     'bd54979624f3ffb713342e0458caff9e17c477f940cda2f0960feebfabbc9556',\n                     'eNrz9NRi4GBgYOBiYGBkYWAEssAYic3EyAymBYCYGcpmbWBgYIOyQVgQqr4NiEWRxMWQzBGHspmBWAZJDQOEBgDj8wKm'],\n 'colormap16': [1685,\n                '5f38e0ead90b97949fb0c98c63f9c68d00ba9181db6631ecb8927376dcc75f4d',\n                'eNrz9NRi4GBgYOBmYGBkYWAEssAYic3EyAymBYCYGcpmbWBgYIOyQVgQqn4SGwODKJK4GJI54lA2MxDLIKlxALJBgpMYRsEoGAWjYBSMZACsEwB78wOS']}\nPACKAGE_PINS = {'PIL/ImageFile.py': '983c4f35e0c51046d4c636998ec77000cd2da208870a03b205e104cd0b2df629',\n 'PIL/_version.py': 'fe7d75d4019326aca24ae57d4ab190cd62ba1225b175051119cd61cd87e42b1d',\n 'PIL/__init__.py': '7361b6ad3878589affe5956e4a4da24d71398b0891fd3a8c15e9362217b4ca01',\n 'PIL/features.py': '14f904863b416914aaa6480735876ec31885b7272ee0d8d92b010c5b1b5e98f5',\n 'PIL/TiffTags.py': '766dea350424b9a2caab3513be794689403c2b7222cd038006f69cff08edb45d',\n 'PIL/TiffImagePlugin.py': '440b2a3a80b280d18cda3fc70d9fd206e2fa44e9897c9550f70b51395047b273',\n 'PIL/_imaging.cpython-311-darwin.so': '6d09b1fb925779fff32b5b8a4c348a42c201d350d0e0fd8cdc00410b6779e596',\n 'PIL/Image.py': '04af3f2db7ffdd61e829fa7aa8f5481d394a0a4129a8fd8aa5b13908928fcd6d',\n 'PIL/.dylibs/libtiff.6.dylib': 'e8988717b7470394a47d5ccd343130796b24ee70b6d885d3818e0e0b53238be1',\n 'pillow-12.3.0.dist-info/WHEEL': 'de2586b77651a8bf1b7778c3609979713ebc901784a837fca80ed4969fc82e57',\n 'pillow-12.3.0.dist-info/METADATA': '8fc07146cab33ef7582361dab6eac596d340b03ec648f18217da7894f73838c8'}\nCASES = tuple(FIXTURES)\nWALL_SECONDS = 30\nFILE_LIMIT = 1024 * 1024\nPIPE_LIMIT = 128 * 1024\n\n\ndef limits():\n    resource.setrlimit(resource.RLIMIT_CORE, (0, 0))\n    resource.setrlimit(resource.RLIMIT_CPU, (10, 10))\n    resource.setrlimit(resource.RLIMIT_FSIZE, (FILE_LIMIT, FILE_LIMIT))\n\n\ndef fixture(case):\n    size, expected_hash, encoded = FIXTURES[case]\n    decoder = zlib.decompressobj()\n    data = decoder.decompress(base64.b64decode(encoded, validate=True), size + 1)\n    if (len(data) != size or not decoder.eof or decoder.unused_data\n            or decoder.unconsumed_tail\n            or hashlib.sha256(data).hexdigest() != expected_hash):\n        raise ValueError(\"fixture integrity check failed\")\n    return data\n\n\ndef field(data, wanted):\n    if len(data) \u003c 8 or data[:4] not in (b\"II*\\0\", b\"MM\\0*\"):\n        raise ValueError(\"not classic TIFF\")\n    endian = \"\u003c\" if data[:2] == b\"II\" else \"\u003e\"\n    directory = struct.unpack_from(endian + \"I\", data, 4)[0]\n    count = struct.unpack_from(endian + \"H\", data, directory)[0]\n    if directory + 2 + count * 12 + 4 \u003e len(data):\n        raise ValueError(\"truncated directory\")\n    for number in range(count):\n        offset = directory + 2 + number * 12\n        tag, kind, length = struct.unpack_from(endian + \"HHI\", data, offset)\n        if tag != wanted:\n            continue\n        size = {1: 1, 2: 1, 3: 2, 4: 4, 7: 1}[kind] * length\n        start = offset + 8 if size \u003c= 4 else struct.unpack_from(endian + \"I\", data, offset + 8)[0]\n        if start + size \u003e len(data):\n            raise ValueError(\"truncated field\")\n        return kind, length, data[start:start + size], endian\n    return None\n\n\ndef checked_image_module(package):\n    if (sys.implementation.name != \"cpython\" or sys.version_info[:2] != (3, 11)\n            or sys.platform != \"darwin\" or platform.machine() != \"arm64\"):\n        raise RuntimeError(\"requires macOS ARM64 CPython 3.11\")\n    for relative, expected in PACKAGE_PINS.items():\n        path = (package / relative).resolve(strict=True)\n        if not path.is_relative_to(package) or hashlib.sha256(path.read_bytes()).hexdigest() != expected:\n            raise RuntimeError(\"reviewed wheel file mismatch\")\n    process_symbols = ctypes.CDLL(None)\n    for symbol in (\"__asan_init\", \"__asan_address_is_poisoned\", \"__asan_region_is_poisoned\"):\n        getattr(process_symbols, symbol)\n    sys.path.insert(0, str(package))\n    from PIL import Image, features\n    if (Image.__version__ != \"12.3.0\" or features.version(\"libtiff\") != \"4.7.1\"\n            or Path(Image.__file__).resolve() != package / \"PIL/Image.py\"\n            or Path(Image.core.__file__).resolve() != package / \"PIL/_imaging.cpython-311-darwin.so\"):\n        raise RuntimeError(\"unexpected Pillow or libtiff\")\n    return Image\n\n\ndef emit(descriptor, message):\n    os.write(descriptor, (json.dumps(message, sort_keys=True) + \"\\n\").encode(\"ascii\"))\n\n\ndef child(case, package):\n    descriptor = os.dup(1)\n    with open(os.devnull, \"wb\") as quiet:\n        os.dup2(quiet.fileno(), 1)\n    try:\n        image_module = checked_image_module(package)\n        data = fixture(case)\n        with image_module.open(io.BytesIO(data)) as image:\n            image.load()\n            if image.size != (1, 1) or image.info.get(\"compression\") != \"packbits\":\n                raise ValueError(\"unexpected input geometry\")\n            bits = 8 if case == \"colormap8\" else 16\n            if image.tag_v2[258] != (bits,):\n                raise ValueError(\"unexpected sample width\")\n            if case == \"colormap_absent\":\n                if 320 in image.tag_v2:\n                    raise ValueError(\"unexpected ColorMap\")\n            elif image.tag_v2[320] != (0,) * 768:\n                raise ValueError(\"unexpected ColorMap planes\")\n            emit(descriptor, {\"ready\": True})\n            with io.BytesIO() as output:\n                image.save(output, format=\"TIFF\")\n                if case == \"colormap16\":\n                    emit(descriptor, {\"unsafe_save_returned\": True})\n                    return 1\n                encoded = output.getvalue()\n                saved_bits = field(encoded, 258)\n                map_field = field(encoded, 320)\n                bits_preserved = (saved_bits is not None and saved_bits[:2] == (3, 1)\n                                  and struct.unpack(saved_bits[3] + \"H\", saved_bits[2])[0] == bits)\n                map_preserved = (map_field is None if case == \"colormap_absent\"\n                                 else map_field is not None and map_field[:2] == (3, 768)\n                                 and map_field[2] == bytes(1536))\n                emit(descriptor, {\"control_passed\": bool(bits_preserved and map_preserved)})\n        return 0\n    except Exception:\n        emit(descriptor, {\"child_failed\": True})\n        return 2\n    finally:\n        os.close(descriptor)\n\n\ndef run_case(case, package, runtime):\n    with tempfile.TemporaryDirectory(prefix=\"pillow-check-\") as temporary:\n        environment = {\n            \"PATH\": \"/usr/bin:/bin:/usr/sbin:/sbin\",\n            \"HOME\": temporary,\n            \"TMPDIR\": temporary,\n            \"LC_ALL\": \"C\",\n            \"DYLD_INSERT_LIBRARIES\": str(runtime),\n            \"ASAN_OPTIONS\": \"detect_leaks=0:halt_on_error=1:abort_on_error=0:exitcode=86:\"\n                            \"disable_coredump=1:symbolize=1:malloc_fill_byte=165:\"\n                            \"max_malloc_fill_size=1048576:log_path=stderr\",\n        }\n        command = [sys.executable, \"-I\", \"-S\", \"-B\", str(Path(__file__).resolve()),\n                   \"--package-path\", str(package), \"--asan-runtime\", str(runtime), \"--child\", case]\n        output = bytearray()\n        diagnostics = bytearray()\n        deadline = time.monotonic() + WALL_SECONDS\n        with subprocess.Popen(command, cwd=temporary, env=environment, stdin=subprocess.DEVNULL,\n                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,\n                              preexec_fn=limits, start_new_session=True) as process:\n            bounded = True\n            try:\n                with selectors.DefaultSelector() as selector:\n                    selector.register(process.stdout, selectors.EVENT_READ, output)\n                    selector.register(process.stderr, selectors.EVENT_READ, diagnostics)\n                    while selector.get_map():\n                        remaining = deadline - time.monotonic()\n                        if remaining \u003c= 0:\n                            raise TimeoutError\n                        for key, events in selector.select(min(remaining, 0.25)):\n                            chunk = os.read(key.fileobj.fileno(), 8192)\n                            if not chunk:\n                                selector.unregister(key.fileobj)\n                            else:\n                                key.data.extend(chunk)\n                                if len(output) + len(diagnostics) \u003e PIPE_LIMIT:\n                                    raise BufferError\n                    process.wait(timeout=max(0.001, deadline - time.monotonic()))\n            except (TimeoutError, BufferError, subprocess.TimeoutExpired):\n                bounded = False\n                try:\n                    os.killpg(process.pid, 9)\n                except ProcessLookupError:\n                    pass\n                process.wait()\n            status = process.returncode\n        try:\n            messages = [json.loads(line) for line in output.splitlines()]\n        except (ValueError, UnicodeError):\n            messages = []\n        ready = bool(messages and messages[0] == {\"ready\": True})\n        if case == \"colormap16\":\n            observed = bool(bounded and ready and status == 86 and len(messages) == 1\n                            and b\"AddressSanitizer: heap-buffer-overflow\" in diagnostics\n                            and b\"READ of size 131072\" in diagnostics\n                            and b\"0 bytes after 1536-byte region\" in diagnostics\n                            and b\"PyImaging_LibTiffEncoderNew\" in diagnostics\n                            and b\"_TIFFmemcpy\" in diagnostics)\n        else:\n            observed = bool(bounded and ready and status == 0\n                            and messages == [{\"ready\": True}, {\"control_passed\": True}]\n                            and b\"AddressSanitizer:\" not in diagnostics)\n        output.clear()\n        diagnostics.clear()\n        return {\"case\": case, \"expected_observation\": observed, \"guard_passed\": ready,\n                \"within_limits\": bounded}\n\n\ndef main():\n    parser = argparse.ArgumentParser(description='Fixed ColorMap extent mismatch: controls first, ASAN-only trigger.')\n    parser.add_argument(\"--package-path\", required=True, type=Path,\n                        help=\"reviewed existing installation root containing PIL\")\n    parser.add_argument(\"--asan-runtime\", required=True, type=Path,\n                        help=\"reviewed existing compatible ASAN dylib; never loaded late\")\n    parser.add_argument(\"--child\", choices=CASES, help=argparse.SUPPRESS)\n    arguments = parser.parse_args()\n    if arguments.child:\n        limits()\n        return child(arguments.child, arguments.package_path.resolve())\n    try:\n        package = arguments.package_path.resolve(strict=True)\n        runtime = arguments.asan_runtime.resolve(strict=True)\n        if not package.is_dir() or not runtime.is_file():\n            raise ValueError(\"invalid existing paths\")\n        for case in CASES:\n            fixture(case)\n        results = [run_case(case, package, runtime) for case in CASES]\n    except Exception:\n        print(json.dumps({\"setup_failed\": True}))\n        return 2\n    for result in results:\n        print(json.dumps(result, sort_keys=True))\n    return 0 if all(result[\"expected_observation\"] for result in results) else 1\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```","status":"published","published":"2026-10-20T20:51:40Z","modified":"2026-10-20T20:51:40Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T14:41:33.669646Z"}],"nvd":[{"id":"CVE-2026-112896","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T22:17:09.76Z","modified":"2026-10-27T14:55:49.017Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/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.1,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H","level":"high","assessor":"primary"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T14:13:00.83844Z"}],"osv":[{"id":"CVE-2026-112896","title":"Pillow: TIFF encoder ColorMap plane-size mismatch causes a heap out-of-bounds read","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T21:59:17.743Z","modified":"2026-11-03T13:27:01.533178138Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-125"],"updated_at":"2026-11-03T14:04:53.650872Z"},{"id":"DEBIAN-CVE-2026-112896","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T22:17:09.76Z","modified":"2026-10-28T10:00:14.850705076Z","cvss":[{"version":"3.1","score":7.1,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:45:50.391389Z"},{"id":"GHSA-pchr-8j38-3x4p","title":"Pillow: TIFF encoder ColorMap plane-size mismatch causes a heap out-of-bounds read","description":"## Summary\n\nA small attacker-supplied TIFF can be opened and loaded normally, then reach an out-of-bounds read when an application saves it back to TIFF with retained metadata. The fixed reproducer is a 1×1, 16-bit grayscale, PackBits-compressed TIFF containing tag 320 (ColorMap) with **768 SHORT values**: three zero-filled planes of 256 entries each. No Python object is forged and no metadata is injected after loading.\n\nPillow's TIFF encoder allocates **768 × 2 = 1536 bytes**, then passes three plane pointers separated by 256 SHORT entries to libtiff. With `BitsPerSample = 16`, libtiff instead expects **65536 SHORT values per plane** and attempts a **131072-byte read** from a plane pointer. Existing ASAN observations identify a heap-buffer-overflow READ crossing the 1536-byte allocation made in `PyImaging_LibTiffEncoderNew`.\n\nThe malformed ColorMap need not be a valid palette for this grayscale image. The issue is that accepted metadata is forwarded on save without reconciling the fixed plane extent with the effective sample depth. Opening/loading alone is not the demonstrated fault; the encoder path is required.\n\n## Impact and prerequisites\n\nAn application must accept the input, retain the relevant TIFF metadata, and invoke the libtiff-backed TIFF save path. The observed consequence is an out-of-bounds read and ASAN termination. Process-memory exposure during encoding is a potential consequence, **not demonstrated by extracting arbitrary heap contents in this report**. No arbitrary read primitive, full memory-disclosure chain, code execution, address-space bypass, or cross-process effect is claimed.\n\nThe 131072-byte attempted copy is not the number of bytes proven disclosed. This report does not run a synthetic-canary serializer or collect an unsanitized encoded output.\n\n## Tested environment and limits of the evidence\n\nThe existing observations were recorded on August 30, 2026, with **CPython 3.11.8 on macOS ARM64**, using the unmodified official **Pillow 12.3.0** wheel and its bundled **libtiff 4.7.1**:\n\n- Wheel: `pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl`\n- Wheel size: `4785266` bytes\n- Wheel SHA-256: `37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6`\n- Pillow source revision: `bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d`\n- Bundled libtiff SHA-256: `e8988717b7470394a47d5ccd343130796b24ee70b6d885d3818e0e0b53238be1`\n\nApple's ASAN runtime was injected at process startup. This is **not an ASAN-rebuilt Pillow wheel**; allocation/memcpy interception detects the reported boundary violation, but the wheel's native instructions are not comprehensively instrumented.\n\nOnly this concrete full-package environment is established here. No affected-version range, current development-branch result, or patched release is asserted. Severity is **Unknown**; no CVSS score is proposed.\n\nSeparate earlier evidence reached the original Pillow 8.0.1 encoder constructor compiled as a component against a modern Python/libtiff environment. **That was not a complete Pillow 8.0.1 installation** and does not establish the historical package's full runtime behavior. It is not part of this reproducer or an affected-version range.\n\n## Observed evidence and controls\n\nThe earlier integration harness performed the natural sequence `Image.open(BytesIO(input))`, `image.load()`, then `image.save(BytesIO(), format=\"TIFF\")`, without replacing the image's metadata. Each case was recorded three times:\n\n| Original fixture | Bytes | Recorded result |\n| --- | ---: | --- |\n| `colormap16` | 1685 | Loaded as `I;16`; save terminated with ASAN exit 86, READ of size 131072 crossing a 1536-byte heap region (3/3). |\n| `colormap8` | 1684 | Same fixed ColorMap, but 8-bit samples; save succeeded and retained SHORT/count 768 (3/3). |\n| `colormap_absent` | 137 | Same 16-bit geometry without ColorMap; save succeeded (3/3). |\n\nAddress-free summary of the recorded native diagnostic:\n\n```text\nAddressSanitizer: heap-buffer-overflow\nREAD of size 131072\n0 bytes after 1536-byte region\nCopy path: memcpy -\u003e _TIFFmemcpy -\u003e setByteArray -\u003e _TIFFsetShortArrayExt\n           -\u003e _TIFFVSetField -\u003e TIFFVSetField -\u003e ImagingLibTiffSetField\n           -\u003e PyImaging_LibTiffEncoderNew\nAllocation: calloc -\u003e PyImaging_LibTiffEncoderNew\n```\n\nInput SHA-256 values (also enforced inside the script):\n\n- `colormap16`: `5f38e0ead90b97949fb0c98c63f9c68d00ba9181db6631ecb8927376dcc75f4d`\n- `colormap8`: `fdb26d9135549ed44824901985086c5ee4f06515699ca3291e498c523935405c`\n- `colormap_absent`: `bd54979624f3ffb713342e0458caff9e17c477f940cda2f0960feebfabbc9556`\n\n## Root cause and source references\n\n- [Pillow `src/encode.c`, lines 870–899, pinned revision](https://github.com/python-pillow/Pillow/blob/bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d/src/encode.c#L870): the ColorMap tuple must contain exactly 768 entries; a 1536-byte allocation is passed with a fixed stride of 256. The check does not reconcile that extent with `BitsPerSample`.\n- [Pillow `src/PIL/TiffImagePlugin.py`, lines 1944–1991, pinned revision](https://github.com/python-pillow/Pillow/blob/bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d/src/PIL/TiffImagePlugin.py#L1944): save carries supplied tags into the libtiff encoder and sorts the tags, so sample depth is established before ColorMap.\n- [libtiff `tif_dir.c`, lines 441–448, pinned revision](https://gitlab.com/libtiff/libtiff/-/blob/5fe20d0e9aba49a6a350ed533459d1505203838f/libtiff/tif_dir.c#L441): `TIFFTAG_COLORMAP` computes the per-plane count as `1L \u003c\u003c td-\u003etd_bitspersample` and copies that many SHORTs from each pointer.\n\nThe reported boundary is Pillow's construction of a fixed-sized array for a native API whose required size depends on metadata; this does not establish a standalone libtiff defect for a correctly sized caller buffer.\n\n## Prior art and disclosure scope\n\n[Pillow PR #5148](https://github.com/python-pillow/Pillow/pull/5148), merged December 28, 2020, is related TIFF metadata prior art, but its archived diff replaces a short-lived stack field-name pointer with the persistent literal `\"CustomField\"`. That is a **different field-name-lifetime fix**, not this plane-size mismatch. The reviewed material does not establish an exact prior public report or fix for this root, and **novelty is not asserted**. This specific report remains NOT_SENT; association with other observations is not a prior disclosure of it.\n\n## Reproduction instructions\n\nUse an **already reviewed, existing** installation of the exact wheel above, an existing compatible macOS ARM64 CPython 3.11 interpreter, and an existing compatible Apple ASAN runtime. `--package-path` is the absolute directory containing `PIL`, not the `PIL` directory itself. The script does not install, download, build, patch, or submit anything. It checks selected key package files against the original installation's hashes and checks the loaded package/version; these checks are not a full installation attestation.\n\nSave the complete Python block below as `reproduce.py` and run:\n\n```sh\npython3.11 -I -S -B reproduce.py \\\n  --package-path /absolute/path/to/reviewed/site-packages \\\n  --asan-runtime /absolute/path/to/reviewed/libclang_rt.asan_osx_dynamic.dylib\n```\n\nBoth arguments are required. The runtime must be loaded at child startup, not by late `dlopen`. Each child resolves `__asan_init`, `__asan_address_is_poisoned`, and `__asan_region_is_poisoned` in its actual process symbol table **before importing Pillow or opening a fixture**. Merely setting `DYLD_INSERT_LIBRARIES` is not accepted as proof of ASAN availability. A missing symbol or package mismatch prevents the operation.\n\nEach of the three cases runs once in a separate child with a **30-second wall limit, 10-second hard CPU limit, core dumps disabled, and 1 MiB regular-file-size limit**. The parent supplies a clean allowlisted environment, isolated Python startup (`-I -S -B`), an empty temporary working directory, no stdin, and a 128 KiB combined pipe-output cap. Timeout/output-cap handling kills the child's process group. These limits are containment measures, not a security sandbox. No TIFF is written to disk. Temporary directories are removed; raw diagnostics are never forwarded or persisted.\n\nThe compressed/base64 constants below are the **unchanged original benign TIFF fixtures**, with decompressed size and SHA-256 checks. There are no external fixture dependencies. The standalone script supplied with this description is byte-for-byte the same as this block.\n\n**Separate August 31, 2026 inline validation:** the exact embedded standalone script passed all three fixed cases: one ASan READ of 131,072 bytes crossing a 1,536-byte allocation and two passing controls. Child guards and limits passed, with no timeout. This is additional bounded evidence, separate from earlier runs; no new build, install, full historical chain or older-version integration replay was performed.\n\n## Expected results from the standalone script\n\nCases run in order: `colormap8`, `colormap_absent`, `colormap16`. Every printed line should have `guard_passed: true`, `within_limits: true`, and `expected_observation: true`. The first two require successful saves and the expected retained sample-depth/ColorMap fields. The last requires ASAN exit 86 **and** the specific READ/extent/stack signatures, not merely any crash. Only the two controls inspect encoded metadata; the unsafe case never exports an encoded TIFF.\n\nThe parent exits 0 only if all three expectations match; 1 means a case did not match (including guard failure or a changed ASAN diagnostic), and 2 means parent setup failed. A failed guard, timeout, unrelated crash, or missing diagnostic is **inconclusive**, not evidence that the defect is absent. Do not bypass the ASAN guard to obtain an output TIFF.\n\n## Complete standalone reproducer\n\n```python\nimport argparse\nimport base64\nimport ctypes\nimport hashlib\nimport io\nimport json\nimport os\nfrom pathlib import Path\nimport platform\nimport resource\nimport selectors\nimport struct\nimport subprocess\nimport sys\nimport tempfile\nimport time\nimport zlib\n\n\nFIXTURES = {'colormap8': [1684,\n               'fdb26d9135549ed44824901985086c5ee4f06515699ca3291e498c523935405c',\n               'eNrz9NRi4GBgYOBmYGBkYWAEssAYic3EyAymQaqYoWzWBgYGNigbhAWh6iexMTCIIomLIZkjDmUzAbEMkhoHIJsBiCYxjIJRMApGwSgY2QAAPu0DiA=='],\n 'colormap_absent': [137,\n                     'bd54979624f3ffb713342e0458caff9e17c477f940cda2f0960feebfabbc9556',\n                     'eNrz9NRi4GBgYOBiYGBkYWAEssAYic3EyAymBYCYGcpmbWBgYIOyQVgQqr4NiEWRxMWQzBGHspmBWAZJDQOEBgDj8wKm'],\n 'colormap16': [1685,\n                '5f38e0ead90b97949fb0c98c63f9c68d00ba9181db6631ecb8927376dcc75f4d',\n                'eNrz9NRi4GBgYOBmYGBkYWAEssAYic3EyAymBYCYGcpmbWBgYIOyQVgQqn4SGwODKJK4GJI54lA2MxDLIKlxALJBgpMYRsEoGAWjYBSMZACsEwB78wOS']}\nPACKAGE_PINS = {'PIL/ImageFile.py': '983c4f35e0c51046d4c636998ec77000cd2da208870a03b205e104cd0b2df629',\n 'PIL/_version.py': 'fe7d75d4019326aca24ae57d4ab190cd62ba1225b175051119cd61cd87e42b1d',\n 'PIL/__init__.py': '7361b6ad3878589affe5956e4a4da24d71398b0891fd3a8c15e9362217b4ca01',\n 'PIL/features.py': '14f904863b416914aaa6480735876ec31885b7272ee0d8d92b010c5b1b5e98f5',\n 'PIL/TiffTags.py': '766dea350424b9a2caab3513be794689403c2b7222cd038006f69cff08edb45d',\n 'PIL/TiffImagePlugin.py': '440b2a3a80b280d18cda3fc70d9fd206e2fa44e9897c9550f70b51395047b273',\n 'PIL/_imaging.cpython-311-darwin.so': '6d09b1fb925779fff32b5b8a4c348a42c201d350d0e0fd8cdc00410b6779e596',\n 'PIL/Image.py': '04af3f2db7ffdd61e829fa7aa8f5481d394a0a4129a8fd8aa5b13908928fcd6d',\n 'PIL/.dylibs/libtiff.6.dylib': 'e8988717b7470394a47d5ccd343130796b24ee70b6d885d3818e0e0b53238be1',\n 'pillow-12.3.0.dist-info/WHEEL': 'de2586b77651a8bf1b7778c3609979713ebc901784a837fca80ed4969fc82e57',\n 'pillow-12.3.0.dist-info/METADATA': '8fc07146cab33ef7582361dab6eac596d340b03ec648f18217da7894f73838c8'}\nCASES = tuple(FIXTURES)\nWALL_SECONDS = 30\nFILE_LIMIT = 1024 * 1024\nPIPE_LIMIT = 128 * 1024\n\n\ndef limits():\n    resource.setrlimit(resource.RLIMIT_CORE, (0, 0))\n    resource.setrlimit(resource.RLIMIT_CPU, (10, 10))\n    resource.setrlimit(resource.RLIMIT_FSIZE, (FILE_LIMIT, FILE_LIMIT))\n\n\ndef fixture(case):\n    size, expected_hash, encoded = FIXTURES[case]\n    decoder = zlib.decompressobj()\n    data = decoder.decompress(base64.b64decode(encoded, validate=True), size + 1)\n    if (len(data) != size or not decoder.eof or decoder.unused_data\n            or decoder.unconsumed_tail\n            or hashlib.sha256(data).hexdigest() != expected_hash):\n        raise ValueError(\"fixture integrity check failed\")\n    return data\n\n\ndef field(data, wanted):\n    if len(data) \u003c 8 or data[:4] not in (b\"II*\\0\", b\"MM\\0*\"):\n        raise ValueError(\"not classic TIFF\")\n    endian = \"\u003c\" if data[:2] == b\"II\" else \"\u003e\"\n    directory = struct.unpack_from(endian + \"I\", data, 4)[0]\n    count = struct.unpack_from(endian + \"H\", data, directory)[0]\n    if directory + 2 + count * 12 + 4 \u003e len(data):\n        raise ValueError(\"truncated directory\")\n    for number in range(count):\n        offset = directory + 2 + number * 12\n        tag, kind, length = struct.unpack_from(endian + \"HHI\", data, offset)\n        if tag != wanted:\n            continue\n        size = {1: 1, 2: 1, 3: 2, 4: 4, 7: 1}[kind] * length\n        start = offset + 8 if size \u003c= 4 else struct.unpack_from(endian + \"I\", data, offset + 8)[0]\n        if start + size \u003e len(data):\n            raise ValueError(\"truncated field\")\n        return kind, length, data[start:start + size], endian\n    return None\n\n\ndef checked_image_module(package):\n    if (sys.implementation.name != \"cpython\" or sys.version_info[:2] != (3, 11)\n            or sys.platform != \"darwin\" or platform.machine() != \"arm64\"):\n        raise RuntimeError(\"requires macOS ARM64 CPython 3.11\")\n    for relative, expected in PACKAGE_PINS.items():\n        path = (package / relative).resolve(strict=True)\n        if not path.is_relative_to(package) or hashlib.sha256(path.read_bytes()).hexdigest() != expected:\n            raise RuntimeError(\"reviewed wheel file mismatch\")\n    process_symbols = ctypes.CDLL(None)\n    for symbol in (\"__asan_init\", \"__asan_address_is_poisoned\", \"__asan_region_is_poisoned\"):\n        getattr(process_symbols, symbol)\n    sys.path.insert(0, str(package))\n    from PIL import Image, features\n    if (Image.__version__ != \"12.3.0\" or features.version(\"libtiff\") != \"4.7.1\"\n            or Path(Image.__file__).resolve() != package / \"PIL/Image.py\"\n            or Path(Image.core.__file__).resolve() != package / \"PIL/_imaging.cpython-311-darwin.so\"):\n        raise RuntimeError(\"unexpected Pillow or libtiff\")\n    return Image\n\n\ndef emit(descriptor, message):\n    os.write(descriptor, (json.dumps(message, sort_keys=True) + \"\\n\").encode(\"ascii\"))\n\n\ndef child(case, package):\n    descriptor = os.dup(1)\n    with open(os.devnull, \"wb\") as quiet:\n        os.dup2(quiet.fileno(), 1)\n    try:\n        image_module = checked_image_module(package)\n        data = fixture(case)\n        with image_module.open(io.BytesIO(data)) as image:\n            image.load()\n            if image.size != (1, 1) or image.info.get(\"compression\") != \"packbits\":\n                raise ValueError(\"unexpected input geometry\")\n            bits = 8 if case == \"colormap8\" else 16\n            if image.tag_v2[258] != (bits,):\n                raise ValueError(\"unexpected sample width\")\n            if case == \"colormap_absent\":\n                if 320 in image.tag_v2:\n                    raise ValueError(\"unexpected ColorMap\")\n            elif image.tag_v2[320] != (0,) * 768:\n                raise ValueError(\"unexpected ColorMap planes\")\n            emit(descriptor, {\"ready\": True})\n            with io.BytesIO() as output:\n                image.save(output, format=\"TIFF\")\n                if case == \"colormap16\":\n                    emit(descriptor, {\"unsafe_save_returned\": True})\n                    return 1\n                encoded = output.getvalue()\n                saved_bits = field(encoded, 258)\n                map_field = field(encoded, 320)\n                bits_preserved = (saved_bits is not None and saved_bits[:2] == (3, 1)\n                                  and struct.unpack(saved_bits[3] + \"H\", saved_bits[2])[0] == bits)\n                map_preserved = (map_field is None if case == \"colormap_absent\"\n                                 else map_field is not None and map_field[:2] == (3, 768)\n                                 and map_field[2] == bytes(1536))\n                emit(descriptor, {\"control_passed\": bool(bits_preserved and map_preserved)})\n        return 0\n    except Exception:\n        emit(descriptor, {\"child_failed\": True})\n        return 2\n    finally:\n        os.close(descriptor)\n\n\ndef run_case(case, package, runtime):\n    with tempfile.TemporaryDirectory(prefix=\"pillow-check-\") as temporary:\n        environment = {\n            \"PATH\": \"/usr/bin:/bin:/usr/sbin:/sbin\",\n            \"HOME\": temporary,\n            \"TMPDIR\": temporary,\n            \"LC_ALL\": \"C\",\n            \"DYLD_INSERT_LIBRARIES\": str(runtime),\n            \"ASAN_OPTIONS\": \"detect_leaks=0:halt_on_error=1:abort_on_error=0:exitcode=86:\"\n                            \"disable_coredump=1:symbolize=1:malloc_fill_byte=165:\"\n                            \"max_malloc_fill_size=1048576:log_path=stderr\",\n        }\n        command = [sys.executable, \"-I\", \"-S\", \"-B\", str(Path(__file__).resolve()),\n                   \"--package-path\", str(package), \"--asan-runtime\", str(runtime), \"--child\", case]\n        output = bytearray()\n        diagnostics = bytearray()\n        deadline = time.monotonic() + WALL_SECONDS\n        with subprocess.Popen(command, cwd=temporary, env=environment, stdin=subprocess.DEVNULL,\n                              stdout=subprocess.PIPE, stderr=subprocess.PIPE,\n                              preexec_fn=limits, start_new_session=True) as process:\n            bounded = True\n            try:\n                with selectors.DefaultSelector() as selector:\n                    selector.register(process.stdout, selectors.EVENT_READ, output)\n                    selector.register(process.stderr, selectors.EVENT_READ, diagnostics)\n                    while selector.get_map():\n                        remaining = deadline - time.monotonic()\n                        if remaining \u003c= 0:\n                            raise TimeoutError\n                        for key, events in selector.select(min(remaining, 0.25)):\n                            chunk = os.read(key.fileobj.fileno(), 8192)\n                            if not chunk:\n                                selector.unregister(key.fileobj)\n                            else:\n                                key.data.extend(chunk)\n                                if len(output) + len(diagnostics) \u003e PIPE_LIMIT:\n                                    raise BufferError\n                    process.wait(timeout=max(0.001, deadline - time.monotonic()))\n            except (TimeoutError, BufferError, subprocess.TimeoutExpired):\n                bounded = False\n                try:\n                    os.killpg(process.pid, 9)\n                except ProcessLookupError:\n                    pass\n                process.wait()\n            status = process.returncode\n        try:\n            messages = [json.loads(line) for line in output.splitlines()]\n        except (ValueError, UnicodeError):\n            messages = []\n        ready = bool(messages and messages[0] == {\"ready\": True})\n        if case == \"colormap16\":\n            observed = bool(bounded and ready and status == 86 and len(messages) == 1\n                            and b\"AddressSanitizer: heap-buffer-overflow\" in diagnostics\n                            and b\"READ of size 131072\" in diagnostics\n                            and b\"0 bytes after 1536-byte region\" in diagnostics\n                            and b\"PyImaging_LibTiffEncoderNew\" in diagnostics\n                            and b\"_TIFFmemcpy\" in diagnostics)\n        else:\n            observed = bool(bounded and ready and status == 0\n                            and messages == [{\"ready\": True}, {\"control_passed\": True}]\n                            and b\"AddressSanitizer:\" not in diagnostics)\n        output.clear()\n        diagnostics.clear()\n        return {\"case\": case, \"expected_observation\": observed, \"guard_passed\": ready,\n                \"within_limits\": bounded}\n\n\ndef main():\n    parser = argparse.ArgumentParser(description='Fixed ColorMap extent mismatch: controls first, ASAN-only trigger.')\n    parser.add_argument(\"--package-path\", required=True, type=Path,\n                        help=\"reviewed existing installation root containing PIL\")\n    parser.add_argument(\"--asan-runtime\", required=True, type=Path,\n                        help=\"reviewed existing compatible ASAN dylib; never loaded late\")\n    parser.add_argument(\"--child\", choices=CASES, help=argparse.SUPPRESS)\n    arguments = parser.parse_args()\n    if arguments.child:\n        limits()\n        return child(arguments.child, arguments.package_path.resolve())\n    try:\n        package = arguments.package_path.resolve(strict=True)\n        runtime = arguments.asan_runtime.resolve(strict=True)\n        if not package.is_dir() or not runtime.is_file():\n            raise ValueError(\"invalid existing paths\")\n        for case in CASES:\n            fixture(case)\n        results = [run_case(case, package, runtime) for case in CASES]\n    except Exception:\n        print(json.dumps({\"setup_failed\": True}))\n        return 2\n    for result in results:\n        print(json.dumps(result, sort_keys=True))\n    return 0 if all(result[\"expected_observation\"] for result in results) else 1\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n```","status":"published","published":"2026-10-20T20:51:40Z","modified":"2026-10-28T11:10:41.233140457Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"}],"cwes":["CWE-125"],"updated_at":"2026-11-02T15:46:52.891208Z"},{"id":"PYSEC-2026-4334","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T22:17:09.76Z","modified":"2026-10-28T11:10:41.233140457Z","cvss":[{"version":"3.1","score":7.1,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H","level":"high"}],"updated_at":"2026-11-02T15:52:57.671918Z"},{"id":"UBUNTU-CVE-2026-112896","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-19T22:17:00Z","modified":"2026-11-04T20:03:33.093714413Z","cvss":[{"version":"4.0","score":8.8,"vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N","level":"high"},{"version":"3.1","score":7.1,"vector":"CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:H","level":"high"}],"updated_at":"2026-11-04T21:44:25.758797Z"},{"id":"BIT-pillow-2026-112896","title":"Pillow: TIFF encoder ColorMap plane-size mismatch causes a heap out-of-bounds read","description":"Pillow is a Python imaging library. Prior to 13.0.0, Pillow's TIFF encoder can read beyond a ColorMap buffer when retained TIFF metadata has a plane size inconsistent with BitsPerSample. A small TIFF can be opened normally and then retain a crafted ColorMap that is reused when the image is saved. An application that re-encodes the attacker's TIFF with libtiff can trigger the out-of-bounds read. This can disclose heap memory in the output or crash the process. This issue is fixed in version 13.0.0.","status":"published","published":"2026-10-26T05:55:57.763Z","modified":"2026-10-29T10:45:10.287446706Z","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:12230-1","title":"python313-Pillow-13.0.0-1.1 on GA media","description":"These are all security issues fixed in the python313-Pillow-13.0.0-1.1 package on the GA media of openSUSE Tumbleweed.","status":"published","published":"2026-10-22T00:00:00Z","modified":"2026-10-23T17:23:11.6560743Z","updated_at":"2026-11-02T16:58:40.245664Z"},{"id":"ROOT-APP-PYPI-CVE-2026-112896","title":"CVE-2026-112896 in pillow - Patched by Root","description":"Root has patched CVE-2026-112896 in the pillow package for Root:PyPI. Multiple fixed versions available.","status":"published","published":"2026-11-06T10:13:42Z","modified":"2026-11-06T11:15:06.444244658Z","updated_at":"2026-11-06T11:42:45.055937Z"},{"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"}]}}
