Every CVE Search/ Get API key

CVE-2026-112898

Pillow: Image.quantize() with empty palette leaks uninitialized stack memory

6.9 MediumFix available

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. 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
None

What an attacker can affect

Confidentiality
Low
Integrity
None
Availability
None

Exploit likelihood

0.34%Unchanged since Oct 20, 2026
90%50%10%1%

What each source reports

CVE List Score used 6.9

Pillow: Image.quantize() with empty palette leaks uninitialized stack memory

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
CVE-2026-112898
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-457
Updated
2026-11-02
EUVDEUVD-2026-104660 6.9

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
EUVD-2026-104660
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Updated
2026-11-02
GitHub AdvisoriesGHSA-rfgq-q3w2-pmv2 6.9

Pillow: Image.quantize() with empty palette leaks uninitialized stack memory

============================================================================
Image.quantize() with empty palette leaks uninitialized stack memory
Location: src/libImaging/Palette.c:216  in ImagingPaletteCacheUpdate()
Commit:   a707c6b21c313880d0771e54965d142dd42358b6
============================================================================

When an application lets a user supply the palette image for Pillow's Image.quantize(palette=...), the user can get back 512 bytes of the server process's stack memory in the pixels of the returned image. An 80-byte PNG with an empty palette is enough, and the leaked bytes survive saving to PNG or GIF. In the runs examined, those bytes included memory addresses inside the stack, heap, libc, libpython and Pillow's own C module, along with the stack-protector canary.

The bug is in ImagingPaletteCacheUpdate() in src/libImaging/Palette.c. It fills a local UINT8 c[512] only inside loops bounded by palette->size. When the palette has zero entries, the array is never written, but it is still copied into the colour cache. topalette() in src/libImaging/Convert.c then emits the cached values as output pixel indices. topalette() rejects a NULL palette but not one with size == 0. Three inputs produce such a zero-entry "P" image: a PNG with a zero-length PLTE chunk opened with Image.open(), a colour-mapped TGA with colour-map length 0, or a bare Image.new("P", ...). The attacker must control the palette image; the source image can be anything, and RGB sources are affected with either dither mode. There is no out-of-bounds access and no memory corruption. The consequence is a bounded stack disclosure that can reveal address layout and the canary of a long-lived worker, with no crash and no log entry. The attached reproducer quantizes a 512-colour strip against the empty-palette PNG and finds 8 of the 64 output words pointing into the process's own [stack] mapping, while a one-entry palette control yields all zeros. The behaviour was introduced in commit 54cb09d8b48c (first released in 9.1.0). The Image.new("P", ...) route exists since 10.1.0.

Details

ImagingPaletteCacheUpdate() (src/libImaging/Palette.c:154) computes, for one 8×8×8 box of the colour cache, the nearest palette index for each of the 512 slots into a local UINT8 c[512] and then copies c[] into the cache. c[] is only ever written inside for (i = 0; i < palette->size; i++). When the palette has zero entries nothing writes it, the untouched stack bytes are copied into the cache, and topalette() (src/libImaging/Convert.c:1145) emits them as the output pixels of Image.quantize(palette=...). The root cause is that topalette() only rejects a NULL palette, never one with palette->size == 0, while ImagingPaletteCacheUpdate() silently assumes size >= 1. A zero-entry "P" image is what Image.open() produces for a PNG whose PLTE chunk has length 0 (or a colour-mapped TGA with colour-map length 0); Image.new("P", size) without a subsequent putpalette() yields one as well. Both dither modes (Dither.NONE and the default Floyd-Steinberg) are affected for RGB sources (an L source takes the grayscale copy path and never consults the cache).

Showing it with the installed build, no special flags. The 80-byte PNG below is 4×4, bit depth 8, colour type 3, with a zero-length PLTE and an all-zero IDAT:

89504e470d0a1a0a 0000000d49484452 0000000400000004 0803000000 9e2f6e4c
00000000 504c5445 4ba88955
0000000b 49444154 789c6360c0040000140001 7dfe1eee
00000000 49454e44 ae426082
pal = Image.open(io.BytesIO(png)); pal.load()      # pal.mode == "P", pal.im.getpalette("RGB") == b""
src = Image.new("RGB", (512, 1))
src.putdata([(r*4, g*4, b*4) for r in range(8) for g in range(8) for b in range(8)])
out = src.quantize(palette=pal, dither=Image.Dither.NONE)
out.tobytes()        # 512 bytes, different in every process

The 512 source colours are…

Record
GHSA-rfgq-q3w2-pmv2
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-457
Updated
2026-11-02
NVD 6.9

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
CVE-2026-112898
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/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
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Weakness
CWE-457
Updated
2026-11-02
OSV 6.9

Pillow: Image.quantize() with empty palette leaks uninitialized stack memory

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
CVE-2026-112898
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-457
Updated
2026-11-02
OSVDEBIAN-CVE-2026-112898 5.3

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
DEBIAN-CVE-2026-112898
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Updated
2026-11-02
Show 8 moreShow less
OSVGHSA-rfgq-q3w2-pmv2 6.9

Pillow: Image.quantize() with empty palette leaks uninitialized stack memory

============================================================================
Image.quantize() with empty palette leaks uninitialized stack memory
Location: src/libImaging/Palette.c:216  in ImagingPaletteCacheUpdate()
Commit:   a707c6b21c313880d0771e54965d142dd42358b6
============================================================================

When an application lets a user supply the palette image for Pillow's Image.quantize(palette=...), the user can get back 512 bytes of the server process's stack memory in the pixels of the returned image. An 80-byte PNG with an empty palette is enough, and the leaked bytes survive saving to PNG or GIF. In the runs examined, those bytes included memory addresses inside the stack, heap, libc, libpython and Pillow's own C module, along with the stack-protector canary.

The bug is in ImagingPaletteCacheUpdate() in src/libImaging/Palette.c. It fills a local UINT8 c[512] only inside loops bounded by palette->size. When the palette has zero entries, the array is never written, but it is still copied into the colour cache. topalette() in src/libImaging/Convert.c then emits the cached values as output pixel indices. topalette() rejects a NULL palette but not one with size == 0. Three inputs produce such a zero-entry "P" image: a PNG with a zero-length PLTE chunk opened with Image.open(), a colour-mapped TGA with colour-map length 0, or a bare Image.new("P", ...). The attacker must control the palette image; the source image can be anything, and RGB sources are affected with either dither mode. There is no out-of-bounds access and no memory corruption. The consequence is a bounded stack disclosure that can reveal address layout and the canary of a long-lived worker, with no crash and no log entry. The attached reproducer quantizes a 512-colour strip against the empty-palette PNG and finds 8 of the 64 output words pointing into the process's own [stack] mapping, while a one-entry palette control yields all zeros. The behaviour was introduced in commit 54cb09d8b48c (first released in 9.1.0). The Image.new("P", ...) route exists since 10.1.0.

Details

ImagingPaletteCacheUpdate() (src/libImaging/Palette.c:154) computes, for one 8×8×8 box of the colour cache, the nearest palette index for each of the 512 slots into a local UINT8 c[512] and then copies c[] into the cache. c[] is only ever written inside for (i = 0; i < palette->size; i++). When the palette has zero entries nothing writes it, the untouched stack bytes are copied into the cache, and topalette() (src/libImaging/Convert.c:1145) emits them as the output pixels of Image.quantize(palette=...). The root cause is that topalette() only rejects a NULL palette, never one with palette->size == 0, while ImagingPaletteCacheUpdate() silently assumes size >= 1. A zero-entry "P" image is what Image.open() produces for a PNG whose PLTE chunk has length 0 (or a colour-mapped TGA with colour-map length 0); Image.new("P", size) without a subsequent putpalette() yields one as well. Both dither modes (Dither.NONE and the default Floyd-Steinberg) are affected for RGB sources (an L source takes the grayscale copy path and never consults the cache).

Showing it with the installed build, no special flags. The 80-byte PNG below is 4×4, bit depth 8, colour type 3, with a zero-length PLTE and an all-zero IDAT:

89504e470d0a1a0a 0000000d49484452 0000000400000004 0803000000 9e2f6e4c
00000000 504c5445 4ba88955
0000000b 49444154 789c6360c0040000140001 7dfe1eee
00000000 49454e44 ae426082
pal = Image.open(io.BytesIO(png)); pal.load()      # pal.mode == "P", pal.im.getpalette("RGB") == b""
src = Image.new("RGB", (512, 1))
src.putdata([(r*4, g*4, b*4) for r in range(8) for g in range(8) for b in range(8)])
out = src.quantize(palette=pal, dither=Image.Dither.NONE)
out.tobytes()        # 512 bytes, different in every process

The 512 source colours are…

Record
GHSA-rfgq-q3w2-pmv2
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
Weakness
CWE-457
Updated
2026-11-02
OSVUBUNTU-CVE-2026-112898 6.9

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
UBUNTU-CVE-2026-112898
CVSS 4.0
6.9 MediumCVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N
CVSS 3.1
5.3 MediumCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N
Updated
2026-11-04
OSVBIT-pillow-2026-112898 No score

Pillow: Image.quantize() with empty palette leaks uninitialized stack memory

Pillow is a Python imaging library. From 9.1.0 until 13.0.0, Pillow can disclose uninitialized stack memory when Image.quantize() uses an empty palette. ImagingPaletteCacheUpdate() leaves a local cache array uninitialized when the palette has zero entries, and the conversion path emits those bytes as output pixel indices. An attacker who controls the palette image can obtain stack contents from a returned PNG or GIF. This can disclose addresses and stack-canary data without an out-of-bounds access, memory corruption, or process crash. This issue is fixed in version 13.0.0.

Record
BIT-pillow-2026-112898
Updated
2026-11-02
OSVCLSA-2026-1793017750 No score

TuxCare security update for pillow (9 CVEs)

TuxCare rebuilt pillow to address 9 CVEs. Fixed in 11.3.0.post11+tuxcare.

Record
CLSA-2026-1793017750
Updated
2026-11-02
OSVCLSA-2026-1793085879 No score

TuxCare security update for pillow (9 CVEs)

TuxCare rebuilt pillow to address 9 CVEs. Fixed in 11.2.1.post13+tuxcare.

Record
CLSA-2026-1793085879
Updated
2026-11-02
OSVCLSA-2026-1793099358 No score

TuxCare security update for pillow (8 CVEs)

TuxCare rebuilt pillow to address 8 CVEs. Fixed in 10.4.0.post15+tuxcare.

Record
CLSA-2026-1793099358
Updated
2026-11-02
OSVCLSA-2026-1793346161 No score

TuxCare security update for pillow (7 CVEs)

TuxCare rebuilt pillow to address 7 CVEs. Fixed in 9.5.0.post13+tuxcare.

Record
CLSA-2026-1793346161
Updated
2026-11-02
OSVopenSUSE-SU-2026:12230-1 No score

python313-Pillow-13.0.0-1.1 on GA media

These are all security issues fixed in the python313-Pillow-13.0.0-1.1 package on the GA media of openSUSE Tumbleweed.

Record
openSUSE-SU-2026:12230-1
Updated
2026-11-02

Affected products

Affected packages

Affected packages
PackageAffectedFixed
pillow
PyPI
9.1.0 up to 13.0.0
13.0.0
pillow
Bitnami
9.1.0 up to 13.0.0
13.0.0
pillow
TuxCare:PyPI
From an advisory covering 9 CVEs
11.3.0 up to 11.3.0.post11+tuxcare, plus listed versions
11.3.0.post1+tuxcare11.3.0.post10+tuxcare11.3.0.post2+tuxcare11.3.0.post3+tuxcare11.3.0.post4+tuxcare
Show 5 moreShow less
11.3.0.post5+tuxcare11.3.0.post6+tuxcare11.3.0.post7+tuxcare11.3.0.post8+tuxcare11.3.0.post9+tuxcare
11.3.0.post11+tuxcare
pillow
TuxCare:PyPI
From an advisory covering 9 CVEs
11.2.1 up to 11.2.1.post13+tuxcare, plus listed versions
11.2.1.post1+tuxcare11.2.1.post10+tuxcare11.2.1.post11+tuxcare11.2.1.post12+tuxcare11.2.1.post2+tuxcare
Show 7 moreShow less
11.2.1.post3+tuxcare11.2.1.post4+tuxcare11.2.1.post5+tuxcare11.2.1.post6+tuxcare11.2.1.post7+tuxcare11.2.1.post8+tuxcare11.2.1.post9+tuxcare
11.2.1.post13+tuxcare
pillow
TuxCare:PyPI
From an advisory covering 8 CVEs
10.4.0 up to 10.4.0.post15+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 9 moreShow less
10.4.0.post14+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.post15+tuxcare
pillow
TuxCare:PyPI
From an advisory covering 7 CVEs
9.5.0 up to 9.5.0.post13+tuxcare, plus listed versions
9.5.0.post1+tuxcare9.5.0.post10+tuxcare9.5.0.post11+tuxcare9.5.0.post12+tuxcare9.5.0.post2+tuxcare
Show 7 moreShow less
9.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.post13+tuxcare
pillow
Debian:12
All versions, plus listed versions
10.0.0-110.1.0-110.2.0-110.3.0-110.3.0-2
Show 19 moreShow less
10.4.0-110.4.0-1.111.1.0-111.1.0-211.1.0-311.1.0-411.1.0-511.2.1-111.3.0-112.0.0-112.1.0-112.1.1-112.1.1-212.2.0-112.3.0-113.0.0-19.4.0-1.19.4.0-1.1+deb12u19.5.0-1
None known
pillow
Debian:13
All versions, plus listed versions
11.1.0-511.1.0-5+deb13u111.1.0-5+deb13u211.1.0-5+deb13u311.1.0-5+deb13u4
Show 9 moreShow less
11.2.1-111.3.0-112.0.0-112.1.0-112.1.1-112.1.1-212.2.0-112.3.0-113.0.0-1
None known
pillow
Debian:14
All versions, plus listed versions
11.1.0-511.2.1-111.3.0-112.0.0-112.1.0-1
Show 5 moreShow less
12.1.1-112.1.1-212.2.0-112.3.0-113.0.0-1
None known
pillow
PyPI
9.1.0 up to 13.0.0, plus listed versions
10.0.010.0.110.1.010.2.010.3.0
Show 15 moreShow less
10.4.011.0.011.1.011.2.111.3.012.0.012.1.012.1.112.2.012.3.09.1.19.2.09.3.09.4.09.5.0
13.0.0
Show 5 moreShow less
Affected packages
PackageAffectedFixed
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
pillow
Ubuntu:24.04:LTS
All versions, plus listed versions
10.0.0-110.1.0-110.2.0-110.2.0-1build110.2.0-1ubuntu110.2.0-1ubuntu1.210.2.0-1ubuntu1.310.2.0-1ubuntu1.4
None known
pillow
Ubuntu:26.10
All versions, plus listed versions
12.1.1-2ubuntu112.3.0-1ubuntu1
None known
pillow
Ubuntu:26.04:LTS
All versions, plus listed versions
11.3.0-1ubuntu111.3.0-1ubuntu212.0.0-1ubuntu112.1.1-1ubuntu112.1.1-2ubuntu1
Show 4 moreShow less
12.1.1-2ubuntu1.112.1.1-2ubuntu1.212.1.1-2ubuntu1.312.1.1-2ubuntu1.4
None known
python-Pillow
openSUSE:Tumbleweed
From an advisory covering 14 CVEs
before 13.0.0-1.1
13.0.0-1.1

History

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

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

In short

CVE-2026-112898 is a medium vulnerability (CVSS 6.9) in Python Pillow, published October 19, 2026. No source lists public exploit code. EPSS puts the chance of exploitation in the next 30 days at 0.34%, the 27th percentile. Fixed in pillow 13.0.0.

References

Show 18 moreShow less
This page doesn't list everything

Left out here: the rest of 2 source descriptions. The JSON record has them.

MoveEnter Open