CVE-2026-11242
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/plugins/chrome_content_browser_client_plugins_part.cc |
modified | |
ifchrome/browser/plugins/plugin_info_host_impl.cc |
modified | |
Contextchrome/browser/plugins/plugin_info_host_impl.h |
modified |
Files Changed
chrome/browser/plugins/chrome_content_browser_client_plugins_part.ccchrome/browser/plugins/plugin_info_host_impl.ccchrome/browser/plugins/plugin_info_host_impl.h
Patch
From bad2242d8208cd32317008d8c39ae6acaf4b1e90 Mon Sep 17 00:00:00 2001
From: Lei Zhang <thestig@chromium.org>
Date: Wed, 08 Apr 2026 09:11:02 -0700
Subject: [PATCH] Remove deprecated int RPH ID usage in PluginInfoHostImpl
Switch to content::GlobalRenderFrameHostToken in PluginInfoHostImpl and
its callers. This modernizes the way the RFH/RPH is being identified.
The switch to RFH is intentional, as it sets up PluginInfoHostImpl to
verify the frame origin information sent from the renderer in an
upcoming CL.
Bug: 379869738,497385823
Change-Id: Id5df22d12b9cbf01a9714b59cbc6d223d0ad133a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737857
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1611567}
---
diff --git a/chrome/browser/plugins/chrome_content_browser_client_plugins_part.cc b/chrome/browser/plugins/chrome_content_browser_client_plugins_part.cc
index 042ed302..753d85a 100644
--- a/chrome/browser/plugins/chrome_content_browser_client_plugins_part.cc
+++ b/chrome/browser/plugins/chrome_content_browser_client_plugins_part.cc
@@ -12,7 +12,6 @@
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
#include "extensions/buildflags/buildflags.h"
#include "mojo/public/cpp/bindings/pending_associated_receiver.h"
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
@@ -23,17 +22,17 @@
namespace {
void BindPluginInfoHost(
- int render_process_id,
+ content::GlobalRenderFrameHostToken rfh_token,
mojo::PendingAssociatedReceiver<chrome::mojom::PluginInfoHost> receiver) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
- content::RenderProcessHost* host =
- content::RenderProcessHost::FromID(render_process_id);
- if (!host)
+ auto* rfh = content::RenderFrameHost::FromFrameToken(rfh_token);
+ if (!rfh) {
return;
+ }
- Profile* profile = Profile::FromBrowserContext(host->GetBrowserContext());
+ Profile* profile = Profile::FromBrowserContext(rfh->GetBrowserContext());
mojo::MakeSelfOwnedAssociatedReceiver(
- std::make_unique<PluginInfoHostImpl>(render_process_id, profile),
+ std::make_unique<PluginInfoHostImpl>(rfh_token, profile),
std::move(receiver));
}
@@ -51,7 +50,7 @@
blink::AssociatedInterfaceRegistry& associated_registry) {
associated_registry.AddInterface<chrome::mojom::PluginInfoHost>(
base::BindRepeating(&BindPluginInfoHost,
- render_frame_host.GetProcess()->GetDeprecatedID()));
+ render_frame_host.GetGlobalFrameToken()));
}
} // namespace plugins
diff --git a/chrome/browser/plugins/plugin_info_host_impl.cc b/chrome/browser/plugins/plugin_info_host_impl.cc
index 8050dd5..c859c57 100644
--- a/chrome/browser/plugins/plugin_info_host_impl.cc
+++ b/chrome/browser/plugins/plugin_info_host_impl.cc
@@ -16,7 +16,6 @@
#include "base/no_destructor.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
-#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/plugins/chrome_plugin_service_filter.h"
#include "chrome/browser/plugins/plugin_metadata.h"
@@ -35,7 +34,6 @@
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/plugin_service_filter.h"
#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_constants.h"
#include "extensions/buildflags/buildflags.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
@@ -126,18 +124,18 @@
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
-// Returns whether a request from a plugin to load |resource| from a renderer
-// with process id |process_id| is a request for an internal resource by an app
-// listed in |accessible_resources| in its manifest.
+// Returns whether a request from a plugin to load `resource` from a renderer
+// with `rfh_token` is a request for an internal resource by an app listed in
+// `accessible_resources` in its manifest.
bool IsPluginLoadingAccessibleResourceInWebView(
extensions::ExtensionRegistry* extension_registry,
- int process_id,
+ const content::GlobalRenderFrameHostToken& rfh_token,
const GURL& resource) {
extensions::WebViewRendererState* renderer_state =
extensions::WebViewRendererState::GetInstance();
std::string partition_id;
- if (!renderer_state->IsGuest(process_id) ||
- !renderer_state->GetPartitionID(process_id, &partition_id)) {
+ if (!renderer_state->IsGuest(rfh_token.child_id) ||
+ !renderer_state->GetPartitionID(rfh_token.child_id, &partition_id)) {
return false;
}
@@ -152,15 +150,18 @@
// Make sure the renderer making the request actually belongs to the
// same extension.
std::string owner_extension;
- return renderer_state->GetOwnerInfo(process_id, nullptr, &owner_extension) &&
+ return renderer_state->GetOwnerInfo(rfh_token.child_id, nullptr,
+ &owner_extension) &&
owner_extension == extension_id;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
} // namespace
-PluginInfoHostImpl::Context::Context(int render_process_id, Profile* profile)
- : render_process_id_(render_process_id),
+PluginInfoHostImpl::Context::Context(
+ content::GlobalRenderFrameHostToken rfh_token,
+ Profile* profile)
+ : rfh_token_(rfh_token),
#if BUILDFLAG(ENABLE_EXTENSIONS)
extension_registry_(extensions::ExtensionRegistry::Get(profile)),
#endif
@@ -170,8 +171,10 @@
PluginInfoHostImpl::Context::~Context() = default;
-PluginInfoHostImpl::PluginInfoHostImpl(int render_process_id, Profile* profile)
- : context_(render_process_id, profile) {
+PluginInfoHostImpl::PluginInfoHostImpl(
+ content::GlobalRenderFrameHostToken rfh_token,
+ Profile* profile)
+ : context_(rfh_token, profile) {
shutdown_subscription_ =
PluginInfoHostImplShutdownNotifierFactory::GetInstance()
->Get(profile)
@@ -246,7 +249,7 @@
if (url.SchemeIs(extensions::kExtensionScheme) && !is_managed &&
plugin_setting == CONTENT_SETTING_BLOCK &&
IsPluginLoadingAccessibleResourceInWebView(extension_registry_,
- render_process_id_, url)) {
+ rfh_token_, url)) {
plugin_setting = CONTENT_SETTING_ALLOW;
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
@@ -266,8 +269,9 @@
if (*status == chrome::mojom::PluginStatus::kAllowed ||
*status == chrome::mojom::PluginStatus::kBlocked) {
if (extensions::WebViewRendererState::GetInstance()->IsGuest(
- render_process_id_))
+ rfh_token_.child_id)) {
*status = chrome::mojom::PluginStatus::kUnauthorized;
+ }
}
#endif
}
@@ -297,10 +301,9 @@
content::PluginServiceFilter* filter =
PluginService::GetInstance()->GetFilter();
- content::RenderProcessHost* rph =
- content::RenderProcessHost::FromID(render_process_id_);
+ auto* rfh = content::RenderFrameHost::FromFrameToken(rfh_token_);
content::BrowserContext* browser_context =
- rph ? rph->GetBrowserContext() : nullptr;
+ rfh ? rfh->GetBrowserContext() : nullptr;
size_t i = 0;
for (; i < matching_plugins.size(); ++i) {
if (!filter ||
@@ -335,6 +338,6 @@
const base::FilePath& path) const {
if (status == chrome::mojom::PluginStatus::kAllowed) {
ChromePluginServiceFilter::GetInstance()->AuthorizePlugin(
- render_process_id_, path);
+ rfh_token_.child_id, path);
}
}
diff --git a/chrome/browser/plugins/plugin_info_host_impl.h b/chrome/browser/plugins/plugin_info_host_impl.h
index c0341afc..c074e00a 100644
--- a/chrome/browser/plugins/plugin_info_host_impl.h
+++ b/chrome/browser/plugins/plugin_info_host_impl.h
@@ -16,6 +16,7 @@
#include "components/content_settings/core/common/content_settings.h"
#include "components/keyed_service/core/keyed_service_shutdown_notifier.h"
#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/global_routing_id.h"
#include "extensions/buildflags/buildflags.h"
#include "media/media_buildflags.h"
@@ -41,12 +42,9 @@
// Contains all the information needed by the PluginInfoHostImpl.
class Context {
Regression Test / PoC
diff --git a/chrome/browser/plugins/plugin_info_host_impl_browsertest.cc b/chrome/browser/plugins/plugin_info_host_impl_browsertest.cc
index 2d3b2b7..cd7d828 100644
--- a/chrome/browser/plugins/plugin_info_host_impl_browsertest.cc
+++ b/chrome/browser/plugins/plugin_info_host_impl_browsertest.cc
@@ -28,7 +28,6 @@
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/render_frame_host.h"
-#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test.h"
@@ -64,15 +63,10 @@
PluginInfoHostImplTest() {}
void SetUpOnMainThread() override {
- int active_render_process_id = browser()
- ->tab_strip_model()
- ->GetActiveWebContents()
- ->GetPrimaryMainFrame()
- ->GetProcess()
- ->GetDeprecatedID();
-
+ auto* web_contents = browser()->tab_strip_model()->GetActiveWebContents();
plugin_info_host_impl_ = std::make_unique<PluginInfoHostImpl>(
- active_render_process_id, browser()->profile());
+ web_contents->GetPrimaryMainFrame()->GetGlobalFrameToken(),
+ browser()->profile());
}
void TearDownOnMainThread() override { plugin_info_host_impl_.reset(); }
diff --git a/chrome/browser/plugins/plugin_info_host_impl_unittest.cc b/chrome/browser/plugins/plugin_info_host_impl_unittest.cc
index 46ba766..1f25d86 100644
--- a/chrome/browser/plugins/plugin_info_host_impl_unittest.cc
+++ b/chrome/browser/plugins/plugin_info_host_impl_unittest.cc
@@ -84,7 +84,7 @@
PluginInfoHostImplTest()
: foo_plugin_path_(FILE_PATH_LITERAL("/path/to/foo")),
bar_plugin_path_(FILE_PATH_LITERAL("/path/to/bar")),
- context_(0, &profile_),
+ context_(content::GlobalRenderFrameHostToken(), &profile_),
host_content_settings_map_(
HostContentSettingsMapFactory::GetForProfile(&profile_)) {}
Original Bug Report
Cross-origin info leak of JS settings via PluginInfoHost::GetPluginInfo
Project Fortify, an experimental security project, has identified the following potential security issue.
Overview: The PluginInfoHost::GetPluginInfo IPC accepts an unvalidated origin parameter from the renderer. A compromised renderer can spoof this origin when querying for non-fully-trusted plugins (like QuickOffice) to leak the user’s site-specific JavaScript content settings, bypassing Site Isolation.
Affected files:
chrome/browser/plugins/plugin_info_host_impl.ccchrome/browser/plugins/plugin_utils.ccchrome/common/plugin.mojomchrome/browser/plugins/chrome_content_browser_client_plugins_part.cc
Estimated timestamp from git blame: 2025-11-13
Description
In Chrome’s plugin architecture, the chrome::mojom::PluginInfoHost interface allows renderer processes to query the status of plugins for a given URL and MIME type. The GetPluginInfo method accepts a url::Origin parameter, which is supposed to represent the origin of the main frame loading the plugin.
However, PluginInfoHostImpl::GetPluginInfo (in chrome/browser/plugins/plugin_info_host_impl.cc) does not validate that the provided origin matches the actual committed origin of the requesting frame. It passes the renderer-supplied origin directly to Context::DecidePluginStatus, which then calls PluginUtils::GetPluginContentSetting.
PluginUtils::GetPluginContentSetting uses this spoofed origin to query the HostContentSettingsMap for the user’s ContentSettingsType::JAVASCRIPT setting. If the queried plugin is not marked as SECURITY_STATUS_FULLY_TRUSTED (e.g., the QuickOffice component extension on ChromeOS), the result of this JavaScript content setting lookup directly dictates the PluginStatus returned to the renderer (kAllowed vs kBlocked).
Impact
A compromised renderer process can exploit this to systematically fingerprint a user’s browser configuration. By rapidly querying the IPC with various target origins, the attacker can map out exactly which sites the user has explicitly allowed or blocked from running JavaScript, constituting a cross-origin information leak.
Potential Exploitation Steps
Note: These are suggested steps based on code analysis; our tooling has not yet executed a live Proof of Concept.
- Compromise a Renderer: An attacker achieves code execution within a standard web renderer process (e.g., via a v8 exploit).
- Bind the Interface: The compromised renderer acquires a handle to the
chrome::mojom::PluginInfoHostinterface. - Spoof the Origin: The attacker invokes
GetPluginInfo, passing a target origin (e.g.,https://victim-bank.com) and a MIME type handled by a non-fully-trusted plugin (e.g.,application/mswordfor QuickOffice on ChromeOS). - Extract the Setting: The browser process processes the IPC without validation, checks the JavaScript content setting for
https://victim-bank.com, and returns the result. - Observe the Result: The attacker observes the returned
PluginInfo->status.kAllowedmeans JavaScript is enabled for the victim site;kBlockedmeans it is disabled.
Suggested Fix
The PluginInfoHost should not trust the origin provided by the renderer process. Currently, PluginInfoHostImpl is instantiated with only the render_process_id.
To fix this, the interface binding in ChromeContentBrowserClientPluginsPart::ExposeInterfacesToRendererForRenderFrameHost should be updated to pass the content::GlobalRenderFrameHostId instead of just the process ID. Then, PluginInfoHostImpl::GetPluginInfo can securely retrieve the RenderFrameHost and use render_frame_host->GetLastCommittedOrigin() (or validate the IPC-provided origin against it) before performing any content setting lookups.
Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0
Results from 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.