Low watch CVSS 7.5 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
7.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Cast
DescriptionInsufficient validation of untrusted input in Cast
ComponentCast
Sub-componentMedia Router
Bug ClassInput Validation
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker496399759
Fix commit51be7f568ef9 (chromium/src) +39/-0
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-26 (per vendor advisory)
Fix landed2026-03-31 (public git)
Disclosed2026-05-05
Patch gap35 days — fix public in source before the advisory

Background

Presentation API
a web API, backed by Media Router, that lets a page start or reconnect to a “presentation” (e.g. casting) identified by a presentation URL and a presentation ID.
`ReconnectPresentation`
the ControllerPresentationServiceDelegateImpl entry point that rejoins an existing presentation, invoked on behalf of a renderer.
`IsValidPresentationUrl`
the Media Router helper that validates a presentation URL’s scheme, already used by the StartPresentation path.
Media-source URN
a urn:x-org.chromium.media:source:... identifier that can name capture sources such as desktop:screen:0:0; a presentation ID of auto-join lets a request attach to a session without the picker UI.

Root Cause Analysis

ControllerPresentationServiceDelegateImpl::ReconnectPresentation consumed the renderer-supplied presentation_urls without validating their scheme, unlike the sibling StartPresentation path which already called IsValidPresentationUrl. A compromised renderer could therefore pass a media-source URN naming a desktop/screen capture source and, combined with the auto-join presentation ID, drive the reconnect flow into a capture session instead of a legitimate presentation — violating the invariant that only supported presentation URL schemes reach the routing logic.

The fix adds if (!std::ranges::all_of(presentation_urls, IsValidPresentationUrl)), which runs the error callback with PresentationError(PresentationErrorType::NO_PRESENTATION_FOUND, "Invalid presentation URL.") and returns before any local_presentation_manager lookup or route join. The shipped regression test ReconnectPresentationWithInvalidUrl confirms that for the URN urn:x-org.chromium.media:source:desktop:screen:0:0 with ID auto-join, JoinRouteInternal is never called and the error callback fires. This is a tightening that brings ReconnectPresentation to parity with StartPresentation.

Key insight
ReconnectPresentation trusted renderer-supplied URLs that StartPresentation already validated; the fix adds the same IsValidPresentationUrl scheme check so malicious capture-source URNs are rejected before routing.

Attack Path

  1. Compromise a renderer the attacker controls a renderer with an active tab-mirroring (presentation) session.
  2. Craft a malicious URN build a presentation request whose presentation_urls contains urn:x-org.chromium.media:source:desktop:screen:0:0, a capture source rather than a valid presentation URL.
  3. Call `ReconnectPresentation` with `auto-join` use the auto-join presentation ID so the reconnect attaches without surfacing the desktop picker UI.
  4. Bypass the picker and escalate capture pre-patch the unvalidated URN reached the routing/join path, which the advisory describes as escalating a tab-mirroring session into full-screen/desktop capture without user approval; the post-patch guard returns NO_PRESENTATION_FOUND first, as the regression test verifies.

Impact Assessment

A compromised renderer could escalate an existing tab-mirroring session into unauthorized desktop or cross-tab screen capture by injecting a capture-source URN into ReconnectPresentation, bypassing the desktop picker; the precondition is an already-compromised renderer and (per the advisory) an active mirroring session. The advisory rates this low severity and classifies it as insufficient validation of untrusted input in Cast. No in-the-wild exploitation is indicated, and the capture escalation is grounded in the advisory and the regression test rather than inferred.

Files Changed

  • components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc
  • components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc

Audit Directions

  • Validation parity across sibling entry points
    when one API path (StartPresentation) validates input, audit every other path that consumes the same parameter (ReconnectPresentation, join/close variants) for the identical check.
  • URN/scheme allowlisting before routing
    search Media Router and other IPC-reachable surfaces for places that accept renderer-supplied URLs/URNs and route them without an explicit scheme allowlist, especially where capture-source URNs are meaningful.
  • `auto-join` / picker-bypass flows
    review code paths reachable with the auto-join presentation ID, which skip user-facing pickers and therefore depend entirely on upstream input validation.
From 51be7f568ef9213f56b17fe0855fc735f677f272 Mon Sep 17 00:00:00 2001
From: mark a. foltz <mfoltz@chromium.org>
Date: Tue, 31 Mar 2026 14:44:07 -0700
Subject: [PATCH] [Media Router] Add URL validation to ReconnectPresentation.

This change adds a call to IsValidPresentationUrl in
ControllerPresentationServiceDelegateImpl::ReconnectPresentation to
ensure that only supported URL schemes are processed.

Previously, ReconnectPresentation lacked validation for the
presentation_urls parameter, allowing a compromised renderer to initiate
unauthorized desktop or cross-tab capture by sending a malicious URN
(e.g., 'urn:x-org.chromium.media:source:desktop:screen:0:0'). By
injecting such a URN with a presentation ID of 'auto-join', an attacker
could bypass the desktop picker UI and escalate an active tab-mirroring
session into a full-screen capture session.

This fix mirrors the validation logic already present in
StartPresentation.

AI disclosure: Prepared with gemini_cli

Fixed: 496399759
Change-Id: I5be5a81b35a0d1fd41de33adecac70bb0e7545c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7718183
Reviewed-by: Muyao Xu <muyaoxu@google.com>
Commit-Queue: Mark Foltz <mfoltz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1608112}
---

diff --git a/components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc b/components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc
index 3cf3b468..7107be1 100644
--- a/components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc
+++ b/components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc
@@ -510,6 +510,13 @@
     return;
   }
 
+  if (!std::ranges::all_of(presentation_urls, IsValidPresentationUrl)) {
+    std::move(error_cb).Run(
+        PresentationError(PresentationErrorType::NO_PRESENTATION_FOUND,
+                          "Invalid presentation URL."));
+    return;
+  }
+
   auto* local_presentation_manager =
       LocalPresentationManagerFactory::GetOrCreateForWebContents(
           &GetWebContents());
diff --git a/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc b/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc
index 866b0d45..bf93ee2e 100644
--- a/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc
+++ b/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc
@@ -805,6 +805,38 @@
           &MockCreatePresentationConnectionCallbacks::OnCreateConnectionError,
           base::Unretained(&mock_create_connection_callbacks)));
 }
+
+TEST_F(ControllerPresentationServiceDelegateImplTest,
+       ReconnectPresentationWithInvalidUrl) {
+  content::WebContentsTester::For(GetWebContents())
+      ->NavigateAndCommit(GURL(kFrameUrl));
+
+  MockCreatePresentationConnectionCallbacks mock_create_connection_callbacks;
+  const std::string kPresentationId("auto-join");
+
+  // A URN that should be blocked.
+  const GURL invalid_url("urn:x-org.chromium.media:source:desktop:screen:0:0");
+  content::PresentationRequest invalid_request(
+      {main_frame_process_id_, main_frame_routing_id_}, {invalid_url},
+      frame_origin_);
+
+  // JoinRouteInternal should NOT be called.
+  EXPECT_CALL(*router_, JoinRouteInternal(_, _, _, _, _, _)).Times(0);
+
+  // Error callback should be called.
+  EXPECT_CALL(mock_create_connection_callbacks, OnCreateConnectionError(_))
+      .Times(1);
+
+  delegate_impl_->ReconnectPresentation(
+      invalid_request, kPresentationId,
+      base::BindOnce(
+          &MockCreatePresentationConnectionCallbacks::OnCreateConnectionSuccess,
+          base::Unretained(&mock_create_connection_callbacks)),
+      base::BindOnce(
+          &MockCreatePresentationConnectionCallbacks::OnCreateConnectionError,
+          base::Unretained(&mock_create_connection_callbacks)));
+}
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace media_router
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc b/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc
index 866b0d45..bf93ee2e 100644
--- a/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc
+++ b/components/media_router/browser/presentation/controller_presentation_service_delegate_impl_unittest.cc
@@ -805,6 +805,38 @@
           &MockCreatePresentationConnectionCallbacks::OnCreateConnectionError,
           base::Unretained(&mock_create_connection_callbacks)));
 }
+
+TEST_F(ControllerPresentationServiceDelegateImplTest,
+       ReconnectPresentationWithInvalidUrl) {
+  content::WebContentsTester::For(GetWebContents())
+      ->NavigateAndCommit(GURL(kFrameUrl));
+
+  MockCreatePresentationConnectionCallbacks mock_create_connection_callbacks;
+  const std::string kPresentationId("auto-join");
+
+  // A URN that should be blocked.
+  const GURL invalid_url("urn:x-org.chromium.media:source:desktop:screen:0:0");
+  content::PresentationRequest invalid_request(
+      {main_frame_process_id_, main_frame_routing_id_}, {invalid_url},
+      frame_origin_);
+
+  // JoinRouteInternal should NOT be called.
+  EXPECT_CALL(*router_, JoinRouteInternal(_, _, _, _, _, _)).Times(0);
+
+  // Error callback should be called.
+  EXPECT_CALL(mock_create_connection_callbacks, OnCreateConnectionError(_))
+      .Times(1);
+
+  delegate_impl_->ReconnectPresentation(
+      invalid_request, kPresentationId,
+      base::BindOnce(
+          &MockCreatePresentationConnectionCallbacks::OnCreateConnectionSuccess,
+          base::Unretained(&mock_create_connection_callbacks)),
+      base::BindOnce(
+          &MockCreatePresentationConnectionCallbacks::OnCreateConnectionError,
+          base::Unretained(&mock_create_connection_callbacks)));
+}
+
 #endif  // !BUILDFLAG(IS_ANDROID)
 
 }  // namespace media_router
Loading diff…

Original Bug Report

reported by vm...@google.com

Unauthorized desktop capture via missing URL validation in ReconnectPresentation

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A compromised renderer can initiate unauthorized desktop or cross-tab capture by sending a malicious URN to ControllerPresentationServiceDelegateImpl::ReconnectPresentation. Because this method lacks URL validation, it allows the attacker to silently escalate an active tab-mirroring session into a full-screen capture session. This bypasses the user-consent UI (desktop_picker_->Show()) normally required for such capture.

Affected files:

  • components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc
  • content/browser/presentation/presentation_service_impl.cc
  • chrome/browser/media/router/mojo/media_router_desktop.cc
  • components/media_router/common/media_source.cc
  • chrome/browser/media/router/providers/cast/cast_activity_manager.cc

Estimated timestamp from git blame: 2024-11-06

Description

A potential vulnerability exists in the implementation of ReconnectPresentation that allows a compromised renderer to initiate unauthorized desktop or cross-tab capture without user consent via the desktop picker UI.

The issue resides in ControllerPresentationServiceDelegateImpl::ReconnectPresentation (in components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc). Unlike StartPresentation, which correctly calls IsValidPresentationUrl to enforce protocol restrictions (e.g., blocking urn: schemes), ReconnectPresentation fails to validate the presentation_urls parameter.

Because GURL accepts urn: as a valid scheme and MediaSource stores the URL spec directly as its ID, an attacker can inject a malicious URN such as urn:x-org.chromium.media:source:desktop:screen:0:0 (for full-screen capture) or urn:x-org.chromium.media:source:desktop:web-contents-media-stream://<pid>:<fid> (for cross-tab capture).

When a ReconnectPresentation request is sent with presentation_id="auto-join" and a malicious URN, MediaRouterDesktop::JoinRoute processes it. If the user is already mirroring a tab to a Cast device, the CAST provider reuses the existing sink and launches a new mirroring activity via CastActivityManager::JoinSession. The MirroringActivity then extracts the attacker-controlled DesktopStreamId from the URN and passes it to the capture service.

Crucially, this JoinRoute path bypasses the desktop_picker_->Show() call, which is the normal user-consent gate for desktop capture and is only present in the CreateRoute path.

Potential Impact

This represents a privilege escalation from tab-mirroring to full-screen or cross-tab capture. While the captured stream is sent to the local Cast sink (e.g., a TV), it constitutes an unauthorized capture of sensitive information from the user’s desktop or other open tabs without their knowledge or the required picker UI prompt.

Preconditions

  1. The attacker must compromise a renderer process.
  2. The user must be actively Cast-mirroring a tab from that compromised renderer.

Suggested Reproduction Steps (Conceptual)

Note: These are potential steps as the AI agent cannot execute code to verify a working PoC.

  1. The user visits an attacker-controlled page and initiates Cast tab-mirroring to a local device.
  2. The attacker exploits a separate bug (e.g., in V8) to compromise the renderer process.
  3. The compromised renderer constructs a malicious presentation URL for full-screen capture: urn:x-org.chromium.media:source:desktop:screen:0:0.
  4. The renderer bypasses Blink’s JavaScript validation and directly sends a blink.mojom.PresentationService.ReconnectPresentation Mojo IPC message to the Browser process. The message uses the malicious URN and sets presentation_id="auto-join".
  5. In the Browser process, ControllerPresentationServiceDelegateImpl::ReconnectPresentation receives the request. Lacking validation, it passes the URN to MediaRouterDesktop::JoinRoute.
  6. The request routes to CastActivityManager::JoinSession. Because presentation_id is auto-join and the default action policy for the newly created desktop source is kCreateSession, it looks up the active sink for the tab.
  7. CastActivityManager seamlessly terminates the tab-mirroring session and calls LaunchSession to start a new full-screen capture session to the same sink using the malicious URN.
  8. MirroringActivity parses the stream ID (screen:0:0), initializes a DesktopCaptureDevice, and begins streaming the user’s entire screen without ever showing the desktop picker UI.

Suggested Fix

Add a call to IsValidPresentationUrl() within ControllerPresentationServiceDelegateImpl::ReconnectPresentation (in components/media_router/browser/presentation/controller_presentation_service_delegate_impl.cc) to validate the provided presentation_urls before processing them, mirroring the logic used in StartPresentation:

  if (presentation_urls.empty()) {
    // ... error handling ...
    return;
  }
  if (!std::ranges::all_of(presentation_urls, IsValidPresentationUrl)) {
    std::move(error_cb).Run(
        PresentationError(PresentationErrorType::NO_PRESENTATION_FOUND,
                          "Invalid presentation URL."));
    return;
  }

Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8


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. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker