CVE-2026-11036
Overview
Files Changed
third_party/blink/renderer/core/html/forms/html_field_set_element.ccthird_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html
Patch
From bbc8fca2df94473ebaaef41d9b18964ad9d43304 Mon Sep 17 00:00:00 2001 From: Dominic Farolino <[email protected]> Date: Tue, 07 Apr 2026 12:38:27 -0700 Subject: [PATCH] DOM: Fix frame desynchronization bug Script should not run during a state-preserving atomic move, i.e., during `Node::moveBefore()`. However, we did not catch the fact that `HTMLFieldSetElement::ChildrenChanged()` calls `blur()` on the focused element, and this CL prevents this from happening during an atomic move. This is because neither focus should change, nor script should run during this. [email protected] Bug: 497964917 Change-Id: Ic7c1ec53df63a21e502b01130ee72395b6cb9fa0 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7736582 Reviewed-by: Noam Rosenthal <[email protected]> Commit-Queue: Dominic Farolino <[email protected]> Cr-Commit-Position: refs/heads/main@{#1610937} --- diff --git a/third_party/blink/renderer/core/html/forms/html_field_set_element.cc b/third_party/blink/renderer/core/html/forms/html_field_set_element.cc index 13beb3c..6210bb1 100644 --- a/third_party/blink/renderer/core/html/forms/html_field_set_element.cc +++ b/third_party/blink/renderer/core/html/forms/html_field_set_element.cc @@ -189,8 +189,9 @@ focused_element = element; } } - if (focused_element) + if (!GetDocument().StatePreservingAtomicMoveInProgress() && focused_element) { focused_element->blur(); + } } FocusableState HTMLFieldSetElement::SupportsFocus( diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html b/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html new file mode 100644 index 0000000..906d013 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>moveBefore should not close a modal dialog</title> +<link rel="author" title="Dominic Farolino" href="[email protected]"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<fieldset disabled id="fs"> + <legend id="L1"> + <input id="victim" onblur="window.blurExecuted = true"> + </legend> + <legend id="L2"></legend> +</fieldset> +<script> +test(() => { + victim.focus(); + window.blurExecuted = false; + fs.moveBefore(L1, null); + assert_false(window.blurExecuted, "Blur event is not fired"); +}, "The 'blur' event is not fired on children of HTMLFieldSetElement during moveBefore()"); +</script>
Regression Test / PoC
diff --git a/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html b/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html new file mode 100644 index 0000000..906d013 --- /dev/null +++ b/third_party/blink/web_tests/external/wpt/dom/nodes/moveBefore/fieldset-child-blur-event.html @@ -0,0 +1,20 @@ +<!DOCTYPE html> +<title>moveBefore should not close a modal dialog</title> +<link rel="author" title="Dominic Farolino" href="[email protected]"> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<body> +<fieldset disabled id="fs"> + <legend id="L1"> + <input id="victim" onblur="window.blurExecuted = true"> + </legend> + <legend id="L2"></legend> +</fieldset> +<script> +test(() => { + victim.focus(); + window.blurExecuted = false; + fs.moveBefore(L1, null); + assert_false(window.blurExecuted, "Blur event is not fired"); +}, "The 'blur' event is not fired on children of HTMLFieldSetElement during moveBefore()"); +</script>
Original Bug Report
Frame Tree Desynchronization via moveBefore() Synchronous Blur
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: A potential frame tree desynchronization vulnerability exists due to HTMLFieldSetElement::ChildrenChanged dispatching synchronous blur events while the StatePreservingAtomicMoveInProgress flag is active. This allows attacker-controlled JavaScript to execute during an atomic move, bypassing iframe lifecycle disconnections and security checks.
Affected files:
third_party/blink/renderer/core/html/forms/html_field_set_element.ccthird_party/blink/renderer/core/dom/node.ccthird_party/blink/renderer/core/dom/container_node.ccthird_party/blink/renderer/core/dom/document.ccthird_party/blink/renderer/core/html/html_frame_element_base.cc
Estimated timestamp from git blame: 2025-01-17
Core Vulnerability: A bypass of the StatePreservingAtomicMoveInProgress invariant permits attacker JavaScript execution during Node::moveBefore(), leading directly to frame tree desynchronization and potential Sandbox Escape / UXSS.
Technical Details
Initial logic and parameters surrounding Node::moveBefore(), the StatePreservingAtomicMoveInProgress flag assertion, and subsequent ContainerNode::AppendChild DOM tree manipulations are validated.
Standard processing applies during the mutation phases, culminating in the invocation of HTMLFieldSetElement::ChildrenChanged when a <legend> is moved within a disabled <fieldset>.
At this juncture, the logic determines the previously focused element inside the moved <legend> has become disabled. The EventDispatchForbiddenScope is explicitly closed. Control jumps directly to focused_element->blur(). Because Element::blur() lacks awareness of the atomic move state, it synchronously dispatches blur and focusout events to author JavaScript while GetDocument().StatePreservingAtomicMoveInProgress() remains true.
From within this event handler, an attacker can detach or re-insert arbitrary iframe elements. The active flag causes ContainerNode::WillRemoveChild to skip ChildFrameDisconnector, and HTMLFrameElementBase::InsertedInto to bypass SECURITY_CHECK(!ContentFrame() || GetDocument().StatePreservingAtomicMoveInProgress()). This results in a “zombie” iframe where the renderer detaches the element without notifying the browser’s RenderFrameHost.
Potential Attacker Steps (Unverified)
- Setup established context:
<fieldset disabled id="fs"><legend id="L1"><input id="victim"></legend><legend id="L2"></legend></fieldset>alongside a target<iframe>. - Standard processing applied: focus the
victiminput and attach a maliciousblurevent listener designed to calltarget.remove()ordocument.body.appendChild(target). - Trigger the move: execute
fs.moveBefore(L1, null). - Vulnerability triggered: The synchronous
blurevent executes the payload while the atomic move flag is active, corrupting the iframe lifecycle state.
Suggested Fix
Instantiate a ScriptForbiddenScope directly within Node::moveBefore() to strictly enforce the invariant, or explicitly defer focus/blur updates in HTMLFieldSetElement::ChildrenChanged if StatePreservingAtomicMoveInProgress is active.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.