Low watch CVSS 8.8 chrome UAF 🔧 Commit mapped

Overview

Low
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in TabStrip
DescriptionUse after free in TabStrip
ComponentTabStrip
Bug ClassUAF
Action tierwatch — corruption primitive but gated (flag) — 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.
Tracker513713427
Fix commit926b0949a8da (chromium/src) +59/-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-16 (per vendor advisory)
Fix landed2026-06-25 (public git)
Disclosed2026-09-01
Patch gap68 days — fix public in source before the advisory

Background

`TabGroup`
the browser-side object modeling a group of tabs (its id, visual data, collapsed state); it can be destroyed synchronously when its last relevant tab leaves or it is auto-closed.
`ToggleTabGroupCollapsedState`
the tab-strip controller method that collapses/expands a group; while collapsing a focused group it may switch the active tab or add a tab.
`kTabGroupsFocusing` / `tab_groups_focusing_auto_close`
the feature under which focusing behavior can automatically close (and synchronously destroy) a group as a side effect of activating a different tab.
`base::WeakPtr` / `ContainsTabGroup`
a weak pointer that becomes null when its target is destroyed, and the group_model() query that reports whether a TabGroup still exists; both are used to detect that the group was freed mid-operation.

Root Cause Analysis

In both BrowserTabStripController::ToggleTabGroupCollapsedState and VerticalTabStripController::ToggleTabGroupCollapsedState, the code held a raw TabGroup* group pointer, then, while collapsing a focused group, called into the TabStripModel to switch the active tab (ActivateTabAt) or add a tab. Under the kTabGroupsFocusing auto-close feature, that activation can trigger the group’s automatic closure, which synchronously destroys the TabGroup, leaving group dangling. The functions then continued to use group — passing it to tabstrip_->ToggleTabGroup(group, ...) / model_->ChangeTabGroupVisuals(...) and reading group->id()/visual data — a use-after-free, because no check confirmed the group still existed after the reentrant model call.

The fix adds a liveness check that runs before those uses: BrowserTabStripController returns early if !model_->group_model()->ContainsTabGroup(group), and VerticalTabStripController captures base::WeakPtr<const TabGroup> weak_group = group->AsWeakPtr() before the activation and returns early if !weak_group afterward. TabGroup gains an AsWeakPtr() accessor and a mutable base::WeakPtrFactory<TabGroup> weak_ptr_factory_ to support the weak check, and the regression test CollapseFocusedGroupTriggersUseAfterFree confirms the group is gone after the toggle.

Key insight
A raw TabGroup* was reused after a reentrant TabStripModel call that, under the focusing auto-close feature, could synchronously free the group; the fix verifies the group’s continued existence (via ContainsTabGroup or a WeakPtr) and returns early before any post-activation use.

Attack Path

  1. Set up a focused group A single tab is placed in a new group and that group is made the focused group with the auto-close feature (tab_groups_focusing_auto_close) enabled, as the regression test does with AddToNewGroup/SetFocusedGroup.
  2. Collapse the focused group ToggleTabGroupCollapsedState is invoked (e.g. by a mouse action on the group header), and because the group is focused it proceeds to switch the active tab / add a tab via the TabStripModel.
  3. Synchronous group destruction The activation triggers the focusing auto-close path, which synchronously closes and destroys the TabGroup, leaving the local group pointer dangling.
  4. Use-after-free The controller continues and dereferences the freed group (passing it to ToggleTabGroup/ChangeTabGroupVisuals and reading its id/visual data), a use-after-free in the browser process; further exploitation into a controlled read/write or code execution is (inferred) and not shown by the patch.

Impact Assessment

The advisory rates this as a use-after-free in TabStrip in the privileged browser process; an attacker or user action that collapses a focused, auto-closeable tab group can cause the controller to dereference a freed TabGroup, yielding memory corruption. Preconditions are the kTabGroupsFocusing auto-close feature being enabled and a focused group being collapsed while its activation auto-closes it. The patch demonstrates only the UAF; escalation to a read/write primitive or code execution is (inferred) and not established here.

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc
modified
VerticalTabStripControllerFocusingAutoCloseBrowserTest
chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
modified

Files Changed

  • chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc
  • chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
  • components/tabs/public/tab_group.h
  • chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc

Audit Directions

  • Raw pointer reused after a reentrant model call
    Audit tab-strip/controller code that holds a TabGroup*, tab, or view pointer across a TabStripModel mutation (activate/add/close) that can synchronously destroy the pointee.
  • Feature-gated synchronous destruction
    Review paths where a new feature (like focusing auto-close) adds a synchronous “close/destroy” side effect to previously side-effect-free operations, and confirm all pre-existing callers revalidate their objects afterward.
  • Missing liveness checks after callbacks/activation
    Look for continued use of objects after ActivateTabAt-style calls without a WeakPtr/Contains* guard preceding the next dereference.
From 926b0949a8dafd79619ef2b13bdff02e57ac289d Mon Sep 17 00:00:00 2001
From: David Pennington <[email protected]>
Date: Thu, 25 Jun 2026 15:25:21 -0700
Subject: [PATCH] Fix BrowserTabStripController UAF on focused-group auto-close

Because the focus feature can close a group, attempting to use the
ptr after the closure can result in UAF. we can either requery the
group from the tabstripmodel or we can reget the ptr, i chose to
use a weakptr to requery the validity of the ptr so that we dont have
to do another lookup.
Also added a regression test for VerticalTabStripController UAF

Bug: 513713427
Change-Id: I4b0d0fa580681e063a2ee33d5511e781ea68a740
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7998801
Reviewed-by: Charles Meng <[email protected]>
Commit-Queue: David Pennington <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1652763}
---
diff --git a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
index a95ae65..c1b3e9b 100644
--- a/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/browser_tab_strip_controller.cc
@@ -462,6 +462,13 @@
     }
   }
 
+  // Under the kTabGroupsFocusingAutoClose feature, switching the active tab or
+  // adding a new tab can cause the group to be automatically closed and
+  // synchronously destroyed. We must check that the group still exists.
+  if (!model_->group_model()->ContainsTabGroup(group)) {
+    return;
+  }
+
   if (origin != ToggleTabGroupCollapsedStateOrigin::kMenuAction ||
       should_toggle_group) {
     tabstrip_->ToggleTabGroup(group, !is_currently_collapsed, origin);
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc
index d753488..05ae574 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller.cc
@@ -6,6 +6,7 @@
 
 #include <variant>
 
+#include "base/memory/weak_ptr.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/metrics/user_metrics.h"
 #include "base/metrics/user_metrics_action.h"
@@ -287,6 +288,11 @@
   bool is_currently_collapsed = group->visual_data()->is_collapsed();
   bool should_toggle_group = true;
 
+  // We use a WeakPtr because switching the active tab or adding
+  // a new tab during a collapse operation can trigger the automatic
+  // closure of the group, which synchronously destroys the TabGroup.
+  base::WeakPtr<const TabGroup> weak_group = group->AsWeakPtr();
+
   tabs::TabInterface* active_tab = model_->GetActiveTab();
   if (!is_currently_collapsed && active_tab && !drag_handler_->IsDragging()) {
     if (active_tab->GetGroup() == group->id()) {
@@ -296,6 +302,7 @@
       // be created.
       const std::optional<int> next_active =
           model_->GetNextExpandedActiveTab(group->id());
+
       if (next_active.has_value()) {
         model_->ActivateTabAt(
             next_active.value(),
@@ -321,6 +328,10 @@
     }
   }
 
+  if (!weak_group) {
+    return;
+  }
+
   if (origin != ToggleTabGroupCollapsedStateOrigin::kMenuAction ||
       should_toggle_group) {
     model_->ChangeTabGroupVisuals(
diff --git a/components/tabs/public/tab_group.h b/components/tabs/public/tab_group.h
index c6c9d323..1b55e2ce 100644
--- a/components/tabs/public/tab_group.h
+++ b/components/tabs/public/tab_group.h
@@ -11,6 +11,7 @@
 #include "base/callback_list.h"
 #include "base/functional/callback_forward.h"
 #include "base/memory/raw_ptr.h"
+#include "base/memory/weak_ptr.h"
 #include "components/tab_groups/tab_group_id.h"
 #include "components/tab_groups/tab_group_visual_data.h"
 #include "components/tabs/public/tab_collection.h"
@@ -50,6 +51,11 @@
   };
   virtual ~TabGroup();
 
+  base::WeakPtr<TabGroup> AsWeakPtr() { return weak_ptr_factory_.GetWeakPtr(); }
+  base::WeakPtr<const TabGroup> AsWeakPtr() const {
+    return weak_ptr_factory_.GetWeakPtr();
+  }
+
   const tab_groups::TabGroupId& id() const { return id_; }
   const tab_groups::TabGroupVisualData* visual_data() const {
     return visual_data_.get();
@@ -137,6 +143,8 @@
 
   bool is_closing_ = false;
   bool is_customized_ = false;
+
+  mutable base::WeakPtrFactory<TabGroup> weak_ptr_factory_{this};
 };
 
 #endif  // COMPONENTS_TABS_PUBLIC_TAB_GROUP_H_
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
index c436ae2..7d91292f 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
@@ -487,4 +487,37 @@
   EXPECT_FALSE(tab0->GetGroup().has_value());
 }
 
+class VerticalTabStripControllerFocusingAutoCloseBrowserTest
+    : public VerticalTabsBrowserTestMixin<InProcessBrowserTest> {
+ public:
+  const std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures()
+      override {
+    return {
+        {features::kTabGroupsFocusing,
+         {{"tab_groups_focusing_auto_close", "true"}}},
+        {tabs::kVerticalTabs, {}},
+    };
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(VerticalTabStripControllerFocusingAutoCloseBrowserTest,
+                       CollapseFocusedGroupTriggersUseAfterFree) {
+  AppendTab();
+
+  TabStripModel* model = browser()->tab_strip_model();
+  ASSERT_EQ(2, model->count());
+
+  tab_groups::TabGroupId group_id = model->AddToNewGroup({0});
+  TabGroup* group = model->group_model()->GetTabGroup(group_id);
+
+  model->SetFocusedGroup(group_id);
+  ASSERT_EQ(0, model->active_index());
+  ASSERT_EQ(group_id, model->GetFocusedGroup());
+
+  vertical_tab_strip_controller()->ToggleTabGroupCollapsedState(
+      group, ToggleTabGroupCollapsedStateOrigin::kMouse);
+
+  EXPECT_FALSE(model->group_model()->ContainsTabGroup(group_id));
+}
+
 }  // namespace
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
index c436ae2..7d91292f 100644
--- a/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
+++ b/chrome/browser/ui/views/tabs/vertical/vertical_tab_strip_controller_browsertest.cc
@@ -487,4 +487,37 @@
   EXPECT_FALSE(tab0->GetGroup().has_value());
 }
 
+class VerticalTabStripControllerFocusingAutoCloseBrowserTest
+    : public VerticalTabsBrowserTestMixin<InProcessBrowserTest> {
+ public:
+  const std::vector<base::test::FeatureRefAndParams> GetEnabledFeatures()
+      override {
+    return {
+        {features::kTabGroupsFocusing,
+         {{"tab_groups_focusing_auto_close", "true"}}},
+        {tabs::kVerticalTabs, {}},
+    };
+  }
+};
+
+IN_PROC_BROWSER_TEST_F(VerticalTabStripControllerFocusingAutoCloseBrowserTest,
+                       CollapseFocusedGroupTriggersUseAfterFree) {
+  AppendTab();
+
+  TabStripModel* model = browser()->tab_strip_model();
+  ASSERT_EQ(2, model->count());
+
+  tab_groups::TabGroupId group_id = model->AddToNewGroup({0});
+  TabGroup* group = model->group_model()->GetTabGroup(group_id);
+
+  model->SetFocusedGroup(group_id);
+  ASSERT_EQ(0, model->active_index());
+  ASSERT_EQ(group_id, model->GetFocusedGroup());
+
+  vertical_tab_strip_controller()->ToggleTabGroupCollapsedState(
+      group, ToggleTabGroupCollapsedStateOrigin::kMouse);
+
+  EXPECT_FALSE(model->group_model()->ContainsTabGroup(group_id));
+}
+
 }  // namespace
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.