Chrome · UI
CVE-2026-106296
Authorization Error in UI
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/core/html/media/html_media_element.cc |
modified | |
TEST_Pthird_party/blink/renderer/core/html/media/html_media_element_test.cc |
modified | |
TEST_Fthird_party/blink/renderer/modules/mediasession/media_session_test.cc |
modified |
Files Changed
third_party/blink/renderer/modules/mediasession/media_session_test.ccthird_party/blink/renderer/modules/mediasession/media_session.ccthird_party/blink/renderer/core/html/media/html_media_element_test.ccthird_party/blink/renderer/core/html/media/html_media_element.cc
Patch
From 9030e52c0a6d90dd8ae7789853a531440dfdab24 Mon Sep 17 00:00:00 2001 From: Tommy Steimel <[email protected]> Date: Tue, 08 Sep 2026 15:32:14 -0700 Subject: [PATCH] Media Session: Don't grant user activation for pause actions Currently, user activation is granted when a page receives any media session action, which allows the page to play a video, put a video into a picture-in-picture window, etc. However, this activation is not actually needed for pause and stop actions, and only serves as a potential abuse vector. This CL changes Media Session to no longer grant user activation for pause and stop actions. Bug: 540078886 Change-Id: I8f6c355e2d279ea1af2ef0e5631de17a5271e7b4 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8373882 Reviewed-by: Dale Curtis <[email protected]> Commit-Queue: Tommy Steimel <[email protected]> Cr-Commit-Position: refs/heads/main@{#1694124} --- diff --git a/third_party/blink/renderer/core/html/media/html_media_element.cc b/third_party/blink/renderer/core/html/media/html_media_element.cc index 2a21c362..550e100f 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element.cc +++ b/third_party/blink/renderer/core/html/media/html_media_element.cc @@ -5334,13 +5334,9 @@ } void HTMLMediaElement::RequestPause(bool triggered_by_user) { - if (triggered_by_user) { - LocalFrame* frame = GetDocument().GetFrame(); - if (frame) { - LocalFrame::NotifyUserActivation( - frame, mojom::blink::UserActivationNotificationType::kInteraction); - } - } + // Never grant user activation for pause actions. Pausing media never requires + // user activation and should not allow websites to trigger restricted APIs + // like popups or clipboard writes on pause events. PauseInternal(triggered_by_user ? WebMediaPlayer::PauseReason::kPauseRequestedByUser : WebMediaPlayer::PauseReason::kPauseRequestedInternally); diff --git a/third_party/blink/renderer/modules/mediasession/media_session.cc b/third_party/blink/renderer/modules/mediasession/media_session.cc index 8c95e017..05765dc8 100644 --- a/third_party/blink/renderer/modules/mediasession/media_session.cc +++ b/third_party/blink/renderer/modules/mediasession/media_session.cc @@ -427,9 +427,16 @@ LocalDOMWindow* window = GetSupplementable()->DomWindow(); if (!window) return; - LocalFrame::NotifyUserActivation( - window->GetFrame(), - mojom::blink::UserActivationNotificationType::kInteraction); + // Do not grant user activation for pause or stop actions. Pausing or stopping + // media never requires user activation and should not allow websites to + // trigger restricted APIs (e.g. popups or clipboard access) via pause events + // or system/accessory actions like removing headphones. + if (action != media_session::mojom::blink::MediaSessionAction::kPause && + action != media_session::mojom::blink::MediaSessionAction::kStop) { + LocalFrame::NotifyUserActivation( + window->GetFrame(), + mojom::blink::UserActivationNotificationType::kInteraction); + } auto v8_action = MojomActionToActionEnum(action); diff --git a/third_party/blink/renderer/core/html/media/html_media_element_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_test.cc index d26b8e7..d9ff029 100644 --- a/third_party/blink/renderer/core/html/media/html_media_element_test.cc +++ b/third_party/blink/renderer/core/html/media/html_media_element_test.cc @@ -67,6 +67,11 @@ triggered_by_user); } +void SimulateRequestPause(HTMLMediaElement* element, bool triggered_by_user) { + static_cast<media::mojom::blink::MediaPlayer*>(element)->RequestPause( + triggered_by_user); +} + enum class TestURLScheme { kHttp, kHttps, @@ -2301,6 +2306,39 @@ Media()->GetDocument().GetFrame())); } +TEST_P(HTMLMediaElementTest, RequestPause_UserTriggered) { + Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp)); + test::RunPendingTasks(); + SetReadyState(HTMLMediaElement::kHaveEnoughData); + test::RunPendingTasks(); + + EXPECT_CALL(*MockMediaPlayer(), Paused()).WillRepeatedly(Return(false)); + EXPECT_CALL(*MockMediaPlayer(), + Pause(WebMediaPlayer::PauseReason::kPauseRequestedByUser)); + SimulateRequestPause(Media(), /*triggered_by_user=*/true); + + // The frame should NOT be granted transient user activation even when + // triggered by the user. + EXPECT_FALSE(LocalFrame::HasTransientUserActivation( + Media()->GetDocument().GetFrame())); +} + +TEST_P(HTMLMediaElementTest, RequestPause_SystemTriggered) { + Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp)); + test::RunPendingTasks(); + SetReadyState(HTMLMediaElement::kHaveEnoughData); + test::RunPendingTasks(); + + EXPECT_CALL(*MockMediaPlayer(), Paused()).WillRepeatedly(Return(false)); + EXPECT_CALL(*MockMediaPlayer(), + Pause(WebMediaPlayer::PauseReason::kPauseRequestedInternally)); + SimulateRequestPause(Media(), /*triggered_by_user=*/false); + + // The frame should NOT be granted transient user activation. + EXPECT_FALSE(LocalFrame::HasTransientUserActivation( + Media()->GetDocument().GetFrame())); +} + TEST_P(HTMLMediaElementTest, PlayedWithUserActivation) { Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp)); test::RunPendingTasks(); diff --git a/third_party/blink/renderer/modules/mediasession/media_session_test.cc b/third_party/blink/renderer/modules/mediasession/media_session_test.cc index 796f060..e6c43d96 100644 --- a/third_party/blink/renderer/modules/mediasession/media_session_test.cc +++ b/third_party/blink/renderer/modules/mediasession/media_session_test.cc @@ -117,6 +117,11 @@ base::SimpleTestTickClock& clock() { return test_clock_; } + void DidReceiveAction( + media_session::mojom::blink::MediaSessionAction action) { + media_session_->DidReceiveAction(action, nullptr); + } + private: base::SimpleTestTickClock test_clock_; @@ -389,4 +394,22 @@ SetPlaybackState(V8MediaSessionPlaybackState::Enum::kPaused); } +TEST_F(MediaSessionTest, DidReceiveAction_PlayGrantsUserActivation) { + EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame())); + DidReceiveAction(media_session::mojom::blink::MediaSessionAction::kPlay); + EXPECT_TRUE(LocalFrame::HasTransientUserActivation(&GetFrame())); +} + +TEST_F(MediaSessionTest, DidReceiveAction_PauseDoesNotGrantUserActivation) { + EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame())); + DidReceiveAction(media_session::mojom::blink::MediaSessionAction::kPause); + EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame())); +} + +TEST_F(MediaSessionTest, DidReceiveAction_StopDoesNotGrantUserActivation) { + EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame())); + DidReceiveAction(media_session::mojom::blink::MediaSessionAction::kStop); + EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame())); +} + } // namespace blink
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/third_party/blink/renderer/core/html/media/html_media_element_test.cc b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
index d26b8e7..d9ff029 100644
--- a/third_party/blink/renderer/core/html/media/html_media_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_media_element_test.cc
@@ -67,6 +67,11 @@
triggered_by_user);
}
+void SimulateRequestPause(HTMLMediaElement* element, bool triggered_by_user) {
+ static_cast<media::mojom::blink::MediaPlayer*>(element)->RequestPause(
+ triggered_by_user);
+}
+
enum class TestURLScheme {
kHttp,
kHttps,
@@ -2301,6 +2306,39 @@
Media()->GetDocument().GetFrame()));
}
+TEST_P(HTMLMediaElementTest, RequestPause_UserTriggered) {
+ Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
+ test::RunPendingTasks();
+ SetReadyState(HTMLMediaElement::kHaveEnoughData);
+ test::RunPendingTasks();
+
+ EXPECT_CALL(*MockMediaPlayer(), Paused()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*MockMediaPlayer(),
+ Pause(WebMediaPlayer::PauseReason::kPauseRequestedByUser));
+ SimulateRequestPause(Media(), /*triggered_by_user=*/true);
+
+ // The frame should NOT be granted transient user activation even when
+ // triggered by the user.
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(
+ Media()->GetDocument().GetFrame()));
+}
+
+TEST_P(HTMLMediaElementTest, RequestPause_SystemTriggered) {
+ Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
+ test::RunPendingTasks();
+ SetReadyState(HTMLMediaElement::kHaveEnoughData);
+ test::RunPendingTasks();
+
+ EXPECT_CALL(*MockMediaPlayer(), Paused()).WillRepeatedly(Return(false));
+ EXPECT_CALL(*MockMediaPlayer(),
+ Pause(WebMediaPlayer::PauseReason::kPauseRequestedInternally));
+ SimulateRequestPause(Media(), /*triggered_by_user=*/false);
+
+ // The frame should NOT be granted transient user activation.
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(
+ Media()->GetDocument().GetFrame()));
+}
+
TEST_P(HTMLMediaElementTest, PlayedWithUserActivation) {
Media()->SetSrc(SrcSchemeToURL(TestURLScheme::kHttp));
test::RunPendingTasks();
diff --git a/third_party/blink/renderer/modules/mediasession/media_session_test.cc b/third_party/blink/renderer/modules/mediasession/media_session_test.cc
index 796f060..e6c43d96 100644
--- a/third_party/blink/renderer/modules/mediasession/media_session_test.cc
+++ b/third_party/blink/renderer/modules/mediasession/media_session_test.cc
@@ -117,6 +117,11 @@
base::SimpleTestTickClock& clock() { return test_clock_; }
+ void DidReceiveAction(
+ media_session::mojom::blink::MediaSessionAction action) {
+ media_session_->DidReceiveAction(action, nullptr);
+ }
+
private:
base::SimpleTestTickClock test_clock_;
@@ -389,4 +394,22 @@
SetPlaybackState(V8MediaSessionPlaybackState::Enum::kPaused);
}
+TEST_F(MediaSessionTest, DidReceiveAction_PlayGrantsUserActivation) {
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame()));
+ DidReceiveAction(media_session::mojom::blink::MediaSessionAction::kPlay);
+ EXPECT_TRUE(LocalFrame::HasTransientUserActivation(&GetFrame()));
+}
+
+TEST_F(MediaSessionTest, DidReceiveAction_PauseDoesNotGrantUserActivation) {
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame()));
+ DidReceiveAction(media_session::mojom::blink::MediaSessionAction::kPause);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame()));
+}
+
+TEST_F(MediaSessionTest, DidReceiveAction_StopDoesNotGrantUserActivation) {
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame()));
+ DidReceiveAction(media_session::mojom::blink::MediaSessionAction::kStop);
+ EXPECT_FALSE(LocalFrame::HasTransientUserActivation(&GetFrame()));
+}
+
} // namespace blink
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