CVE-2026-112898 Overview
CVE-2026-112898 is an uninitialized memory use vulnerability [CWE-457] in the Pillow Python imaging library. The flaw affects versions 9.1.0 through 13.0.0 and allows disclosure of uninitialized stack memory when Image.quantize() is called with an empty palette. The function ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries. The conversion path then emits those uninitialized bytes as output pixel indices in the resulting image. An attacker who controls the palette image can retrieve stack contents from a returned PNG or GIF, exposing memory addresses and stack canary values.
Critical Impact
Attackers can harvest stack addresses and canary values from server-generated images, enabling bypass of ASLR and stack protection in downstream exploitation chains.
Affected Products
- Pillow versions 9.1.0 through 12.x
- Any Python application invoking Image.quantize() on attacker-supplied palette images
- Web services that process user-uploaded images and return quantized PNG or GIF output
Discovery Timeline
- 2026-10-19 - CVE-2026-112898 published to NVD
- 2026-10-20 - Last updated in NVD database
- Version 13.0.0 - Pillow releases fixed version
Technical Details for CVE-2026-112898
Vulnerability Analysis
The vulnerability resides in Pillow's palette conversion code path within src/libImaging/Palette.c and src/libImaging/Convert.c. When Image.quantize() executes against a palette image containing zero entries, the ImagingPaletteCacheUpdate() function initializes a distance array d[] but skips initialization of the companion cache array c[]. The subsequent iteration loop that normally populates c[] from palette entries never executes because palette->size is zero. As a result, c[] retains whatever stack data occupied those memory locations prior to function entry.
The conversion routine then reads from the uninitialized c[] array and writes those raw bytes directly into the output image as pixel index values. When the attacker encodes the output as PNG or GIF, the pixel data serializes the leaked stack contents. The leak occurs without triggering any out-of-bounds access, memory corruption, or process crash, making it silent and highly reliable for information gathering.
Root Cause
The root cause is a missing initialization statement for the cache array in ImagingPaletteCacheUpdate(). The code assumed palette->size would always be greater than zero and that the subsequent population loop would initialize every entry in c[]. The empty palette edge case violates this assumption. The patch adds c[i] = 0 to the initialization loop and additionally rejects empty palettes at the entry point to quantize().
Attack Vector
An attacker crafts a palette image with zero entries and submits it to any application that calls Image.quantize() on untrusted input. The application returns a PNG or GIF whose pixel bytes encode uninitialized stack memory. The attacker decodes the image and extracts leaked pointers, stack canary values, or other sensitive runtime data. The leak is remote, requires no authentication, and leaves no trace in standard application logs.
// Security patch - src/libImaging/Convert.c
alpha = mode == IMAGING_MODE_PA;
- if (palette == NULL) {
+ if (palette != NULL) {
+ if (palette->size == 0) {
+ return (Imaging)ImagingError_ValueError("empty palette");
+ }
+ } else {
/* FIXME: make user configurable */
if (imIn->bands == 1) {
palette = ImagingPaletteNew(IMAGING_MODE_RGB);
// Security patch - src/libImaging/Palette.c
for (i = 0; i < BOXVOLUME; i++) {
d[i] = (unsigned int)~0;
+ c[i] = 0;
}
for (i = 0; i < palette->size; i++) {
Source: GitHub Pillow Commit 13943c8
Detection Methods for CVE-2026-112898
Indicators of Compromise
- Inbound image uploads containing palette structures with zero palette entries
- Repeated small image uploads followed by downloads of quantized output from the same client
- Server-returned PNG or GIF files with high-entropy pixel data inconsistent with the submitted content
- Application logs showing frequent calls to Image.quantize() on externally supplied palette images
Detection Strategies
- Inventory running Python environments and identify Pillow versions between 9.1.0 and 12.x using pip list or software composition analysis tooling
- Hook or wrap PIL.Image.quantize() in application code to log palette size and reject zero-entry palettes
- Scan container images and CI artifacts for vulnerable Pillow wheels prior to deployment
Monitoring Recommendations
- Monitor web application telemetry for anomalous upload-then-download patterns targeting image processing endpoints
- Alert on outbound image responses with abnormal byte distributions from image conversion services
- Track dependency manifests across repositories to flag any pinned Pillow version below 13.0.0
How to Mitigate CVE-2026-112898
Immediate Actions Required
- Upgrade Pillow to version 13.0.0 or later across all production, staging, and development environments
- Audit application code paths that invoke Image.quantize(), convert(), or related palette operations on untrusted input
- Rebuild and redeploy container images that bundle Pillow as a transitive dependency
- Rotate any secrets or tokens stored in memory of long-running services that processed untrusted palette images
Patch Information
The fix is included in Pillow Release 13.0.0. The upstream commit 13943c8f adds explicit initialization of the cache array and raises a ValueError when quantize() is called with an empty palette. Full technical details are available in GitHub Security Advisory GHSA-rfgq-q3w2-pmv2.
Workarounds
- Validate palette image inputs before calling Image.quantize() and reject any image whose palette contains zero entries
- Wrap Image.quantize() calls in application-level guards that check palette.getpalette() length prior to invocation
- Restrict image conversion services to internal, authenticated callers until patching completes
# Upgrade Pillow to the fixed version
pip install --upgrade "Pillow>=13.0.0"
# Verify installed version
python -c "import PIL; print(PIL.__version__)"
# Application-level input validation example
# if palette_image.getpalette() is None or len(palette_image.getpalette()) == 0:
# raise ValueError("empty palette rejected")
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

