Feb 12, 2026·6 min read·88 visits
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.
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.
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.
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).
Exploiting this requires some finesse, but the primitive is strong. We have a controlled linear underflow write.
xoff: -64.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.
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 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:
PSD plugin from loading if you don't strictly need it.Just update the package.
pip install --upgrade Pillow
# Verify you are on >= 12.1.1
python3 -c "import PIL; print(PIL.__version__)"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| Product | Affected Versions | Fixed Version |
|---|---|---|
Pillow python-pillow | >= 10.3.0, < 12.1.1 | 12.1.1 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-787 (Out-of-bounds Write) |
| CVSS Score | 8.9 (High) |
| Attack Vector | Network (Image Upload) |
| Impact | RCE / Denial of Service |
| Component | src/decode.c (_setimage) |
| Fix Version | 12.1.1 |
The product writes data past the end, or before the beginning, of the intended buffer.
A vulnerability in the Slack and Mattermost platform adapters for NousResearch hermes-agent permits an unauthenticated remote attacker to execute arbitrary mass mentions. By leveraging prompt injection, an attacker can bypass output sanitization logic and trigger workspace-wide notification exhaustion.
CVE-2026-9306 is a critical unauthenticated Insecure Direct Object Reference (IDOR) vulnerability located in the QuantumNous new-api application, affecting versions up to and including 0.12.1. The flaw is caused by improper middleware ordering combined with a lack of object-level authorization checks. This allows remote, unauthenticated attackers to retrieve sensitive Midjourney images belonging to other users by supplying a valid task identifier.
The instagrapi library prior to version 2.6.9 contains an improper input validation vulnerability within its challenge handling mechanism. Maliciously crafted server responses can manipulate the client into forwarding session cookies and credentials to an external attacker-controlled domain.
GHSA-QQQM-5547-774X is a critical path traversal vulnerability in the FileBrowser Quantum application, specifically within the Go backend package. The vulnerability resides in the HTTP handler responsible for processing bulk file modifications via the public API. Unauthenticated attackers can exploit an order-of-operations flaw in the path sanitization logic to bypass intended directory restrictions. This allows adversaries to arbitrarily read, move, and overwrite files on the underlying filesystem by supplying specially crafted HTTP PATCH requests.
The qs query string parsing and serialization library for Node.js is vulnerable to a synchronous Denial of Service (DoS) attack. The vulnerability manifests as a process-terminating TypeError when processing arrays with null or undefined elements under specific configuration parameters.
The aiosend library prior to version 3.0.6 contains a pre-authentication Denial of Service (DoS) vulnerability in its webhook handling mechanism. The software processes and deserializes incoming JSON payloads before verifying the cryptographic signature, allowing unauthenticated attackers to exhaust server CPU and memory resources by sending large, complex payloads.