CVE-2026-7334
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forash/user_education/user_education_help_bubble_controller.cc |
modified | |
ifash/user_education/user_education_help_bubble_controller.cc |
modified | |
ElementContextash/user_education/user_education_help_bubble_controller.h |
modified | |
ElementIdentifierash/user_education/user_education_help_bubble_controller.h |
modified | |
HelpBubbleash/user_education/user_education_help_bubble_controller.h |
modified |
Files Changed
ash/BUILD.gnash/user_education/user_education_help_bubble_controller.ccash/user_education/user_education_help_bubble_controller.hash/user_education/user_education_help_bubble_controller_unittest.cc
Patch
From 62fb787c83e538d31a5aebe8cf402f340f5f06fd Mon Sep 17 00:00:00 2001 From: Dana Fried <[email protected]> Date: Thu, 16 Apr 2026 11:28:22 -0700 Subject: [PATCH] Reland "[User Education] Harden help bubble teardown" This reverts commit ba9f9ef4244dd3a351ee3d57a00ce4edb5821b8f. Reason for revert: Fixed tests on linux-chromeos-debug CI builder. Tests on that builder are one of the few component builds left in Desktop chrome; the tests were using metadata that was not component build safe. We added back in the HelpBubbleViewsAsh class to make those tests safe on that builder. This should not appreciably affect production Chrome. Original change's description: > Revert "[User Education] Harden help bubble teardown" > > This reverts commit e7b909682621f1b969f7f845fc9c41728a03072c. > > Reason for revert: Failing tests UserEducationHelpBubbleControllerTest.Metadata All/HelpBubbleFactoryViewsAshBrowserTest.CreateBubble/1 > Example: https://ci.chromium.org/ui/p/chromium/builders/ci/linux-chromeos-dbg/44923/overview > > Original change's description: > > [User Education] Harden help bubble teardown > > > > This changes all Views-based help bubbles (which is nearly all of them) > > to use MakeCloseSynchronous(). It also streamlines the teardown logic to > > prevent UAFs: > > - All execution paths in help bubbles that can call callbacks are > > safe against `this` being deleted. > > - Code paths that could be called during teardown early-exit if > > teardown has started. > > - Help bubble teardown can be synchronous if desired. > > - `HelpBubbleFactoryRegistry` will no longer briefly hold a reference > > to a help bubble that may have been deleted. > > - HelpBubbleViewsAsh has been eliminated in favor of wrapping a > > HelpBubbleViewAsh in a HelpBubbleViews. > > > > This CL breaks `AddOnCloseCallback()` into `AddOnClosingCallback()` and > > `AddOnClosedCallback()`, and uses them in the appropriate places. > > > > Primary files to review would be: > > - help_bubble.h|cc > > - help_bubble_view.h|cc > > - help_bubble_views.h|cc > > > > The vast majority of other changes are just refactors due to API > > changes. > > > > In a follow-up, we may replace `AddOnCloseCallback()` with a > > `MakeCloseSynchronous()` type method that further ties it to help bubble > > ownership. > > > > Bug: 496456528 > > Change-Id: Ie999b965cf40e2e091f2de99f2f7b80338bcce15 > > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7744206 > > Commit-Queue: Dana Fried <[email protected]> > > Reviewed-by: Elly <[email protected]> > > Reviewed-by: Erik Chen <[email protected]> > > Cr-Commit-Position: refs/heads/main@{#1614772} > > Bug: 496456528 > No-Presubmit: true > No-Tree-Checks: true > No-Try: true > Change-Id: I9e890e46cd13bfc1f58520eefffa11a1805e0da2 > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7762386 > Bot-Commit: [email protected] <[email protected]> > Owners-Override: Simon Ziegltrum <[email protected]> > Commit-Queue: Simon Ziegltrum <[email protected]> > Cr-Commit-Position: refs/heads/main@{#1615005} Bug: 496456528 Change-Id: Ib602979553a5cd8d257c317834258625fb8ca763 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7765574 Reviewed-by: Darryl James <[email protected]> Auto-Submit: Dana Fried <[email protected]> Reviewed-by: Eshwar Stalin <[email protected]> Commit-Queue: Dana Fried <[email protected]> Cr-Commit-Position: refs/heads/main@{#1616015} --- diff --git a/ash/BUILD.gn b/ash/BUILD.gn index 710a6b4..9453755 100644 --- a/ash/BUILD.gn +++ b/ash/BUILD.gn @@ -4704,6 +4704,7 @@ "//components/ukm:test_support", "//components/user_education/common", "//components/user_education/common:events", + "//components/user_education/views", "//components/user_education/views:test_support", "//components/user_manager", "//components/user_manager:test_support", @@ -5435,6 +5436,7 @@ "//components/session_manager/core", "//components/session_manager/core:test_support", "//components/user_education/common", + "//components/user_education/views", "//components/user_manager", "//components/viz/test:test_support", "//device/bluetooth", diff --git a/ash/user_education/user_education_help_bubble_controller.cc b/ash/user_education/user_education_help_bubble_controller.cc index 9063861..49903048 100644 --- a/ash/user_education/user_education_help_bubble_controller.cc +++ b/ash/user_education/user_education_help_bubble_controller.cc @@ -67,30 +67,6 @@ return g_instance; } -std::optional<HelpBubbleId> UserEducationHelpBubbleController::GetHelpBubbleId( - ui::ElementIdentifier element_id, - ui::ElementContext element_context) const { - if (help_bubble_ && help_bubble_->IsA<HelpBubbleViewsAsh>()) { - // Cache the `bubble_view` with its associated anchor. - auto* bubble_view = help_bubble_->AsA<HelpBubbleViewsAsh>()->bubble_view(); - auto* anchor_view = bubble_view->GetAnchorView(); - - // Find all `tracked_views` matching `element_id` and `element_context`. - const views::ElementTrackerViews::ViewList tracked_views = - views::ElementTrackerViews::GetInstance()->GetAllMatchingViews( - element_id, element_context); - - // A help bubble exists for a `tracked_view` if the `tracked_view` is the - // `anchor_view` for the help bubble. - for (const auto* tracked_view : tracked_views) { - if (tracked_view == anchor_view) { - return bubble_view->id(); - } - } - } - return std::nullopt; -} - base::CallbackListSubscription UserEducationHelpBubbleController::AddHelpBubbleAnchorBoundsChangedCallback( base::RepeatingClosure callback) { diff --git a/ash/user_education/user_education_help_bubble_controller.h b/ash/user_education/user_education_help_bubble_controller.h index ef8aaef8..8450137 100644 --- a/ash/user_education/user_education_help_bubble_controller.h +++ b/ash/user_education/user_education_help_bubble_controller.h @@ -16,11 +16,6 @@ #include "base/memory/raw_ptr.h" #include "base/types/pass_key.h" -namespace ui { -class ElementContext; -class ElementIdentifier; -} // namespace ui - namespace user_education { class HelpBubble; } // namespace user_education @@ -46,15 +41,6 @@ // NOTE: Exists if and only if user education features are enabled. static UserEducationHelpBubbleController* Get(); - // Returns the unique identifier for the help bubble currently being shown for - // the tracked element associated with the specified `element_id` in the - // specified `element_context`. If no help bubble is currently being shown for - // the tracked element or if the tracked element does not exist, an absent - // value is returned. - std::optional<HelpBubbleId> GetHelpBubbleId( - ui::ElementIdentifier element_id, - ui::ElementContext element_context) const; - // Adds a `callback` to be invoked whenever a help bubble's anchor bounds // change until the returned subscription is destroyed. [[nodiscard]] base::CallbackListSubscription diff --git a/ash/user_education/user_education_help_bubble_controller_unittest.cc b/ash/user_education/user_education_help_bubble_controller_unittest.cc index 202198c..87ae543 100644 --- a/ash/user_education/user_education_help_bubble_controller_unittest.cc +++ b/ash/user_education/user_education_help_bubble_controller_unittest.cc @@ -20,6 +20,7 @@ #include "base/test/scoped_feature_list.h" #include "components/user_education/common/help_bubble/help_bubble.h" #include "components/user_education/common/help_bubble/help_bubble_params.h" +#include "components/user_education/views/help_bubble_views.h" #include "components/user_education/views/help_bubble_views_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/window.h" @@ -63,7 +64,9 @@ HelpBubbleViewAsh* GetHelpBubbleView(HelpBubble* help_bubble) { return help_bubble->IsA<HelpBubbleViewsAsh>() - ? help_bubble->AsA<HelpBubbleViewsAsh>()->bubble_view() + ? views::AsViewClass<HelpBubbleViewAsh>( + help_bubble->AsA<HelpBubbleViewsAsh>() + ->bubble_view_for_testing()) : nullptr; } @@ -234,7 +237,8 @@ // Destroy `help_bubble`. views::test::WidgetDestroyedWaiter waiter(help_bubble_view->GetWidget()); - help_bubble->Close(); + help_bubble->Close( + user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
Regression Test / PoC
diff --git a/ash/user_education/user_education_help_bubble_controller_unittest.cc b/ash/user_education/user_education_help_bubble_controller_unittest.cc
index 202198c..87ae543 100644
--- a/ash/user_education/user_education_help_bubble_controller_unittest.cc
+++ b/ash/user_education/user_education_help_bubble_controller_unittest.cc
@@ -20,6 +20,7 @@
#include "base/test/scoped_feature_list.h"
#include "components/user_education/common/help_bubble/help_bubble.h"
#include "components/user_education/common/help_bubble/help_bubble_params.h"
+#include "components/user_education/views/help_bubble_views.h"
#include "components/user_education/views/help_bubble_views_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/window.h"
@@ -63,7 +64,9 @@
HelpBubbleViewAsh* GetHelpBubbleView(HelpBubble* help_bubble) {
return help_bubble->IsA<HelpBubbleViewsAsh>()
- ? help_bubble->AsA<HelpBubbleViewsAsh>()->bubble_view()
+ ? views::AsViewClass<HelpBubbleViewAsh>(
+ help_bubble->AsA<HelpBubbleViewsAsh>()
+ ->bubble_view_for_testing())
: nullptr;
}
@@ -234,7 +237,8 @@
// Destroy `help_bubble`.
views::test::WidgetDestroyedWaiter waiter(help_bubble_view->GetWidget());
- help_bubble->Close();
+ help_bubble->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
waiter.Wait();
help_bubble = nullptr;
help_bubble_view = nullptr;
@@ -284,7 +288,8 @@
// Close the `help_bubble`.
ASSERT_TRUE(help_bubble);
- help_bubble->Close();
+ help_bubble->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
help_bubble = nullptr;
// Verify expectations.
diff --git a/ash/user_education/views/help_bubble_view_ash_unittest.cc b/ash/user_education/views/help_bubble_view_ash_unittest.cc
index de09c10a..17ae1d903 100644
--- a/ash/user_education/views/help_bubble_view_ash_unittest.cc
+++ b/ash/user_education/views/help_bubble_view_ash_unittest.cc
@@ -80,12 +80,12 @@
// Verifies that help bubbles are contained within the correct parent window.
TEST_F(HelpBubbleViewAshTest, ParentWindow) {
- auto* const help_bubble_view = CreateHelpBubbleView();
- EXPECT_TRUE(help_bubble_view->anchor_widget()
+ auto const help_bubble = CreateHelpBubbleView();
+ EXPECT_TRUE(help_bubble.bubble_view->anchor_widget()
->GetNativeWindow()
->GetRootWindow()
->GetChildById(kShellWindowId_HelpBubbleContainer)
- ->Contains(help_bubble_view->GetWidget()->GetNativeWindow()));
+ ->Contains(help_bubble.widget->GetNativeWindow()));
}
// HelpBubbleViewAshBodyIconTest -----------------------------------------------
@@ -154,8 +154,8 @@
}
// Create `help_bubble_view`.
- auto* const help_bubble_view = CreateHelpBubbleView(std::move(params));
- ASSERT_NE(help_bubble_view, nullptr);
+ auto const help_bubble = CreateHelpBubbleView(std::move(params));
+ ASSERT_NE(help_bubble.bubble_view, nullptr);
// Cache `expected_body_icon` based on order of precedence.
const gfx::VectorIcon& expected_body_icon =
@@ -167,7 +167,8 @@
views::ElementTrackerViews::GetInstance()
->GetUniqueViewAs<views::ImageView>(
HelpBubbleViewAsh::kBodyIconIdForTesting,
- views::ElementTrackerViews::GetContextForView(help_bubble_view)),
+ views::ElementTrackerViews::GetContextForView(
+ help_bubble.bubble_view)),
Conditional(&expected_body_icon != &gfx::VectorIcon::EmptyIcon(),
Property(&views::ImageView::GetImageModel,
Eq(ui::ImageModel::FromVectorIcon(
@@ -178,33 +179,33 @@
// Verifies that help bubbles have the appropriate background color.
TEST_F(HelpBubbleViewAshTest, BackgroundColor) {
- const auto* const help_bubble_view = CreateHelpBubbleView();
- EXPECT_EQ(help_bubble_view->background_color(),
+ const auto help_bubble = CreateHelpBubbleView();
+ EXPECT_EQ(help_bubble.bubble_view->background_color(),
cros_tokens::kCrosSysDialogContainer);
}
// Verifies that help bubbles can activate.
TEST_F(HelpBubbleViewAshTest, CanActivate) {
- const auto* const help_bubble_view = CreateHelpBubbleView();
- EXPECT_TRUE(help_bubble_view->CanActivate());
+ const auto help_bubble = CreateHelpBubbleView();
+ EXPECT_TRUE(help_bubble.bubble_view->CanActivate());
}
TEST_F(HelpBubbleViewAshTest, RootViewAccessibleName) {
- auto* const help_bubble_view = CreateHelpBubbleView();
+ auto const help_bubble = CreateHelpBubbleView();
ui::AXNodeData root_view_data;
- help_bubble_view->GetWidget()
- ->GetRootView()
+ help_bubble.widget->GetRootView()
->GetViewAccessibility()
.GetAccessibleNodeData(&root_view_data);
EXPECT_EQ(
root_view_data.GetString16Attribute(ax::mojom::StringAttribute::kName),
- help_bubble_view->GetAccessibleWindowTitle());
+ help_bubble.bubble_view->GetAccessibleWindowTitle());
}
// Verifies that help bubbles do not handle events within their shadows.
TEST_F(HelpBubbleViewAshTest, HitTest) {
- auto* const help_bubble_view = CreateHelpBubbleView();
- auto* const help_bubble_widget = help_bubble_view->GetWidget();
+ auto const help_bubble = CreateHelpBubbleView();
+ auto* const help_bubble_view = help_bubble.bubble_view.get();
+ auto* const help_bubble_widget = help_bubble.widget.get();
auto* const help_bubble_window = help_bubble_widget->GetNativeWindow();
auto* const root_window = help_bubble_window->GetRootWindow();
auto* const root_window_targeter = root_window->targeter();
diff --git a/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc b/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc
index 87dc0b9..fe564005 100644
--- a/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc
+++ b/chrome/browser/ui/ash/user_education/views/help_bubble_factory_views_ash_browsertest.cc
@@ -33,11 +33,9 @@
// Aliases.
using ::ash::HelpBubbleContext;
using ::ash::HelpBubbleViewAsh;
-using ::ash::HelpBubbleViewsAsh;
using ::ash::kHelpBubbleContextKey;
using ::user_education::HelpBubbleParams;
using ::user_education::HelpBubbleView;
-using ::user_education::HelpBubbleViews;
// Helpers ---------------------------------------------------------------------
@@ -111,6 +109,5 @@
// The help `bubble` should be Ash-specific depending on `context`.
bool is_ash_context = context == HelpBubbleContext::kAsh;
- ASSERT_EQ(bubble->IsA<HelpBubbleViewsAsh>(), is_ash_context);
- ASSERT_NE(bubble->IsA<HelpBubbleViews>(), is_ash_context);
+ ASSERT_EQ(bubble->IsA<ash::HelpBubbleViewsAsh>(), is_ash_context);
}
diff --git a/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc b/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc
index baec88a1..00d4bd1e 100644
--- a/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc
+++ b/chrome/browser/ui/user_education/show_promo_in_page_browsertest.cc
@@ -98,7 +98,8 @@
ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open());
// Closing the help bubble should destroy the object.
- handle->GetHelpBubbleForTesting()->Close();
+ handle->GetHelpBubbleForTesting()->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
ASSERT_FALSE(handle);
}
@@ -143,7 +144,8 @@
ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open());
// Closing the help bubble should destroy the object.
- handle->GetHelpBubbleForTesting()->Close();
+ handle->GetHelpBubbleForTesting()->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
ASSERT_FALSE(handle);
}
@@ -180,7 +182,8 @@
ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open());
// Closing the help bubble should destroy the object.
- handle->GetHelpBubbleForTesting()->Close();
+ handle->GetHelpBubbleForTesting()->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
ASSERT_FALSE(handle);
}
@@ -217,7 +220,8 @@
ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open());
// Closing the help bubble should destroy the object.
- handle->GetHelpBubbleForTesting()->Close();
+ handle->GetHelpBubbleForTesting()->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
ASSERT_FALSE(handle);
}
@@ -361,6 +365,7 @@
ASSERT_TRUE(handle->GetHelpBubbleForTesting()->is_open());
// Closing the help bubble should destroy the object.
- handle->GetHelpBubbleForTesting()->Close();
+ handle->GetHelpBubbleForTesting()->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
ASSERT_FALSE(handle);
}
diff --git a/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc b/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc
index 4624921..8443d2c 100644
--- a/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc
+++ b/chrome/browser/ui/views/user_education/custom_webui_help_bubble_interactive_uitest.cc
@@ -324,7 +324,10 @@
CustomWebUIHelpBubble::kWebViewIdForTesting),
ClickElement(kWebViewElementId, kCancelButton),
WaitForEvent(kToolbarAppMenuButtonElementId, kCallbackEvent),
- Do([&help_bubble]() { help_bubble->Close(); }),
+ Do([&help_bubble]() {
+ help_bubble->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
+ }),
WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting),
CheckIsAnchor(kToolbarAppMenuButtonElementId, false));
}
@@ -431,7 +434,8 @@
CheckIsDismissed(kCustomWebUIHelpBubbleTestFeature, true));
}
-// Regression test for https://crbug.com/496456528
+// Regression tests for https://crbug.com/496456528
+
IN_PROC_BROWSER_TEST_F(CustomWebUIHelpBubbleUiTest, ShowPromoAndCloseBrowser) {
RunTestSequence(
MaybeShowPromo(kCustomWebUIHelpBubbleTestFeature,
@@ -441,3 +445,24 @@
WaitForHide(kBrowserViewElementId),
WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting));
}
+
+IN_PROC_BROWSER_TEST_F(CustomWebUIHelpBubbleUiTest, ShowPromoAndCloseBubble) {
+ RunTestSequence(
+ MaybeShowPromo(kCustomWebUIHelpBubbleTestFeature,
+ CustomHelpBubbleShown{
+ CustomWebUIHelpBubble::kHelpBubbleIdForTesting}),
+ WithView(CustomWebUIHelpBubble::kHelpBubbleIdForTesting,
+ [](views::View* view) { view->GetWidget()->Close(); }),
+ WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting));
+}
+
+IN_PROC_BROWSER_TEST_F(CustomWebUIHelpBubbleUiTest,
+ ShowPromoAndCloseBubbleNow) {
+ RunTestSequence(
+ MaybeShowPromo(kCustomWebUIHelpBubbleTestFeature,
+ CustomHelpBubbleShown{
+ CustomWebUIHelpBubble::kHelpBubbleIdForTesting}),
+ WithView(CustomWebUIHelpBubble::kHelpBubbleIdForTesting,
+ [](views::View* view) { view->GetWidget()->CloseNow(); }),
+ WaitForHide(CustomWebUIHelpBubble::kHelpBubbleIdForTesting));
+}
diff --git a/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc b/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc
index ca0eaa8..d3bd7d8 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_factory_views_browsertest.cc
@@ -77,7 +77,8 @@
ASSERT_TRUE(help_bubble_);
ASSERT_TRUE(help_bubble_->IsA<user_education::HelpBubbleViews>());
EXPECT_TRUE(help_bubble_->is_open());
- EXPECT_TRUE(help_bubble_->Close());
+ EXPECT_TRUE(help_bubble_->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed));
EXPECT_FALSE(help_bubble_->is_open());
}
diff --git a/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc b/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc
index 4ccd7f5..5eabf44 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_factory_webui_interactive_uitest.cc
@@ -155,7 +155,10 @@
}
auto CloseHelpBubble() {
- return Do(base::BindLambdaForTesting([this]() { help_bubble_->Close(); }));
+ return Do(base::BindLambdaForTesting([this]() {
+ help_bubble_->Close(
+ user_education::HelpBubble::CloseReason::kProgrammaticallyClosed);
+ }));
}
auto CheckHandlerHasHelpBubble(ElementSpecifier anchor,
diff --git a/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc b/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc
index 0547162..7c62e19c 100644
--- a/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc
+++ b/chrome/browser/ui/views/user_education/help_bubble_view_timeout_unittest.cc
@@ -12,6 +12,7 @@
#include "chrome/browser/ui/views/user_education/browser_user_education_service.h"
#include "components/user_education/common/help_bubble/help_bubble_params.h"
#include "components/user_education/views/help_bubble_view.h"
+#include "components/user_education/views/help_bubble_view_info.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/widget/widget_observer.h"
@@ -19,14 +20,7 @@
using user_education::HelpBubbleButtonParams;
using user_education::HelpBubbleParams;
... (truncated)
Original Bug Report
Security: T0 Heap-Use-After-Free in views::Widget during Browser Teardown
Steps to reproduce the problem
Hello, I encountered a crash while running tests on Chromium. I’m not sure if it’s related to my HTML file, but the terminal output indicates the root cause of the problem. Obviously, to recreate this problem, I’m compiling Chromium from scratch on a different computer. I know the problem will recur if I encounter this bubble when I log in, but I’ve only encountered it once in 4 days. I suspect it’s a promotional bubble shown to new users, so I’ll try compiling from scratch on another computer.
Problem Description
I downloaded and compiled Chromium for the first time on my computer; this was my first time using the Chromium browser. My HTML file contained instructions to open the calculator, and coincidentally, perhaps because I had just downloaded it, a blue promotional bubble for Chromium appeared in the upper right corner. At the same time, a window popped up asking if I wanted to open the calculator because of my HTML file, but I ignored both warnings and closed the tabs by clicking the ‘X’ buttons. Meanwhile, the browser crashed and gave an error output in my terminal.
VULNERABILITY DETAILS
Heap-Use-After-Free (Read of size 8) in the main Browser Process (T0), which completely bypasses the sandbox. The vulnerable object is views::Widget, and ASan confirms it is NOT protected by MiraclePtr/BackupRefPtr.
ROOT CAUSE ANALYSIS
This is a synchronous race condition between Chromium’s Browser teardown sequence (Browser::~Browser()) and the macOS native window notification system (NSNotificationCenter).
Phase A (Allocation):
The IPH system triggers a Help Bubble (e.g., CustomWebUIHelpBubble::CreateForController<extensions::ZeroStatePromoController>).
Phase B (The Free):
When the browser is closed (via Cmd+Q), Browser::~Browser() initiates the destruction sequence. The BubbleWidget is destroyed and its memory is synchronously freed via user_education::HelpBubble::Close().
Phase C (The Use):
Although the C++ object is freed, the macOS NSNotificationCenter fires a windowWillClose: notification. This invokes remote_cocoa::NativeWidgetNSWindowBridge::OnWindowWillClose(), which calls back into views::NativeWidgetMac::WindowDestroying() and views::Widget::HandleWidgetDestroying(). This attempts to read the freed C++ Widget state, causing a critical T0 UAF.
REPRODUCTION STEPS
Note: Reproducing this consistently on a local, unbranded build is extremely difficult because the FeatureEngagementTracker heuristics block the ZeroStatePromo bubble from rendering reliably (e.g., it waits for “browser initialization complete” and checks Finch configs).
To reproduce internally:
- Use internal developer flags to force a WebUI Help Bubble to render (e.g., force
IPH_ExtensionsZeroStatePromoonchrome://extensions). - While that blue Help Bubble is actively visible on the macOS screen, force-close the browser (Cmd + Q).
- The ASan trace will immediately catch the UAF during teardown.
Please review the attached ASan log for the complete stack trace. Given the T0 context and lack of MiraclePtr protection, I kindly request a review of the severity.
Summary
Security: T0 Heap-Use-After-Free in views::Widget during Browser Teardown
Custom Questions
Type of crash:
browser
Crash state:
AddressSanitizer: heap-use-after-free (Read of size 8) Crash location: views::Widget::HandleWidgetDestroying()
Thread: T0 (Main Browser Process) MiraclePtr Status: NOT PROTECTED
- Please see the attached ASan log (.txt) for the complete allocation, free, and crash stack traces.
Reporter credit:
Batuhan Eşref KOÇ
Additional Data
Category: Security
Chrome Channel: Not sure
Regression: N/A \