All vulnerabilities
CVE-2026-107378GHSA-c3jg-qh8m-j3h2PYSEC-2026-4204

CairoSVG: Quadratic-time DoS parsing a crafted SVG <path>

Kozea cairosvgpipLibraries & packages
CVSS
8.7High
ExploitationPublic exploit codeCISA's assessment says proof of concept code is public, assessed 8 Oct 2026. Not in the KEV catalogCISA via CVE List
Exploit odds (EPSS)0.38%Chance of exploitation activity in the next 30 days, higher than 31% of CVEs. Scored 23 Oct 2026FIRST EPSS
Fixed in2.9.1Fixed versionGitHub
Published8 Oct 2026Updated 15 Oct 2026CVE List

What fixes it

  • 2.9.1cairosvg, pipGitHub
  • 2.9.1cairosvg, PyPIOSVpip install "cairosvg>=2.9.1"

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Summary

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:

  1. Tokenizer — 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²) over the whole attribute.
  2. draw_markers — 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²).

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.

Text from GitHub Advisory Database

Affected versions 53

  • cairosvg, pip<= 2.9.0GitHub
  • CairoSVGaffected: < 2.9.1CVE List
  • cairosvg, PyPIfrom first release, fixed in 2.9.1OSV
  • cairosvg, PyPI0.1OSV
  • cairosvg, PyPI0.1.1OSV
  • cairosvg, PyPI0.1.2OSV
  • cairosvg, PyPI0.2OSV
  • cairosvg, PyPI0.3OSV
  • cairosvg, PyPI0.3.1OSV
  • cairosvg, PyPI0.4OSV
  • cairosvg, PyPI0.4.1OSV
  • cairosvg, PyPI0.4.2OSV
  • cairosvg, PyPI0.4.3OSV
  • cairosvg, PyPI0.4.4OSV
  • cairosvg, PyPI0.5OSV
  • cairosvg, PyPI1.0OSV
  • cairosvg, PyPI1.0.1OSV
  • cairosvg, PyPI1.0.10OSV
  • cairosvg, PyPI1.0.11OSV
  • cairosvg, PyPI1.0.12OSV
  • cairosvg, PyPI1.0.13OSV
  • cairosvg, PyPI1.0.14OSV
  • cairosvg, PyPI1.0.15OSV
  • cairosvg, PyPI1.0.16OSV
  • cairosvg, PyPI1.0.17OSV
  • cairosvg, PyPI1.0.18OSV
  • cairosvg, PyPI1.0.19OSV
  • cairosvg, PyPI1.0.2OSV
  • cairosvg, PyPI1.0.20OSV
  • cairosvg, PyPI1.0.21OSV
  • cairosvg, PyPI1.0.22OSV
  • cairosvg, PyPI1.0.3OSV
  • cairosvg, PyPI1.0.4OSV
  • cairosvg, PyPI1.0.5OSV
  • cairosvg, PyPI1.0.6OSV
  • cairosvg, PyPI1.0.7OSV
  • cairosvg, PyPI1.0.8OSV
  • cairosvg, PyPI1.0.9OSV
  • cairosvg, PyPI2.0.0OSV
  • cairosvg, PyPI2.0.0rc1OSV
  • cairosvg, PyPI2.0.0rc2OSV
  • cairosvg, PyPI2.0.0rc3OSV
  • cairosvg, PyPI2.0.0rc4OSV
  • cairosvg, PyPI2.0.0rc5OSV
  • cairosvg, PyPI2.0.0rc6OSV
  • cairosvg, PyPI2.0.1OSV
  • cairosvg, PyPI2.0.2OSV
  • cairosvg, PyPI2.0.3OSV
  • cairosvg, PyPI2.1.0OSV
  • cairosvg, PyPI2.1.1OSV
  • cairosvg, PyPI2.1.2OSV
  • cairosvg, PyPI2.1.3OSV
  • cairosvg, PyPI2.2.0OSV

References 6