CVE-2026-9954
Overview
Files Changed
chrome/browser/ui/views/tabs/tab_strip_action_container.cc
Patch
From 213ddd42e466ba2014ce8db75c43ccad3c39d220 Mon Sep 17 00:00:00 2001
From: Yue Liu <yueliu1@microsoft.com>
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 <tluk@chromium.org>
Reviewed-by: Chang Liu <changliu1@microsoft.com>
Commit-Queue: Chang Liu <changliu1@microsoft.com>
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();
}
Original Bug Report
[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<TabStripNudgeAnimationSession>(
button, this,
TabStripNudgeAnimationSession::AnimationSessionType::kShow,
base::BindOnce(&TabStripActionContainer::OnAnimationSessionEnded,
base::Unretained(this)), // <-- 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()->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.