Chrome · UI
CVE-2026-113244
Input Validation in UI
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fcontent/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc |
modified |
Files Changed
content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cccontent/browser/bad_message.htools/metrics/histograms/metadata/stability/enums.xmlcontent/browser/browser_interface_binders.cc
Patch
From 0041349ae316cf2183a70c9d444b4281151d5ecb Mon Sep 17 00:00:00 2001
From: Benjamin Keen <bkeen@google.com>
Date: Wed, 16 Sep 2026 11:08:16 -0700
Subject: [PATCH] Enforce permissions policy and fenced frame checks for video PiP
Ensure that the PictureInPictureService Mojo bindings are rejected if
requested from within a fenced frame, or when disallowed by the
picture-in-picture permissions policy.
Browser side user activation checks are omitted as per
content/SECURITY.md, which notes that missing user activation in the
browser is not currently treated as a security boundary.
Bug: 514078112
Change-Id: I23b19da86b5c0003ccbce132cd2eb55d9cd125d0
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8407465
Reviewed-by: Tommy Steimel <steimel@chromium.org>
Reviewed-by: Giovanni Ortuno Urquidi <ortuno@chromium.org>
Reviewed-by: Camille Lamy <clamy@chromium.org>
Commit-Queue: Benjamin Keen <bkeen@google.com>
Cr-Commit-Position: refs/heads/main@{#1699085}
---
diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h
index 568abf9..4ebb96b7 100644
--- a/content/browser/bad_message.h
+++ b/content/browser/bad_message.h
@@ -427,6 +427,8 @@
RFPH_SYNCHRONIZE_VISUAL_PROPERTIES_WITHOUT_CPFC = 399,
RFPH_UPDATE_VIEWPORT_INTERSECTION_WITHOUT_CPFC = 400,
RFH_DEDICATED_WORKER_HOST_FACTORY_PDF_PROCESS_BLOCKED = 401,
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_FOR_FENCED_FRAME = 402,
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_BLOCKED_BY_PERMISSIONS_POLICY = 403,
// Please add new elements here. The naming convention is abbreviated class
// name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/content/browser/browser_interface_binders.cc b/content/browser/browser_interface_binders.cc
index e43daedd..1d55322 100644
--- a/content/browser/browser_interface_binders.cc
+++ b/content/browser/browser_interface_binders.cc
@@ -828,6 +828,31 @@
->Bind(std::move(receiver));
}
+void BindPictureInPictureService(
+ RenderFrameHost* host,
+ mojo::PendingReceiver<blink::mojom::PictureInPictureService> receiver) {
+ // Fenced frames are not allowed to use Picture-in-Picture.
+ if (host->IsNestedWithinFencedFrame()) {
+ bad_message::ReceivedBadMessage(
+ host->GetProcess(),
+ bad_message::BadMessageReason::
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_FOR_FENCED_FRAME);
+ return;
+ }
+
+ // Enforce Permissions Policy browser-side.
+ if (!host->IsFeatureEnabled(
+ network::mojom::PermissionsPolicyFeature::kPictureInPicture)) {
+ bad_message::ReceivedBadMessage(
+ host->GetProcess(),
+ bad_message::BadMessageReason::
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_BLOCKED_BY_PERMISSIONS_POLICY);
+ return;
+ }
+
+ PictureInPictureServiceImpl::Create(host, std::move(receiver));
+}
+
template <auto Method, typename Interface>
void BindRenderFrameHostImpl(RenderFrameHost* host,
mojo::PendingReceiver<Interface> receiver)
@@ -1392,8 +1417,7 @@
map->Add<blink::mojom::KeyboardLockService>(
&KeyboardLockServiceImpl::CreateMojoService);
map->Add<blink::mojom::MediaSessionService>(&MediaSessionServiceImpl::Create);
- map->Add<blink::mojom::PictureInPictureService>(
- &PictureInPictureServiceImpl::Create);
+ map->Add<blink::mojom::PictureInPictureService>(&BindPictureInPictureService);
map->Add<blink::mojom::WakeLockService>(&WakeLockServiceImpl::Create);
#if BUILDFLAG(ENABLE_VR)
map->Add<device::mojom::VRService>(&VRServiceImpl::Create);
diff --git a/tools/metrics/histograms/metadata/stability/enums.xml b/tools/metrics/histograms/metadata/stability/enums.xml
index e954265b..6b50f81e 100644
--- a/tools/metrics/histograms/metadata/stability/enums.xml
+++ b/tools/metrics/histograms/metadata/stability/enums.xml
@@ -587,6 +587,10 @@
<int value="400" label="RFPH_UPDATE_VIEWPORT_INTERSECTION_WITHOUT_CPFC"/>
<int value="401"
label="RFH_DEDICATED_WORKER_HOST_FACTORY_PDF_PROCESS_BLOCKED"/>
+ <int value="402"
+ label="BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_FOR_FENCED_FRAME"/>
+ <int value="403"
+ label="BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_BLOCKED_BY_PERMISSIONS_POLICY"/>
</enum>
<enum name="BadMessageReasonExtensions">
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
index 11890ea..1b82a1d 100644
--- a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
@@ -11,11 +11,14 @@
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
+#include "content/browser/bad_message.h"
#include "content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.h"
#include "content/public/browser/overlay_window.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
+#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_content_browser_client.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
@@ -28,8 +31,7 @@
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
-#include "mojo/public/cpp/test_support/test_utils.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom.h"
@@ -982,4 +984,58 @@
EXPECT_FALSE(session_remote_out);
}
+TEST_F(PictureInPictureServiceImplTest, Bind_BlockedByPermissionsPolicy) {
+ // Create a child frame where picture-in-picture is disallowed by Permissions
+ // Policy.
+ network::ParsedPermissionsPolicy permissions_policy;
+ permissions_policy.emplace_back(
+ network::mojom::PermissionsPolicyFeature::kPictureInPicture);
+ TestRenderFrameHost* child_rfh =
+ main_test_rfh()->AppendChildWithPolicy("subframe", permissions_policy);
+ auto navigation = NavigationSimulator::CreateRendererInitiated(
+ GURL("https://example.com"), child_rfh);
+ navigation->Commit();
+
+ ASSERT_FALSE(child_rfh->IsFeatureEnabled(
+ network::mojom::PermissionsPolicyFeature::kPictureInPicture));
+
+ base::HistogramTester histogram_tester;
+ mojo::Remote<blink::mojom::PictureInPictureService> service_remote;
+ child_rfh->browser_interface_broker_receiver_for_testing()
+ .internal_state()
+ ->impl()
+ ->GetInterface(service_remote.BindNewPipeAndPassReceiver());
+
+ EXPECT_EQ(1, child_rfh->GetProcess()->bad_msg_count());
+ histogram_tester.ExpectUniqueSample(
+ "Stability.BadMessageTerminated.Content",
+ bad_message::BadMessageReason::
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_BLOCKED_BY_PERMISSIONS_POLICY,
+ 1);
+}
+
+TEST_F(PictureInPictureServiceImplTest, Bind_BlockedForFencedFrame) {
+ TestRenderFrameHost* fenced_frame_rfh = main_test_rfh()->AppendFencedFrame();
+ auto navigation = NavigationSimulator::CreateRendererInitiated(
+ GURL("https://example.com"), fenced_frame_rfh);
+ navigation->Commit();
+ fenced_frame_rfh =
+ static_cast<TestRenderFrameHost*>(navigation->GetFinalRenderFrameHost());
+ ASSERT_TRUE(fenced_frame_rfh->IsNestedWithinFencedFrame());
+
+ base::HistogramTester histogram_tester;
+ mojo::Remote<blink::mojom::PictureInPictureService> service_remote;
+ fenced_frame_rfh->browser_interface_broker_receiver_for_testing()
+ .internal_state()
+ ->impl()
+ ->GetInterface(service_remote.BindNewPipeAndPassReceiver());
+
+ EXPECT_EQ(1, fenced_frame_rfh->GetProcess()->bad_msg_count());
+ histogram_tester.ExpectUniqueSample(
+ "Stability.BadMessageTerminated.Content",
+ bad_message::BadMessageReason::
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_FOR_FENCED_FRAME,
+ 1);
+}
+
} // namespace content
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
index 11890ea..1b82a1d 100644
--- a/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
+++ b/content/browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc
@@ -11,11 +11,14 @@
#include "base/memory/raw_ptr.h"
#include "base/test/bind.h"
+#include "base/test/metrics/histogram_tester.h"
#include "build/build_config.h"
+#include "content/browser/bad_message.h"
#include "content/browser/picture_in_picture/video_picture_in_picture_window_controller_impl.h"
#include "content/public/browser/overlay_window.h"
#include "content/public/browser/web_contents_delegate.h"
#include "content/public/common/content_client.h"
+#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_content_browser_client.h"
#include "content/test/test_render_frame_host.h"
#include "content/test/test_render_view_host.h"
@@ -28,8 +31,7 @@
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
-#include "mojo/public/cpp/test_support/fake_message_dispatch_context.h"
-#include "mojo/public/cpp/test_support/test_utils.h"
+#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "third_party/blink/public/mojom/frame/fullscreen.mojom.h"
#include "third_party/blink/public/mojom/picture_in_picture/picture_in_picture.mojom.h"
@@ -982,4 +984,58 @@
EXPECT_FALSE(session_remote_out);
}
+TEST_F(PictureInPictureServiceImplTest, Bind_BlockedByPermissionsPolicy) {
+ // Create a child frame where picture-in-picture is disallowed by Permissions
+ // Policy.
+ network::ParsedPermissionsPolicy permissions_policy;
+ permissions_policy.emplace_back(
+ network::mojom::PermissionsPolicyFeature::kPictureInPicture);
+ TestRenderFrameHost* child_rfh =
+ main_test_rfh()->AppendChildWithPolicy("subframe", permissions_policy);
+ auto navigation = NavigationSimulator::CreateRendererInitiated(
+ GURL("https://example.com"), child_rfh);
+ navigation->Commit();
+
+ ASSERT_FALSE(child_rfh->IsFeatureEnabled(
+ network::mojom::PermissionsPolicyFeature::kPictureInPicture));
+
+ base::HistogramTester histogram_tester;
+ mojo::Remote<blink::mojom::PictureInPictureService> service_remote;
+ child_rfh->browser_interface_broker_receiver_for_testing()
+ .internal_state()
+ ->impl()
+ ->GetInterface(service_remote.BindNewPipeAndPassReceiver());
+
+ EXPECT_EQ(1, child_rfh->GetProcess()->bad_msg_count());
+ histogram_tester.ExpectUniqueSample(
+ "Stability.BadMessageTerminated.Content",
+ bad_message::BadMessageReason::
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_BLOCKED_BY_PERMISSIONS_POLICY,
+ 1);
+}
+
+TEST_F(PictureInPictureServiceImplTest, Bind_BlockedForFencedFrame) {
+ TestRenderFrameHost* fenced_frame_rfh = main_test_rfh()->AppendFencedFrame();
+ auto navigation = NavigationSimulator::CreateRendererInitiated(
+ GURL("https://example.com"), fenced_frame_rfh);
+ navigation->Commit();
+ fenced_frame_rfh =
+ static_cast<TestRenderFrameHost*>(navigation->GetFinalRenderFrameHost());
+ ASSERT_TRUE(fenced_frame_rfh->IsNestedWithinFencedFrame());
+
+ base::HistogramTester histogram_tester;
+ mojo::Remote<blink::mojom::PictureInPictureService> service_remote;
+ fenced_frame_rfh->browser_interface_broker_receiver_for_testing()
+ .internal_state()
+ ->impl()
+ ->GetInterface(service_remote.BindNewPipeAndPassReceiver());
+
+ EXPECT_EQ(1, fenced_frame_rfh->GetProcess()->bad_msg_count());
+ histogram_tester.ExpectUniqueSample(
+ "Stability.BadMessageTerminated.Content",
+ bad_message::BadMessageReason::
+ BIBI_BIND_PICTURE_IN_PICTURE_SERVICE_FOR_FENCED_FRAME,
+ 1);
+}
+
} // namespace content
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