CVE-2026-8538
Overview
Files Changed
content/browser/renderer_host/embedded_frame_sink_provider_impl.cc
Patch
From 57596dc3c60c5d648592a1cefba086d674f71444 Mon Sep 17 00:00:00 2001 From: kylechar <[email protected]> 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 <[email protected]> Reviewed-by: Jonathan Ross <[email protected]> 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)); }
Original Bug Report
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.cccomponents/viz/service/frame_sinks/frame_sink_bundle_impl.cccomponents/viz/service/frame_sinks/frame_sink_manager_impl.cccomponents/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):
- Identify the Victim’s IDs: The Attacker determines the Victim’s
client_id(a sequentialRenderProcessHostID starting from small integers) andsink_id(a sequential integer generated byRendererBlinkPlatformImpl, starting at0x80000000). Both are easily predictable. - Register a Spoofed Bundle: The Attacker calls
RegisterEmbeddedFrameSinkBundleon theirEmbeddedFrameSinkProviderMojo interface. They provide aFrameSinkBundleIdwhere theclient_idis set to the Victim’s ID, and thebundle_idis an arbitrary unused integer. - Gain Control: The GPU process creates the bundle. The Attacker now holds a
viz::mojom::FrameSinkBundleremote that the GPU process believes belongs to the Victim. - Execute the Attack: The Attacker can now call methods on this bundle remote, passing the Victim’s
sink_id. The GPU process’sFrameSinkBundleImpl::GetFrameSinkcombines the spoofedclient_idwith the providedsink_idand successfully resolves the Victim’s legitimateCompositorFrameSinkImpl, 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 sendingBeginFramesignals 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/emptyLocalSurfaceId. While they cannot inject a valid frame (because theLocalSurfaceIdcontains a 128-bitUnguessableTokenthey cannot guess), providing an invalid one causesCompositorFrameSinkSupport::MaybeSubmitCompositorFrameto fail validation. This failure triggersCompositorFrameSinkImplto callResetWithReasonon 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.