High chrome UAF 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Browser
DescriptionUse after free in Browser
ComponentBrowser
Sub-componentChroMenu
Bug ClassUAF
Tracker575090745
Fix commit8ecdbc248b46 (chromium/src) +78/-3
CISA KEVNot listed
CreditedGoogle
Disclosed2026-10-27

Background

`AppMenuZoomView`
the views control (chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc) rendering the zoom row (-/+ buttons and a zoom_label_) inside the browser’s app menu.
`ActionItem` / `AddActionChangedCallback`
the zoom action is an actions::ActionItem owned by BrowserActions that outlives any single menu; AppMenuZoomView subscribes to its changes to keep zoom_label_ text in sync.
`Label::SetText()` and native accessibility events
updating label text synchronously raises a platform accessibility event. On Windows that is a UIA kTextChanged (UIA_Text_TextChangedEventId).
COM STA message pump re-entrancy
raising a UIA event on the Windows single-threaded-apartment pump can process an incoming UIA request inline, e.g. IExpandCollapseProvider::Collapse() on the app-menu button, which closes and destroys the menu while the callback is still on the stack.
`ScopedAccessibilityEventBlocker`
a views helper that suppresses accessibility event emission for the scope of its lifetime.

Root Cause Analysis

The zoom label’s action-changed callback was a lambda bound with a raw views::Label* label (zoom_label_) and the ActionItem*. When the zoom percentage changed, the callback compared and then called label->SetText(...) followed by label->GetViewAccessibility().AnnounceAlert(...). The hazard is re-entrancy through accessibility: SetText() synchronously raises the native kTextChanged UIA event, which can spin the COM STA message pump and let an incoming UIA request (such as Collapse() on the app-menu button) run inline. That request closes and destroys the menu — and with it AppMenuZoomView and its zoom_label_ — while SetText() and the enclosing callback are still executing. Because the callback held a raw pointer to the now-freed Label (and zoom_child outlives the menu, so the callback itself can still fire), the continued use of label (the subsequent AnnounceAlert() and the tail of SetText()) is a use-after-free.

The fix removes both the dangling-raw-pointer binding and the re-entrant event. The callback is now bound with weak_ptr_factory_.GetWeakPtr() (a base::WeakPtr<AppMenuZoomView>) instead of the raw zoom_label_; it early-returns if !self || !self->zoom_label_, and only then dereferences self->zoom_label_. Additionally, the SetText() call is wrapped in a views::ScopedAccessibilityEventBlocker so the intermediate kTextChanged event is suppressed and cannot re-entrantly destroy the menu mid-update; the explicit AnnounceAlert() at the end still fires the intended single alert. A base::WeakPtrFactory<AppMenuZoomView> is added to the header. The advisory’s Use after free in Browser matches the diff precisely; the component (“Browser”) is just the surface. The regression test ZoomLabelUpdateSurvivesReentrantMenuDestruction simulates a UIA client closing/destroying the menu during the kTextChanged raise and asserts survival.

Key insight
The zoom-label action callback held a raw pointer to a Label and let Label::SetText() synchronously raise a UIA kTextChanged event that could spin the Windows STA pump and destroy the whole menu (and label) re-entrantly, so the callback kept using freed memory — fixed with a WeakPtr guard plus an accessibility-event blocker around SetText().

Attack Path

  1. Open the app menu User/attacker-influenced flow opens the browser app menu showing the zoom row. (local UI precondition)
  2. UIA client present A UIA/accessibility client (assistive tech, automation, or a malicious local UIA client) is connected on Windows. (gating: Windows + UIA)
  3. Change zoom The zoom percentage changes while the menu is open, firing the action-changed callback and zoom_label_->SetText().
  4. Re-entrant close SetText() raises kTextChanged; the STA pump processes an inline IExpandCollapseProvider::Collapse() that closes and destroys the menu, freeing AppMenuZoomView/zoom_label_.
  5. Primitive The still-running callback dereferences the freed Label (e.g., AnnounceAlert()), a browser-process use-after-free. (escalation inferred)

Impact Assessment

Runs in the browser process, Windows-specific (the re-entrancy comes from the COM STA UIA pump). Gating is significant: it requires the app menu open, a zoom-percentage change, and an active UIA client capable of issuing a Collapse() during the kTextChanged raise — i.e., local accessibility tooling or a malicious local UIA client rather than direct web content. The achieved primitive is a re-entrant use-after-free of AppMenuZoomView/zoom_label_; its strength and any escalation beyond a crash are (inferred), as the diff shows the lifetime/re-entrancy fix but not an exploit sink. This is a genuine UAF, not a mislabel.

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc
modified
TEST_F
chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
modified
if
chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
modified

Files Changed

  • chrome/browser/ui/views/app_menu/app_menu_zoom_view.h
  • chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc
  • chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc

Audit Directions

  • Raw pointers in `AddActionChangedCallback`/callback-list bindings
    Audit views callbacks bound with raw View*/Label* where the subscription source (e.g., an ActionItem) outlives the view; these dangle if the view is destroyed first.
  • `SetText()` / label updates on Windows
    Review UI update paths that call Label::SetText() (or otherwise raise native accessibility events) while on a destructible menu/dialog stack; synchronous UIA dispatch can spin the STA pump and re-enter.
  • Re-entrant menu/dialog destruction
    Look for other menu interactions (expand/collapse, invoke) reachable via UIA that can close the menu mid-callback, and verify callbacks are WeakPtr-guarded.
  • Missing accessibility-event scoping
    Identify multi-step UI mutations that should wrap intermediate steps in ScopedAccessibilityEventBlocker to avoid premature/synchronous event dispatch.
From 8ecdbc248b46ef86145e571c7e342c0cb03c4975 Mon Sep 17 00:00:00 2001
From: Steven Luong <stluong@chromium.org>
Date: Thu, 22 Oct 2026 10:08:21 -0700
Subject: [PATCH] [ChroMenu] Fix potential UAF when updating zoom label text

When the zoom percentage changes while the app menu is open,
AppMenuZoomView updates `zoom_label_` via `Label::SetText()` and then
calls `AnnounceAlert()`.
On Windows, `Label::SetText()` synchronously raises a native
`kTextChanged` accessibility event (`UIA_Text_TextChangedEventId`),
which can spin the COM STA message pump and process an incoming UIA
request (such as `IExpandCollapseProvider::Collapse()` on the app menu
button) that closes and destroys the menu while `SetText()` and the
action-changed callback are still on the stack.
To prevent this re-entrant use-after-free:
1. Bind a `base::WeakPtr<AppMenuZoomView>` in the zoom label's action
   changed callback instead of an unowned raw pointer.
2. Wrap `label->SetText()` in a `views::ScopedAccessibilityEventBlocker`
   so the intermediate `kTextChanged` event is suppressed before the
   explicit `AnnounceAlert()` call at the end of the callback.

Fixed: 575090745
Change-Id: Ic10de222f12c7c90f5b6aa9dd4868c9ae638c57a
Bug: 575090745
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8616544
Auto-Submit: Steven Luong <stluong@chromium.org>
Commit-Queue: Darryl James <dljames@chromium.org>
Commit-Queue: Steven Luong <stluong@chromium.org>
Reviewed-by: Darryl James <dljames@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1724911}
---
diff --git a/chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc b/chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc
index 7612c2e..32738bea 100644
--- a/chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc
+++ b/chrome/browser/ui/views/app_menu/app_menu_zoom_view.cc
@@ -121,15 +121,33 @@
       zoom_label_->SetPreferredSize(gfx::Size(
           GetZoomLabelMaxWidth(), zoom_label_->GetPreferredSize().height()));
 
+      // Bind a WeakPtr to `this` rather than a raw pointer to `zoom_label_`
+      // because `zoom_child` is owned by BrowserActions and outlives the menu;
+      // synchronous native accessibility event dispatch (e.g., UIA on Windows)
+      // can also spin an STA message pump and re-entrantly close/destroy the
+      // menu while action callbacks are being notified. This is needed to
+      // prevent a UAF bug as reported in crbug.com/575090745.
       zoom_label_subscription_ =
           zoom_child->AddActionChangedCallback(base::BindRepeating(
-              [](views::Label* label, actions::ActionItem* item) {
+              [](base::WeakPtr<AppMenuZoomView> self,
+                 actions::ActionItem* item) {
+                if (!self || !self->zoom_label_) {
+                  return;
+                }
+                views::Label* label = self->zoom_label_;
                 if (label->GetText() != item->GetText()) {
-                  label->SetText(std::u16string(item->GetText()));
+                  // Block the intermediate kTextChanged event during SetText()
+                  // so synchronous UIA event dispatch cannot re-entrantly
+                  // destroy `label` mid-update before AnnounceAlert() runs.
+                  {
+                    views::ScopedAccessibilityEventBlocker blocker(
+                        label->GetViewAccessibility());
+                    label->SetText(std::u16string(item->GetText()));
+                  }
                   label->GetViewAccessibility().AnnounceAlert(item->GetText());
                 }
               },
-              zoom_label_, zoom_child));
+              weak_ptr_factory_.GetWeakPtr(), zoom_child));
     } else {
       views::ImageButton* const zoom_child_button =
           AddChildView(CreateZoomButton(zoom_child));
diff --git a/chrome/browser/ui/views/app_menu/app_menu_zoom_view.h b/chrome/browser/ui/views/app_menu/app_menu_zoom_view.h
index af65716..3a317de 100644
--- a/chrome/browser/ui/views/app_menu/app_menu_zoom_view.h
+++ b/chrome/browser/ui/views/app_menu/app_menu_zoom_view.h
@@ -11,6 +11,7 @@
 #include "base/callback_list.h"
 #include "base/containers/flat_map.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "ui/actions/actions.h"
 #include "ui/base/metadata/metadata_header_macros.h"
 #include "ui/color/color_id.h"
@@ -80,6 +81,7 @@
   raw_ptr<views::ImageButton> zoom_plus_button_ = nullptr;
   base::CallbackListSubscription zoom_label_subscription_;
   std::vector<base::CallbackListSubscription> button_subscriptions_;
+  base::WeakPtrFactory<AppMenuZoomView> weak_ptr_factory_{this};
 };
 
 #endif  // CHROME_BROWSER_UI_VIEWS_APP_MENU_APP_MENU_ZOOM_VIEW_H_
diff --git a/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc b/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
index 3b70064..2eabe11 100644
--- a/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
+++ b/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
@@ -1604,6 +1604,61 @@
 }
 #endif  // !BUILDFLAG(IS_MAC)
 
+// Regression test for crbug.com/575090745: Firing a synchronous native
+// accessibility event during zoom label text updates can spin the Windows COM
+// STA message pump and re-entrantly close and destroy the menu.
+TEST_F(ActionAppMenuTest, ZoomLabelUpdateSurvivesReentrantMenuDestruction) {
+  content::RenderViewHostTestEnabler rvh_test_enabler;
+  tabs::MockTabInterface mock_tab;
+  std::unique_ptr<content::WebContents> web_contents =
+      content::WebContentsTester::CreateTestWebContents(profile_.get(),
+                                                        nullptr);
+  zoom::ZoomController::CreateForWebContents(web_contents.get());
+
+  EXPECT_CALL(mock_window_interface_, GetActiveTabInterface())
+      .WillRepeatedly(testing::Return(&mock_tab));
+  EXPECT_CALL(mock_tab, GetContents())
+      .WillRepeatedly(testing::Return(web_contents.get()));
+
+  auto menu = std::make_unique<ActionAppMenu>(&mock_window_interface_,
+                                              base::DoNothing());
+  menu->RunMenu(button_->button_controller(), views::MenuRunner::NO_FLAGS);
+  ASSERT_TRUE(menu->IsShowing());
+
+  views::MenuItemView* const root = menu->root_menu_item_for_testing();
+  ASSERT_TRUE(root);
+
+  views::MenuItemView* zoom_item = root->GetMenuItemByID(kActionZoomSubmenu);
+  ASSERT_TRUE(zoom_item);
+
+  auto* zoom_view =
+      views::AsViewClass<AppMenuZoomView>(zoom_item->children()[0]);
+  ASSERT_TRUE(zoom_view);
+
+  views::Label* const zoom_label = zoom_view->zoom_label_for_testing();
+  ASSERT_NE(zoom_label, nullptr);
+
+  // Simulate a UIA client re-entrantly invoking AppMenuButton::CloseMenu()
+  // during the kTextChanged accessibility event raise inside Label::SetText().
+  zoom_label->GetViewAccessibility().set_accessibility_events_callback(
+      base::BindLambdaForTesting(
+          [&menu](const ui::AXPlatformNodeDelegate*, ax::mojom::Event event) {
+            if (event == ax::mojom::Event::kTextChanged && menu) {
+              menu->CloseMenu();
+              menu.reset();
+            }
+          }));
+
+  actions::ActionItem* normal_action =
+      actions::ActionManager::Get().FindAction(kActionZoomNormal, nullptr);
+  ASSERT_TRUE(normal_action);
+
+  normal_action->SetText(base::FormatPercent(200));
+  EXPECT_EQ(zoom_label->GetText(), base::FormatPercent(200));
+  EXPECT_TRUE(menu->IsShowing());
+  menu->CloseMenu();
+}
+
 TEST_F(ActionAppMenuTest, FullscreenActionUpdatesAccessibilityName) {
   base::MockCallback<base::RepeatingClosure> on_menu_closed;
   ActionAppMenu menu(&mock_window_interface_, on_menu_closed.Get());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc b/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
index 3b70064..2eabe11 100644
--- a/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
+++ b/chrome/browser/ui/views/app_menu/action_app_menu_unittest.cc
@@ -1604,6 +1604,61 @@
 }
 #endif  // !BUILDFLAG(IS_MAC)
 
+// Regression test for crbug.com/575090745: Firing a synchronous native
+// accessibility event during zoom label text updates can spin the Windows COM
+// STA message pump and re-entrantly close and destroy the menu.
+TEST_F(ActionAppMenuTest, ZoomLabelUpdateSurvivesReentrantMenuDestruction) {
+  content::RenderViewHostTestEnabler rvh_test_enabler;
+  tabs::MockTabInterface mock_tab;
+  std::unique_ptr<content::WebContents> web_contents =
+      content::WebContentsTester::CreateTestWebContents(profile_.get(),
+                                                        nullptr);
+  zoom::ZoomController::CreateForWebContents(web_contents.get());
+
+  EXPECT_CALL(mock_window_interface_, GetActiveTabInterface())
+      .WillRepeatedly(testing::Return(&mock_tab));
+  EXPECT_CALL(mock_tab, GetContents())
+      .WillRepeatedly(testing::Return(web_contents.get()));
+
+  auto menu = std::make_unique<ActionAppMenu>(&mock_window_interface_,
+                                              base::DoNothing());
+  menu->RunMenu(button_->button_controller(), views::MenuRunner::NO_FLAGS);
+  ASSERT_TRUE(menu->IsShowing());
+
+  views::MenuItemView* const root = menu->root_menu_item_for_testing();
+  ASSERT_TRUE(root);
+
+  views::MenuItemView* zoom_item = root->GetMenuItemByID(kActionZoomSubmenu);
+  ASSERT_TRUE(zoom_item);
+
+  auto* zoom_view =
+      views::AsViewClass<AppMenuZoomView>(zoom_item->children()[0]);
+  ASSERT_TRUE(zoom_view);
+
+  views::Label* const zoom_label = zoom_view->zoom_label_for_testing();
+  ASSERT_NE(zoom_label, nullptr);
+
+  // Simulate a UIA client re-entrantly invoking AppMenuButton::CloseMenu()
+  // during the kTextChanged accessibility event raise inside Label::SetText().
+  zoom_label->GetViewAccessibility().set_accessibility_events_callback(
+      base::BindLambdaForTesting(
+          [&menu](const ui::AXPlatformNodeDelegate*, ax::mojom::Event event) {
+            if (event == ax::mojom::Event::kTextChanged && menu) {
+              menu->CloseMenu();
+              menu.reset();
+            }
+          }));
+
+  actions::ActionItem* normal_action =
+      actions::ActionManager::Get().FindAction(kActionZoomNormal, nullptr);
+  ASSERT_TRUE(normal_action);
+
+  normal_action->SetText(base::FormatPercent(200));
+  EXPECT_EQ(zoom_label->GetText(), base::FormatPercent(200));
+  EXPECT_TRUE(menu->IsShowing());
+  menu->CloseMenu();
+}
+
 TEST_F(ActionAppMenuTest, FullscreenActionUpdatesAccessibilityName) {
   base::MockCallback<base::RepeatingClosure> on_menu_closed;
   ActionAppMenu menu(&mock_window_interface_, on_menu_closed.Get());
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.