CVE-2026-13897
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchromecast/browser/cast_web_view_default.cc |
modified |
Files Changed
chromecast/browser/DEPSchromecast/browser/cast_web_view_default.cc
Patch
From a1352ac52c0f83e63c829cf3d4174d6ea1191812 Mon Sep 17 00:00:00 2001
From: Simeon Anfinrud <sanfin@chromium.org>
Date: Mon, 11 May 2026 20:24:53 -0700
Subject: [PATCH] [chromecast] Fix Potential media access permission bypass via cross-origin navigation in CastWebViewDefault
Modifies CastWebViewDefault::RequestMediaAccessPermission and CheckMediaAccessPermission
to validate the security_origin against the PermissionController. This prevents
cross-origin navigations from inheriting media permissions granted to the container.
Bug: 501877896
Test: Compiled and passed unit tests.
Change-Id: I468a4a8afe23684c70c322c432a36286dea20281
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7765508
Auto-Submit: Simeon Anfinrud <sanfin@chromium.org>
Reviewed-by: Sandeep Vijayasekar <sandv@google.com>
Commit-Queue: Simeon Anfinrud <sanfin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1629025}
---
diff --git a/chromecast/browser/DEPS b/chromecast/browser/DEPS
index c3acf0ef..1a3dabf 100644
--- a/chromecast/browser/DEPS
+++ b/chromecast/browser/DEPS
@@ -76,6 +76,7 @@
"+third_party/blink/public/common",
"+third_party/blink/public/mojom/autoplay",
"+third_party/blink/public/mojom/loader/resource_load_info.mojom.h",
+ "+third_party/blink/public/mojom/permissions/permission.mojom.h",
"+third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h",
"+third_party/blink/public/mojom/mediastream/media_stream.mojom.h",
"+third_party/blink/public/mojom/messaging",
diff --git a/chromecast/browser/cast_web_view_default.cc b/chromecast/browser/cast_web_view_default.cc
index e3cdc0da..641976a 100644
--- a/chromecast/browser/cast_web_view_default.cc
+++ b/chromecast/browser/cast_web_view_default.cc
@@ -20,15 +20,19 @@
#include "chromecast/browser/renderer_prelauncher.h"
#include "chromecast/chromecast_buildflags.h"
#include "chromecast/graphics/cast_screen.h"
+#include "content/public/browser/browser_context.h"
#include "content/public/browser/media_capture_devices.h"
#include "content/public/browser/media_session.h"
+#include "content/public/browser/permission_controller.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/site_instance.h"
#include "net/base/net_errors.h"
+#include "third_party/blink/public/common/permissions/permission_utils.h"
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
+#include "third_party/blink/public/mojom/permissions/permission.mojom.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "url/gurl.h"
@@ -199,7 +203,21 @@
LOG(WARNING) << __func__ << ": media access is disabled.";
return false;
}
- return true;
+ if (!render_frame_host) {
+ return false;
+ }
+
+ auto permission_descriptor = blink::mojom::PermissionDescriptor::New();
+ permission_descriptor->name =
+ type == blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE
+ ? blink::mojom::PermissionName::AUDIO_CAPTURE
+ : blink::mojom::PermissionName::VIDEO_CAPTURE;
+
+ content::PermissionController* permission_controller =
+ web_contents_->GetBrowserContext()->GetPermissionController();
+ return permission_controller->GetPermissionStatusForCurrentDocument(
+ permission_descriptor, render_frame_host) ==
+ blink::mojom::PermissionStatus::GRANTED;
}
bool CastWebViewDefault::DidAddMessageToConsole(
@@ -238,9 +256,22 @@
content::WebContents* web_contents,
const content::MediaStreamRequest& request,
content::MediaResponseCallback callback) {
- if (!chromecast::IsFeatureEnabled(kAllowUserMediaAccess) &&
- !params_->allow_media_access) {
- LOG(WARNING) << __func__ << ": media access is disabled.";
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
+ request.render_process_id, request.render_frame_id);
+ bool audio_allowed =
+ request.audio_type ==
+ blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE &&
+ CheckMediaAccessPermission(rfh,
+ url::Origin::Create(request.security_origin),
+ request.audio_type);
+ bool video_allowed =
+ request.video_type ==
+ blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE &&
+ CheckMediaAccessPermission(rfh,
+ url::Origin::Create(request.security_origin),
+ request.video_type);
+ if (!audio_allowed && !video_allowed) {
+ LOG(WARNING) << __func__ << ": media access is denied.";
std::move(callback).Run(
blink::mojom::StreamDevicesSet(),
blink::mojom::MediaStreamRequestResult::NOT_SUPPORTED,
@@ -259,8 +290,7 @@
stream_devices_set.stream_devices.emplace_back(
blink::mojom::StreamDevices::New());
blink::mojom::StreamDevices& devices = *stream_devices_set.stream_devices[0];
- if (request.audio_type ==
- blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE) {
+ if (audio_allowed) {
const blink::MediaStreamDevice* device = GetRequestedDeviceOrDefault(
audio_devices, request.requested_audio_device_ids);
if (device) {
@@ -270,8 +300,7 @@
}
}
- if (request.video_type ==
- blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) {
+ if (video_allowed) {
const blink::MediaStreamDevice* device = GetRequestedDeviceOrDefault(
video_devices, request.requested_video_device_ids);
if (device) {
Original Bug Report
Potential media access permission bypass via cross-origin navigation in CastWebViewDefault
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: CastWebViewDefault grants media access permissions (microphone/camera) based solely on a per-WebView flag, ignoring the requesting frame’s security origin. Because cross-origin top-level navigations within a Cast application reuse the same WebContents container, an attacker can navigate the container to their origin and inherit these media permissions. This bypasses the origin-aware allowlist in CastPermissionManager and could allow silent audio and video capture.
Affected files:
chromecast/browser/cast_web_view_default.ccchromecast/browser/cast_permission_manager.ccchromecast/cast_core/runtime/browser/runtime_application_service_impl.ccchromecast/browser/mojom/cast_web_service.mojomchromecast/browser/cast_content_browser_client.ccchromecast/browser/cast_web_contents_impl.cc
Estimated timestamp from git blame: 2022-06-09
Summary
In the Chromecast browser implementation, the CastWebViewDefault class fails to validate the requesting security origin when granting media access permissions. Permissions for DEVICE_AUDIO_CAPTURE (microphone) and DEVICE_VIDEO_CAPTURE (camera) are granted if the WebView-wide allow_media_access flag is set, regardless of the origin of the frame making the request. This allows cross-origin navigations within an authorized container to inherit powerful permissions.
Vulnerability Details
When a web page calls navigator.mediaDevices.getUserMedia(), the request is sent over IPC to the browser’s MediaStreamManager. To authorize the initial request, MediaStreamUIProxy calls RequestMediaAccessPermission directly on the WebContentsDelegate (bypassing the central PermissionController).
In the Cast runtime, the WebContentsDelegate is implemented by CastWebViewDefault. In chromecast/browser/cast_web_view_default.cc, the RequestMediaAccessPermission and CheckMediaAccessPermission methods determine whether to grant access using only the params_->allow_media_access member or a global feature flag. The security_origin parameter provided to these methods is completely ignored.
This container-bound permission model becomes a vulnerability during top-level navigations. If a Cast application requests a new URL (e.g., via window.top.location), CastWebViewDefault::OpenURLFromTab explicitly loads the new URL into the existing WebContents to prevent opening multiple windows. Because the container is reused, the allow_media_access flag remains true.
Consequently, the origin-based allowlist implemented in CastPermissionManager::IsRequestingOriginAllowed is never consulted for the initial media grant. An attacker who can trigger a navigation within a media-enabled app can silently access the microphone and camera without user interaction.
Potential Steps to Trigger
Note: These are potential steps derived from static code analysis, as our tooling agent does not currently have the ability to run code or execute a live proof-of-concept.
- A user launches a legitimate Cast application that is configured to require media access (setting
allow_media_access = trueon theCastWebView). - The attacker exploits an open redirect or XSS vulnerability within the legitimate application, executing JavaScript that causes the top-level frame to navigate to
https://attacker.com/malicious.html. - The navigation request flows to
CastWebViewDefault::OpenURLFromTab, which callsLoadURLon the existingWebContents. - The attacker’s page loads and executes
navigator.mediaDevices.getUserMedia({ audio: true, video: true }). - The request arrives at
CastWebViewDefault::RequestMediaAccessPermissionin the browser process. - The method checks
params_->allow_media_access, evaluates it astrue(since it is tied to the container, not the origin), and immediately grants the request. - The attacker receives a live audio and video stream from the Chromecast device.
Suggested Fix
Modify CastWebViewDefault::RequestMediaAccessPermission and CheckMediaAccessPermission to validate the security_origin against the application’s base URL, similar to the logic found in CastPermissionManager::IsRequestingOriginAllowed. Alternatively, route the initial media access permission check through the PermissionController so that the robust origin and feature enforcement logic in CastPermissionManager is properly applied to getUserMedia requests.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.