CVE-2026-7919
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
HierarchyMutatingObserverui/aura/window_unittest.cc |
modified | |
ifui/aura/window_unittest.cc |
modified | |
TEST_Fui/aura/window_unittest.cc |
modified | |
TestLayerAnimationObserverui/aura/window_unittest.cc |
modified |
Files Changed
ash/wm/window_state.ccui/aura/window.ccui/aura/window_unittest.cc
Patch
From 43bf0cbc0d5d3a2183c46833dd172c016a57815b Mon Sep 17 00:00:00 2001 From: Andrew Paseltiner <[email protected]> Date: Fri, 03 Apr 2026 13:21:20 -0700 Subject: [PATCH] Fix iterator invalidation in aura::Window and Ash ID clash This CL addresses two related issues that could lead to memory safety vulnerabilities: 1. Update aura::Window notification loops to use WindowTracker. NotifyRemovingFromRootWindow, NotifyAddedToRootWindow, and NotifyWindowHierarchyChangeDown now safely handle potential hierarchy mutations during child iteration. 2. Upgrade ID validation in MoveAllTransientChildrenToNewRoot. Upgraded the ID validation from DCHECK_GE to CHECK_GE to ensure that container_id is non-negative in all builds. This prevents incorrect reparenting to the Wallpaper Window (which often has an ID of -1) and subsequent iterator invalidation. Fixed: 498832921 Change-Id: Ie38a4f864ffdc0baa6c59d83c3f4e59babbe2528 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7726886 Reviewed-by: Mitsuru Oshima <[email protected]> Commit-Queue: Andrew Paseltiner <[email protected]> Cr-Commit-Position: refs/heads/main@{#1609938} --- diff --git a/ash/wm/window_state.cc b/ash/wm/window_state.cc index d34bdeb..70c2f2c 100644 --- a/ash/wm/window_state.cc +++ b/ash/wm/window_state.cc @@ -40,6 +40,7 @@ #include "ash/wm/wm_event.h" #include "ash/wm/wm_metrics.h" #include "base/check_is_test.h" +#include "base/check_op.h" #include "base/containers/adapters.h" #include "base/containers/fixed_flat_map.h" #include "base/debug/crash_logging.h" @@ -265,7 +266,7 @@ if (!transient_child->parent()) continue; const int container_id = transient_child->parent()->GetId(); - DCHECK_GE(container_id, 0); + CHECK_GE(container_id, 0); aura::Window* container = dst_root->GetChildById(container_id); if (container->Contains(transient_child)) continue; diff --git a/ui/aura/window.cc b/ui/aura/window.cc index 832b4fc..1437ffcb 100644 --- a/ui/aura/window.cc +++ b/ui/aura/window.cc @@ -1226,9 +1226,10 @@ UnregisterFrameSinkId(); for (WindowObserver& observer : observers_) observer.OnWindowRemovingFromRootWindow(this, new_root); - for (Window::Windows::const_iterator it = children_.begin(); - it != children_.end(); ++it) { - (*it)->NotifyRemovingFromRootWindow(new_root); + + WindowTracker tracker(children_); + while (!tracker.windows().empty()) { + tracker.Pop()->NotifyRemovingFromRootWindow(new_root); } } @@ -1237,9 +1238,10 @@ RegisterFrameSinkId(); for (WindowObserver& observer : observers_) observer.OnWindowAddedToRootWindow(this); - for (Window::Windows::const_iterator it = children_.begin(); - it != children_.end(); ++it) { - (*it)->NotifyAddedToRootWindow(); + + WindowTracker tracker(children_); + while (!tracker.windows().empty()) { + tracker.Pop()->NotifyAddedToRootWindow(); } } @@ -1261,9 +1263,9 @@ void Window::NotifyWindowHierarchyChangeDown( const WindowObserver::HierarchyChangeParams& params) { NotifyWindowHierarchyChangeAtReceiver(params); - for (Window::Windows::const_iterator it = children_.begin(); - it != children_.end(); ++it) { - (*it)->NotifyWindowHierarchyChangeDown(params); + WindowTracker tracker(children_); + while (!tracker.windows().empty()) { + tracker.Pop()->NotifyWindowHierarchyChangeDown(params); } } diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc index e57db79..c59b14ef 100644 --- a/ui/aura/window_unittest.cc +++ b/ui/aura/window_unittest.cc @@ -13,6 +13,7 @@ #include "base/compiler_specific.h" #include "base/memory/raw_ptr.h" +#include "base/scoped_observation.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" @@ -3592,6 +3593,55 @@ } } +namespace { + +class HierarchyMutatingObserver : public WindowObserver { + public: + explicit HierarchyMutatingObserver(Window* window_to_remove) + : window_to_remove_(window_to_remove) {} + + HierarchyMutatingObserver(const HierarchyMutatingObserver&) = delete; + HierarchyMutatingObserver& operator=(const HierarchyMutatingObserver&) = + delete; + + void OnWindowAddedToRootWindow(Window* window) override { + if (window_to_remove_) { + window_to_remove_->parent()->RemoveChild(window_to_remove_); + window_to_remove_ = nullptr; + } + } + + private: + raw_ptr<Window> window_to_remove_; +}; + +} // namespace + +// Tests that modifying the hierarchy during NotifyAddedToRootWindow doesn't +// cause a crash. +TEST_F(WindowTest, MutateHierarchyDuringNotifyAddedToRootWindow) { + std::unique_ptr<Window> parent_window(CreateTestWindow({.window_id = 0})); + std::unique_ptr<Window> w1(CreateTestWindow({.window_id = 1})); + std::unique_ptr<Window> w2(CreateTestWindow({.window_id = 2})); + std::unique_ptr<Window> w3(CreateTestWindow({.window_id = 3})); + + parent_window->AddChild(w1.get()); + parent_window->AddChild(w2.get()); + parent_window->AddChild(w3.get()); + + // Add an observer to w2 that removes w1. + // When parent_window is added to the root, it will notify its children: w1, + // w2, w3. + // 1. Notify w1. + // 2. Notify w2. w2's observer removes w1. + // 3. Notify w3. + HierarchyMutatingObserver observer(w1.get()); + base::ScopedObservation<Window, WindowObserver> observation(&observer); + observation.Observe(w2.get()); + + root_window()->AddChild(parent_window.get()); +} + class TestLayerAnimationObserver : public ui::LayerAnimationObserver { public: TestLayerAnimationObserver()
Regression Test / PoC
diff --git a/ui/aura/window_unittest.cc b/ui/aura/window_unittest.cc
index e57db79..c59b14ef 100644
--- a/ui/aura/window_unittest.cc
+++ b/ui/aura/window_unittest.cc
@@ -13,6 +13,7 @@
#include "base/compiler_specific.h"
#include "base/memory/raw_ptr.h"
+#include "base/scoped_observation.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
@@ -3592,6 +3593,55 @@
}
}
+namespace {
+
+class HierarchyMutatingObserver : public WindowObserver {
+ public:
+ explicit HierarchyMutatingObserver(Window* window_to_remove)
+ : window_to_remove_(window_to_remove) {}
+
+ HierarchyMutatingObserver(const HierarchyMutatingObserver&) = delete;
+ HierarchyMutatingObserver& operator=(const HierarchyMutatingObserver&) =
+ delete;
+
+ void OnWindowAddedToRootWindow(Window* window) override {
+ if (window_to_remove_) {
+ window_to_remove_->parent()->RemoveChild(window_to_remove_);
+ window_to_remove_ = nullptr;
+ }
+ }
+
+ private:
+ raw_ptr<Window> window_to_remove_;
+};
+
+} // namespace
+
+// Tests that modifying the hierarchy during NotifyAddedToRootWindow doesn't
+// cause a crash.
+TEST_F(WindowTest, MutateHierarchyDuringNotifyAddedToRootWindow) {
+ std::unique_ptr<Window> parent_window(CreateTestWindow({.window_id = 0}));
+ std::unique_ptr<Window> w1(CreateTestWindow({.window_id = 1}));
+ std::unique_ptr<Window> w2(CreateTestWindow({.window_id = 2}));
+ std::unique_ptr<Window> w3(CreateTestWindow({.window_id = 3}));
+
+ parent_window->AddChild(w1.get());
+ parent_window->AddChild(w2.get());
+ parent_window->AddChild(w3.get());
+
+ // Add an observer to w2 that removes w1.
+ // When parent_window is added to the root, it will notify its children: w1,
+ // w2, w3.
+ // 1. Notify w1.
+ // 2. Notify w2. w2's observer removes w1.
+ // 3. Notify w3.
+ HierarchyMutatingObserver observer(w1.get());
+ base::ScopedObservation<Window, WindowObserver> observation(&observer);
+ observation.Observe(w2.get());
+
+ root_window()->AddChild(parent_window.get());
+}
+
class TestLayerAnimationObserver : public ui::LayerAnimationObserver {
public:
TestLayerAnimationObserver()
Original Bug Report
Potential OOB Read in aura::Window notifications via iterator invalidation
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the security team.
Overview: Methods in aura::Window iterate over child windows using raw iterators while firing observer callbacks. If a callback modifies the window hierarchy, such as ChromeOS transient window reparenting, the iterator is invalidated. This causes a potential out-of-bounds read on the vector’s backing buffer, leading to a sandbox escape and RCE in the browser process.
Affected files:
ui/aura/window.cc
Estimated timestamp from git blame: 2025-08-19
Overview
There is a potential Out-Of-Bounds (OOB) read and Use-After-Free (UAF) vulnerability in the window hierarchy notification loops of aura::Window (ui/aura/window.cc). Methods like NotifyAddedToRootWindow, NotifyRemovingFromRootWindow, and NotifyWindowHierarchyChangeDown iterate over the children_ vector using a raw const_iterator while synchronously firing observer callbacks.
If an observer alters the window hierarchy during these loops (e.g., adding or removing a sibling window), the children_ vector is mutated, invalidating the iterator.
Vulnerability Details
In ChromeOS (Ash), a highly exploitable path exists via ash::WindowState::OnWindowAddedToRootWindow. When a window is moved across root windows (e.g., moving to a different display), NotifyAddedToRootWindow is called recursively. When the notification reaches a transient parent window, the WindowState observer triggers MoveAllTransientChildrenToNewRoot.
This function attempts to move the window’s transient children to the new root window by finding the corresponding container ID. Because typical renderer-backed windows retain the default ID of -1 (kInitialId), the search dst_root->GetChildById(-1) incorrectly resolves to the first window with ID -1, which is typically the system’s Wallpaper Window.
The logic then calls WallpaperWindow->AddChild(transient_child). If transient_child was a direct sibling of the transient parent, this reparenting implicitly calls RemoveChild on the parent whose children_ vector is currently being iterated.
When an element before the current iterator is removed from a std::vector, the remaining elements shift left and the vector’s size decreases. The iterator now points to the new end(). At the end of the loop body, ++it increments the iterator past the new end(). The loop condition it != children_.end() evaluates to true, and the loop continues, dereferencing an out-of-bounds pointer.
Impact
The OOB read accesses adjacent heap memory outside the vector’s backing buffer. An attacker can groom the heap to place a fake Window object in this space. The loop executes a virtual method call (*it)->NotifyAddedToRootWindow(), allowing the attacker to hijack control flow, resulting in potential Remote Code Execution (RCE) and Sandbox Escape in the highly privileged Browser process. BRP (MiraclePtr) does not mitigate this, as the UAF/OOB occurs on the vector’s backing buffer, not the pointed-to object.
Potential Attacker Steps
Note: These are suggested/potential steps derived from code analysis; our tooling agent does not currently have the capability to run code or provide a working Proof of Concept.
- Setup: A compromised renderer creates an
aura::Windowhierarchy containing a parent (W_parent), a transient parent (Child1), and a transient child (W_transient). - Stacking Manipulation: The attacker carefully orders the creation so that
W_transientis located beforeChild1inW_parent’schildren_vector (e.g., by adding the transient child to the parent before the transient parent). - Trigger Move: The attacker initiates a window move across root windows (e.g., via the Multi-Screen Window Placement API or a fullscreen transition).
- Iterator Invalidation:
W_parent->NotifyAddedToRootWindow()begins iterating over its children. It skipsW_transient(index 0) and processesChild1(index 1). - Reparenting: The observer for
Child1triggersMoveAllTransientChildrenToNewRoot, which incorrectly reparentsW_transientto the Wallpaper Window due to the-1ID clash. - Exploitation:
W_transientis erased fromW_parent, shifting the vector. The iterator increments past the newend(), reads the attacker-groomed fakeWindowpointer from the heap, and executes a hijacked virtual call.
Suggested Fix
- Safe Iteration: Update
NotifyAddedToRootWindow,NotifyRemovingFromRootWindow, andNotifyWindowHierarchyChangeDowninui/aura/window.ccto useWindowTrackeror iterate over a copy of thechildren_vector. This pattern is already successfully utilized inNotifyWindowVisibilityChangedDownto prevent exactly this class of bug. - Resolve ID Clash: Address the logic bug in
MoveAllTransientChildrenToNewRoot(ash/wm/window_state.cc) so it correctly validates whetherGetChildByIdis returning the intended parent container, particularly when dealing with the default-1ID.
Evaluated with Chrome root at commit: ff3d2b74fa39431785bd60e51463b08fcc71ee33
Results 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.