Feb 12, 2026·6 min read·114 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.
An authentication bypass in the SiYuan personal knowledge management system before version 3.7.0 exposes a dynamic icon rendering endpoint. This endpoint processes client-supplied Go template directives. By submitting a crafted request, an unauthenticated remote attacker can leverage registered database template functions to execute arbitrary read-only SQL queries and exfiltrate workspace contents.
CVE-2026-54069 is a critical authentication bypass vulnerability in the SiYuan Note personal knowledge management system. The flaw is located in the HTTP server's middleware handling API authorization, which unconditionally trusts requests carrying a 'chrome-extension://' scheme in the Origin HTTP header, granting administrative access without validating API tokens.
CVE-2026-54089 is a critical authentication bypass vulnerability in File Browser affecting instances configured with proxy-based authentication. An unauthenticated remote attacker with direct network access can impersonate arbitrary users or register new accounts by spoofing configured HTTP headers.
The malicious Cargo package 'exploration' was uploaded to the crates.io registry. During compilation or package import, the crate executes code designed to establish an outbound TCP/HTTP connection, download an external second-stage binary, and execute the binary locally on the host machine. This creates an unauthenticated remote code execution vector impacting developer environments and continuous integration pipelines.
CVE-2026-54088 is a critical command injection vulnerability in File Browser prior to version 2.63.6. When Hook Authentication is enabled, the application interpolates unsanitized credentials into a shell command, allowing unauthenticated remote code execution.
An authenticated remote code execution vulnerability exists in NotrinosERP (versions up to and including 1.0.0) within the Human Resource Management (HRM) module. Users with employee management permissions can upload arbitrary file types, including PHP scripts, which are written directly to a web-accessible directory. This allows for arbitrary code execution in the context of the web-server user.