Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in TabStrip
DescriptionUse after free in TabStrip
ComponentTabStrip
Bug ClassUAF
Tracker504175497
Fix commit213ddd42e466 (chromium/src) +2/-2
CISA KEVNot listed
Creditedyueliu of Microsoft
Disclosed2026-05-27

Files Changed

  • chrome/browser/ui/views/tabs/tab_strip_action_container.cc
From 213ddd42e466ba2014ce8db75c43ccad3c39d220 Mon Sep 17 00:00:00 2001
From: Yue Liu <[email protected]>
Date: Mon, 20 Apr 2026 01:57:44 -0700
Subject: [PATCH] [Tabs] Use WeakPtr for OnAnimationSessionEnded callback in nudge animations

ShowTabStripNudge and HideTabStripNudge construct a
TabStripNudgeAnimationSession with an on_animation_ended_ callback
bound to base::Unretained(this). When the animation completes inside
the synchronous Show()/Hide() call (is_executing_show_or_hide_ is
true), MarkAnimationDone posts the callback to the current task
runner, so it fires after the message loop turns. If
TabStripActionContainer is destroyed in between (for example because
the TabStrip is rebuilt or the browser closes during the show/hide
sequence), the deferred callback dereferences a freed object.

The sibling glic-actor-task-icon path already binds the same callback
with weak_factory_.GetWeakPtr(); switch the two remaining call sites
to use the WeakPtr as well so the deferred run becomes a no-op when
the container is gone.

Bug: 504175497
Change-Id: I44af8217f4d31cc742f6fa8fa5f06cf9aea43c80
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7776390
Reviewed-by: Thomas Lukaszewicz <[email protected]>
Reviewed-by: Chang Liu <[email protected]>
Commit-Queue: Chang Liu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1617334}
---

diff --git a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
index 79a55f8..291f6404 100644
--- a/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
+++ b/chrome/browser/ui/views/tabs/tab_strip_action_container.cc
@@ -927,7 +927,7 @@
         button, this,
         TabStripNudgeAnimationSession::AnimationSessionType::kShow,
         base::BindOnce(&TabStripActionContainer::OnAnimationSessionEnded,
-                       base::Unretained(this)),
+                       weak_factory_.GetWeakPtr()),
         (button != glic_button_ && button != glic_actor_task_icon_));
     animation_session_->Start();
   }
@@ -982,7 +982,7 @@
         button, this,
         TabStripNudgeAnimationSession::AnimationSessionType::kHide,
         base::BindOnce(&TabStripActionContainer::OnAnimationSessionEnded,
-                       base::Unretained(this)),
+                       weak_factory_.GetWeakPtr()),
         (button != glic_button_ && button != glic_actor_task_icon_));
     animation_session_->Start();
   }
Loading diff…

Original Bug Report

reported by [email protected]

[Tabs] Use-after-free in TabStripActionContainer nudge animation-end callback

What

In chrome/browser/ui/views/tabs/tab_strip_action_container.cc, the two nudge entry points ExecuteShowTabStripNudge and ExecuteHideTabStripNudge construct a TabStripNudgeAnimationSession with its on_animation_ended_ callback bound via base::Unretained(this):

animation_session_ = std::make_unique&lt;TabStripNudgeAnimationSession&gt;(
    button, this,
    TabStripNudgeAnimationSession::AnimationSessionType::kShow,
    base::BindOnce(&TabStripActionContainer::OnAnimationSessionEnded,
                   base::Unretained(this)),   // &lt;-- unsafe
    ...);

TabStripNudgeAnimationSession::MarkAnimationDone does NOT always invoke the callback synchronously. When the animation completes inside the synchronous Show()/Hide() call path (i.e. is_executing_show_or_hide_ is true), it defers the callback via PostTask:

if (is_executing_show_or_hide_) {
  base::SingleThreadTaskRunner::GetCurrentDefault()-&gt;PostTask(
      FROM_HERE, std::move(on_animation_ended_));
} else {
  std::move(on_animation_ended_).Run();
}

If TabStripActionContainer is destroyed between the PostTask and the task running (for example because the TabStrip is rebuilt, a profile switch happens, or the browser window closes while the show/hide sequence is still in-flight), the deferred callback dereferences a freed TabStripActionContainer.

View on issue tracker