GHSA-C3JG-QH8M-J3H2
Vulnerability from github – Published: 2026-10-08 19:41 – Updated: 2026-10-08 19:41Summary
Rendering an untrusted SVG whose <path d="..."> contains many segments is O(n²) CPU. A single <path> under 1 MiB burns tens of seconds. Two independent O(n²) sites in cairosvg/path.py:
- Tokenizer — the path-data parser consumes the
dstring with awhile string:loop that repeatedly slices/re-scans the remaining string (each step is O(len remaining)), giving O(n²) over the whole attribute. - draw_markers — marker handling drains
node.verticeswithwhile node.vertices: ... node.vertices.pop(0);list.pop(0)is O(n), so draining n vertices is O(n²).
Both are hit on a normal render path (svg2png/svg2pdf), attacker controls only the SVG document.
PoC (installed cairosvg 2.9.0)
import cairosvg
d = "M0 0 " + "L1 1 " * 100000
svg = f'<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><path d="{d}"/></svg>'
cairosvg.svg2png(bytestring=svg.encode()) # ~4.4 s for a 488 KB doc
| path segments | SVG size | time |
|---|---|---|
| 50,000 | 244 KB | 1.14 s |
| 100,000 | 488 KB | 4.36 s |
| 200,000 | ~960 KB | ~18 s |
Doubling segments ≈ 4× time ⇒ quadratic. Sub-MiB input ⇒ ~18 s CPU; any service rendering user-supplied SVG (thumbnails, avatars, PDF export) is a DoS target.
Reachability
Public API svg2png / svg2pdf / svg2ps on an untrusted SVG string.
Suggested fix
Tokenize with a single forward scan / index (or re.finditer) instead of re-slicing the remainder; drain vertices with an index or collections.deque.popleft instead of list.pop(0). Optionally cap path-segment count.
{
"affected": [
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 2.9.0"
},
"package": {
"ecosystem": "PyPI",
"name": "cairosvg"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.9.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-107378"
],
"database_specific": {
"cwe_ids": [
"CWE-407"
],
"github_reviewed": true,
"github_reviewed_at": "2026-10-08T19:41:22Z",
"nvd_published_at": "2026-10-08T18:17:23Z",
"severity": "HIGH"
},
"details": "## Summary\n\nRendering an untrusted SVG whose `\u003cpath d=\"...\"\u003e` contains many segments is O(n\u00b2) CPU. A single `\u003cpath\u003e` under 1 MiB burns tens of seconds. Two independent O(n\u00b2) sites in `cairosvg/path.py`:\n\n1. **Tokenizer** \u2014 the path-data parser consumes the `d` string with a `while string:` loop that repeatedly slices/re-scans the *remaining* string (each step is O(len remaining)), giving O(n\u00b2) over the whole attribute.\n2. **draw_markers** \u2014 marker handling drains `node.vertices` with `while node.vertices: ... node.vertices.pop(0)`; `list.pop(0)` is O(n), so draining n vertices is O(n\u00b2).\n\nBoth are hit on a normal render path (`svg2png`/`svg2pdf`), attacker controls only the SVG document.\n\n## PoC (installed cairosvg 2.9.0)\n\n```python\nimport cairosvg\nd = \"M0 0 \" + \"L1 1 \" * 100000\nsvg = f\u0027\u003csvg xmlns=\"http://www.w3.org/2000/svg\" width=\"10\" height=\"10\"\u003e\u003cpath d=\"{d}\"/\u003e\u003c/svg\u003e\u0027\ncairosvg.svg2png(bytestring=svg.encode()) # ~4.4 s for a 488 KB doc\n```\n\n| path segments | SVG size | time |\n|---|---|---|\n| 50,000 | 244 KB | 1.14 s |\n| 100,000 | 488 KB | 4.36 s |\n| 200,000 | ~960 KB | ~18 s |\n\nDoubling segments \u2248 4\u00d7 time \u21d2 quadratic. Sub-MiB input \u21d2 ~18 s CPU; any service rendering user-supplied SVG (thumbnails, avatars, PDF export) is a DoS target.\n\n## Reachability\n\nPublic API `svg2png` / `svg2pdf` / `svg2ps` on an untrusted SVG string.\n\n## Suggested fix\n\nTokenize with a single forward scan / index (or `re.finditer`) instead of re-slicing the remainder; drain `vertices` with an index or `collections.deque.popleft` instead of `list.pop(0)`. Optionally cap path-segment count.",
"id": "GHSA-c3jg-qh8m-j3h2",
"modified": "2026-10-08T19:41:22Z",
"published": "2026-10-08T19:41:22Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/Kozea/CairoSVG/security/advisories/GHSA-c3jg-qh8m-j3h2"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-107378"
},
{
"type": "WEB",
"url": "https://github.com/Kozea/CairoSVG/commit/9d63f049f9988d0ddda3eb94564ac3a50a286523"
},
{
"type": "WEB",
"url": "https://github.com/Kozea/CairoSVG/commit/a4d585eb374724b79676e9cceaa9e9a1a4358565"
},
{
"type": "PACKAGE",
"url": "https://github.com/Kozea/CairoSVG"
},
{
"type": "WEB",
"url": "https://github.com/Kozea/CairoSVG/releases/tag/2.9.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "CairoSVG: Quadratic-time DoS parsing a crafted SVG \u003cpath\u003e"
}
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.