{ "schema_version": "1.4.0", "id": "GHSA-vjc4-5qp5-m44j", "modified": "2026-07-20T23:18:33Z", "published": "2026-07-20T23:18:32Z", "aliases": [ "CVE-2026-59204" ], "summary": "Pillow JPEG2000 tiled decode retains a growing scratch buffer and can be used for denial of service", "details": "### Summary\n`src/libImaging/Jpeg2KDecode.c:853` accumulates `total_component_width` across every tile in a JPEG2000 image instead of recomputing it per tile. That accumulated value is then used in the `tile_bytes` calculation at `src/libImaging/Jpeg2KDecode.c:868`, which can make the decoder grow `state->buffer` via `realloc` at `src/libImaging/Jpeg2KDecode.c:876` up to roughly one full image's decompressed size even when each tile is small. A crafted tiled JPEG2000 file can therefore force substantially higher transient memory usage and trigger out-of-memory failures during decoding. Based on current evidence, the supported impact is denial of service, not memory corruption.\n\n### Details\n- Location: `src/libImaging/Jpeg2KDecode.c:853`\n- Root cause: `total_component_width` is initialized only once before the tile loop and keeps growing across tiles. It is then used to derive `tile_bytes`, so later tiles are treated as if they had the combined component width of all earlier tiles.\n- Dangerous operation: `tile_bytes` is promoted into `tile_info.data_size`, then `state->buffer` is grown with `realloc` at `src/libImaging/Jpeg2KDecode.c:876`.\n- Reachability: any attacker-controlled JPEG2000 image with many tiles reaches this path during normal `Image.open(...).load()` decoding.\n\n\n### PoC\nThe attached helper script and testcase were used:\n[exercise_j2k_tile_realloc.zip](https://github.com/user-attachments/files/28099912/exercise_j2k_tile_realloc.zip)\n\n\nGenerate the testcase:\n\n```bash\npythonexercise_j2k_tile_realloc.py make poc_3664_rgba_tile1832.jp2 \\\n --size 3664 --tile 1832\n```\n\nExpected geometry from the helper:\n\n- image size: `3664 x 3664`\n- mode: `RGBA`\n- tile size: `1832 x 1832` (`2x2` tiles)\n- `image_bytes=53699584`\n- uncapped RSS observed:\n - vulnerable build: `maxrss_kb=180264`\n - fixed comparison build: `maxrss_kb=138404`\n\nLoad it with the current vulnerable build:\n\n```bash\npython exercise_j2k_tile_realloc.py load poc_3664_rgba_tile1832.jp2\n```\n\nLoad it again under a 160 MB address-space cap:\n\n```bash\npython exercise_j2k_tile_realloc.py load poc_3664_rgba_tile1832.jp2 --limit-mb 160\n```\n\n### Impact\nConservative impact: denial of service through memory exhaustion during JPEG2000 decoding.", "severity": [ { "type": "CVSS_V4", "score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N" } ], "affected": [ { "package": { "ecosystem": "PyPI", "name": "pillow" }, "ranges": [ { "type": "ECOSYSTEM", "events": [ { "introduced": "8.2.0" }, { "fixed": "12.3.0" } ] } ] } ], "references": [ { "type": "WEB", "url": "https://github.com/python-pillow/Pillow/security/advisories/GHSA-vjc4-5qp5-m44j" }, { "type": "ADVISORY", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-59204" }, { "type": "WEB", "url": "https://github.com/python-pillow/Pillow/pull/9704" }, { "type": "WEB", "url": "https://github.com/python-pillow/Pillow/commit/13ada41172142f2fd9f0906f615a00ea623a11ca" }, { "type": "PACKAGE", "url": "https://github.com/python-pillow/Pillow" }, { "type": "WEB", "url": "https://github.com/python-pillow/Pillow/releases/tag/12.3.0" } ], "database_specific": { "cwe_ids": [ "CWE-770", "CWE-789" ], "severity": "HIGH", "github_reviewed": true, "github_reviewed_at": "2026-07-20T23:18:32Z", "nvd_published_at": "2026-07-14T16:17:02Z" } }