Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker517040438
Fix commitd698fc88d0d0 (chromium/src) +51/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

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
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