Chrome · Core
CVE-2026-87543
Logic Error in Core
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
content/browser/bad_message.hcontent/browser/browser_interface_binders.cccontent/browser/security_exploit_browsertest.ccthird_party/blink/renderer/modules/vibration/vibration_controller.cc
Patch
From 0488f340b54088e3ecf978b6a2ff7227ccb187de Mon Sep 17 00:00:00 2001 From: Shunya Shishido <[email protected]> Date: Thu, 30 Jul 2026 22:58:59 -0700 Subject: [PATCH] Reject VibrationManager bind requests from fenced frames The renderer already refuses navigator.vibrate() inside a fenced-frame tree, but the browser-side device::mojom::VibrationManager binder did not enforce the same restriction. Mirror the adjacent BindBatteryMonitor() pattern: factor the lambda into BindVibrationManager(), check IsNestedWithinFencedFrame(), and terminate the renderer with the new BIBI_BIND_VIBRATION_MANAGER_FOR_FENCED_FRAME reason instead of binding the receiver to the device service. In addition, update VibrationController::vibrate() in Blink to check IsInFencedFrameTree() before calling From(navigator). This prevents VibrationController instantiation and unconditional Mojo interface binding in fenced frames, avoiding unexpected renderer terminations during web platform tests (navigator-vibrate.https.html). Add a content_browsertest that requests the interface directly on a fenced frame's BrowserInterfaceBroker and asserts the renderer is terminated with the new bad-message reason. TAG=agy CONV=0f7e37f2-2d45-401e-9bf7-0a3ac2b8fc8e Bug: 502452118 Change-Id: Ic81c2d205150577c13c50f8bcd4d4328145012a1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8173483 Reviewed-by: Rakina Zata Amni <[email protected]> Reviewed-by: Alexei Svitkine <[email protected]> Reviewed-by: Takashi Toyoshima <[email protected]> Commit-Queue: Shunya Shishido <[email protected]> Cr-Commit-Position: refs/heads/main@{#1671637} --- diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h index 8fa45ea3..baddc1a5 100644 --- a/content/browser/bad_message.h +++ b/content/browser/bad_message.h @@ -394,6 +394,7 @@ RFH_INVALID_CONNECTION_ALLOWLIST_ATTRIBUTE = 366, PMM_UNSUBSCRIBE_IN_FENCED_FRAME = 367, PMM_GET_SUBSCRIPTION_IN_FENCED_FRAME = 368, + BIBI_BIND_VIBRATION_MANAGER_FOR_FENCED_FRAME = 369, // 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 94c73d9..6f080e0 100644 --- a/content/browser/browser_interface_binders.cc +++ b/content/browser/browser_interface_binders.cc @@ -741,6 +741,20 @@ GetDeviceService().BindBatteryMonitor(std::move(receiver)); } +void BindVibrationManager( + RenderFrameHost* host, + mojo::PendingReceiver<device::mojom::VibrationManager> receiver) { + if (host->IsNestedWithinFencedFrame()) { + bad_message::ReceivedBadMessage( + host->GetProcess(), bad_message::BadMessageReason:: + BIBI_BIND_VIBRATION_MANAGER_FOR_FENCED_FRAME); + return; + } + GetDeviceService().BindVibrationManager( + std::move(receiver), static_cast<RenderFrameHostImpl*>(host) + ->CreateVibrationManagerListener()); +} + #if BUILDFLAG(ENABLE_COMPUTE_PRESSURE) void BindPressureManager( RenderFrameHost* host, @@ -1434,13 +1448,7 @@ map->Add<blink::mojom::AnchorElementInteractionHost>( &AnchorElementInteractionHostImpl::Create); - map->Add<device::mojom::VibrationManager>( - [](RenderFrameHost* host, - mojo::PendingReceiver<device::mojom::VibrationManager> receiver) { - GetDeviceService().BindVibrationManager( - std::move(receiver), static_cast<RenderFrameHostImpl*>(host) - ->CreateVibrationManagerListener()); - }); + map->Add<device::mojom::VibrationManager>(&BindVibrationManager); #if BUILDFLAG(IS_CHROMEOS) if (base::FeatureList::IsEnabled(features::kWebLockScreenApi)) { diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc index 2a3793a..c1d86a44 100644 --- a/content/browser/security_exploit_browsertest.cc +++ b/content/browser/security_exploit_browsertest.cc @@ -102,7 +102,9 @@ #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/embedded_test_server/http_request.h" #include "net/traffic_annotation/network_traffic_annotation_test_helper.h" +#include "services/device/public/mojom/battery_monitor.mojom.h" #include "services/device/public/mojom/screen_orientation.mojom.h" +#include "services/device/public/mojom/vibration_manager.mojom.h" #include "services/network/public/cpp/network_switches.h" #include "services/network/public/cpp/resource_request.h" #include "services/network/public/cpp/resource_request_body.h" @@ -4175,6 +4177,66 @@ << handle->GetInitiatorOrigin()->Serialize(); } +// Verify that binding VibrationManager is rejected when requested from within a +// fenced frame. +IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames, + VibrationManagerBindRejectedInFencedFrame) { + GURL main_url(https_server()->GetURL("a.test", "/simple_page.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + + GURL fenced_url( + https_server()->GetURL("a.test", "/fenced_frames/title1.html")); + RenderFrameHost* fenced_rfh_generic = + fenced_frame_test_helper().CreateFencedFrame( + shell()->web_contents()->GetPrimaryMainFrame(), fenced_url); + ASSERT_TRUE(fenced_rfh_generic); + RenderFrameHostImpl* fenced_rfh = + static_cast<RenderFrameHostImpl*>(fenced_rfh_generic); + + RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess()); + + mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& broker_receiver = + fenced_rfh->browser_interface_broker_receiver_for_testing(); + blink::mojom::BrowserInterfaceBroker* broker = + broker_receiver.internal_state()->impl(); + + mojo::Remote<device::mojom::VibrationManager> remote; + broker->GetInterface(remote.BindNewPipeAndPassReceiver()); + + EXPECT_EQ(bad_message::BIBI_BIND_VIBRATION_MANAGER_FOR_FENCED_FRAME, + kill_waiter.Wait()); +} + +// Verify that binding BatteryMonitor is rejected when requested from within a +// fenced frame. +IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames, + BatteryMonitorBindRejectedInFencedFrame) { + GURL main_url(https_server()->GetURL("a.test", "/simple_page.html")); + EXPECT_TRUE(NavigateToURL(shell(), main_url)); + + GURL fenced_url( + https_server()->GetURL("a.test", "/fenced_frames/title1.html")); + RenderFrameHost* fenced_rfh_generic = + fenced_frame_test_helper().CreateFencedFrame( + shell()->web_contents()->GetPrimaryMainFrame(), fenced_url); + ASSERT_TRUE(fenced_rfh_generic); + RenderFrameHostImpl* fenced_rfh = + static_cast<RenderFrameHostImpl*>(fenced_rfh_generic); + + RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess()); + + mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& broker_receiver = + fenced_rfh->browser_interface_broker_receiver_for_testing(); + blink::mojom::BrowserInterfaceBroker* broker = + broker_receiver.internal_state()->impl(); + + mojo::Remote<device::mojom::BatteryMonitor> remote; + broker->GetInterface(remote.BindNewPipeAndPassReceiver()); + + EXPECT_EQ(bad_message::BIBI_BIND_BATTERY_MONITOR_FOR_FENCED_FRAME, + kill_waiter.Wait()); +} + namespace { // Interceptor that replaces the origin in the DidCommitProvisionalLoadParams diff --git a/third_party/blink/renderer/modules/vibration/vibration_controller.cc b/third_party/blink/renderer/modules/vibration/vibration_controller.cc index 9950d048..3ba3da13 100644 --- a/third_party/blink/renderer/modules/vibration/vibration_controller.cc +++ b/third_party/blink/renderer/modules/vibration/vibration_controller.cc @@ -108,6 +108,15 @@ // reference to |window| or |navigator| was retained in another window. if (!navigator.DomWindow()) return false; + + LocalFrame* frame = navigator.DomWindow()->GetFrame(); + if (frame && frame->IsInFencedFrameTree()) { + Intervention::GenerateReport( + frame, "NavigatorVibrate", + "Blocked call to navigator.vibrate inside a fenced frame."); + return false; + } + return From(navigator).Vibrate(pattern); } @@ -133,13 +142,6 @@ UseCounter::Count(DomWindow(), WebFeature::kNavigatorVibrate); LocalFrame* frame = DomWindow()->GetFrame(); - if (frame->IsInFencedFrameTree()) { - Intervention::GenerateReport( - frame, "NavigatorVibrate", - "Blocked call to navigator.vibrate inside a fenced frame."); - return false; - } -
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/security_exploit_browsertest.cc b/content/browser/security_exploit_browsertest.cc
index 2a3793a..c1d86a44 100644
--- a/content/browser/security_exploit_browsertest.cc
+++ b/content/browser/security_exploit_browsertest.cc
@@ -102,7 +102,9 @@
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
+#include "services/device/public/mojom/battery_monitor.mojom.h"
#include "services/device/public/mojom/screen_orientation.mojom.h"
+#include "services/device/public/mojom/vibration_manager.mojom.h"
#include "services/network/public/cpp/network_switches.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/resource_request_body.h"
@@ -4175,6 +4177,66 @@
<< handle->GetInitiatorOrigin()->Serialize();
}
+// Verify that binding VibrationManager is rejected when requested from within a
+// fenced frame.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
+ VibrationManagerBindRejectedInFencedFrame) {
+ GURL main_url(https_server()->GetURL("a.test", "/simple_page.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ GURL fenced_url(
+ https_server()->GetURL("a.test", "/fenced_frames/title1.html"));
+ RenderFrameHost* fenced_rfh_generic =
+ fenced_frame_test_helper().CreateFencedFrame(
+ shell()->web_contents()->GetPrimaryMainFrame(), fenced_url);
+ ASSERT_TRUE(fenced_rfh_generic);
+ RenderFrameHostImpl* fenced_rfh =
+ static_cast<RenderFrameHostImpl*>(fenced_rfh_generic);
+
+ RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess());
+
+ mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& broker_receiver =
+ fenced_rfh->browser_interface_broker_receiver_for_testing();
+ blink::mojom::BrowserInterfaceBroker* broker =
+ broker_receiver.internal_state()->impl();
+
+ mojo::Remote<device::mojom::VibrationManager> remote;
+ broker->GetInterface(remote.BindNewPipeAndPassReceiver());
+
+ EXPECT_EQ(bad_message::BIBI_BIND_VIBRATION_MANAGER_FOR_FENCED_FRAME,
+ kill_waiter.Wait());
+}
+
+// Verify that binding BatteryMonitor is rejected when requested from within a
+// fenced frame.
+IN_PROC_BROWSER_TEST_F(SecurityExploitBrowserTestFencedFrames,
+ BatteryMonitorBindRejectedInFencedFrame) {
+ GURL main_url(https_server()->GetURL("a.test", "/simple_page.html"));
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
+
+ GURL fenced_url(
+ https_server()->GetURL("a.test", "/fenced_frames/title1.html"));
+ RenderFrameHost* fenced_rfh_generic =
+ fenced_frame_test_helper().CreateFencedFrame(
+ shell()->web_contents()->GetPrimaryMainFrame(), fenced_url);
+ ASSERT_TRUE(fenced_rfh_generic);
+ RenderFrameHostImpl* fenced_rfh =
+ static_cast<RenderFrameHostImpl*>(fenced_rfh_generic);
+
+ RenderProcessHostBadIpcMessageWaiter kill_waiter(fenced_rfh->GetProcess());
+
+ mojo::Receiver<blink::mojom::BrowserInterfaceBroker>& broker_receiver =
+ fenced_rfh->browser_interface_broker_receiver_for_testing();
+ blink::mojom::BrowserInterfaceBroker* broker =
+ broker_receiver.internal_state()->impl();
+
+ mojo::Remote<device::mojom::BatteryMonitor> remote;
+ broker->GetInterface(remote.BindNewPipeAndPassReceiver());
+
+ EXPECT_EQ(bad_message::BIBI_BIND_BATTERY_MONITOR_FOR_FENCED_FRAME,
+ kill_waiter.Wait());
+}
+
namespace {
// Interceptor that replaces the origin in the DidCommitProvisionalLoadParams
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