Improper resource allocation In pillow
Description
Pillow: FontFile.compile(): Image.new() called without _decompression_bomb_check()
Description
PIL/FontFile.py FontFile.compile() assembles per-glyph images into a single combined bitmap using Image.new("1", (xsize, ysize)) without calling Image._decompression_bomb_check(). This is the base-class method shared by both BdfFontFile and PcfFontFile, and it is triggered whenever a loaded font is converted to an ImageFont or saved.
Neither BdfFontFile.BdfFontFile(fp) nor PcfFontFile.PcfFontFile(fp) is registered with Image.register_open(), so Pillow's standard decompression bomb guard never fires for font objects. The compile step is the final opportunity to check the combined allocation — and it has no check.
Vulnerable code (PIL/FontFile.py lines ~64–92):
def compile(self) -> None: if self.bitmap: return h = w = maxwidth = 0 lines = 1 for glyph in self.glyph: # up to 256 glyph slots if glyph:...
"Slow accumulation" attack — per-glyph dimensions stay BELOW warning threshold:
Metric | Per-glyph (800 × 875) | Combined bitmap (256 glyphs) |
|---|---|---|
Pixel count | 700,000 | 179,200,000 |
DecompressionBombWarning threshold (89.4M) | 0.008× — no warning | 2.0× — above warning |
DecompressionBombError threshold (178.9M) | 0.004× — no error | 1.001× — above error |
With PCF-maximum glyph height (65,535):
Metric | Value |
|---|---|
lines | 256 (one per glyph slot, width=800 forces a wrap every glyph) |
h (max glyph height) | 65,535 |
xsize | 800 |
ysize = lines × h | 256 × 65,535 = 16,776,960 |
Total pixels | 800 × 16,776,960 = 13,421,568,000 |
Ratio vs. DecompressionBombError threshold | 75× |
Memory (mode "1", 1 bit/pixel) | ~1.6 GB |
Steps to reproduce
Proof of Concept script:
#!/usr/bin/env python3 """ PoC: FontFile.compile() bomb bypass 256 glyphs at 800x875 each (individually below warning threshold) → compile() creates 800x224000 = 179.2M px bitmap with NO bomb check """ from PIL import FontFile, Image ...
Expected output:
[OK] bomb check WOULD block (800, 224000): Image size (179200000 pixels) exceeds limit of 178956970 pixels, could be decompression bomb DOS attack. [BYPASS] compile() succeeded: bitmap=(800, 224000) pixels=179,200,000 (1.001× DecompressionBombError threshold) No DecompressionBombError raised at any point.
Verified live on Pillow 12.2.0 — compile() succeeds with no exception.
Real-world trigger using BDF font file:
from PIL import BdfFontFile import io # compile() combined: 800 × 16,776,960 = 13.4B px — 75× error threshold font = BdfFontFile.BdfFontFile(open("crafted_256glyph.bdf", "rb")) font.to_imagefont() # → compile() → ~1.6 GB allocation, NO bomb check
Attack scenarios:
Scenario | Effect |
|---|---|
Web font preview ( BdfFontFile(upload).to_imagefont()) | DoS with crafted .bdf upload |
Server-side font renderer that loads PCF → to_imagefont() | OOM crash |
Font pipeline: load → render text | One malicious font file kills the process |
Impact
Availability: HIGH — compile() creates a combined bitmap whose pixel count scales as WIDTH × lines × max_glyph_height with no upper bound check. With max PCF glyph height (65,535) and 256 glyphs, the combined allocation is ~1.6 GB. With BDF (text-format, unbounded height), the allocation is limited only by system memory.
Confidentiality: None
Integrity: None
Affected call paths:
BdfFontFile.BdfFontFile(fp).to_imagefont() → FontFile.compile()
BdfFontFile.BdfFontFile(fp).save(filename) → FontFile.compile()
PcfFontFile.PcfFontFile(fp).to_imagefont() → FontFile.compile()
PcfFontFile.PcfFontFile(fp).save(filename) → FontFile.compile()
Neither BdfFontFile nor PcfFontFile is loaded via Image.open(), so the standard decompression bomb guard is entirely absent from the font loading code path. compile() is the only point where the combined allocation size is known, and it has no check.
Confirmed unpatched on python-pillow/Pillow main branch as of 2026-06-08.
Mitigation
Update Impact
Minimal update. May introduce new vulnerabilities or breaking changes.
Ecosystem | Component | Affected version | Patched versions |
|---|---|---|---|
debian 11 | - | ||
debian 12 | - | ||
debian 13 | - | ||
rpm rhel8 | 0:5.1.1-22.el8_10 | ||
debian 14 | 12.3.0-1 | ||
pypi | 12.3.0 | ||
rpm rhel8.6 | 0:5.1.1-20.el8_6 |
Aliases
References