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

CVE-2026-112892: Pillow MSP RLE Decoder DOS Vulnerability

CVE-2026-112892 is a denial of service vulnerability in Pillow's MSP RLE decoder that allows attackers to exhaust system memory through crafted images. This article covers technical details, affected versions, and mitigation strategies.

Published:

CVE-2026-112892 Overview

Pillow, the widely used Python imaging library, contains a memory exhaustion flaw in its MSP run-length encoding (RLE) decoder. The decoder expands run-length output into a BytesIO buffer without enforcing the declared image size as an upper bound. An attacker who can submit a crafted MSP image to an application that loads pixel data can force unbounded in-memory allocation. The condition bypasses Pillow's existing decompression-bomb check because the declared pixel count remains within accepted limits. All versions from 4.1.0 up to but not including 13.0.0 are affected. The issue is tracked under [CWE-409: Improper Handling of Highly Compressed Data (Data Amplification)].

Critical Impact

Remote attackers can trigger service-wide denial of service by submitting a small, specially crafted MSP file to any Pillow-backed image processing endpoint.

Affected Products

  • Python Pillow versions 4.1.0 through 12.x
  • Any application or service that calls Image.open() and loads pixel data from untrusted MSP files
  • Downstream frameworks and web services bundling vulnerable Pillow releases

Discovery Timeline

  • 2026-10-19 - CVE-2026-112892 published to NVD
  • 2026-10-22 - Last updated in NVD database

Technical Details for CVE-2026-112892

Vulnerability Analysis

The flaw resides in src/PIL/MspImagePlugin.py, which decodes Microsoft Paint (MSP) version 2 bitmap files using an RLE scheme. For each scanline, the decoder reads run opcodes and appends either repeated bytes (runtype == 0) or literal bytes directly to an in-memory data buffer. The decoder trusts the row-level opcode stream to produce exactly xsize output bytes per row and does not clamp the written length to the declared image width.

A crafted MSP file can declare a modest image size that passes Pillow's decompression-bomb heuristic while embedding row data whose expanded run count greatly exceeds xsize. Because the buffer is a BytesIO instance that grows on demand, each oversized row allocates additional memory proportional to the attacker-controlled run counts. The process consumes available RAM until the operating system kills it or the host becomes unresponsive.

Root Cause

The root cause is a missing bounds check between declared output size and actual decoded output. The decoder appends runval * runcount or row[idx : idx + runcount] to the shared buffer without comparing accumulated row length against self.state.xsize. [CWE-409] describes this exact amplification pattern: compressed input expanding to disproportionately large output.

Attack Vector

Exploitation requires only that a vulnerable application accept an MSP file from an untrusted source and invoke pixel loading, for example through Image.open(path).load() or format conversion. No authentication or user interaction is required when the processing pipeline is network-exposed, such as avatar uploads, document conversion services, or image moderation backends.

python
# Patch from Pillow 13.0.0 - src/PIL/MspImagePlugin.py
# Bounds the decoded row length to the declared image width
                    msg = f"Truncated MSP file, expected {rowlen} bytes on row {y}"
                    raise OSError(msg)
                idx = 0
                row_data = bytearray()
                while idx < rowlen:
                    runtype = row[idx]
                    idx += 1
                    if runtype == 0:
                        runcount, runval = struct.unpack_from("Bc", row, idx)
                        runcount = min(runcount, self.state.xsize - len(row_data))
                        row_data += runval * runcount
                        idx += 2
                    else:
                        runcount = min(runtype, self.state.xsize - len(row_data))
                        row_data += row[idx : idx + runcount]
                        idx += runcount
                data += row_data

            except struct.error as e:
                msg = f"Corrupted MSP file in row {y}"

Source: Pillow commit 5dcb630b. The patch introduces a per-row row_data buffer and clamps every runcount to the remaining space in the declared row width, preventing unbounded expansion.

Detection Methods for CVE-2026-112892

Indicators of Compromise

  • Python worker processes terminated by the Linux OOM killer shortly after processing uploaded images
  • Sharp spikes in resident set size (RSS) for processes importing PIL.MspImagePlugin
  • Inbound files with the .msp extension or magic bytes DanM / LinS reaching image processing endpoints
  • Repeated OSError: Corrupted MSP file entries in application logs preceded by elevated memory usage

Detection Strategies

  • Inventory Python environments and flag installations where pip show pillow reports a version below 13.0.0
  • Instrument image upload handlers to log file format and size, then alert on MSP uploads given their rarity in modern workflows
  • Add runtime memory ceilings per worker and alert when a single request allocates abnormal amounts of memory

Monitoring Recommendations

  • Track cgroup memory pressure and OOM events on hosts running image conversion services
  • Correlate web access logs for MSP uploads with downstream worker restarts
  • Monitor dependency manifests (requirements.txt, poetry.lock, SBOMs) in CI for pinned Pillow versions below 13.0.0

How to Mitigate CVE-2026-112892

Immediate Actions Required

  • Upgrade Pillow to version 13.0.0 or later in all production, staging, and build environments
  • Rebuild and redeploy container images that bundle Pillow, since cached base layers may retain vulnerable versions
  • Audit application code for direct or transitive use of PIL.MspImagePlugin and restrict accepted upload formats to those actually required

Patch Information

The fix is included in Pillow release 13.0.0 and detailed in GitHub Security Advisory GHSA-gg2r-wvfm-q9rx. Install with pip install --upgrade "Pillow>=13.0.0".

Workarounds

  • Reject MSP files at the application boundary by validating file magic bytes before invoking Pillow
  • Deregister the MSP plugin at runtime with Image.registered_extensions().pop('.msp', None) and remove MspImagePlugin from Image.ID
  • Enforce per-process memory limits using resource.setrlimit(resource.RLIMIT_AS, ...) or container memory limits to contain amplification
bash
# Verify installed Pillow version and upgrade
python -c "import PIL; print(PIL.__version__)"
pip install --upgrade "Pillow>=13.0.0"

# Optional runtime hardening: disable MSP decoder at import time
python -c "from PIL import Image, MspImagePlugin; Image.unregister_extension('MSP', '.msp'); Image.unregister_mime('MSP')"

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.