Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Extensions
DescriptionUse after free in Extensions
ComponentExtensions
Bug ClassUAF
Tracker500566906
Fix commit79f10bb24c33 (chromium/src) +7/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Changed Functions

FunctionChangeNotes
if
chrome/renderer/chrome_content_renderer_client.cc
modified

Files Changed

  • chrome/renderer/chrome_content_renderer_client.cc
From 79f10bb24c3338cc1494b13d9fa34a7ad3d48131 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
Date: Thu, 07 May 2026 06:12:40 -0700
Subject: [PATCH] extensions: Fix Use-After-Free in IsSafeRedirectTarget

When checking if a redirect to a chrome-extension:// URL is safe, the
code was obtaining a raw pointer to an Extension object. This check can
be executed on a worker thread, creating a race condition if the
extension is unloaded on the main thread.

This CL updates the lookup to use a scoped_refptr, keeping the extension
alive for the duration of the safety check. This mirrors the fix
previously applied in AppendContentSecurityPolicy (crrev.com/c/6941414).

Fixed: 500566906
Change-Id: Icd92771eca3aab2d29c17ec1b1c6fff443acad4a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7824059
Reviewed-by: Colin Blundell <[email protected]>
Commit-Queue: Andrew Paseltiner <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1626880}
---

diff --git a/chrome/renderer/chrome_content_renderer_client.cc b/chrome/renderer/chrome_content_renderer_client.cc
index b8d8b08..c4ddee88 100644
--- a/chrome/renderer/chrome_content_renderer_client.cc
+++ b/chrome/renderer/chrome_content_renderer_client.cc
@@ -1587,14 +1587,17 @@
     const std::optional<url::Origin>& request_initiator) {
 #if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
   if (target_url.SchemeIs(extensions::kExtensionScheme)) {
-    const extensions::Extension* extension =
-        extensions::RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
-            target_url, /*include_guid=*/true);
+    // Use a scoped_refptr to keep the extension alive, since this code can be
+    // executed on a worker thread. See https://crbug.com/500566906.
+    scoped_refptr<const extensions::Extension> extension =
+        extensions::RendererExtensionRegistry::Get()
+            ->GetRefCountedExtensionOrAppByURL(target_url,
+                                               /*include_guid=*/true);
     if (!extension) {
       return false;
     }
     if (extensions::WebAccessibleResourcesInfo::IsResourceWebAccessibleRedirect(
-            extension, target_url, request_initiator, upstream_url)) {
+            extension.get(), target_url, request_initiator, upstream_url)) {
       return true;
     }
     return extension->guid() == upstream_url.GetHost();
Loading diff…

Original Bug Report

reported by [email protected]

UAF in ChromeContentRendererClient::IsSafeRedirectTarget via extension unload race

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 security team.

Overview: A Use-After-Free (UAF) vulnerability exists in the renderer process when a worker thread handles a network redirect to a chrome-extension:// URL. The code obtains a raw pointer to an Extension object, which can be synchronously destroyed on the main thread during an extension unload, leaving the worker thread to dereference freed memory.

Affected files:

  • chrome/renderer/chrome_content_renderer_client.cc

Estimated timestamp from git blame: 2026-02-23

Summary

A potential Use-After-Free (UAF) vulnerability exists in ChromeContentRendererClient::IsSafeRedirectTarget. When checking if a redirect to a chrome-extension:// URL is safe, the method retrieves a raw const Extension* from the RendererExtensionRegistry. Because this check can be executed on a worker thread (e.g., from a Service Worker fetch intercept), it races against the main thread processing an UnloadExtension IPC, which can synchronously destroy the Extension object.

Technical Details

  1. Worker Thread Execution: When a Service Worker executes a fetch() that encounters a redirect, the Network Service sends the redirect IPC to the worker thread. The call chain MojoURLLoaderClient::OnReceiveRedirect -> RendererBlinkPlatformImpl::IsRedirectSafe -> ChromeContentRendererClient::IsSafeRedirectTarget executes entirely on the worker thread.
  2. Raw Pointer Acquisition: Inside IsSafeRedirectTarget, the code queries the registry:
    const extensions::Extension* extension =
        extensions::RendererExtensionRegistry::Get()->GetExtensionOrAppByURL(
            target_url, /*include_guid=*/true);
    
    GetExtensionOrAppByURL acquires a lock, extracts a raw pointer from its internal scoped_refptr map, and releases the lock. The worker thread now holds a raw pointer to the Extension object.
  3. The Race Condition: Concurrently, the victim extension may be unloaded (e.g., disabled by the user, updated, or its background process crashes). The browser sends a mojom::Renderer::UnloadExtension IPC, which is processed on the renderer’s main thread by Dispatcher::UnloadExtension.
  4. Synchronous Destruction: The main thread calls RendererExtensionRegistry::Get()->Remove(id). Crucially, if the victim extension has no running content scripts in this specific renderer process, the registry holds the only scoped_refptr to the Extension object. Removing it from the registry drops the reference count to zero, and the memory is synchronously freed on the main thread.
  5. Use-After-Free: The worker thread resumes and bypasses the if (!extension) check (since the pointer address is not null). It then passes the dangling pointer to extensions::WebAccessibleResourcesInfo::IsResourceWebAccessibleRedirect(extension, ...), which dereferences the freed memory to access internal fields like manifest_data_, guid(), and id(), resulting in a UAF-read primitive.

Potential Reproduction Steps

Note: Our tooling agent cannot run code yet, so these steps are theoretical based on static analysis.

  1. An attacker registers a Service Worker on their malicious origin (attacker.com).
  2. A victim with an installed target extension (e.g., ID aaaa...) visits the attacker’s site.
  3. The attacker ensures the victim extension has no content scripts executing in the current renderer process.
  4. The Service Worker executes a continuous loop of fetch() requests to an attacker-controlled endpoint.
  5. The attacker-controlled endpoint delays responses, then issues HTTP 302 Redirects to an asset within the victim extension (e.g., chrome-extension://aaaa.../resource.html).
  6. Concurrently, the attacker triggers an event that causes the extension to unload (e.g., crashing the extension’s background page if a separate bug exists, or waiting for a natural extension update).
  7. The race between the worker thread handling the redirect and the main thread processing the unload triggers the UAF.

Suggested Fix

ChromeContentRendererClient::IsSafeRedirectTarget should be updated to use GetRefCountedExtensionOrAppByURL to obtain a scoped_refptr<const Extension>. This will ensure the Extension object remains alive for the duration of the method, even if it is removed from the registry on the main thread. This mirrors the fix previously applied in ChromeContentRendererClient::AppendContentSecurityPolicy (crbug.com/443038597).

// Suggested Fix in chrome/renderer/chrome_content_renderer_client.cc
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  if (target_url.SchemeIs(extensions::kExtensionScheme)) {
    scoped_refptr<const extensions::Extension> extension =
        extensions::RendererExtensionRegistry::Get()->GetRefCountedExtensionOrAppByURL(
            target_url, /*include_guid=*/true);
    if (!extension) {
      return false;
    }
    if (extensions::WebAccessibleResourcesInfo::IsResourceWebAccessibleRedirect(
            extension.get(), target_url, request_initiator, upstream_url)) {
      return true;
    }
    return extension->guid() == upstream_url.GetHost();
  }
#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)

Evaluated with Chrome root at commit: 137d451a126685dd5010e6609db9f6d4a78d8234


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
Links in the report