CVE-2026-96639
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
Attack profile
How easy it is to exploit
- Attack vector
- Network
- Complexity
- Low
- Privileges needed
- None
- User interaction
- None
What an attacker can affect
- Confidentiality
- High
- Integrity
- High
- Availability
- High
Exploit likelihood
What each source reports
CVE List Score used
9.8
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
CVE-2026-96639- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-125, CWE-787
- Updated
- 2026-11-02
EUVDEUVD-2026-107573
9.8
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
EUVD-2026-107573- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
GitHub AdvisoriesGHSA-v9pv-5gc8-w2m6
9.8
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Summary
Pillow multi-frame image loaders can retain native ImagingCore storage allocated for one frame while changing the Python-side image mode and decoder tiles for a later frame.
If frame 0 allocates storage for a narrow mode such as L, and a later frame of the same dimensions switches to a wider mode such as RGB or CMYK, Pillow may decode wider pixels into the stale narrow native buffer. This causes native
out-of-bounds writes and process crashes. In the MPO/JPEG path, I also demonstrated command execution in matching Python/Pillow runtime profiles through Pillow's documented ImageSequence.Iterator + frame.load() path.
Details
The root cause appears to be that ImageFile.load_prepare() only reallocates native storage when _im is None. It does not verify that the retained native ImagingCore storage still matches self.mode and self.size.
Several multi-frame seek() / _open() paths can update self._mode and decoder tiles for a later frame without invalidating _im. The decoder then writes or reads according to the new frame mode while the native storage still belongs to the old frame mode.
Verified affected surfaces:
-
PSD
PsdImageFile.seek()can switch from grayscale/nativeLstorage to RGB layer decoding while retaining_im. This produces attacker-controlled native out-of-bounds writes and reliable crashes. -
MPO/JPEG
A crafted two-frame MPO can make frame 0 allocate
Lstorage, then make frame 1 decodeCMYK/RGBdata into the retainedLstorage. The documentedImageSequence.Iteratorpath reaches this withframe.load(). Verified native memory corruption, crash, over-read behavior, and scoped harmless-marker command execution on Pillow 12.3.0. -
DCX
DcxImageFile.seek()callsPcxImageFile._open(), which changes mode/tile state without a native-storage mode guard. A crafted DCX withLthenRGBPCX frames produces retained-storage mismatch and crash. -
MIC
MIC can call
TiffImageFile._open()directly and bypass TIFF's normal guardedseek()behavior. With aNextIFD-based reachable frame transition, this produces retained-storage mismatch and crash.
TIFF appears to be a useful positive control because its seek() path checks
both size and mode before reusing storage.
Impact
- process crash / denial of service,
- native out-of-bounds write,
- native over-read in the MPO path.
Stronger proven impact:
- command execution in matching Python/Pillow runtime profiles through the MPO/JPEG path.
Server-side deployment impact:
- Create a local vulnerable web upload service that accepts an unauthenticated MPO upload and automatically processes it with Pillow.
- The attack is one HTTP request and requires no user interaction after upload.
- The fixed harmless command-output marker succeeded 10/10 times.
- The
_im=Nonecontrol was clean 10/10 times.
Reproduction Evidence
Tested version:
Pillow 12.3.0
MPO terminal proof environments:
Python 3.12.3 + Pillow 12.3.0, clean PyPI environment:
marker 10/10, control 0/10
Python 3.13.14 + Pillow 12.3.0, prepared profile:
marker 10/10, control 0/10
Server-side web-upload proof:
control 1..10: MARKER_MISSING
control_summary ok 0 missing 10 /10
vulnerable 1..10: MARKER_OK marker_output <server-user>
vulnerable_summary ok 10 missing 0 /10
The terminal proof uses a fixed harmless marker command only. It does not use a reverse shell, persistence, data exfiltration, or arbitrary attacker-selected commands.
I can provide the following private artifacts to maintainers:
- minimal crash fixtures for PSD, MPO, DCX, and MIC,
- non-weaponized MPO marker PoC,
- local vulnerable web upload demo,
- gdb/GEF geometry receipts,
- control-run logs,
- proposed regression-test shape.
Minimal Reproduction Shape
For the MPO path, the vulnerable consumer shape is:
from io import BytesIO
from PIL import Image, ImageSequence
source =…
- Record
GHSA-v9pv-5gc8-w2m6- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-125, CWE-787
- Updated
- 2026-11-02
NVD
9.8
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
CVE-2026-96639- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-125, CWE-787
- Updated
- 2026-11-02
OSV
9.8
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
CVE-2026-96639- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-125, CWE-787
- Updated
- 2026-11-02
OSVDEBIAN-CVE-2026-96639
9.8
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
DEBIAN-CVE-2026-96639- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
Show 7 moreShow less
OSVGHSA-v9pv-5gc8-w2m6
9.8
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Summary
Pillow multi-frame image loaders can retain native ImagingCore storage allocated for one frame while changing the Python-side image mode and decoder tiles for a later frame.
If frame 0 allocates storage for a narrow mode such as L, and a later frame of the same dimensions switches to a wider mode such as RGB or CMYK, Pillow may decode wider pixels into the stale narrow native buffer. This causes native
out-of-bounds writes and process crashes. In the MPO/JPEG path, I also demonstrated command execution in matching Python/Pillow runtime profiles through Pillow's documented ImageSequence.Iterator + frame.load() path.
Details
The root cause appears to be that ImageFile.load_prepare() only reallocates native storage when _im is None. It does not verify that the retained native ImagingCore storage still matches self.mode and self.size.
Several multi-frame seek() / _open() paths can update self._mode and decoder tiles for a later frame without invalidating _im. The decoder then writes or reads according to the new frame mode while the native storage still belongs to the old frame mode.
Verified affected surfaces:
-
PSD
PsdImageFile.seek()can switch from grayscale/nativeLstorage to RGB layer decoding while retaining_im. This produces attacker-controlled native out-of-bounds writes and reliable crashes. -
MPO/JPEG
A crafted two-frame MPO can make frame 0 allocate
Lstorage, then make frame 1 decodeCMYK/RGBdata into the retainedLstorage. The documentedImageSequence.Iteratorpath reaches this withframe.load(). Verified native memory corruption, crash, over-read behavior, and scoped harmless-marker command execution on Pillow 12.3.0. -
DCX
DcxImageFile.seek()callsPcxImageFile._open(), which changes mode/tile state without a native-storage mode guard. A crafted DCX withLthenRGBPCX frames produces retained-storage mismatch and crash. -
MIC
MIC can call
TiffImageFile._open()directly and bypass TIFF's normal guardedseek()behavior. With aNextIFD-based reachable frame transition, this produces retained-storage mismatch and crash.
TIFF appears to be a useful positive control because its seek() path checks
both size and mode before reusing storage.
Impact
- process crash / denial of service,
- native out-of-bounds write,
- native over-read in the MPO path.
Stronger proven impact:
- command execution in matching Python/Pillow runtime profiles through the MPO/JPEG path.
Server-side deployment impact:
- Create a local vulnerable web upload service that accepts an unauthenticated MPO upload and automatically processes it with Pillow.
- The attack is one HTTP request and requires no user interaction after upload.
- The fixed harmless command-output marker succeeded 10/10 times.
- The
_im=Nonecontrol was clean 10/10 times.
Reproduction Evidence
Tested version:
Pillow 12.3.0
MPO terminal proof environments:
Python 3.12.3 + Pillow 12.3.0, clean PyPI environment:
marker 10/10, control 0/10
Python 3.13.14 + Pillow 12.3.0, prepared profile:
marker 10/10, control 0/10
Server-side web-upload proof:
control 1..10: MARKER_MISSING
control_summary ok 0 missing 10 /10
vulnerable 1..10: MARKER_OK marker_output <server-user>
vulnerable_summary ok 10 missing 0 /10
The terminal proof uses a fixed harmless marker command only. It does not use a reverse shell, persistence, data exfiltration, or arbitrary attacker-selected commands.
I can provide the following private artifacts to maintainers:
- minimal crash fixtures for PSD, MPO, DCX, and MIC,
- non-weaponized MPO marker PoC,
- local vulnerable web upload demo,
- gdb/GEF geometry receipts,
- control-run logs,
- proposed regression-test shape.
Minimal Reproduction Shape
For the MPO path, the vulnerable consumer shape is:
from io import BytesIO
from PIL import Image, ImageSequence
source =…
- Record
GHSA-v9pv-5gc8-w2m6- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Weakness
- CWE-125, CWE-787
- Updated
- 2026-11-02
OSVPYSEC-2026-4339
9.8
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
PYSEC-2026-4339- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-02
OSVUBUNTU-CVE-2026-96639
9.8
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
UBUNTU-CVE-2026-96639- CVSS 3.1
- 9.8 Critical
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H - Updated
- 2026-11-04
OSVBIT-pillow-2026-96639 No score
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Pillow is a Python imaging library. From 11.0.0 until 13.0.0, multi-frame loaders can retain an ImagingCore allocation created for a narrow frame mode while a later frame of the same dimensions changes the logical mode and decoder tiles to wider RGB or CMYK data. ImageFile.load_prepare() reuses the stale native storage because _im is non-null without verifying that its mode and size still match. Crafted PSD, MPO, DCX, or MIC frame transitions can then write wider pixels into the narrow allocation, causing native out-of-bounds writes and crashes. The MPO path can also cause a native over-read and, in demonstrated runtime-specific profiles, command execution. This issue is fixed in version 13.0.0.
- Record
BIT-pillow-2026-96639- Updated
- 2026-11-02
OSVCLSA-2026-1792743656 No score
TuxCare security update for pillow (6 CVEs)
TuxCare rebuilt pillow to address 6 CVEs. Fixed in 11.3.0.post9+tuxcare.
- Record
CLSA-2026-1792743656- Updated
- 2026-11-02
OSVCLSA-2026-1792752131 No score
TuxCare security update for pillow (6 CVEs)
TuxCare rebuilt pillow to address 6 CVEs. Fixed in 11.2.1.post11+tuxcare.
- Record
CLSA-2026-1792752131- Updated
- 2026-11-02
OSVROOT-APP-PYPI-CVE-2026-96639 No score
CVE-2026-96639 in pillow - Patched by Root
Root has patched CVE-2026-96639 in the pillow package for Root:PyPI. Multiple fixed versions available.
- Record
ROOT-APP-PYPI-CVE-2026-96639- Updated
- 2026-11-05
Affected products
Affected packages
| Package | Affected | Fixed |
|---|---|---|
pillowPyPI |
11.0.0 up to 13.0.0 | 13.0.0 |
pillowBitnami |
11.0.0 up to 13.0.0 | 13.0.0 |
pillowTuxCare:PyPI From an advisory covering 6 CVEs |
11.3.0 up to 11.3.0.post9+tuxcare, plus listed versions 11.3.0.post1+tuxcare11.3.0.post2+tuxcare11.3.0.post3+tuxcare11.3.0.post4+tuxcare11.3.0.post5+tuxcare11.3.0.post6+tuxcare11.3.0.post7+tuxcare11.3.0.post8+tuxcare |
11.3.0.post9+tuxcare |
pillowTuxCare:PyPI From an advisory covering 6 CVEs |
11.2.1 up to 11.2.1.post11+tuxcare, plus listed versions 11.2.1.post1+tuxcare11.2.1.post10+tuxcare11.2.1.post2+tuxcare11.2.1.post3+tuxcare11.2.1.post4+tuxcare Show 5 moreShow less11.2.1.post5+tuxcare11.2.1.post6+tuxcare11.2.1.post7+tuxcare11.2.1.post8+tuxcare11.2.1.post9+tuxcare |
11.2.1.post11+tuxcare |
pillowDebian:12 |
All versions, plus listed versions 10.0.0-110.1.0-110.2.0-110.3.0-110.3.0-2 Show 19 moreShow less10.4.0-110.4.0-1.111.1.0-111.1.0-211.1.0-311.1.0-411.1.0-511.2.1-111.3.0-112.0.0-112.1.0-112.1.1-112.1.1-212.2.0-112.3.0-113.0.0-19.4.0-1.19.4.0-1.1+deb12u19.5.0-1 |
None known |
pillowDebian:13 |
All versions, plus listed versions 11.1.0-511.1.0-5+deb13u111.1.0-5+deb13u211.1.0-5+deb13u311.1.0-5+deb13u4 Show 9 moreShow less11.2.1-111.3.0-112.0.0-112.1.0-112.1.1-112.1.1-212.2.0-112.3.0-113.0.0-1 |
None known |
pillowDebian:14 |
All versions, plus listed versions 11.1.0-511.2.1-111.3.0-112.0.0-112.1.0-1 Show 5 moreShow less12.1.1-112.1.1-212.2.0-112.3.0-113.0.0-1 |
None known |
pillowPyPI |
11.0.0 up to 13.0.0, plus listed versions 11.1.011.2.111.3.012.0.012.1.012.1.112.2.012.3.0 |
13.0.0 |
pillowRoot:PyPI |
before 12.3.0+aikido.3 | 12.3.0+aikido.312.3.0+aikido.411.0.0+aikido.1412.2.0+aikido.9 |
pillow-python2Ubuntu:Pro:20.04:LTS |
All versions, plus listed versions 6.2.1-3~build16.2.1-36.2.1-3ubuntu0.1~esm16.2.1-3ubuntu0.1~esm2 |
None known |
pillowUbuntu:26.10 |
All versions, plus listed versions 12.1.1-2ubuntu112.3.0-1ubuntu1 |
None known |
pillowUbuntu:26.04:LTS |
All versions, plus listed versions 11.3.0-1ubuntu111.3.0-1ubuntu212.0.0-1ubuntu112.1.1-1ubuntu112.1.1-2ubuntu1 Show 4 moreShow less12.1.1-2ubuntu1.112.1.1-2ubuntu1.212.1.1-2ubuntu1.312.1.1-2ubuntu1.4 |
None known |
History
| Recorded | Changed | Sources |
|---|---|---|
| affected | ||
| affected | OSV | |
| affected | OSV | |
| affected | OSV | |
| affected, facts | OSV | |
| first indexed | CVE List, EUVD, GitHub Advisories, NVD, OSV |
Dates are when Every CVE recorded each change. Upstream dates may differ.
In short
CVE-2026-96639 is a critical vulnerability (CVSS 9.8) in Python Pillow, published October 21, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.66%, the 51st percentile. Fixed in pillow 13.0.0.
References
github.com/python-pillow/Pillow/security/advisories/GHSA-v9pv-5gc8-w2m6
github.com/python-pillow/Pillow/pull/10105
github.com/python-pillow/Pillow/commit/78d97d994579aef81aafe123773d525f40f2522a
github.com/python-pillow/Pillow/releases/tag/13.0.0
nvd.nist.gov/vuln/detail/CVE-2026-96639
github.com/advisories/GHSA-v9pv-5gc8-w2m6
Left out here: the rest of 2 source descriptions. The JSON record has them.