CVE-2026-118025
Overview
Background
- SVG `<use>` element
- references another element by id and renders a clone of it; the clone lives in an anonymous use-element shadow tree attached to the
<use>element, not in the main document tree. - `nsGenericHTMLFrameElement`
- the base class (
dom/html/nsGenericHTMLFrameElement.cpp) for frame-owning HTML elements such as<iframe>,<frame>, and<object>; it owns annsFrameLoaderthat actually loads and runs the subframe’s content. - `EnsureFrameLoader()`
- lazily creates the
mFrameLoaderso the element can load its referenced document; creating a frame loader is what causes the subframe to navigate/load and its content (including inlineonloadhandlers) to run. - `IsInComposedDoc()` / `IsStaticDocument()` / `IsInSVGUseShadowTree()`
- predicates gating frame-loader creation: composed-doc membership, print/snapshot static documents, and (newly) membership in an SVG
<use>shadow tree.
Root Cause Analysis
EnsureFrameLoader() returned early (declining to create a frame loader) only when the element was not in a composed document, already had a frame loader, or lived in a static document. It did not check whether the element was inside an SVG <use> element’s shadow tree. So when a document contained a frame-owning element (e.g. an <iframe> inside a <foreignObject>) within a subtree referenced by <use>, the cloned copy of that element in the use-shadow-tree would reach EnsureFrameLoader(), create its own nsFrameLoader, and load/run as a real frame — the regression test shows the cloned <iframe>’s inline onload handler firing a second time (loads should be [false], i.e. only the original instance’s handler runs). Per the SVG spec and the linked svgwg issue #876, elements cloned into a <use> shadow tree are presentational and must not instantiate active frames; the old code let them do so.
The patch adds || IsInSVGUseShadowTree() to the early-return condition in EnsureFrameLoader(), so a frame-owning element that is part of a <use> shadow-tree clone no longer creates a frame loader and therefore does not load the referenced content or run its handlers. This is the whole fix plus a web-platform test. The advisory classes this as “Other issue in the SVG component,” which is deliberately vague; the diff substantiates a logic/spec-conformance error (unintended frame loading and script/handler execution for cloned frames), not a memory-safety sink. There is no freed-memory or corruption path visible in the diff — the security concern is the duplicate/unexpected frame instantiation and associated handler execution in a context that should be inert.
EnsureFrameLoader() failed to exclude elements inside an SVG <use> shadow tree, so a frame-owning element cloned by <use> would spin up its own frame loader and load/run as a live frame instead of being an inert presentational clone.Attack Path
- Web content
author a page with an SVG whose referenced subtree contains a frame-owning element, e.g.
<foreignObject><iframe onload=...></iframe></foreignObject>inside a<g id="g">. - Instantiate clone
add
<use href="#g"/>, cloning the subtree (including the<iframe>) into the<use>shadow tree. - Frame loader created
the cloned
<iframe>’sEnsureFrameLoader()runs without the use-shadow-tree guard and creates a frame loader for the clone. - Unintended load/execution
the cloned frame loads its content and runs script/handlers (the test observes the inline
onloadfiring for the clone). - Primitive
unexpected instantiation and content/handler execution of a frame that should be an inert
<use>clone (logic/spec violation; no memory-safety primitive shown by the diff).
Impact Assessment
<use> shadow tree creating a real frame loader and loading/running as a live subframe — duplicate/unexpected frame loads and inline-handler execution contrary to the SVG spec. The diff shows a logic (spec-conformance) error, not a memory-safety bug; Mozilla’s generic “Other issue” label is consistent with that, and the security severity (moderate) stems from the unintended load/script-execution behavior rather than any corruption primitive, which is not present in this diff.Changed Functions
| Function | Change | Notes |
|---|---|---|
promise_testtesting/web-platform/tests/svg/struct/scripted/use-iframe-onload.html |
modified |
Files Changed
dom/html/nsGenericHTMLFrameElement.cpptesting/web-platform/tests/svg/struct/scripted/use-iframe-onload.html
Audit Directions
- Other active-content creation in use-shadow-treesaudit sibling lazy-instantiation paths (plugin/object loaders, media element loading, script-bearing elements) for the same missing
IsInSVGUseShadowTree()guard when cloned by<use>. - Shadow-tree clone invariantsreview how use-element (and other anonymous) shadow-tree clones are treated versus their originals for any case where the clone wrongly gains “live”/document-connected behavior.
- `EnsureFrameLoader` callerscheck every caller/trigger of
EnsureFrameLoaderto confirm none bypasses the guard for an element already inside a<use>shadow tree.
Patch
diff --git a/dom/html/nsGenericHTMLFrameElement.cpp b/dom/html/nsGenericHTMLFrameElement.cpp
index f323d7b37d2..bd52333131e 100644
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -110,7 +110,8 @@ Nullable<WindowProxyHolder> nsGenericHTMLFrameElement::GetContentWindow() {
}
void nsGenericHTMLFrameElement::EnsureFrameLoader() {
- if (!IsInComposedDoc() || mFrameLoader || OwnerDoc()->IsStaticDocument()) {
+ if (!IsInComposedDoc() || mFrameLoader || OwnerDoc()->IsStaticDocument() ||
+ IsInSVGUseShadowTree()) {
// If frame loader is there, we just keep it around, cached
return;
}
diff --git a/testing/web-platform/tests/svg/struct/scripted/use-iframe-onload.html b/testing/web-platform/tests/svg/struct/scripted/use-iframe-onload.html
new file mode 100644
index 00000000000..735b2b0deb2
--- /dev/null
+++ b/testing/web-platform/tests/svg/struct/scripted/use-iframe-onload.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>An iframe cloned by an SVG use element doesn't run its inline onload handler</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/struct.html#UseElement">
+<link rel="help" href="https://github.com/w3c/svgwg/issues/876">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.loads = [];
+</script>
+<svg>
+ <g id="g">
+ <foreignObject width="100" height="100">
+ <iframe onload="loads.push(this.getRootNode() instanceof ShadowRoot)"></iframe>
+ </foreignObject>
+ </g>
+ <use href="#g"/>
+</svg>
+<script>
+ promise_test(async function() {
+ await new Promise(resolve => window.addEventListener("load", resolve));
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ await new Promise(resolve => step_timeout(resolve, 0));
+ assert_array_equals(loads, [false], "Only the original iframe's onload handler should run");
+ }, "Inline onload handler of an iframe in a use element shadow tree doesn't run");
+</script>
Regression Test / PoC
diff --git a/testing/web-platform/tests/svg/struct/scripted/use-iframe-onload.html b/testing/web-platform/tests/svg/struct/scripted/use-iframe-onload.html
new file mode 100644
index 00000000000..735b2b0deb2
--- /dev/null
+++ b/testing/web-platform/tests/svg/struct/scripted/use-iframe-onload.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>An iframe cloned by an SVG use element doesn't run its inline onload handler</title>
+<link rel="help" href="https://svgwg.org/svg2-draft/struct.html#UseElement">
+<link rel="help" href="https://github.com/w3c/svgwg/issues/876">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<script>
+ window.loads = [];
+</script>
+<svg>
+ <g id="g">
+ <foreignObject width="100" height="100">
+ <iframe onload="loads.push(this.getRootNode() instanceof ShadowRoot)"></iframe>
+ </foreignObject>
+ </g>
+ <use href="#g"/>
+</svg>
+<script>
+ promise_test(async function() {
+ await new Promise(resolve => window.addEventListener("load", resolve));
+ await new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve)));
+ await new Promise(resolve => step_timeout(resolve, 0));
+ assert_array_equals(loads, [false], "Only the original iframe's onload handler should run");
+ }, "Inline onload handler of an iframe in a use element shadow tree doesn't run");
+</script>