CVE-2026-13898
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifcomponents/cast_receiver/renderer/content_renderer_client_mixins_impl.cc |
modified | |
URLLoaderThrottleProvidercomponents/cast_receiver/renderer/content_renderer_client_mixins_impl.h |
modified | |
ifcomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc |
modified | |
UrlRewriteRulesProvidercomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h |
modified | |
WrappingURLLoaderThrottleProvidercomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h |
modified |
Files Changed
components/cast_receiver/renderer/content_renderer_client_mixins_impl.cccomponents/cast_receiver/renderer/content_renderer_client_mixins_impl.hcomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cccomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
Patch
From ed5420981f153739ccf8082007704bd4e36b9e07 Mon Sep 17 00:00:00 2001
From: Simeon Anfinrud <sanfin@chromium.org>
Date: Thu, 14 May 2026 13:44:32 -0700
Subject: [PATCH] [chromecast] Fix Potential Renderer UAF in cast_receiver
Introduce a base::Lock in ContentRendererClientMixinsImpl to synchronize
access to url_rewrite_rules_providers_. This prevents a UAF caused by
unsynchronized access between the main thread and worker threads.
Bug: 501925480
Test: Compiled and passed unit tests.
Change-Id: I2c9cf3cc4ce290eb60e31b2af9924e0a6e7566bd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7765493
Commit-Queue: Simeon Anfinrud <sanfin@chromium.org>
Reviewed-by: Shawn Quereshi <shawnq@google.com>
Cr-Commit-Position: refs/heads/main@{#1630754}
---
diff --git a/components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc b/components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc
index 3ca9c4a..093e1ddb 100644
--- a/components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc
+++ b/components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc
@@ -39,12 +39,15 @@
new media_control::MediaPlaybackOptions(&render_frame);
// Create the new UrlRewriteRulesProvider.
- url_rewrite_rules_providers_.emplace(
- render_frame.GetWebFrame()->GetLocalFrameToken(),
- std::make_unique<UrlRewriteRulesProvider>(
- &render_frame,
- base::BindOnce(&ContentRendererClientMixinsImpl::OnRenderFrameRemoved,
- weak_factory_.GetWeakPtr())));
+ auto provider = std::make_unique<UrlRewriteRulesProvider>(
+ &render_frame,
+ base::BindOnce(&ContentRendererClientMixinsImpl::OnRenderFrameRemoved,
+ weak_factory_.GetWeakPtr()));
+ {
+ base::AutoLock lock(url_rewrite_rules_providers_lock_);
+ url_rewrite_rules_providers_.emplace(
+ render_frame.GetWebFrame()->GetLocalFrameToken(), std::move(provider));
+ }
}
bool ContentRendererClientMixinsImpl::DeferMediaLoad(
@@ -79,21 +82,29 @@
void ContentRendererClientMixinsImpl::OnRenderFrameRemoved(
const blink::LocalFrameToken& frame_token) {
- size_t result = url_rewrite_rules_providers_.erase(frame_token);
- if (result != 1U) {
- LOG(WARNING)
- << "Can't find the URL rewrite rules provider for render frame: "
- << frame_token;
+ std::unique_ptr<UrlRewriteRulesProvider> provider_to_delete;
+ {
+ base::AutoLock lock(url_rewrite_rules_providers_lock_);
+ auto it = url_rewrite_rules_providers_.find(frame_token);
+ if (it != url_rewrite_rules_providers_.end()) {
+ provider_to_delete = std::move(it->second);
+ url_rewrite_rules_providers_.erase(it);
+ } else {
+ LOG(WARNING)
+ << "Can't find the URL rewrite rules provider for render frame: "
+ << frame_token;
+ }
}
}
-UrlRewriteRulesProvider*
-ContentRendererClientMixinsImpl::GetUrlRewriteRulesProvider(
+scoped_refptr<url_rewrite::UrlRequestRewriteRules>
+ContentRendererClientMixinsImpl::GetUrlRequestRewriteRules(
const blink::LocalFrameToken& frame_token) {
+ base::AutoLock lock(url_rewrite_rules_providers_lock_);
auto rules_it = url_rewrite_rules_providers_.find(frame_token);
return rules_it == url_rewrite_rules_providers_.end()
? nullptr
- : rules_it->second.get();
+ : rules_it->second->GetCachedRules();
}
bool ContentRendererClientMixinsImpl::IsCorsExemptHeader(
diff --git a/components/cast_receiver/renderer/content_renderer_client_mixins_impl.h b/components/cast_receiver/renderer/content_renderer_client_mixins_impl.h
index 6e2be978..6dadf672 100644
--- a/components/cast_receiver/renderer/content_renderer_client_mixins_impl.h
+++ b/components/cast_receiver/renderer/content_renderer_client_mixins_impl.h
@@ -10,9 +10,13 @@
#include "base/containers/flat_map.h"
#include "base/functional/callback.h"
+#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
+#include "base/synchronization/lock.h"
+#include "base/thread_annotations.h"
#include "components/cast_receiver/renderer/public/content_renderer_client_mixins.h"
#include "components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h"
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
namespace blink {
class URLLoaderThrottleProvider;
@@ -62,15 +66,17 @@
void OnRenderFrameRemoved(const blink::LocalFrameToken& frame_token);
// WrappingURLLoaderThrottleProvider::Client implementation.
- UrlRewriteRulesProvider* GetUrlRewriteRulesProvider(
+ scoped_refptr<url_rewrite::UrlRequestRewriteRules> GetUrlRequestRewriteRules(
const blink::LocalFrameToken& frame_token) override;
bool IsCorsExemptHeader(std::string_view header) override;
IsCorsExemptHeadersCallback is_cors_exempt_header_callback_;
+ base::Lock url_rewrite_rules_providers_lock_;
base::flat_map<blink::LocalFrameToken /* frame_token */,
std::unique_ptr<UrlRewriteRulesProvider>>
- url_rewrite_rules_providers_;
+ url_rewrite_rules_providers_
+ GUARDED_BY(url_rewrite_rules_providers_lock_);
base::WeakPtrFactory<ContentRendererClientMixinsImpl> weak_factory_{this};
};
diff --git a/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc b/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
index ed03f1b..1b76223 100644
--- a/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
+++ b/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
@@ -54,17 +54,13 @@
if (!local_frame_token.has_value()) {
return throttles;
}
- auto* provider =
- client_->GetUrlRewriteRulesProvider(local_frame_token.value());
- if (provider) {
- auto rules = provider->GetCachedRules();
- if (rules) {
- throttles.emplace_back(std::make_unique<url_rewrite::URLLoaderThrottle>(
- rules,
- base::BindRepeating(
- &WrappingURLLoaderThrottleProvider::Client::IsCorsExemptHeader,
- base::Unretained(client_))));
- }
+ auto rules = client_->GetUrlRequestRewriteRules(local_frame_token.value());
+ if (rules) {
+ throttles.emplace_back(std::make_unique<url_rewrite::URLLoaderThrottle>(
+ rules,
+ base::BindRepeating(
+ &WrappingURLLoaderThrottleProvider::Client::IsCorsExemptHeader,
+ base::Unretained(client_))));
}
return throttles;
diff --git a/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h b/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
index f2499f3..38a7265 100644
--- a/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
+++ b/components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
@@ -12,7 +12,9 @@
#include "base/containers/flat_map.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ref.h"
+#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
+#include "components/url_rewrite/common/url_request_rewrite_rules.h"
#include "third_party/blink/public/platform/url_loader_throttle_provider.h"
namespace network {
@@ -21,7 +23,6 @@
namespace cast_receiver {
-class UrlRewriteRulesProvider;
class WrappingURLLoaderThrottleProvider
: public blink::URLLoaderThrottleProvider {
@@ -31,10 +32,10 @@
public:
virtual ~Client();
- // Returns the UrlRewriteRulesProvider associated with RenderFrame with id
- // `frame_token`, or nullptr if no such provider exists.
- virtual UrlRewriteRulesProvider* GetUrlRewriteRulesProvider(
- const blink::LocalFrameToken& frame_token) = 0;
+ // Returns the UrlRequestRewriteRules associated with RenderFrame with id
+ // `frame_token`, or nullptr if no such rules exist.
+ virtual scoped_refptr<url_rewrite::UrlRequestRewriteRules>
+ GetUrlRequestRewriteRules(const blink::LocalFrameToken& frame_token) = 0;
// Returns whether |header| is a cors exempt header.
virtual bool IsCorsExemptHeader(std::string_view header) = 0;
Original Bug Report
Renderer UAF in cast_receiver due to unsynchronized access to UrlRewriteRulesProvider
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports without the Chrome Security team.
Overview: A data race exists in ContentRendererClientMixinsImpl where url_rewrite_rules_providers_ is accessed across threads without synchronization. Concurrent modification on the main thread and read from a worker thread leads to a Use-After-Free. This can be leveraged into an arbitrary atomic increment primitive in the renderer process.
Affected files:
components/cast_receiver/renderer/content_renderer_client_mixins_impl.cccomponents/cast_receiver/renderer/content_renderer_client_mixins_impl.hcomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
Estimated timestamp from git blame: 2023-11-07
Technical Details
A potential Use-After-Free (UAF) and data race vulnerability exists in the cast_receiver component, specifically in cast_receiver::ContentRendererClientMixinsImpl. This component manages URL rewrite rules for different frames using a base::flat_map<blink::LocalFrameToken, std::unique_ptr<UrlRewriteRulesProvider>> named url_rewrite_rules_providers_.
The url_rewrite_rules_providers_ map is modified on the renderer’s main thread during frame creation (RenderFrameCreated calls emplace()) and destruction (OnRenderFrameRemoved calls erase()).
However, the map is read via GetUrlRewriteRulesProvider() which is called by WrappingURLLoaderThrottleProvider::CreateThrottles(). This method can execute on worker threads (for dedicated/shared worker subresource fetches) or background threads (when BackgroundResourceFetch is enabled). There is no locking mechanism protecting url_rewrite_rules_providers_ in ContentRendererClientMixinsImpl.
Potential Exploitation Steps
While we do not have a working Proof of Concept, code analysis suggests an attacker could exploit this via the following steps:
- Frame Setup: The attacker’s webpage on the main thread creates a subframe (iframe).
- Worker Setup: The page spawns a Web Worker that will initiate subresource fetches.
- Fetch & Map Read: The worker thread executes a fetch. During throttle creation,
WrappingURLLoaderThrottleProvider::CreateThrottlescallsclient_->GetUrlRewriteRulesProvider(...). This performs a binary search on theflat_mapand returns a raw pointer to theUrlRewriteRulesProvider. This pointer is stored in a local stack variableprovider. - Race Condition: A context switch occurs back to the main thread. The attacker’s script removes the subframe created in step 1.
- Destruction: Frame removal triggers
OnRenderFrameRemovedon the main thread, which callserase()on the map. Becausebase::flat_mapis backed by astd::vector, this shifts elements and destroys thestd::unique_ptrholding theUrlRewriteRulesProvider, freeing the object in the heap. - Heap Spray: The attacker sprays the heap from the main thread or another worker, reclaiming the freed memory with attacker-controlled data designed to mimic a
UrlRewriteRulesProviderobject. - Use-After-Free: Context switches back to the worker thread. The worker thread executes
auto rules = provider->GetCachedRules();using the now-danglingproviderpointer. - Atomic Increment:
GetCachedRules()internally reads ascoped_refptrfrom the attacker-controlled memory and returns it by value. Returning by value invokes the copy constructor, which callsAddRef()on the attacker-provided pointer. Because the target object inherits frombase::RefCountedThreadSafe, this provides the attacker with a highly reliable arbitrary atomic increment primitive.
This primitive can be used to corrupt reference counts of other objects, leading to further memory corruption and ultimately Remote Code Execution (RCE) in the sandboxed renderer process.
Note: MiraclePtr (BackupRefPtr) does not mitigate this specific UAF because the dangling provider pointer is held as a stack-local raw pointer rather than a class member.
Affected Files
components/cast_receiver/renderer/content_renderer_client_mixins_impl.hcomponents/cast_receiver/renderer/content_renderer_client_mixins_impl.cccomponents/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
Suggested Fix
- Introduce a
base::LockinContentRendererClientMixinsImplto protect all accesses (reads and writes) tourl_rewrite_rules_providers_. - Instead of returning a raw pointer from
GetUrlRewriteRulesProvider(), the map should storescoped_refptr<url_rewrite::UrlRequestRewriteRules>directly, orGetUrlRewriteRulesProvider()should return thescoped_refptrby value while holding the lock. This ensures the rules stay alive for the duration of the worker thread’s usage, even if the frame is destroyed.
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
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. And please feel free to reach out to me directly if you have concerns or feedback on the project.