CVE-2026-13888
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/renderer/chrome_content_renderer_client.cc |
modified |
Files Changed
chrome/renderer/chrome_content_renderer_client.cc
Patch
From 79f10bb24c3338cc1494b13d9fa34a7ad3d48131 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
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 <blundell@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
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();
Original Bug Report
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
- 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 chainMojoURLLoaderClient::OnReceiveRedirect->RendererBlinkPlatformImpl::IsRedirectSafe->ChromeContentRendererClient::IsSafeRedirectTargetexecutes entirely on the worker thread. - Raw Pointer Acquisition: Inside
IsSafeRedirectTarget, the code queries the registry:const extensions::Extension* extension = extensions::RendererExtensionRegistry::Get()->GetExtensionOrAppByURL( target_url, /*include_guid=*/true);GetExtensionOrAppByURLacquires a lock, extracts a raw pointer from its internalscoped_refptrmap, and releases the lock. The worker thread now holds a raw pointer to theExtensionobject. - 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::UnloadExtensionIPC, which is processed on the renderer’s main thread byDispatcher::UnloadExtension. - 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 onlyscoped_refptrto theExtensionobject. Removing it from the registry drops the reference count to zero, and the memory is synchronously freed on the main thread. - 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 toextensions::WebAccessibleResourcesInfo::IsResourceWebAccessibleRedirect(extension, ...), which dereferences the freed memory to access internal fields likemanifest_data_,guid(), andid(), 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.
- An attacker registers a Service Worker on their malicious origin (
attacker.com). - A victim with an installed target extension (e.g., ID
aaaa...) visits the attacker’s site. - The attacker ensures the victim extension has no content scripts executing in the current renderer process.
- The Service Worker executes a continuous loop of
fetch()requests to an attacker-controlled endpoint. - 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). - 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).
- 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.