Skip to main content
Vulnerability Database/CVE-2026-112898

CVE-2026-112898: Pillow Information Disclosure Vulnerability

CVE-2026-112898 is an information disclosure flaw in Pillow that exposes uninitialized stack memory through empty palettes. Attackers can extract sensitive data including addresses and stack-canary values. This article covers technical details, affected versions, impact, and mitigation steps.

Published:

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.

c
// 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
bash
# 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.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.