Skip to content

fix: clamp SVG rasterization size to prevent memory-exhaustion DoS - #7

Merged
seanghay merged 1 commit into
seanghay:mainfrom
bunlongheng:fix/security-svg-dimension-dos
Aug 7, 2026
Merged

fix: clamp SVG rasterization size to prevent memory-exhaustion DoS#7
seanghay merged 1 commit into
seanghay:mainfrom
bunlongheng:fix/security-svg-dimension-dos

Conversation

@bunlongheng

Copy link
Copy Markdown
Contributor

Following up on #6 (thanks for the quick merge!) with a small defensive hardening fix.

Problem

rasterizeSvg() in src/skia/image.ts reads an SVG's intrinsic size and passes it straight to MakeSurface():

const [width, height] = readContainerSize(dom, markup);
const surface = kit.MakeSurface(Math.ceil(width), Math.ceil(height));

The size comes from the SVG's own width/height attributes (or viewBox), none of which is bounded by the document's byte length. Because a raster surface costs width * height * 4 bytes, a tiny document can request an enormous allocation:

SVG bytes Declared size Surface allocation
~100 8000 x 8000 ~256 MB (measured)
~100 20000 x 20000 ~1.6 GB
~100 60000 x 60000 ~14 GB

SVG is a documented Photo input and flows through SoneImage.decode, so any application that renders SVG bytes/URLs from an untrusted source is exposed to a memory-exhaustion denial of service from a ~100-byte payload. This is uncontrolled resource consumption (CWE-400 / CWE-789).

Fix

Clamp the longest side to 8192 (a common GPU max-texture limit, far above any realistic layout target) and scale the other axis proportionally, so the image still renders correctly - just bounded in resolution. Non-finite or non-positive declared dimensions (malformed width/viewBox) now fall back to the CSS default replaced-element size instead of reaching MakeSurface.

The change is confined to rasterizeSvg's sizing; normal SVGs are unaffected.

Testing

  • Added test/image-svg-dos.test.ts: a 60000x60000 and a 50000x25000 SVG now rasterize within the bound (aspect ratio preserved), while a normal 200x120 SVG is unchanged. The DoS test fails on main (unbounded allocation / throw) and passes with this fix.
  • Full suite green: 397 passed.

No behavior change for legitimately sized documents; purely a bound on adversarial ones.

An SVG can declare an arbitrary intrinsic size via its width/height
attributes or viewBox, none of which is bounded by the document's byte
length. rasterizeSvg() passed those values straight to MakeSurface(),
whose backing store costs width * height * 4 bytes, so a ~100-byte SVG
declaring 60000x60000 requested ~14 GB - an untrusted-input DoS for any
caller that decodes SVG bytes/URLs (a documented Photo input).

Clamp the longest side to 8192 (common GPU max-texture limit), scaling
the other axis proportionally so the image still renders correctly, just
bounded in resolution. Non-finite or non-positive declared dimensions now
fall back to the CSS default replaced-element size instead of reaching
MakeSurface. Adds a regression test.
@vercel

vercel Bot commented Aug 6, 2026

Copy link
Copy Markdown

@bunlongheng is attempting to deploy a commit to the Seanghay Yath's projects Team on Vercel.

A member of the Team first needs to authorize it.

@seanghay
seanghay merged commit bbbf8a9 into seanghay:main Aug 7, 2026
6 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants