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 <[email protected]> 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 <[email protected]> Reviewed-by: Frank Liberato <[email protected]> Commit-Queue: Phil Yan <[email protected]> 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