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 <[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(); }
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.