Critical watch CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Sub-componentMacOS
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker517040438
Fix commitd698fc88d0d0 (chromium/src) +51/-0
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-05-27 (per vendor advisory)
Fix landed2026-05-28 (public git)
Disclosed2026-06-08
Patch gap11 days — fix public in source before the advisory

Background

`NativeWidgetNSWindowBridge`
the macOS remote-cocoa object that bridges a Chromium Widget to its underlying NSWindow; the edited method runs when a fullscreen transition completes.
`FullscreenControllerTransitionComplete`
the bridge method (the hunk’s enclosing function, confirmed by the regression test calling FullscreenControllerTransitionComplete(true)) that finishes a fullscreen transition and re-adds deferred child windows.
`OrderChildren` / `ShowAsModalSheet`
OrderChildren re-orders child windows and can synchronously present a modal sheet (beginSheet:), entering a nested AppKit run loop during which the parent window/bridge may be closed and destroyed.
`base::WeakPtr` / `factory_`
a self-nulling pointer obtained from the bridge’s WeakPtrFactory; it becomes null if the bridge is destroyed inside the nested loop.

Root Cause Analysis

On macOS, FullscreenControllerTransitionComplete calls OrderChildren() to re-add children skipped during the transition, and OrderChildren() can synchronously run a modal-sheet animation (ShowAsModalSheet/beginSheet:) that spins a nested run loop. If the window is closed during that loop, the NativeWidgetNSWindowBridge (this) is destroyed, yet the pre-patch code continued after OrderChildren() to dereference members — host_->OnWindowFullscreenTransitionComplete(...) and immersive_mode_controller_ — on the freed bridge, a use-after-free. The invariant violated is that this must still be alive after the re-entrant OrderChildren() before its members are used.

The fix takes base::WeakPtr<NativeWidgetNSWindowBridge> weak_ptr = factory_.GetWeakPtr() immediately before OrderChildren() and, right after it returns, does if (!weak_ptr) { return; }, aborting completion when the bridge was destroyed. This works because the bridge’s destruction nulls the weak pointer, so the subsequent member dereferences are skipped.

Key insight
The core mistake was assuming OrderChildren() could not destroy the bridge, when a synchronous modal-sheet animation inside it can; the fix captures a WeakPtr before the call and returns early if it is invalidated, preventing the post-call member accesses from touching freed memory.

Attack Path

  1. Fullscreen transition in progress A window on macOS is transitioning out of fullscreen, which will invoke FullscreenControllerTransitionComplete.
  2. Modal sheet during child ordering A child widget wanting to be visible causes OrderChildren() to synchronously present a modal sheet (ShowAsModalSheet/beginSheet:), entering a nested run loop.
  3. Close the window During that nested loop the parent window is closed (the regression test calls parent_widget->CloseNow() on NSWindowWillBeginSheetNotification), destroying the NativeWidgetNSWindowBridge.
  4. Use after free Control returns to FullscreenControllerTransitionComplete and, pre-patch, dereferences the freed bridge via host_ and immersive_mode_controller_ (inferred downstream consequence; the patch shows the weak-pointer check now prevents it).

Impact Assessment

The bug is a use-after-free of the NativeWidgetNSWindowBridge in the browser/app-shim process, reachable only on macOS and only when a modal sheet animation runs during a fullscreen-exit transition while the window is being closed — a timing-dependent, UI-driven race. The vendor advisory rates it critical and does not report in-the-wild exploitation. Escalation beyond the UAF/crash to controlled reuse or code execution is (inferred) and not demonstrated by the diff.

Changed Functions

FunctionChangeNotes
if
components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
modified
TEST_F
ui/views/widget/native_widget_mac_unittest.mm
modified

Files Changed

  • components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
  • ui/views/widget/native_widget_mac_unittest.mm

Audit Directions

  • Re-entrant child-ordering / modal presentation
    audit macOS remote-cocoa paths that call OrderChildren, ShowAsModalSheet, or any beginSheet:/modal API and then touch this or members, requiring a WeakPtr recheck after the call.
  • Nested AppKit run loops
    treat every synchronous animation/sheet/alert that can spin an AppKit run loop as a point where the bridge or widget may be destroyed, and revalidate lifetime on return.
  • Post-transition member use
    review other fullscreen/transition-complete and window-lifecycle callbacks for member dereferences that assume survival across a synchronous sub-call.
From d698fc88d0d073cf6be985297e6cf41a13225c2d Mon Sep 17 00:00:00 2001
From: Bryan Oltman <[email protected]>
Date: Thu, 28 May 2026 13:30:15 -0700
Subject: [PATCH] [macOS] Prevent UAF during fullscreen controller transition complete

Closing a window while transitioning out of fullscreen can cause a
use-after-free crash if a modal sheet animation is run. This CL checks
if the NativeWidgetNSWindowBridge was destroyed during child window
ordering and aborts completion if so.

Fixed: 517040438
Change-Id: I89b979cbf716202036f6484059bec002b3779064
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7881662
Commit-Queue: Bryan Oltman <[email protected]>
Reviewed-by: Keren Zhu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1637915}
---

diff --git a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
index 6e0a503..11db2ce 100644
--- a/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
+++ b/components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm
@@ -1637,7 +1637,14 @@
   UpdateWindowDisplay();
 
   // Add any children that were skipped during the fullscreen transition.
+  // A weak pointer is needed because OrderChildren() can synchronously run a
+  // modal sheet animation (ShowAsModalSheet), entering a nested run-loop during
+  // which this bridge may be destroyed.
+  base::WeakPtr<NativeWidgetNSWindowBridge> weak_ptr = factory_.GetWeakPtr();
   OrderChildren();
+  if (!weak_ptr) {
+    return;
+  }
 
   host_->OnWindowFullscreenTransitionComplete(is_fullscreen);
   if (is_fullscreen && immersive_mode_controller_) {
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index 2312da6..7b74e5c 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -144,6 +144,11 @@
     bridge_->CheckAndNotifyAllWorkspacesStateChanged();
   }
 
+  remote_cocoa::NativeWidgetNSWindowBridge* bridge() { return &*bridge_; }
+  void set_wants_to_be_visible(bool visible) {
+    bridge_->wants_to_be_visible_ = visible;
+  }
+
  private:
   const raw_ref<remote_cocoa::NativeWidgetNSWindowBridge> bridge_;
 };
@@ -1746,6 +1751,45 @@
   }
 }
 
+// Test that if the bridge is destroyed synchronously during a modal sheet
+// animation inside FullscreenControllerTransitionComplete(), the weak pointer
+// check correctly prevents a Use-After-Free (UAF) upon return
+// (https://crbug.com/517040438).
+TEST_F(NativeWidgetMacTest, FullscreenTransitionCompleteBridgeDestruction) {
+  NSWindow* native_parent = MakeClosableTitledNativeParent();
+  @autoreleasepool {
+    Widget* parent_widget =
+        Widget::GetWidgetForNativeWindow(gfx::NativeWindow(native_parent));
+    ASSERT_TRUE(parent_widget);
+
+    Widget* sheet_widget = views::DialogDelegate::CreateDialogWidget(
+        NativeWidgetMacTest::MakeModalDialog(ui::mojom::ModalType::kWindow),
+        gfx::NativeWindow(), parent_widget->GetNativeView());
+
+    BridgedNativeWidgetTestApi(sheet_widget).set_wants_to_be_visible(true);
+
+    remote_cocoa::NativeWidgetNSWindowBridge* parent_bridge =
+        NativeWidgetMacNSWindowHost::GetFromNativeWindow(
+            gfx::NativeWindow(native_parent))
+            ->GetInProcessNSWindowBridge();
+
+    // Register an observer to close the parent widget synchronously when the
+    // child modal sheet starts its presentation animation. This simulates the
+    // synchronous bridge destruction during the on-stack beginSheet: call.
+    id observer = [[NSNotificationCenter defaultCenter]
+        addObserverForName:NSWindowWillBeginSheetNotification
+                    object:native_parent
+                     queue:nil
+                usingBlock:^(NSNotification* note) {
+                  parent_widget->CloseNow();
+                }];
+
+    parent_bridge->FullscreenControllerTransitionComplete(true);
+
+    [[NSNotificationCenter defaultCenter] removeObserver:observer];
+  }
+}
+
 // Exercise a scenario where the task posted in the asynchronous Close() could
 // eventually complete on a destroyed NSWindowDelegate. Regression test for
 // https://crbug.com/851376.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm
index 2312da6..7b74e5c 100644
--- a/ui/views/widget/native_widget_mac_unittest.mm
+++ b/ui/views/widget/native_widget_mac_unittest.mm
@@ -144,6 +144,11 @@
     bridge_->CheckAndNotifyAllWorkspacesStateChanged();
   }
 
+  remote_cocoa::NativeWidgetNSWindowBridge* bridge() { return &*bridge_; }
+  void set_wants_to_be_visible(bool visible) {
+    bridge_->wants_to_be_visible_ = visible;
+  }
+
  private:
   const raw_ref<remote_cocoa::NativeWidgetNSWindowBridge> bridge_;
 };
@@ -1746,6 +1751,45 @@
   }
 }
 
+// Test that if the bridge is destroyed synchronously during a modal sheet
+// animation inside FullscreenControllerTransitionComplete(), the weak pointer
+// check correctly prevents a Use-After-Free (UAF) upon return
+// (https://crbug.com/517040438).
+TEST_F(NativeWidgetMacTest, FullscreenTransitionCompleteBridgeDestruction) {
+  NSWindow* native_parent = MakeClosableTitledNativeParent();
+  @autoreleasepool {
+    Widget* parent_widget =
+        Widget::GetWidgetForNativeWindow(gfx::NativeWindow(native_parent));
+    ASSERT_TRUE(parent_widget);
+
+    Widget* sheet_widget = views::DialogDelegate::CreateDialogWidget(
+        NativeWidgetMacTest::MakeModalDialog(ui::mojom::ModalType::kWindow),
+        gfx::NativeWindow(), parent_widget->GetNativeView());
+
+    BridgedNativeWidgetTestApi(sheet_widget).set_wants_to_be_visible(true);
+
+    remote_cocoa::NativeWidgetNSWindowBridge* parent_bridge =
+        NativeWidgetMacNSWindowHost::GetFromNativeWindow(
+            gfx::NativeWindow(native_parent))
+            ->GetInProcessNSWindowBridge();
+
+    // Register an observer to close the parent widget synchronously when the
+    // child modal sheet starts its presentation animation. This simulates the
+    // synchronous bridge destruction during the on-stack beginSheet: call.
+    id observer = [[NSNotificationCenter defaultCenter]
+        addObserverForName:NSWindowWillBeginSheetNotification
+                    object:native_parent
+                     queue:nil
+                usingBlock:^(NSNotification* note) {
+                  parent_widget->CloseNow();
+                }];
+
+    parent_bridge->FullscreenControllerTransitionComplete(true);
+
+    [[NSNotificationCenter defaultCenter] removeObserver:observer];
+  }
+}
+
 // Exercise a scenario where the task posted in the asynchronous Close() could
 // eventually complete on a destroyed NSWindowDelegate. Regression test for
 // https://crbug.com/851376.
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF in NativeWidgetNSWindowBridge::FullscreenControllerTransitionComplete

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 (UAF) vulnerability exists in the macOS browser process of Google Chrome during fullscreen transitions. When NativeWidgetNSWindowBridge::FullscreenControllerTransitionComplete() invokes OrderChildren(), it can synchronously run a modal sheet animation that enters a nested AppKit run-loop. If a window close task is processed during this nested loop, the bridge is synchronously destroyed, causing a use-after-free when control returns to the caller.

Affected files:

  • components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm

Estimated timestamp from git blame: 2022-03-15

Summary

A potential Use-After-Free (UAF) vulnerability exists in the macOS browser process of Google Chrome inside the NativeWidgetNSWindowBridge class during fullscreen transitions. The issue arises because FullscreenControllerTransitionComplete() calls OrderChildren(), which can synchronously run a modal sheet animation that enters a nested AppKit run-loop. If the window is closed while this nested loop is running, the bridge is synchronously destroyed, causing a UAF when the stack unwinds and accesses fields of the freed bridge.

Vulnerability Analysis

In components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm, the method FullscreenControllerTransitionComplete() is defined as:

void NativeWidgetNSWindowBridge::FullscreenControllerTransitionComplete(
    bool is_fullscreen) {
  DCHECK(!fullscreen_controller_.IsInFullscreenTransition());
  UpdateWindowGeometry();
  UpdateWindowDisplay();

  // Add any children that were skipped during the fullscreen transition.
  OrderChildren();                                            // <--- Can spin nested run-loop

  host_->OnWindowFullscreenTransitionComplete(is_fullscreen); // <--- UAF on return if destroyed
  if (is_fullscreen && immersive_mode_controller_) {          // <--- UAF
    immersive_mode_controller_->FullscreenTransitionCompleted();
  }
}

When OrderChildren() executes, if there is a window-modal sheet child (where modal_type_ == ui::mojom::ModalType::kWindow) that wants to be visible but is not yet shown, it calls child->ShowAsModalSheet().

For an in-process bridge (browser-process path), MustPostTaskToRunModalSheetAnimation() returns false (defined in ui/views/cocoa/native_widget_mac_ns_window_host.mm). Therefore, begin_sheet_closure is executed synchronously, invoking AppKit’s -[NSWindow beginSheet:completionHandler:] on-stack:

void NativeWidgetNSWindowBridge::ShowAsModalSheet() {
  ...
  if (host_helper_->MustPostTaskToRunModalSheetAnimation()) { ... }
  else {
    std::move(begin_sheet_closure).Run();   // Synchronous beginSheet:
  }
}

As AppKit’s beginSheet: executes, it spins a nested run-loop to run the sheet presentation animation, which continues to pump the main thread’s message loop.

Bridge Destruction Inside the Nested Run-Loop

While this nested run-loop is running, if a window close request occurs, CloseWindow() is called. Since the window state is kFullscreen, the close is not deferred by the fullscreen controller. A window close task is posted to the current thread’s runner:

base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
    FROM_HERE, base::BindOnce(^{ [window close]; }));

This task is processed and executed immediately inside the active nested run-loop. The execution of [window close] invokes the delegate’s callback: -[ViewsNSWindowDelegate windowWillClose:] -> OnWindowWillClose() -> host_->OnWindowHasClosed() -> native_widget_mac_->WindowDestroyed() -> ns_window_host_.reset()

This synchronously destroys the NativeWidgetMacNSWindowHost and its owned NativeWidgetNSWindowBridge instance whose FullscreenControllerTransitionComplete() frame is still active on the call stack.

Upon exiting the nested run-loop, OrderChildren() returns. The subsequent lines in FullscreenControllerTransitionComplete() access this->host_ and this->immersive_mode_controller_ on the freed bridge allocation, causing a Use-After-Free (UAF).

Suggested Trigger Scenario

Note: These are potential steps. Our security review tooling does not have the ability to run code, and we do not have a functional proof-of-concept exploit.

  1. A page opens a popup hosting an in-process NativeWidgetNSWindowBridge.
  2. A window-modal sheet child is created and deferred (e.g. while the popup is hidden/inactive).
  3. The page requests fullscreen (element.requestFullscreen()).
  4. During the fullscreen transition, AppKit triggers windowDidEnterFullScreen: -> OnWindowDidEnterFullscreen() -> FullscreenControllerTransitionComplete() -> OrderChildren().
  5. OrderChildren() calls ShowAsModalSheet(), starting the synchronous beginSheet: nested loop.
  6. While the nested loop is running, the page calls window.close(), which posts a [window close] task that runs immediately, destroying the bridge.
  7. The nested loop finishes, and the bridge is dereferenced during the return of FullscreenControllerTransitionComplete(), triggering a UAF.

Proposed Fix

Use a base::WeakPtr to verify if the bridge has been destroyed during the nested run-loop before continuing execution inside FullscreenControllerTransitionComplete():

void NativeWidgetNSWindowBridge::FullscreenControllerTransitionComplete(
    bool is_fullscreen) {
  DCHECK(!fullscreen_controller_.IsInFullscreenTransition());
  UpdateWindowGeometry();
  UpdateWindowDisplay();

  // Add any children that were skipped during the fullscreen transition.
  auto weak_ptr = factory_.GetWeakPtr();
  OrderChildren();
  if (!weak_ptr) {
    return;
  }

  host_->OnWindowFullscreenTransitionComplete(is_fullscreen);
  if (is_fullscreen && immersive_mode_controller_) {
    immersive_mode_controller_->FullscreenTransitionCompleted();
  }
}

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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