Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Published Oct 21, 2026 · Updated Nov 3, 2026
Original summary
Normalized public record · Updated Nov 3, 2026
Pillow multi-frame loaders can reuse stale native image storage after mode transitions
Publisher and source text is retained separately from QFinch interpretation.
QFinch assessment
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.
Potential 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=None control was clean 10/10 times. Reproduction Evidence Tested version: MPO terminal proof environments: Server-side web-upload proof: 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: The crafted image shape is: Frame 0 allocates native L storage. Frame 1 changes the logical mode and decoder state but can reuse the old native storage. The frame 1 decoder writes using the wider mode into the narrow storage. Suggested Fix Direction Before reusing native ImagingCore storage, Pillow should verify that the retained storage is compatible with the current logical image mode and size. A central guard in ImageFile.loadprepare() could compare native storage mode/size against self.mode / self.size and allocate fresh storage when they differ. Alternatively, every mode-changing seek() / open() path should explicitly invalidate im before decoding. Caution: TIFF has specialized tile-size behavior and already has a mode/size guard, so a central fix should avoid regressing TIFF tile-buffer handling. Credit Methodology Note AI-assisted analysis was used for discovery and verification support. Claims in this report are limited to the local artifacts, controls, and command outputs described above.
Technical context
The root cause appears to be that ImageFile.loadprepare() 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: 1. PSD 2. MPO/JPEG Verified native memory corruption, crash, over-read behavior, and scoped harmless-marker command execution on Pillow 12.3.0. 3. DCX 4. MIC TIFF appears to be a useful positive control because its seek() path checks both size and mode before reusing storage.
Evidence: public source recordsAffected products
| Affected package, product or range | Fix status |
|---|---|
| Consult the referenced vendor advisory for affected versions. | Consult source advisory |
Matching a version is only one part of assessing exposure. QFinch has not assessed your environment on this public page.
Remediation
Apply the vendor-supported fixed release or mitigation, then verify the affected component is no longer reachable.
- Confirm the affected product and reachable code path exist in your environment.
- Apply the vendor-supported patch or mitigation from the primary advisory.
- Rescan the exact deployed revision and retain verification evidence.
Evidence coverage
- SSVC priority
- MEDIUM
- Confidence
- Not available
- Aliases
- None reported
Scores appear only when corresponding evidence exists. Missing data is never interpreted as zero risk.
Sources and record history
Record history
Published Oct 21, 2026 · Last refreshed Nov 3, 2026