Chrome · Media
CVE-2026-110171
UAF in Media
Overview
High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcontent/renderer/media/renderer_web_media_player_delegate.cc |
modified | |
forcontent/renderer/media/renderer_web_media_player_delegate.cc |
modified | |
TEST_Fcontent/renderer/media/renderer_web_media_player_delegate_browsertest.cc |
modified |
Files Changed
content/renderer/media/renderer_web_media_player_delegate_browsertest.cccontent/renderer/media/renderer_web_media_player_delegate.cc
Patch
From 8b59df19e200d81d59d6f75985fe0f9a00b744ff Mon Sep 17 00:00:00 2001 From: Dale Curtis <[email protected]> Date: Wed, 07 Oct 2026 13:54:59 -0700 Subject: [PATCH] Guard against reentrancy in OnPageVisibilityChanged OnPageShown() and OnPageHidden() can synchronously run script (e.g. via text-track cue updates) that removes the frame and destroys the RendererWebMediaPlayerDelegate. Snapshot player IDs before iterating and check a WeakPtr after each observer notification. Fixed: 570675082 Change-Id: I5e784dcd7bb779fa689ba832ae683de827f62792 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8526472 Reviewed-by: Frank Liberato <[email protected]> Auto-Submit: Dale Curtis <[email protected]> Commit-Queue: Dale Curtis <[email protected]> Cr-Commit-Position: refs/heads/main@{#1713842} --- diff --git a/content/renderer/media/renderer_web_media_player_delegate.cc b/content/renderer/media/renderer_web_media_player_delegate.cc index 2727f894..d62b0953 100644 --- a/content/renderer/media/renderer_web_media_player_delegate.cc +++ b/content/renderer/media/renderer_web_media_player_delegate.cc @@ -6,6 +6,8 @@ #include <stdint.h> +#include <vector> + #include "base/functional/bind.h" #include "base/logging.h" #include "base/metrics/user_metrics_action.h" @@ -204,17 +206,34 @@ if (is_shown) { RecordAction(base::UserMetricsAction("Media.Shown")); - - for (base::IDMap<Observer*>::iterator it(&id_map_); !it.IsAtEnd(); - it.Advance()) { - it.GetCurrentValue()->OnPageShown(); - } } else { RecordAction(base::UserMetricsAction("Media.Hidden")); + } - for (base::IDMap<Observer*>::iterator it(&id_map_); !it.IsAtEnd(); - it.Advance()) { - it.GetCurrentValue()->OnPageHidden(); + // Create a list of players before making any possibly reentrant calls to + // OnPageShown() or OnPageHidden(). + std::vector<int> player_ids; + player_ids.reserve(id_map_.size()); + for (base::IDMap<Observer*>::iterator it(&id_map_); !it.IsAtEnd(); + it.Advance()) { + player_ids.push_back(it.GetCurrentKey()); + } + + auto weak_this = weak_ptr_factory_.GetWeakPtr(); + for (int player_id : player_ids) { + Observer* player = id_map_.Lookup(player_id); + if (!player) { + continue; + } + + if (is_shown) { + player->OnPageShown(); + } else { + player->OnPageHidden(); + } + + if (!weak_this) { + return; } } diff --git a/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc b/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc index 6ea3fe4a..a737f50 100644 --- a/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc +++ b/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc @@ -199,4 +199,52 @@ } } +TEST_F(RendererWebMediaPlayerDelegateTest, DestroyingDelegateOnPageHidden) { + const int delegate_id_1 = delegate_manager_->AddObserver(&observer_1_); + const int delegate_id_2 = delegate_manager_->AddObserver(&observer_2_); + + int call_count = 0; + auto destroy_delegate = [&]() { + ++call_count; + delegate_manager_->RemoveObserver(delegate_id_1); + delegate_manager_->RemoveObserver(delegate_id_2); + delegate_manager_.reset(); + }; + + EXPECT_CALL(observer_1_, OnPageHidden()) + .Times(testing::AtMost(1)) + .WillRepeatedly(destroy_delegate); + EXPECT_CALL(observer_2_, OnPageHidden()) + .Times(testing::AtMost(1)) + .WillRepeatedly(destroy_delegate); + + delegate_manager_->OnPageVisibilityChanged( + blink::mojom::PageVisibilityState::kHidden); + EXPECT_EQ(1, call_count); +} + +TEST_F(RendererWebMediaPlayerDelegateTest, DestroyingDelegateOnPageShown) { + const int delegate_id_1 = delegate_manager_->AddObserver(&observer_1_); + const int delegate_id_2 = delegate_manager_->AddObserver(&observer_2_); + + int call_count = 0; + auto destroy_delegate = [&]() { + ++call_count; + delegate_manager_->RemoveObserver(delegate_id_1); + delegate_manager_->RemoveObserver(delegate_id_2); + delegate_manager_.reset(); + }; + + EXPECT_CALL(observer_1_, OnPageShown()) + .Times(testing::AtMost(1)) + .WillRepeatedly(destroy_delegate); + EXPECT_CALL(observer_2_, OnPageShown()) + .Times(testing::AtMost(1)) + .WillRepeatedly(destroy_delegate); + + delegate_manager_->OnPageVisibilityChanged( + blink::mojom::PageVisibilityState::kVisible); + EXPECT_EQ(1, call_count); +} + } // namespace media
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc b/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc
index 6ea3fe4a..a737f50 100644
--- a/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc
+++ b/content/renderer/media/renderer_web_media_player_delegate_browsertest.cc
@@ -199,4 +199,52 @@
}
}
+TEST_F(RendererWebMediaPlayerDelegateTest, DestroyingDelegateOnPageHidden) {
+ const int delegate_id_1 = delegate_manager_->AddObserver(&observer_1_);
+ const int delegate_id_2 = delegate_manager_->AddObserver(&observer_2_);
+
+ int call_count = 0;
+ auto destroy_delegate = [&]() {
+ ++call_count;
+ delegate_manager_->RemoveObserver(delegate_id_1);
+ delegate_manager_->RemoveObserver(delegate_id_2);
+ delegate_manager_.reset();
+ };
+
+ EXPECT_CALL(observer_1_, OnPageHidden())
+ .Times(testing::AtMost(1))
+ .WillRepeatedly(destroy_delegate);
+ EXPECT_CALL(observer_2_, OnPageHidden())
+ .Times(testing::AtMost(1))
+ .WillRepeatedly(destroy_delegate);
+
+ delegate_manager_->OnPageVisibilityChanged(
+ blink::mojom::PageVisibilityState::kHidden);
+ EXPECT_EQ(1, call_count);
+}
+
+TEST_F(RendererWebMediaPlayerDelegateTest, DestroyingDelegateOnPageShown) {
+ const int delegate_id_1 = delegate_manager_->AddObserver(&observer_1_);
+ const int delegate_id_2 = delegate_manager_->AddObserver(&observer_2_);
+
+ int call_count = 0;
+ auto destroy_delegate = [&]() {
+ ++call_count;
+ delegate_manager_->RemoveObserver(delegate_id_1);
+ delegate_manager_->RemoveObserver(delegate_id_2);
+ delegate_manager_.reset();
+ };
+
+ EXPECT_CALL(observer_1_, OnPageShown())
+ .Times(testing::AtMost(1))
+ .WillRepeatedly(destroy_delegate);
+ EXPECT_CALL(observer_2_, OnPageShown())
+ .Times(testing::AtMost(1))
+ .WillRepeatedly(destroy_delegate);
+
+ delegate_manager_->OnPageVisibilityChanged(
+ blink::mojom::PageVisibilityState::kVisible);
+ EXPECT_EQ(1, call_count);
+}
+
} // namespace media
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.
References
On This Page