Critical watch ✔︎ verified CVSS 7.5 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Sub-componentOzone/X11
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable — factors set by independent review by AI
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Independent review by AIConfirmed
Reviewer notes: Line citations (:691-694, :692, :693, :675-684, :676-680) and the regression test are accurate. The analysis twice attributes the fullscreen/bounds-change observer path to the advisory, which only says 'Use after free in Views'.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker518043597
Fix commit7340932fd7cf (chromium/src) +62/-0
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-30 (per vendor advisory)
Fix landed2026-06-01 (public git)
Disclosed2026-06-08
Patch gap7 days — fix public in source before the advisory

Background

`DesktopWindowTreeHostPlatform`
the Views desktop host object that wraps a platform window and drives window state transitions such as maximize, minimize and restore.
`platform_window()`
accessor for the underlying ui::PlatformWindow (e.g. X11Window) that the host delegates state changes to, and which can synchronously call back into observers.
`weak_factory_`
a base::WeakPtrFactory member used to obtain a WeakPtr to this so a method can detect whether the host was destroyed during a reentrant call.

Root Cause Analysis

Re-analyzed on 2026-10-03 against the actual fix commit 7340932fd7cf. An earlier analysis was withdrawn because it had been written against a commit that was not the security fix (fc4613ff876d).
In the pre-fix source, DesktopWindowTreeHostPlatform::Restore() (ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc:691-694) calls platform_window()->Restore() at :692 and then unconditionally calls Show(ui::mojom::WindowShowState::kNormal, gfx::Rect()) at :693. The call at :692 can synchronously re-enter other code (per the fix commit message and the reporter, on Linux/X11 restoring a fullscreen window can trigger a synchronous bounds change that notifies observers; the advisory says only “Use after free in Views”), and an observer may synchronously destroy the host, leaving this freed when control returns. Because the subsequent Show() at :693 dereferences members of the freed this, the result is a use-after-free. The sibling method Maximize() at :675-684 already guards against exactly this: it takes auto weak_ptr = weak_factory_.GetWeakPtr() at :676 before platform_window()->Maximize() at :677, then if (!weak_ptr) { return; } at :678-680 before touching this again. Restore() omitted this invariant. The diff restores it by inserting auto weak_ptr = weak_factory_.GetWeakPtr(); before the delegated call and if (!weak_ptr) { return; } after it, so Show() runs only if the host survived. A regression unit test (RestoreSurvivesSynchronousCloseDuringBoundsChange) drives a stub whose Restore() fires OnBoundsChanged while an observer closes the widget.
Key insight
Restore() delegated to the platform window and then used this again without checking whether the synchronous delegation had destroyed the host; the fix adds the same WeakPtr-liveness guard already used by Maximize() and SetFullscreen().

Attack Path

  1. Enter fullscreen A window belonging to the target is placed in fullscreen state, with a mechanism able to request a programmatic restore. (inferred — specific chrome.windows/PWA entry points are from the reporter, not the shown code.)
  2. Register a destroying observer An observer reacting to the window’s synchronous bounds/state change closes or destroys the widget the moment the restore transition fires. (inferred from the fix commit message, the reporter and the regression test’s closing observer; the X11 observer notification path is not in the shown source.)
  3. Trigger restore Restore() runs, calls platform_window()->Restore() (:692), which synchronously notifies the observer and frees the host.
  4. Use freed host Control returns to Restore() and executes Show() at :693 on the now-freed this, a use-after-free in the browser process.

Impact Assessment

An attacker who can cause a synchronous widget destruction during restore gains a use-after-free on the DesktopWindowTreeHostPlatform object in the browser process on Linux/X11, which the advisory rates critical severity; the advisory records no in-the-wild exploitation. Escalation beyond the memory-corruption primitive is not shown by the material and would be (inferred).

Changed Functions

FunctionChangeNotes
if
ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
modified
RestoreBoundsChangeStubWindow
ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
modified
RestoreBoundsChangePlatformWindowFactoryDelegate
ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
modified
RestoreBoundsChangePlatformWindowFactoryDelegate
ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
modified

Files Changed

  • ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
  • ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc

Audit Directions

  • Delegate-then-use-this
    Any method that calls into platform_window() (or another delegate that can run observers synchronously) and then touches this afterward must take and re-check a WeakPtr, as Maximize() and SetFullscreen() do.
  • Sibling-method parity
    When one method in a class adopts a lifetime guard for a reentrant call, audit every sibling performing the same delegation for the missing guard.
  • Synchronous observer callbacks
    Treat bounds/state-change notifications as potential reentrancy points that can destroy the notifying object mid-call.
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
index b958f49..b953949d0 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
@@ -689,7 +689,11 @@
 }
 
 void DesktopWindowTreeHostPlatform::Restore() {
+  auto weak_ptr = weak_factory_.GetWeakPtr();
   platform_window()->Restore();
+  if (!weak_ptr) {
+    return;
+  }
   Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());
 }
 
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
index 5bdaf338..264680e 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
@@ -797,6 +797,64 @@
   // This should not crash or trigger UAF.
   host_platform->Maximize();
 }
+
+class RestoreBoundsChangeStubWindow : public ui::StubWindow {
+ public:
+  RestoreBoundsChangeStubWindow(ui::PlatformWindowDelegate* delegate,
+                                gfx::AcceleratedWidget widget,
+                                const gfx::Rect& bounds)
+      : StubWindow(delegate, false, bounds) {
+    InitDelegateWithWidget(delegate, widget);
+  }
+
+  void Restore() override {
+    delegate()->OnBoundsChanged({/*origin_changed=*/true});
+  }
+};
+
+class RestoreBoundsChangePlatformWindowFactoryDelegate
+    : public aura::WindowTreeHostPlatform::
+          PlatformWindowFactoryDelegateForTesting {
+ public:
+  RestoreBoundsChangePlatformWindowFactoryDelegate() {
+    aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+        this);
+  }
+  RestoreBoundsChangePlatformWindowFactoryDelegate(
+      const RestoreBoundsChangePlatformWindowFactoryDelegate&) = delete;
+  RestoreBoundsChangePlatformWindowFactoryDelegate& operator=(
+      const RestoreBoundsChangePlatformWindowFactoryDelegate&) = delete;
+  ~RestoreBoundsChangePlatformWindowFactoryDelegate() override {
+    aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+        nullptr);
+  }
+
+  std::unique_ptr<ui::PlatformWindow> Create(
+      aura::WindowTreeHostPlatform* host) override {
+    return std::make_unique<RestoreBoundsChangeStubWindow>(
+        host, ++last_accelerated_widget_, gfx::Rect(100, 100, 100, 100));
+  }
+
+  gfx::AcceleratedWidget last_accelerated_widget_ = gfx::kNullAcceleratedWidget;
+};
+
+TEST_F(DesktopWindowTreeHostPlatformTest,
+       RestoreSurvivesSynchronousCloseDuringBoundsChange) {
+  RestoreBoundsChangePlatformWindowFactoryDelegate
+      scoped_platform_window_factory_delegate;
+
+  std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+  widget->Show();
+
+  auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+      widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+  ASSERT_TRUE(host_platform);
+
+  CloseOnBoundsChangedWidgetObserver observer(widget.get());
+
+  // This should not crash or trigger UAF.
+  host_platform->Restore();
+}
 #endif  // !BUILDFLAG(IS_FUCHSIA)
 
 }  // namespace views
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
index 5bdaf338..264680e 100644
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_platform_unittest.cc
@@ -797,6 +797,64 @@
   // This should not crash or trigger UAF.
   host_platform->Maximize();
 }
+
+class RestoreBoundsChangeStubWindow : public ui::StubWindow {
+ public:
+  RestoreBoundsChangeStubWindow(ui::PlatformWindowDelegate* delegate,
+                                gfx::AcceleratedWidget widget,
+                                const gfx::Rect& bounds)
+      : StubWindow(delegate, false, bounds) {
+    InitDelegateWithWidget(delegate, widget);
+  }
+
+  void Restore() override {
+    delegate()->OnBoundsChanged({/*origin_changed=*/true});
+  }
+};
+
+class RestoreBoundsChangePlatformWindowFactoryDelegate
+    : public aura::WindowTreeHostPlatform::
+          PlatformWindowFactoryDelegateForTesting {
+ public:
+  RestoreBoundsChangePlatformWindowFactoryDelegate() {
+    aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+        this);
+  }
+  RestoreBoundsChangePlatformWindowFactoryDelegate(
+      const RestoreBoundsChangePlatformWindowFactoryDelegate&) = delete;
+  RestoreBoundsChangePlatformWindowFactoryDelegate& operator=(
+      const RestoreBoundsChangePlatformWindowFactoryDelegate&) = delete;
+  ~RestoreBoundsChangePlatformWindowFactoryDelegate() override {
+    aura::WindowTreeHostPlatform::SetPlatformWindowFactoryDelegateForTesting(
+        nullptr);
+  }
+
+  std::unique_ptr<ui::PlatformWindow> Create(
+      aura::WindowTreeHostPlatform* host) override {
+    return std::make_unique<RestoreBoundsChangeStubWindow>(
+        host, ++last_accelerated_widget_, gfx::Rect(100, 100, 100, 100));
+  }
+
+  gfx::AcceleratedWidget last_accelerated_widget_ = gfx::kNullAcceleratedWidget;
+};
+
+TEST_F(DesktopWindowTreeHostPlatformTest,
+       RestoreSurvivesSynchronousCloseDuringBoundsChange) {
+  RestoreBoundsChangePlatformWindowFactoryDelegate
+      scoped_platform_window_factory_delegate;
+
+  std::unique_ptr<Widget> widget = CreateWidgetWithNativeWidget();
+  widget->Show();
+
+  auto* host_platform = DesktopWindowTreeHostPlatform::GetHostForWidget(
+      widget->GetNativeWindow()->GetHost()->GetAcceleratedWidget());
+  ASSERT_TRUE(host_platform);
+
+  CloseOnBoundsChangedWidgetObserver observer(widget.get());
+
+  // This should not crash or trigger UAF.
+  host_platform->Restore();
+}
 #endif  // !BUILDFLAG(IS_FUCHSIA)
 
 }  // namespace views
Loading diff…

Original Bug Report

reported by [email protected]

Potential Use-after-free in DesktopWindowTreeHostPlatform::Restore on Linux/X11

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential use-after-free vulnerability exists in DesktopWindowTreeHostPlatform::Restore on Linux/X11. Calling platform_window()->Restore() on a fullscreen window can trigger a synchronous bounds change that deletes the host window. Because Restore() lacks a weak-pointer guard, it proceeds to execute on the freed object.

Affected files:

  • ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
  • ui/ozone/platform/x11/x11_window.cc

Estimated timestamp from git blame: 2019-09-27

Root Cause Analysis

In DesktopWindowTreeHostPlatform::Restore(), the method delegates the restoration operation to the underlying platform-specific window and then unconditionally calls Show() on this without verifying if the object survived the synchronous delegation:

// ui/views/widget/desktop_aura/desktop_window_tree_host_platform.cc
void DesktopWindowTreeHostPlatform::Restore() {
  platform_window()->Restore();                                  // [1] May synchronously delete |this|
  Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());        // [2] Potential Use-after-free on freed |this|
}

Unlike sibling methods in the same class (such as SetFullscreen() and Maximize()), Restore() is missing a base::WeakPtr check to guard its lifetime.

On Linux/X11, when platform_window()->Restore() is called on a fullscreen window, the implementation (ui::X11Window::Restore()) calls SetFullscreen(false, ...) to exit fullscreen state. This method synchronously triggers platform_window_delegate_->OnBoundsChanged(...) to notify observers of layout transitions.

If an observer (e.g., reacting to bounds/layout changes) synchronously closes or destroys the widget, the entire DesktopWindowTreeHostPlatform object is deleted from the heap. When control flow returns to DesktopWindowTreeHostPlatform::Restore(), the subsequent call to Show() executes on a freed pointer.

Since this is a raw C++ pointer and the first dereferenced member fields inside Show() (such as platform_window_) are standard std::unique_ptr instances rather than raw_ptr<>, this use-after-free is not mitigated by MiraclePtr.

Suggested Steps to Trigger (Potential)

Note: These steps are theoretical and suggested based on code analysis; our tooling does not currently have the capability to execute code or verify via a live Proof-of-Concept.

  1. Establish an extension with the chrome.windows permission or an installed PWA (with experimental window-management features enabled) and transition its window into fullscreen state.
  2. Register a synchronous event observer (such as on bounds or state changes) that immediately calls close/destroy on the window when a transition occurs.
  3. Request a window restore operation via chrome.windows.update(id, {state: 'normal'}) or window.restore().
  4. Ensure the synchronous observer destroys the host when the bounds change is notified.
  5. Observe if the browser process crashes or accesses freed memory when resuming execution inside Restore().

Suggested Fix

To remediate this issue, obtain a weak pointer before delegating to the platform window, and return early if the host has been destroyed:

void DesktopWindowTreeHostPlatform::Restore() {
  auto weak_ptr = weak_factory_.GetWeakPtr();
  platform_window()->Restore();
  if (!weak_ptr) {
    return;
  }
  Show(ui::mojom::WindowShowState::kNormal, gfx::Rect());
}

Evaluated with Chrome root at commit: fb72408a8493c46bc75fae1c70d03daec96b3040


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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.

View on issue tracker