---
id: CVE-2026-96638
aliases:
  - GHSA-24rc-8hj2-v98c
  - BIT-pillow-2026-96638
title: >-
  Pillow: BMP RLE decoder memory-amplification DoS via unbounded delta escape /
  absolute mode
summary: >-
  Pillow: BMP RLE decoder memory-amplification DoS via unbounded delta escape /
  absolute mode
severity: medium
cvss: 6.5
cvssVector: 'CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:N/A:H'
vendor: pillow
product: pillow
ecosystem: pip
affected:
  - 'pillow >= 9.1.0, < 13.0.0'
patched:
  - pillow 13.0.0
published: '2026-10-22'
updated: '2026-10-29'
sourceUpdated: '2026-10-29T10:55:36.262902070Z'
source: OSV
sourceUrl: 'https://osv.dev/vulnerability/GHSA-24rc-8hj2-v98c'
references:
  - url: >-
      https://github.com/python-pillow/Pillow/security/advisories/GHSA-24rc-8hj2-v98c
  - url: 'https://nvd.nist.gov/vuln/detail/CVE-2026-96638'
  - url: 'https://github.com/python-pillow/Pillow/pull/10015'
  - url: >-
      https://github.com/python-pillow/Pillow/commit/e7794835e323ff2691d9b3a974cade1a44301b9a
  - url: 'https://github.com/python-pillow/Pillow'
  - url: 'https://github.com/python-pillow/Pillow/releases/tag/13.0.0'
  - url: 'https://github.com/advisories/GHSA-24rc-8hj2-v98c'
tags:
  - osv
  - pip
  - cve.org
  - ghsa
epss: 0.00345
epssPercentile: 0.27998
ssvc:
  exploitation: none
  automatable: 'no'
  technicalImpact: partial
  timestamp: '2026-10-28T18:20:26.959004Z'
cwe:
  - CWE-400
  - CWE-770
ingestedAt: '2026-10-21T21:13:32.882Z'
---

## Overview

## Summary
BmpRleDecoder can be forced to allocate gigabytes of memory from a kilobyte-sized crafted BMP file via unbounded growth in the delta escape and absolute mode branches.

## Affected versions
All versions up to and including current HEAD (v13.0.0.dev0, commit e7a7ea84)

## Root cause
BmpRleDecoder.decode() (BmpImagePlugin.py:327-395) builds a Python bytearray `data` with dest_length = xsize*ysize, looping `while len(data) < dest_length`. The loop condition is only checked at the TOP; individual branches append attacker-controlled amounts UNBOUNDED by dest_length:

- delta escape (line 370): data += b"\x00" * (right + up * self.state.xsize) with right,up in 0..255 -> up to ~255*xsize bytes in ONE iteration.
- absolute mode (line 385): data += bytes_read (up to 255).

Because a single delta can push len(data) to ~256 * (row size), a tiny RLE stream combined with a large-but-legal xsize (xsize*ysize just under MAX_IMAGE_PIXELS, ~89M) yields a multi-GB `data` bytearray (then copied again by bytes(data) and set_as_raw). No heap OOB (bytearray/set_as_raw are bounds-safe), but a severe memory-amplification DoS: the attacker file stays tiny.

## Reproduction
Python 3.13, Pillow HEAD e7a7ea84

Attached: pillow_poc_bmp_rle_dos.py

A 1084-byte BMP (BI_RLE8, width=3,000,000, height=1 -> 3M px, passes bomb check) with one delta escape (00 02 00 150) produces ~429 MB `data`; measured peak RSS 911 MB. Scaling width toward the bomb limit and up=255 produces tens of GB from a ~1 KB file.

## Suggested fix
Cap growth at dest_length in the delta and absolute branches, e.g. clamp the delta fill to (dest_length - len(data)) and stop appending once len(data) >= dest_length; reject deltas that would move the cursor beyond the image, matching the existing per-row clamp at line 341-343.

Credit: Yazan Balawneh - CyStack Security Team (GitHub: sonicnew)

[pillow_poc_bmp_rle_dos.py](https://github.com/user-attachments/files/30244974/pillow_poc_bmp_rle_dos.py)

## Affected packages

- `pillow >= 9.1.0, < 13.0.0`

## Remediation

Upgrade to a patched release:

- `pillow 13.0.0`
