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 <steimel@chromium.org>
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 <dalecurtis@chromium.org>
Commit-Queue: Tommy Steimel <steimel@chromium.org>
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