Chrome · Media
CVE-2026-17994
Logic Error in Media
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java |
modified |
Files Changed
chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.javachrome/android/junit/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureControllerUnitTest.javacomponents/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.javacontent/browser/media/session/media_session_android.cccontent/browser/media/session/media_session_android.hcontent/public/android/java/src/org/chromium/content/browser/MediaSessionImpl.javacontent/public/android/java/src/org/chromium/content_public/browser/MediaSession.java
Patch
From 5e7d3e1916e8b3aeb95946172f12c1d7a071e1ed Mon Sep 17 00:00:00 2001
From: Phil Yan <philyan@chromium.org>
Date: Tue, 09 Jun 2026 09:17:24 -0700
Subject: [PATCH] [media] Parameterize MediaSession resume in Android JNI
The Android Auto-PiP media controller propagated system-initiated
events (such as screen-off dismissal or window stashing) as
user-initiated UI actions. This mismatch could potentially forge
a fresh user activation ('kInteraction') in Blink.
This CL fixes this logic by:
1. Parameterizing the JNI bridge `MediaSessionAndroid::Resume` and
the Java class `MediaSession` to accept a `SuspendType` parameter.
2. Updating `FullscreenVideoPictureInPictureController` to pass
`SuspendType.SYSTEM` during screen-off dismissal and stashing.
3. Updating all other usages, tests, and mocks accordingly.
Bug: 520663771
Change-Id: Ic87d5812b34174ae1061b75d09616493dbd8ba28
Fix: 520663771
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7910926
Reviewed-by: Ted Choc <tedchoc@chromium.org>
Reviewed-by: Frank Liberato <liberato@chromium.org>
Commit-Queue: Phil Yan <philyan@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644023}
---
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java b/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java
index fc3e602..be6e9eb 100644
--- a/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java
+++ b/chrome/android/java/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureController.java
@@ -307,7 +307,7 @@
// `stashed`. Also note that `mIsSuspendedForStash` can be reset elsewhere, to help to
// account for missed events from Android.
if (mIsPlaying && stashed && !mIsSuspendedForStash) {
- mediaSession.suspend(SuspendType.UI);
+ mediaSession.suspend(SuspendType.SYSTEM);
mIsSuspendedForStash = true;
} else if (!mIsPlaying && !stashed && mIsSuspendedForStash) {
// Don't resume if we didn't pause it on the transition into stash. For example, don't
@@ -335,7 +335,7 @@
if (mediaSession == null || mIsPlaying || !mIsSuspendedForStash) {
return;
}
- mediaSession.resume();
+ mediaSession.resume(SuspendType.SYSTEM);
mIsSuspendedForStash = false;
}
@@ -636,7 +636,7 @@
final MediaSession mediaSession = getMediaSession();
if (mediaSession != null && mIsPlaying) {
- mediaSession.suspend(SuspendType.UI);
+ mediaSession.suspend(SuspendType.SYSTEM);
}
}
}
diff --git a/chrome/android/junit/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureControllerUnitTest.java b/chrome/android/junit/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureControllerUnitTest.java
index 3197fab9..7432370 100644
--- a/chrome/android/junit/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureControllerUnitTest.java
+++ b/chrome/android/junit/src/org/chromium/chrome/browser/media/FullscreenVideoPictureInPictureControllerUnitTest.java
@@ -182,7 +182,7 @@
// Stash while media is playing.
mWebContentsObserverCaptor.getValue().mediaStartedPlaying(0, true, true);
mController.onStashReported(true);
- verify(mMediaSession, times(1)).suspend(SuspendType.UI);
+ verify(mMediaSession, times(1)).suspend(SuspendType.SYSTEM);
mWebContentsObserverCaptor.getValue().mediaStoppedPlaying(0);
// Un-stash while media is still paused.
@@ -191,7 +191,7 @@
FullscreenVideoPictureInPictureController.UNSTASH_DELAY_MILLIS + 10L,
TimeUnit.MILLISECONDS);
runUntilIdle();
- verify(mMediaSession, times(1)).resume();
+ verify(mMediaSession, times(1)).resume(SuspendType.SYSTEM);
}
/**
@@ -207,7 +207,7 @@
// Stashing paused video should do nothing.
mController.onStashReported(true);
- verify(mMediaSession, times(0)).suspend(SuspendType.UI);
+ verify(mMediaSession, times(0)).suspend(SuspendType.SYSTEM);
// Un-stash should also do nothing.
mController.onStashReported(false);
@@ -215,7 +215,7 @@
FullscreenVideoPictureInPictureController.UNSTASH_DELAY_MILLIS + 10L,
TimeUnit.MILLISECONDS);
runUntilIdle();
- verify(mMediaSession, times(0)).resume();
+ verify(mMediaSession, times(0)).resume(SuspendType.SYSTEM);
}
/** If video starts playing during a normal stash, unstash should no-op. */
@@ -226,7 +226,7 @@
// Stash normally.
mWebContentsObserverCaptor.getValue().mediaStartedPlaying(0, true, true);
mController.onStashReported(true);
- verify(mMediaSession, times(1)).suspend(SuspendType.UI);
+ verify(mMediaSession, times(1)).suspend(SuspendType.SYSTEM);
mWebContentsObserverCaptor.getValue().mediaStoppedPlaying(0);
// Restart playback while still stashed.
@@ -238,7 +238,7 @@
FullscreenVideoPictureInPictureController.UNSTASH_DELAY_MILLIS + 10L,
TimeUnit.MILLISECONDS);
runUntilIdle();
- verify(mMediaSession, times(0)).resume();
+ verify(mMediaSession, times(0)).resume(SuspendType.SYSTEM);
}
@Test
@@ -254,7 +254,7 @@
mController.onResume();
verify(mActivity, times(0)).moveTaskToBack(true);
// The media should be paused, though, just as if pip had closed.
- verify(mMediaSession, times(1)).suspend(SuspendType.UI);
+ verify(mMediaSession, times(1)).suspend(SuspendType.SYSTEM);
// When the device is unlocked, we will get `onStart`. This should cause pip to close
// because it's still deferred from the `onResume` call, above.
diff --git a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
index 95d1e9ed..eaf3ce8 100644
--- a/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
+++ b/components/browser_ui/media/android/java/src/org/chromium/components/browser_ui/media/MediaSessionHelper.java
@@ -163,7 +163,7 @@
if (mMediaSessionObserver.getMediaSession() == null) return;
- mMediaSessionObserver.getMediaSession().resume();
+ mMediaSessionObserver.getMediaSession().resume(SuspendType.UI);
}
@Override
diff --git a/content/browser/media/session/media_session_android.cc b/content/browser/media/session/media_session_android.cc
index 5aa7602..c15dff98 100644
--- a/content/browser/media/session/media_session_android.cc
+++ b/content/browser/media/session/media_session_android.cc
@@ -162,9 +162,11 @@
}
}
-void MediaSessionAndroid::Resume(JNIEnv* env) {
+void MediaSessionAndroid::Resume(
+ JNIEnv* env,
+ media_session::mojom::MediaSession::SuspendType suspend_type) {
DCHECK(media_session_);
- media_session_->Resume(MediaSession::SuspendType::kUI);
+ media_session_->Resume(suspend_type);
}
void MediaSessionAndroid::Suspend(
diff --git a/content/browser/media/session/media_session_android.h b/content/browser/media/session/media_session_android.h
index 0dfd959b1..0c93dff 100644
--- a/content/browser/media/session/media_session_android.h
+++ b/content/browser/media/session/media_session_android.h
@@ -53,7 +53,8 @@
const std::optional<media_session::MediaPosition>& position) override;
// MediaSession method wrappers.
- void Resume(JNIEnv* env);
+ void Resume(JNIEnv* env,
+ media_session::mojom::MediaSession::SuspendType suspend_type);
void Suspend(JNIEnv* env,
media_session::mojom::MediaSession::SuspendType suspend_type);
void Stop(JNIEnv* env);
diff --git a/content/public/android/java/src/org/chromium/content/browser/MediaSessionImpl.java b/content/public/android/java/src/org/chromium/content/browser/MediaSessionImpl.java
index 8bdfff2..36e5f92 100644
--- a/content/public/android/java/src/org/chromium/content/browser/MediaSessionImpl.java
+++ b/content/public/android/java/src/org/chromium/content/browser/MediaSessionImpl.java
@@ -79,8 +79,8 @@
}
@Override
- public void resume() {
- MediaSessionImplJni.get().resume(mNativeMediaSessionAndroid);
+ public void resume(@SuspendType.EnumType int suspendType) {
+ MediaSessionImplJni.get().resume(mNativeMediaSessionAndroid, suspendType);
}
@Override
@@ -196,7 +196,9 @@
@NativeMethods
public interface Natives {
- void resume(long nativeMediaSessionAndroid);
+ void resume(
+ long nativeMediaSessionAndroid,
+ @JniType("media_session::mojom::MediaSession::SuspendType") int suspendType);
void suspend(
long nativeMediaSessionAndroid,
diff --git a/content/public/android/java/src/org/chromium/content_public/browser/MediaSession.java b/content/public/android/java/src/org/chromium/content_public/browser/MediaSession.java
index 6af471b..d7ba9f7b 100644
--- a/content/public/android/java/src/org/chromium/content_public/browser/MediaSession.java
+++ b/content/public/android/java/src/org/chromium/content_public/browser/MediaSession.java
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