Rendering an untrusted SVG whose contains many segments is O(n²) CPU. A single under 1 MiB burns tens of seconds. Two independent O(n²) sites in cairosvg/path.py:
- 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.
- drawmarkers — 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'' 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.
// reachabilityPublic API svg2png / svg2pdf / svg2ps on an untrusted SVG string.
// suggested fixTokenize 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.