CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2026-25990
8.9

Pillow Fight: Weaponizing Photoshop Files via OOB Writes

Alon Barad
Alon Barad
Software Engineer

Feb 12, 2026·6 min read·14 visits

PoC Available

Executive Summary (TL;DR)

Pillow versions 10.3.0 to <12.1.1 fail to sanity-check negative coordinates in PSD layers. By crafting a Photoshop file with a layer offset like `x=-100`, an attacker can trick the C backend into writing data before the start of the image buffer. This heap corruption primitive can be leveraged for RCE.

A high-severity Out-of-Bounds Write vulnerability exists in Pillow, the de facto Python Imaging Library, specifically within its Photoshop Document (PSD) handler. The flaw arises from a failure to validate negative image offsets in the C extension modules, allowing attackers to write pixel data to arbitrary memory locations preceding the allocated buffer. This can lead to heap corruption, denial of service, or potentially remote code execution when processing malicious images.

The Hook: Parsing is Hard, C is Harder

If you write Python, you use Pillow. It's the library that powers image processing for Django, Flask, and practically every data science pipeline that touches a JPEG. But under the hood, Pillow isn't just friendly Python code; it's a wrapper around a massive, ancient C codebase designed to parse the most cursed file formats known to man.

Enter the Photoshop Document (PSD). Adobe's spec for PSD is notorious for being less of a standard and more of a memory dump of a 1990s Macintosh. In CVE-2026-25990, the vulnerability isn't in the complex compression algorithms or the color profiles—it's in basic arithmetic. The kind of arithmetic that C developers have been getting wrong since the Nixon administration.

This vulnerability is a classic "Out-of-Bounds Write," but with a twist. Usually, we worry about writing past the end of a buffer. Here, we are writing before it. It's a negative offset bug, allowing an attacker to reach back into the heap and overwrite whatever unfortunate data structure sits just prior to our image buffer.

The Flaw: Trusting the Coordinates

To understand the bug, you have to look at how Pillow handles image layers (or "tiles"). When you load a PSD, it might consist of multiple layers, each with a specific position on the canvas. These positions are defined by xoff (x-offset) and yoff (y-offset).

The vulnerability lives in src/decode.c and src/encode.c, specifically in the _setimage function. This function initializes the state for the image decoder. The developers were responsible citizens: they checked if the image was too big. They checked if the layer extended past the right edge of the canvas. They checked if it extended past the bottom edge.

But they forgot one direction: Left.

The code implicitly assumed that xoff and yoff would be positive integers. In the C language, buffer[-1] is not a helpful syntax for accessing the last element like in Python; it is a direct memory instruction to access the address base_address - sizeof(type). By supplying a negative offset in the PSD metadata, the validation checks passed (because width + (-10) is still less than max_width), but the memory pointer calculation pointed into the abyss.

The Code: The Smoking Gun

Let's look at the vulnerable logic in src/decode.c. The original code tried to ensure the tile didn't extend outside the image boundaries.

// Vulnerable Code
if (state->xsize <= 0 || 
    state->xsize + state->xoff > (int)im->xsize ||
    state->ysize <= 0 || 
    state->ysize + state->yoff > (int)im->ysize) {
    PyErr_SetString(PyExc_ValueError, "tile cannot extend outside image");
    return NULL;
}

Do you see the gap? If xoff is -100, the check state->xsize + (-100) > im->xsize evaluates to false (assuming xsize is normal). The check passes.

However, later in the rendering loop, the destination pointer is calculated roughly like this:

pixel_dest = buffer + (y + state->yoff) * stride + (x + state->xoff);

If state->xoff is negative, pixel_dest points before buffer.

The fix, implemented in commit 9000313cc5d4a31bdcdd6d7f0781101abab553aa, is embarrassingly simple. They just added checks for zero-floors:

- if (state->xsize <= 0 || state->xsize + state->xoff > (int)im->xsize ||
+ if (state->xoff < 0 || state->xsize <= 0 ||
+     state->xsize + state->xoff > (int)im->xsize || state->yoff < 0 ||

This is the difference between a secure application and a segfault (or RCE).

The Exploit: Grooming the Heap

Exploiting this requires some finesse, but the primitive is strong. We have a controlled linear underflow write.

  1. The Setup: We need to target a Python web application that accepts PSD uploads (common in design collaboration tools or conversion services).
  2. Heap Grooming: We send several small, benign images to fragment the heap and align our target objects. Ideally, we want a juicy C-structure (like a Python object header or a function pointer table) to sit immediately before the memory block that will be allocated for our malicious image.
  3. The Trigger: We upload the malicious PSD. It declares a layer with xoff: -64.
  4. The Corruption: Pillow allocates buffer B for the image. The decoder starts writing pixel data at B - 64.

If we positioned our chunks correctly, we overwrite the metadata of the previous chunk. By controlling the pixel data (the colors in the PSD layer), we control exactly what is written to that metadata. If we overwrite a reference count, we trigger a premature free. If we overwrite a type pointer, we cause type confusion. If we overwrite a function pointer, we hijack control flow directly.

The Impact: Why You Should Care

This isn't just a Denial of Service (though crashing the worker process is the most likely outcome for a script kiddie). The CVSS score of 8.9 reflects the reality that memory corruption in a language runtime like Python (via C extensions) is catastrophic.

In a cloud environment, image processing is often offloaded to worker nodes (Celery, RQ). If an attacker gets RCE on the worker, they are inside your perimeter. They can dump environment variables (AWS keys, database credentials) or pivot to the internal network.

Because this is an interactionless exploit (the server just needs to process the file), it is wormable within specific ecosystems. Any service that auto-generates thumbnails for PSDs is immediately vulnerable.

The Fix: Patch or Perish

The remediation is straightforward: Update Pillow to version 12.1.1 immediately.

If you are stuck on an older version due to dependency hell (we've all been there), you have two options, neither of them good:

  1. Disable PSD Support: Pillow supports disabling specific plugins. You can block the PSD plugin from loading if you don't strictly need it.
  2. Input Sanitization: This is risky, but you could write a pre-parser that reads the PSD binary headers and rejects any file with negative layer offsets before passing it to Pillow. However, parsing PSDs correctly is exactly what Pillow failed to do, so you'll probably get it wrong too.

Just update the package.

pip install --upgrade Pillow
# Verify you are on >= 12.1.1
python3 -c "import PIL; print(PIL.__version__)"

Official Patches

PillowOfficial Release Notes
GitHubCommit Diff

Fix Analysis (1)

Technical Appendix

CVSS Score
8.9/ 10
CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P

Affected Systems

Python applications using Pillow < 12.1.1Web applications accepting PSD uploadsImage processing pipelines (AWS Lambda, Celery workers)

Affected Versions Detail

Product
Affected Versions
Fixed Version
Pillow
python-pillow
>= 10.3.0, < 12.1.112.1.1
AttributeDetail
CWE IDCWE-787 (Out-of-bounds Write)
CVSS Score8.9 (High)
Attack VectorNetwork (Image Upload)
ImpactRCE / Denial of Service
Componentsrc/decode.c (_setimage)
Fix Version12.1.1

MITRE ATT&CK Mapping

T1190Exploit Public-Facing Application
Initial Access
T1203Exploitation for Client Execution
Execution
CWE-787
Out-of-bounds Write

The product writes data past the end, or before the beginning, of the intended buffer.

Known Exploits & Detection

NucleiDetection Template Available

Vulnerability Timeline

CVE Reserved
2026-02-09
Patch Committed (9000313)
2026-02-10
Public Advisory & Release
2026-02-11

References & Sources

  • [1]GHSA Advisory
  • [2]NVD Detail
  • [3]OffSeq Threat Radar Analysis

Attack Flow Diagram

Press enter or space to select a node. You can then use the arrow keys to move the node around. Press delete to remove it and escape to cancel.
Press enter or space to select an edge. You can then press delete to remove it or escape to cancel.