Skip to content

[vector_graphics_compiler] Stack Overflow and CPU/Memory DoS on SVGs with circular references or exponential expansions #186750

Description

@jeffkwoh

Description

The vector_graphics_compiler tool is vulnerable to Stack Overflow crashes and CPU/Memory Denial of Service (DoS) exhaustion during the AST parsing and resolution phases if an SVG contains circular references or exponential nested expansions.

We have identified two distinct vulnerability vectors:

  1. Infinite Recursion (Stack Overflow): Circular reference loops inside masks, patterns, deferred <use> nodes, or <clipPath> definitions lead to infinite recursion, causing the Dart thread to run out of stack space and crash the compiler.
  2. Exponential DAG Expansion (Billion Laughs / CPU DoS): Acyclic Directed Acyclic Graphs (DAGs) that reference lower-level groups multiple times do not trigger cycle detection, but expand exponentially. A 30-level deep DAG expands to over 1 billion nodes, locking up the CPU and exhausting heap memory (OOM), crashing downstream compile servers or client builders.

Steps to Reproduce

The bugs can be triggered by compiling any SVG that contains circular or highly nested references. Below are three minimal reproduction SVGs:

1. Circular <use> Node Loop (Triggers Stack Overflow)

<svg viewBox="0 0 100 100">
  <g id="groupA">
    <use xlink:href="#groupB" />
  </g>
  <g id="groupB">
    <use xlink:href="#groupA" />
  </g>
  <use xlink:href="#groupA" />
</svg>

2. Circular ClipPath Node Loop (Triggers Stack Overflow)

<svg viewBox="0 0 100 100">
  <g id="groupA">
    <use xlink:href="#groupB" />
  </g>
  <g id="groupB">
    <use xlink:href="#groupA" />
  </g>
  <clipPath id="clip1">
    <use xlink:href="#groupA" />
  </clipPath>
  <rect width="100" height="100" fill="blue" clip-path="url(#clip1)"/>
</svg>

3. Exponential DAG Expansion (Triggers CPU/Memory DoS)

<svg viewBox="0 0 100 100">
  <defs>
    <path id="leaf" d="M 0,0 L 10,10" />
    <g id="lvl1"><use href="#leaf" /><use href="#leaf" /></g>
    <g id="lvl2"><use href="#lvl1" /><use href="#lvl1" /></g>
    <!-- ... nested up to lvl30 ... -->
    <g id="lvl30"><use href="#lvl29" /><use href="#lvl29" /></g>
  </defs>
  <use href="#lvl30" />
</svg>

Stack Trace (Circular Reference Crash)

When running the compiler parser test with the circular clip-path SVG, it crashes with a Stack Overflow:

Stack Overflow
package:vector_graphics_compiler/src/svg/parser.dart 1705:5           _Resolver.getClipPath.extractPathsFromNode
package:vector_graphics_compiler/src/svg/parser.dart 1721:16          _Resolver.getClipPath.extractPathsFromNode
package:vector_graphics_compiler/src/svg/parser.dart 1721:16          _Resolver.getClipPath.extractPathsFromNode
package:vector_graphics_compiler/src/svg/parser.dart 1719:9           _Resolver.getClipPath.extractPathsFromNode
package:vector_graphics_compiler/src/svg/parser.dart 1721:16          _Resolver.getClipPath.extractPathsFromNode
...
package:vector_graphics_compiler/src/svg/resolver.dart 30:48          ResolvingVisitor.visitClipNode
package:vector_graphics_compiler/src/svg/node.dart 348:20             ClipNode.accept
package:vector_graphics_compiler/src/svg/resolver.dart 187:16         ResolvingVisitor.visitViewportNode
package:vector_graphics_compiler/src/svg/node.dart 133:20             ViewportNode.accept
package:vector_graphics_compiler/src/svg/parser.dart 864:27           SvgParser.parse

Expected Behavior

The compiler should be robust against all forms of reference-based resource exhaustion. Instead of crashing or hanging, it should:

  1. Break Circular Loops: Perform DFS active ancestor tracking to identify circular loops in masks, patterns, deferred nodes, and clip paths, breaking them gracefully by returning empty fallback representations (allowing the base elements to render safely without crashing).
  2. Limit Cumulative Expansions: Enforce a strict, cumulative reference expansion safety ceiling (e.g., capping total expansions at 10,000) to cleanly abort compilation on malicious exponential DAG expansion payloads (Billion Laughs).

Proposed Proposal / Mitigation

Add two layers of defense inside vector_graphics_compiler (resolver.dart and parser.dart):

  1. DFS Cycle-Guards: Wrap recursive AST resolution in try/finally blocks, using Set<String> scopes (_activeMasks, _activePatterns, _activeDeferred, and activeDeferred) to track and break circular dependencies.
  2. Global Cumulative Expansion Cap: Implement a monotonic counter (_deferredExpansionCount) that accumulates the number of resolved reference events. If this counter crosses 10,000, throw a handled StateError, preventing compile-time thread hangs and Out-Of-Memory crashes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    c: crashStack traces logged to the consoleengineflutter/engine related. See also e: labels.has reproducible stepsThe issue has been confirmed reproducible and is ready to work onp: vector_graphicsThe vector_graphics package setpackageflutter/packages repository. See also p: labels.team-engineOwned by Engine team

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions