CVE-2026-59204: Pillow JPEG2000 Tiled Decode Memory Exhaustion
A crafted multi-tile JPEG2000 image can force Pillow to grow its internal scratch buffer far beyond any single tile's size, exhausting process memory and crashing the decoder.
The problem
In `src/libImaging/Jpeg2KDecode.c`, the variable `total_component_width` is initialised once before the tile loop and keeps accumulating across every tile decoded.
That runaway value feeds the `tile_bytes` calculation, which drives a `realloc` on `state->buffer`. By the final tile the buffer can reach roughly the fully-decompressed size of the entire image. A crafted JPEG2000 file with many tiles therefore forces transient memory usage well above what the image data alone would require, leading to out-of-memory failures on constrained hosts.
Affected versions are Pillow 8.2.0 through 12.2.x, because the accumulation bug was itself introduced by the earlier CVE-2021-25287/25288 fix.
Proof of concept
A working proof-of-concept for CVE-2026-59204 in pillow, with the exact payload below.
# Step 1: generate the crafted JPEG2000 file (3664x3664 RGBA, 2x2 tiles)
python exercise_j2k_tile_realloc.py make poc_3664_rgba_tile1832.jp2 \
--size 3664 --tile 1832
# Step 2: trigger the excessive allocation (no memory cap)
python exercise_j2k_tile_realloc.py load poc_3664_rgba_tile1832.jp2
# Step 3: trigger OOM under a 160 MB address-space cap
python exercise_j2k_tile_realloc.py load poc_3664_rgba_tile1832.jp2 --limit-mb 160
# Equivalent minimal Python reproducer (vulnerable build)
from PIL import Image
Image.open("poc_3664_rgba_tile1832.jp2").load()The root cause is a missing reset of `total_component_width` inside the per-tile decode loop in `Jpeg2KDecode.c`. On each iteration the variable grows by the current tile's component widths instead of being recalculated from scratch, so later tiles are treated as if they carried the combined width of all prior tiles.
This bloated value propagates into `tile_bytes` and then into the `realloc` call at line 876, making the scratch buffer grow monotonically toward the fully-decompressed image size.
The patch (commit 13ada41, PR #9704) moves the `total_component_width = 0` initialisation to inside the tile loop, so it resets before each tile's component widths are summed. CWE-770 (Allocation of Resources Without Limits or Throttling) and CWE-789 (Memory Allocation with Excessive Size Value) both apply.
The fix
Upgrade Pillow to 12.3.0, which contains the one-line fix that resets `total_component_width` at the start of each tile iteration. No workaround is available short of avoiding JPEG2000 inputs from untrusted sources (pass `formats=[...]` excluding `jpeg2k` to `Image.open()` as a temporary mitigation).
Reported by Fr3v1.
Related research
- high · 7.5CVE-2026-54059CVE-2026-54059: Pillow PcfFontFile Decompression Bomb Protection Bypass
- high · 7.5CVE-2026-54060CVE-2026-54060: Pillow FontFile.compile() Decompression Bomb Bypass
- high · 7.5CVE-2026-55379CVE-2026-55379: Pillow BdfFontFile Decompression Bomb Protection Bypass
- high · 7.5CVE-2026-55380CVE-2026-55380: Pillow GdImageFile Decompression Bomb (DoS)