Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Cast Receiver
DescriptionUse after free in Cast Receiver
ComponentCast Receiver
Bug ClassUAF
Tracker501925480
Fix commited5420981f15 (chromium/src) +46/-32
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc
modified
URLLoaderThrottleProvider
components/cast_receiver/renderer/content_renderer_client_mixins_impl.h
modified
if
components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
modified
UrlRewriteRulesProvider
components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
modified
WrappingURLLoaderThrottleProvider
components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
modified

Files Changed

  • components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc
  • components/cast_receiver/renderer/content_renderer_client_mixins_impl.h
  • components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc
  • components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.h
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;
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cc
  • components/cast_receiver/renderer/content_renderer_client_mixins_impl.h
  • components/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:

  1. Frame Setup: The attacker’s webpage on the main thread creates a subframe (iframe).
  2. Worker Setup: The page spawns a Web Worker that will initiate subresource fetches.
  3. Fetch & Map Read: The worker thread executes a fetch. During throttle creation, WrappingURLLoaderThrottleProvider::CreateThrottles calls client_->GetUrlRewriteRulesProvider(...). This performs a binary search on the flat_map and returns a raw pointer to the UrlRewriteRulesProvider. This pointer is stored in a local stack variable provider.
  4. Race Condition: A context switch occurs back to the main thread. The attacker’s script removes the subframe created in step 1.
  5. Destruction: Frame removal triggers OnRenderFrameRemoved on the main thread, which calls erase() on the map. Because base::flat_map is backed by a std::vector, this shifts elements and destroys the std::unique_ptr holding the UrlRewriteRulesProvider, freeing the object in the heap.
  6. 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 UrlRewriteRulesProvider object.
  7. Use-After-Free: Context switches back to the worker thread. The worker thread executes auto rules = provider->GetCachedRules(); using the now-dangling provider pointer.
  8. Atomic Increment: GetCachedRules() internally reads a scoped_refptr from the attacker-controlled memory and returns it by value. Returning by value invokes the copy constructor, which calls AddRef() on the attacker-provided pointer. Because the target object inherits from base::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.h
  • components/cast_receiver/renderer/content_renderer_client_mixins_impl.cc
  • components/cast_receiver/renderer/wrapping_url_loader_throttle_provider.cc

Suggested Fix

  1. Introduce a base::Lock in ContentRendererClientMixinsImpl to protect all accesses (reads and writes) to url_rewrite_rules_providers_.
  2. Instead of returning a raw pointer from GetUrlRewriteRulesProvider(), the map should store scoped_refptr<url_rewrite::UrlRequestRewriteRules> directly, or GetUrlRewriteRulesProvider() should return the scoped_refptr by 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.

View on issue tracker