High watch CVSS 5.3 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
5.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in GPU
DescriptionInsufficient validation of untrusted input in GPU
ComponentGPU
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.
Tracker496415073
Fix commit57596dc3c60c (chromium/src) +5/-0
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:N/I:N/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-03-26 (per vendor advisory)
Fix landed2026-03-28 (public git)
Disclosed2026-05-12
Patch gap45 days — fix public in source before the advisory

Background

`EmbeddedFrameSinkProviderImpl`
The browser-process implementation of a Mojo interface that a renderer calls to create/register compositor frame-sink objects on its behalf.
`FrameSinkBundleId` / `client_id()`
An identifier for a bundle of frame sinks whose client_id() component is meant to name the owning renderer; renderer_client_id_ is the trusted client id the browser assigned to this receiver.
`CreateFrameSinkBundle`
The mojo-exposed method (the enclosing function of the added check) that forwards the bundle registration to host_frame_sink_manager_->CreateFrameSinkBundle.
`ReportBadMessage`
The Mojo mechanism to reject a malformed/illegitimate IPC and terminate the misbehaving renderer.

Root Cause Analysis

The browser-process CreateFrameSinkBundle entry point accepted a FrameSinkBundleId straight from the renderer and forwarded it to host_frame_sink_manager_->CreateFrameSinkBundle without checking that the bundle’s client_id() matched the receiver’s own renderer_client_id_. Because the client_id() associates the bundle with a particular renderer process, an unvalidated value let one renderer register a bundle under another renderer’s client id, violating the invariant that a renderer may only operate on frame-sink objects belonging to itself.

The patch adds an early check: if bundle_id.client_id() != renderer_client_id_, it calls receivers_.ReportBadMessage("Invalid client ID") and returns before the forward, so a mismatched registration is rejected and the renderer terminated. This tightens validation at the privilege boundary where untrusted IPC input crosses into the more-trusted browser process.

Key insight
The browser trusted the renderer-supplied client_id() inside the bundle id instead of binding it to the receiver’s known renderer_client_id_; the fix adds an equality check that rejects any bundle id whose client id does not match the calling renderer.

Attack Path

  1. Compromised/malicious renderer An attacker controls a renderer process and holds the Mojo remote for EmbeddedFrameSinkProviderImpl.
  2. Forge a bundle id The renderer constructs a FrameSinkBundleId whose client_id() names a different renderer rather than its own.
  3. Call the unvalidated entry point It invokes CreateFrameSinkBundle with the forged id; pre-patch the browser forwarded it to host_frame_sink_manager_->CreateFrameSinkBundle without checking the client id.
  4. Cross-renderer frame-sink registration The browser registers/associates a frame-sink bundle under another renderer’s identity, an insufficient-input-validation flaw at the renderer→browser boundary; concrete downstream corruption or cross-origin effects are (inferred) and not shown in the diff.

Impact Assessment

A malicious or compromised renderer could register a frame-sink bundle under a client id it does not own, confusing ownership of compositor objects in the browser process; the advisory describes this as insufficient validation of untrusted input in GPU. The vendor advisory rates it high severity. Any escalation beyond the unauthorized registration (e.g., cross-process memory corruption or a sandbox-boundary compromise) is (inferred) and not demonstrated by the patch, which only adds the identity check.

Files Changed

  • content/browser/renderer_host/embedded_frame_sink_provider_impl.cc

Audit Directions

  • Renderer-supplied ids vs. receiver identity
    Audit every browser-side Mojo method that takes a FrameSinkId/FrameSinkBundleId (or similar id embedding a client_id) and confirm the embedded client id is compared against the connection’s trusted renderer_client_id_.
  • Forward-without-validation entry points
    Review *ProviderImpl/*HostImpl methods that forward a struct from IPC directly into a manager (host_frame_sink_manager_ and peers), flagging any that lack a ReportBadMessage guard on identity fields.
  • Consistency of validation across siblings
    Where one entry point validates a client id, check sibling methods on the same interface that create or look up resources for the same omission.
From 57596dc3c60c5d648592a1cefba086d674f71444 Mon Sep 17 00:00:00 2001
From: kylechar <kylechar@chromium.org>
Date: Fri, 27 Mar 2026 17:51:46 -0700
Subject: [PATCH] Validate client_id when registering bundle

This ensures the bundle is associated with the right renderer process.

Fixed: 496415073
Change-Id: I6a766373ee1e64a8d7c83e55b4ab9a4d90073f02
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7706850
Commit-Queue: Kyle Charbonneau <kylechar@chromium.org>
Reviewed-by: Jonathan Ross <jonross@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1606581}
---

diff --git a/content/browser/renderer_host/embedded_frame_sink_provider_impl.cc b/content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
index aa2c5aa..213a44cf 100644
--- a/content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
+++ b/content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
@@ -77,6 +77,11 @@
     const viz::FrameSinkBundleId& bundle_id,
     mojo::PendingReceiver<viz::mojom::FrameSinkBundle> receiver,
     mojo::PendingRemote<viz::mojom::FrameSinkBundleClient> client) {
+  if (bundle_id.client_id() != renderer_client_id_) {
+    receivers_.ReportBadMessage("Invalid client ID");
+    return;
+  }
+
   host_frame_sink_manager_->CreateFrameSinkBundle(
       bundle_id, std::move(receiver), std::move(client));
 }
Loading diff…

Original Bug Report

reported by vm...@google.com

Missing client_id validation in EmbeddedFrameSinkProviderImpl enables cross-process DoS and UI freezing

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

Overview: The browser process fails to validate the client_id within a FrameSinkBundleId when a renderer registers a new frame sink bundle. A compromised renderer can exploit this to create a bundle associated with a victim renderer’s ID, allowing it to freeze the victim’s rendering or intentionally crash its composition pipeline (Denial of Service).

Affected files:

  • content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
  • components/viz/service/frame_sinks/frame_sink_bundle_impl.cc
  • components/viz/service/frame_sinks/frame_sink_manager_impl.cc
  • components/viz/service/frame_sinks/compositor_frame_sink_impl.cc

Estimated timestamp from git blame: 2022-11-08

Vulnerability

The content::EmbeddedFrameSinkProviderImpl class handles requests from renderers to create and manage CompositorFrameSinks. To ensure a renderer can only manage its own sinks, most methods in this class (e.g., RegisterEmbeddedFrameSink, CreateCompositorFrameSink) explicitly verify that the client_id component of any provided FrameSinkId matches the renderer_client_id_ assigned to that specific provider instance.

However, the RegisterEmbeddedFrameSinkBundle method completely omits this authorization check for the provided viz::FrameSinkBundleId:

// content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
void EmbeddedFrameSinkProviderImpl::RegisterEmbeddedFrameSinkBundle(
    const viz::FrameSinkBundleId& bundle_id,
    mojo::PendingReceiver<viz::mojom::FrameSinkBundle> receiver,
    mojo::PendingRemote<viz::mojom::FrameSinkBundleClient> client) {
  // MISSING: Validation that bundle_id.client_id() == renderer_client_id_
  host_frame_sink_manager_->CreateFrameSinkBundle(
      bundle_id, std::move(receiver), std::move(client));
}

A similar omission exists in CreateBundledCompositorFrameSink, which checks the frame_sink_id but ignores the bundle_id.

Because of this missing validation, the browser process blindly forwards the attacker-controlled bundle_id to the GPU process. The GPU process (viz::FrameSinkManagerImpl) creates a FrameSinkBundleImpl using this spoofed ID and binds the attacker’s Mojo receiver to it.

Potential Exploitation Steps

A compromised renderer (Attacker) could potentially execute the following steps to interfere with a cross-origin renderer (Victim):

  1. Identify the Victim’s IDs: The Attacker determines the Victim’s client_id (a sequential RenderProcessHost ID starting from small integers) and sink_id (a sequential integer generated by RendererBlinkPlatformImpl, starting at 0x80000000). Both are easily predictable.
  2. Register a Spoofed Bundle: The Attacker calls RegisterEmbeddedFrameSinkBundle on their EmbeddedFrameSinkProvider Mojo interface. They provide a FrameSinkBundleId where the client_id is set to the Victim’s ID, and the bundle_id is an arbitrary unused integer.
  3. Gain Control: The GPU process creates the bundle. The Attacker now holds a viz::mojom::FrameSinkBundle remote that the GPU process believes belongs to the Victim.
  4. Execute the Attack: The Attacker can now call methods on this bundle remote, passing the Victim’s sink_id. The GPU process’s FrameSinkBundleImpl::GetFrameSink combines the spoofed client_id with the provided sink_id and successfully resolves the Victim’s legitimate CompositorFrameSinkImpl, without checking if that sink was ever explicitly added to this specific bundle.

From here, the Attacker has two primary reliable ways to impact the Victim:

  • Silent Rendering Freeze: The Attacker calls SetNeedsBeginFrame(false, victim_sink_id). This method requires no unguessable tokens. It immediately stops the GPU process from sending BeginFrame signals to the Victim’s sink, permanently freezing its visual output (e.g., freezing a cross-origin <video> or iframe).
  • Cross-Process Denial of Service (DoS): The Attacker calls Submit() with a fake/empty LocalSurfaceId. While they cannot inject a valid frame (because the LocalSurfaceId contains a 128-bit UnguessableToken they cannot guess), providing an invalid one causes CompositorFrameSinkSupport::MaybeSubmitCompositorFrame to fail validation. This failure triggers CompositorFrameSinkImpl to call ResetWithReason on its Mojo receiver, forcefully terminating the Victim renderer’s connection to that frame sink.

Suggested Fix

Add explicit validation to EmbeddedFrameSinkProviderImpl to ensure the bundle_id.client_id() matches the authorized renderer_client_id_.

void EmbeddedFrameSinkProviderImpl::RegisterEmbeddedFrameSinkBundle(
    const viz::FrameSinkBundleId& bundle_id,
    // ...
) {
  if (bundle_id.client_id() != renderer_client_id_) {
    receivers_.ReportBadMessage("Invalid client ID");
    return;
  }
  // ...
}

A similar check should be added to CreateBundledCompositorFrameSink.

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