Overview

High
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in GPU
DescriptionInsufficient validation of untrusted input in GPU
ComponentGPU
Bug ClassLogic Error
Tracker496415073
Fix commit57596dc3c60c (chromium/src) +5/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-12

Files Changed

  • content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
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