Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Extensions
DescriptionInsufficient policy enforcement in Extensions
ComponentExtensions
Bug ClassLogic Error
Tracker497599683
Fix commitbb9b6967ae77 (chromium/src) +31/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
modified

Files Changed

  • chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
  • extensions/browser/extension_navigation_registry.cc
From bb9b6967ae773cbf7c7077b6cdebefa6ec3a2b81 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
Date: Thu, 30 Apr 2026 13:11:46 -0700
Subject: [PATCH] Extensions: Fix logic bug in ExtensionNavigationRegistry::CanRedirect

This ensures that an extension cannot redirect to another extension's
non-web-accessible resources by claiming a redirect recorded by that
extension. This correctly validates that the extension initiating the
redirect matches the one claiming it.

This issue was introduced in crrev.com/c/6230814.

Fixed: 497599683
Change-Id: I48a2cb864f3161945578f9978a946ba041d0496d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7808324
Commit-Queue: Andrew Paseltiner <[email protected]>
Reviewed-by: Devlin Cronin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1623380}
---

diff --git a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
index 3b71088..a8715d2 100644
--- a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
@@ -45,10 +45,12 @@
 #include "extensions/browser/api/web_request/web_request_api_constants.h"
 #include "extensions/browser/api/web_request/web_request_api_helpers.h"
 #include "extensions/browser/api/web_request/web_request_info.h"
+#include "extensions/browser/extension_navigation_registry.h"
 #include "extensions/buildflags/buildflags.h"
 #include "extensions/common/api/declarative_net_request.h"
 #include "extensions/common/api/web_request.h"
 #include "extensions/common/constants.h"
+#include "extensions/common/extension_builder.h"
 #include "extensions/common/features/feature.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/http/http_util.h"
@@ -307,6 +309,34 @@
                                                                  kEventName));
 }
 
+// Regression test for ExtensionNavigationRegistry::CanRedirect logic bug.
+// This ensures that an extension cannot redirect to another extension's
+// non-web-accessible resources by claiming a redirect recorded by that
+// extension. See crbug.com/497599683.
+TEST_F(ExtensionWebRequestTest, CanRedirectLogicBug) {
+  ExtensionNavigationRegistry* registry =
+      ExtensionNavigationRegistry::Get(&profile_);
+  int64_t nav_id = 42;
+  GURL target_url("chrome-extension://victim/resource.html");
+  ExtensionId attacker_id = "attacker";
+  ExtensionId victim_id = "victim";
+
+  auto attacker_extension =
+      ExtensionBuilder("Attacker").SetID(attacker_id).Build();
+  auto victim_extension = ExtensionBuilder("Victim").SetID(victim_id).Build();
+
+  // Record a redirect initiated by the attacker.
+  registry->RecordExtensionRedirect(nav_id, target_url, attacker_id);
+
+  // The victim extension should NOT be allowed to claim a redirect recorded by
+  // attacker.
+  EXPECT_FALSE(registry->CanRedirect(nav_id, target_url, *victim_extension));
+
+  // Re-record for the attacker check.
+  registry->RecordExtensionRedirect(nav_id, target_url, attacker_id);
+  EXPECT_TRUE(registry->CanRedirect(nav_id, target_url, *attacker_extension));
+}
+
 namespace {
 
 void TestInitFromValue(content::BrowserContext* browser_context,
diff --git a/extensions/browser/extension_navigation_registry.cc b/extensions/browser/extension_navigation_registry.cc
index d30eaf0..4c23b80 100644
--- a/extensions/browser/extension_navigation_registry.cc
+++ b/extensions/browser/extension_navigation_registry.cc
@@ -86,7 +86,7 @@
     return true;
   }
 
-  return true;
+  return false;
 }
 
 }  // namespace extensions
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
index 3b71088..a8715d2 100644
--- a/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
+++ b/chrome/browser/extensions/api/web_request/web_request_api_unittest.cc
@@ -45,10 +45,12 @@
 #include "extensions/browser/api/web_request/web_request_api_constants.h"
 #include "extensions/browser/api/web_request/web_request_api_helpers.h"
 #include "extensions/browser/api/web_request/web_request_info.h"
+#include "extensions/browser/extension_navigation_registry.h"
 #include "extensions/buildflags/buildflags.h"
 #include "extensions/common/api/declarative_net_request.h"
 #include "extensions/common/api/web_request.h"
 #include "extensions/common/constants.h"
+#include "extensions/common/extension_builder.h"
 #include "extensions/common/features/feature.h"
 #include "google_apis/gaia/gaia_urls.h"
 #include "net/http/http_util.h"
@@ -307,6 +309,34 @@
                                                                  kEventName));
 }
 
+// Regression test for ExtensionNavigationRegistry::CanRedirect logic bug.
+// This ensures that an extension cannot redirect to another extension's
+// non-web-accessible resources by claiming a redirect recorded by that
+// extension. See crbug.com/497599683.
+TEST_F(ExtensionWebRequestTest, CanRedirectLogicBug) {
+  ExtensionNavigationRegistry* registry =
+      ExtensionNavigationRegistry::Get(&profile_);
+  int64_t nav_id = 42;
+  GURL target_url("chrome-extension://victim/resource.html");
+  ExtensionId attacker_id = "attacker";
+  ExtensionId victim_id = "victim";
+
+  auto attacker_extension =
+      ExtensionBuilder("Attacker").SetID(attacker_id).Build();
+  auto victim_extension = ExtensionBuilder("Victim").SetID(victim_id).Build();
+
+  // Record a redirect initiated by the attacker.
+  registry->RecordExtensionRedirect(nav_id, target_url, attacker_id);
+
+  // The victim extension should NOT be allowed to claim a redirect recorded by
+  // attacker.
+  EXPECT_FALSE(registry->CanRedirect(nav_id, target_url, *victim_extension));
+
+  // Re-record for the attacker check.
+  registry->RecordExtensionRedirect(nav_id, target_url, attacker_id);
+  EXPECT_TRUE(registry->CanRedirect(nav_id, target_url, *attacker_extension));
+}
+
 namespace {
 
 void TestInitFromValue(content::BrowserContext* browser_context,
Loading diff…

Original Bug Report

reported by [email protected]

ExtensionNavigationRegistry::CanRedirect logic bug allows cross-extension WAR bypass

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A logic error in ExtensionNavigationRegistry::CanRedirect unconditionally returns true, failing to validate extension IDs. When combined with a browser-initiated navigation (e.g., chrome.tabs.create), an attacker extension can redirect to another extension’s private resources, completely bypassing Web Accessible Resource (WAR) checks.

Affected files:

  • extensions/browser/extension_navigation_registry.cc
  • extensions/browser/extension_navigation_throttle.cc
  • extensions/browser/api/declarative_net_request/indexed_rule.cc
  • extensions/browser/api/web_request/extension_web_request_event_router.cc

Estimated timestamp from git blame: 2025-02-06

Summary

There is a logic bug in ExtensionNavigationRegistry::CanRedirect where the function unconditionally returns true instead of false. This undermines the validation intended to ensure that only the extension that initiated a redirect can redirect to its own resources. In combination with how ExtensionNavigationThrottle handles browser-initiated navigations, this allows an attacker extension to force the browser to load private, non-Web Accessible Resources (WAR) belonging to a different extension.

Technical Details

In extensions/browser/extension_navigation_registry.cc, the CanRedirect function is implemented as follows:

bool ExtensionNavigationRegistry::CanRedirect(int64_t navigation_id,
                                              const GURL& gurl,
                                              const Extension& extension) {
  // ... lookup logic ...
  if (metadata.extension_id == extension.id()) {
    return true;
  }

  return true; // Logic bug: should return false
}

The final return true (at line 89) makes the previous equality check for extension_id redundant. This function is used by ExtensionNavigationThrottle to determine if an extension-initiated redirect to a non-WAR resource should be permitted.

Potential Attack Chain

Note: These are suggested steps; we have not run a live proof-of-concept for this issue.

  1. Malicious Redirect Rule: An attacker extension uses declarativeNetRequest (or blocking webRequest) to create a redirect rule. The rule targets a URL (e.g., http://example.com/trigger) and redirects it to a private resource of a target Extension B (e.g., chrome-extension://<ExtensionB_ID>/internal/admin.html). The DNR rule validation does not restrict chrome-extension:// targets to the extension defining the rule.
  2. Browser-Initiated Navigation: The attacker extension initiates a navigation to the trigger URL using an API that creates a browser-initiated navigation, such as chrome.tabs.create({url: "http://example.com/trigger"}). Because this is browser-initiated, the navigation request does not have an initiator origin.
  3. Throttle Bypass 1: The redirect occurs. ExtensionNavigationThrottle encounters the redirect to a non-WAR resource. It calls ExtensionNavigationRegistry::CanRedirect to see if the redirecting extension owns the target resource. Due to the logic bug, this returns true even though the attacker’s ID does not match the victim extension’s ID. This prevents an immediate block at line 344 of extension_navigation_throttle.cc.
  4. Throttle Bypass 2: Execution continues to line 356. For browser-initiated navigations (like those from chrome.tabs.create), navigation_handle()->GetInitiatorOrigin() returns std::nullopt. ExtensionNavigationThrottle::WillStartOrRedirectRequest sees this and immediately returns PROCEED (line 357), completely bypassing the subsequent second-stage cross-origin WAR check that would normally catch this.

Impact

This vulnerability allows an extension to bypass the Web Accessible Resources security boundary. An attacker can:

  • Detect if specific extensions are installed by attempting to navigate to their private pages (fingerprinting).
  • Perform “confused deputy” or CSRF-style attacks against other extensions if those extensions have private pages that perform actions based on URL parameters or page load events.

Suggested Fix

In extensions/browser/extension_navigation_registry.cc, change the final return statement in CanRedirect from return true; to return false;. Additionally, ensure that the TODO (crbug.com/40060076) regarding verifying WAR access for the recorded extension is addressed to provide robust validation.

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.

View on issue tracker