NEW320108
[SVG] <use> shadow tree strips <defs> and other non-rendered elements, breaking selector matching against the instance tree
https://bugs.webkit.org/show_bug.cgi?id=320108
Summary [SVG] <use> shadow tree strips <defs> and other non-rendered elements, breaki...
Taher
Reported 2026-07-23 11:17:30 PDT
ssia
Attachments
Radar WebKit Bug Importer
Comment 1 2026-07-23 11:18:31 PDT
Karl Dubost
Comment 2 2026-08-18 22:29:00 PDT
For future readers: It is not a spec issue or a performance issue. The origin was a batch of fuzzer crashes in 2012. CODE TODAY Source/WebCore/svg/SVGUseElement.cpp:223-258 https://github.com/WebKit/WebKit/blob/7247fcb1ca9690c80edd9cba7cbe915418987484/Source/WebCore/svg/SVGUseElement.cpp#L223-L258 `isDisallowedElement()`. A `switch` over 21 element names that returns "allowed"; everything else falls to `default: return true`, meaning disallowed. Allowed: `a circle desc ellipse g image line metadata path polygon polyline rect svg switch symbol text textPath title tref tspan use` Applied at :416-437 (removeDisallowedElementsFromSubtree), -> called from cloneTarget at :533 and from cloneDataAndChildren at :548. It runs on every <use>, same-document and cross-document alike. Also used at :510 to reject the target itself. Everything in SVG 2 that is NOT on that list is deleted from the clone: clipPath defs filter foreignObject linearGradient marker mask pattern radialGradient script stop style view and all 25 filter primitives (`feBlend`, `feGaussianBlur`, …), plus the SVG Animations elements (`animate`, `set`, `animateMotion`, `animateTransform`, `mpath`, `discard`), which are not in SVG 2's own element list but are equally absent from the 21. `tref` is a dead entry. SVG 2 removed the element, WebKit still has the class and still lists it. Onto the WHY in the next comment.
Karl Dubost
Comment 3 2026-08-18 22:41:01 PDT
WHY THIS DISALLOW/ALLOW LIST? Five commits touch this function, ever. git log --follow -S isDisallowedElement -- Source/WebCore/svg/SVGUseElement.cpp 2007-10-12: ddfe0ae70e5f, oliver, bug 13611 / bug 14631 isDisallowedElement is born as a denylist of exactly one element: static bool isDisallowedElement(Node* element) { // <foreignObject> should never be contained in a <use> tree. Too dangerous side effects possible. if (element->hasTagName(SVGNames::foreignObjectTag)) return true; return false; } That is the entire original intent: keep <foreignObject> out, because arbitrary HTML inside a cloned tree was thought unsafe. Later a line was added for SMIL animation elements. No spec citation, no performance claim. 2012-02-29: af16cc1c8cf3, Stephen Chenney (schenney@chromium.org), bug 77764 This is the commit that created today's behaviour. It inverted the one-element denylist into the 21-name allowlist, unchanged in membership ever since. Its own stated reason, verbatim from the ChangeLog: > Modify the isDisallowedElement method to disallow all of the disallowed > elements, instead of just a few. It is now a whitelist implementation. > > This also fixes bug 78807, bug 78838 and bug 79798 related to > memory corruption issues. > > These test all use invalid elements in the <use> and crash in the absence > of this patch. Bug 77764 and all three crash bugs are still security-restricted today. They return "You are not authorized to access bug #77764", So the public record of why is only this commit message plus the three tests it landed. Those tests are readable. All three put a <style> element inside the referenced subtree and combine it with editing or counters. That is the real defect class: an element with a document-wide side effect (registering a stylesheet) being cloned into a non-document tree. <glyph>(an SVG Fonts element WebKit has since removed) is the other one. So the allowlist is a wide net thrown to catch a narrow class of bugs. Two elements were actually implicated. Nineteen-plus categories were removed. 2012-05-01: 5a99f824e7af, bug 85202 Reuses the predicate to skip building the instance tree for a disallowed target. No membership change. 2015-01-26 / 2015-02-08 / 2015-02-12: b6027563341b, 257551de9b73, 8a4c131c2266 Darin Adler's three-part rewrite: bugs 140875, bug 141374, bug 141382. * SVGElementInstance is deleted entirely and the shadow tree becomes the only representation. * subtreeContainsDisallowedElement (a scan-then-bail) is replaced by removeDisallowedElementsFromSubtree (clone, then delete), * and the data structure goes HashSet<String> → HashSet<QualifiedName> → a static array loop → today's switch. Membership: unchanged, all three times. Nobody re-examined the list; it was carried through as-is. 2026: unchanged isDisallowedElement is byte-identical between my checkout HEAD and origin/main (0 commits behind, verified today). SVGUseElement.cpp's most recent change is a mechanical smart-pointer sweep. The 2012 crashes were crashes in the SVGElementInstance machinery. That machinery no longer exists. The reason the list was widened in 2012 was deleted in 2015.
Karl Dubost
Comment 4 2026-08-18 22:49:17 PDT
SPEC PROSE on all of this. SVG 1.1 The comment misreads SVG 1.1 The comment above the allowlist, in both WebKit and Blink, opens with: > "Any 'svg', 'symbol', 'g', graphics element or other 'use' is potentially > a template object that can be re-used (i.e., "instanced") > in the SVG document via a 'use' element." That sentence is real. It is the first sentence of the descriptive lead-in to SVG 1.1 §5.6. But it describes what a <use> may usefully point at. It is not a conformance requirement, and it says nothing about what may sit inside the thing pointed at. The very next paragraph of the same section says the opposite of filtering: > The effect of a 'use' element is as if the contents of the referenced element > were deeply cloned into a separate non-exposed DOM tree and §5.11.9 says nested references produce > "recursive expansion of the indirect references to form a complete tree". There is no restriction on shadow-tree contents in SVG 1.1. SVG 2 SVG 2 removed the restriction explicitly SVG 2 5.5.1 The use-element shadow tree, a note: > Previous versions of SVG restricted the contents of the shadow tree > to SVG graphics elements. This specification allows any valid SVG document subtree > to be cloned. > Cloning non-graphical content, however, will not usually have any visible effect. And immediately above it, > Within a use-element shadow tree, 'script' elements are inert (do not execute). SVG 2 expects <script> to be cloned and then neutered, not deleted. WebKit deletes it. SVG 2 handles <script> and says nothing about anything else that can execute code or load a resource. Emilio filed svgwg#876 on 2022-02-25, "Clarify <iframe> behavior in <svg:use>": https://github.com/w3c/svgwg/issues/876 > But <iframe> isn't, which means I can load an <iframe> and execute script. Open, zero replies, four and a half years later. So the 2007 comment's worry ("Too dangerous side effects possible") still needs an answer from the WG. <use> adding <foreignObject> is an interesting issue too.
Karl Dubost
Comment 5 2026-08-18 22:57:03 PDT
Other Browsers: BLINK the same list, minus one third_party/blink/renderer/core/svg/svg_use_element.cc:292-318. Same comment, same 20 names (no tref, which Blink removed from the engine), same unconditional pruning at :445 and :488. It is literally the same code: Chenney's 2012 patch landed in shared WebKit before the fork, and git log -S allowed_element_tags in Chromium finds only the 2018 file move. * Blink just finished modernising <use> shadow trees for SVG 2. Svg2Cascade shipped to stable in M144 and the flag was deleted on 2026-03-06 (31915b25f73c0, Chromium bug 40550039). Part of that work was 1cadd18aaa22f (2025-11-05), "Stop instantiating <symbol> as <svg> in use shadow trees" * They went through all of that and left the allowlist alone. So the allowlist is not something Blink has decided to keep on the merits; it is something a deliberate SVG 2 pass walked past. GECKO dom/svg/SVGUseElement.cpp. Three differences that matter: 1. The predicate is a class test, not a name list. IsForbiddenUseNode, :344-350 is !svg || !svg->IsSVGGraphicsElement(). Which means Gecko's kept-set is the SVGGraphicsElement subclasses: * a * defs * foreignObject * g + geometry (circle ellipse line path polygon polyline rect) + image + text content (text tspan textPath) + switch + viewport (svg symbol) + use. The two sets cross over. Gecko keeps defs and foreignObject ( bug 320108 and bug 43911 ). WebKit keeps desc, title, metadata, which Gecko drops. Neither is a subset of the other. 2. It is behind a pref, svg.use-element.graphics-element-restrictions, default 1 = restrict cross-document only. So for same-document <use> (the common case, every icon sprite in a page) Gecko filters nothing at all and has shipped that way for years. The permissive behaviour is already in the field. 3. They wrote down the reasoning. The comment above RemoveForbiddenNodes, :366-386 quotes the SVG 1.1 sentence, quotes the SVG 2 note that lifted it, cites svgwg#876, and ends "So, fairly confusing, all-in-all." The pref comment says value 0 "maps to what SVG 2 seems to call for", value 2 to "SVG 1.1 / the behavior of other UAs", and that the default 1 is "a compromise". The commit is 23030960cbda, Emilio Cobos Álvarez, 2022-02-26, Gecko bug 1754522, "Limit cross-document content loadable via <svg:use>": > It seems Blink / WebKit run this even if in the same document, > but it seems less-potentially-breaking to restrict this to the cross-document case. So Gecko's restriction was added for cross-origin/cross-document security, in 2022, and deliberately not extended to same-document content. INKSCAPE prunes nothing src/object/sp-use.cpp, SPUse::href_changed gates only the top-level target (must cast to SPItem) and never filters descendants. As an SVG Generator (SVG 2 1.4) it is not an authority on viewer behaviour, but it tells us what an authoring tool emits and expects.
Karl Dubost
Comment 6 2026-08-18 23:00:01 PDT
WPT * svg/struct/reftests/use-foreignObject.html (WPT) requires a <symbol> containing a <foreignObject> to render green * svg/linking/reftests/use-descendant-combinator-003.html requires that a defs … selector not match an instance. Adding <defs> back to the allowlist will not, on its own, change any selector result. Bug 320107 (the styling/scoping bug) has to land first, or the two changes have to go together. Basically it would be possible to add the elements back.
Note You need to log in before you can comment on or make changes to this bug.