Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Permissions
DescriptionInsufficient validation of untrusted input in Permissions
ComponentPermissions
Bug ClassLogic Error
Tracker497008295
Fix commit6aa2c02f8a37 (chromium/src) +24/-12
CISA KEVNot listed
CreditedGoogle
Disclosed2026-05-05

Files Changed

  • chrome/browser/permissions/chrome_permissions_client.cc
From 6aa2c02f8a3779a4081348ccb3b97c1a936ab3fc Mon Sep 17 00:00:00 2001
From: Antonio Sartori <[email protected]>
Date: Mon, 30 Mar 2026 04:54:27 -0700
Subject: [PATCH] [permissions] Fix origin matching in GetCanonicalOriginOverride

This CL fixes a regression introduced by https://crrev.com/c/7515301,
removed some full origin checks and replaced them with host-only
checks in ChromePermissionClient.

Bug: 497008295
Change-Id: I2ef8ea2d967ebdb42928dffc86b8f68a8ee9219d
Fixed: 497008295
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7707943
Auto-Submit: Antonio Sartori <[email protected]>
Reviewed-by: Balazs Engedy <[email protected]>
Commit-Queue: Balazs Engedy <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1607042}
---

diff --git a/chrome/browser/permissions/chrome_permissions_client.cc b/chrome/browser/permissions/chrome_permissions_client.cc
index f87b0b5e..9d5465e 100644
--- a/chrome/browser/permissions/chrome_permissions_client.cc
+++ b/chrome/browser/permissions/chrome_permissions_client.cc
@@ -659,16 +659,20 @@
   // New Tab Page:
   // Bypass embedding origin check as the `requesting_origin` will later be
   // transformed to the DSE origin in `GetCanonicalOriginOverride()`.
-  if (embedding_origin.host() == chrome::kChromeUINewTabHost ||
-      embedding_origin.host() == chrome::kChromeUINewTabPageHost) {
+  if (embedding_origin ==
+          GURL(chrome::kChromeUINewTabURL).DeprecatedGetOriginAsURL() ||
+      embedding_origin ==
+          GURL(chrome::kChromeUINewTabPageURL).DeprecatedGetOriginAsURL()) {
     return true;
   }
 
   // Omnibox Popup and Contextual Tasks:
   // Bypass embedding origin check as the `requesting_origin` will later be
   // transformed to the DSE origin in `GetCanonicalOriginOverride()`.
-  if (embedding_origin.host() == chrome::kChromeUIOmniboxPopupHost ||
-      embedding_origin.host() == chrome::kChromeUIContextualTasksHost) {
+  if (embedding_origin ==
+          GURL(chrome::kChromeUIOmniboxPopupURL).DeprecatedGetOriginAsURL() ||
+      embedding_origin == GURL(chrome::kChromeUIContextualTasksURL)
+                              .DeprecatedGetOriginAsURL()) {
     return true;
   }
 
@@ -681,8 +685,10 @@
   // New Tab Page:
   // Transform chrome:// origins to the DSE origin so that permissions are
   // stored under and shared with the DSE.
-  if (embedding_origin.host() == chrome::kChromeUINewTabHost) {
-    if (requesting_origin.host() == chrome::kChromeUINewTabPageHost) {
+  if (embedding_origin ==
+      GURL(chrome::kChromeUINewTabURL).DeprecatedGetOriginAsURL()) {
+    if (requesting_origin ==
+        GURL(chrome::kChromeUINewTabPageURL).DeprecatedGetOriginAsURL()) {
       return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
           .DeprecatedGetOriginAsURL();
     }
@@ -693,8 +699,10 @@
   // Transform chrome:// origins to the DSE origin so that permissions are
   // stored under and shared with the DSE.
   if (requesting_origin == embedding_origin &&
-      (requesting_origin.host() == chrome::kChromeUIOmniboxPopupHost ||
-       requesting_origin.host() == chrome::kChromeUIContextualTasksHost)) {
+      (requesting_origin ==
+           GURL(chrome::kChromeUIOmniboxPopupURL).DeprecatedGetOriginAsURL() ||
+       requesting_origin == GURL(chrome::kChromeUIContextualTasksURL)
+                                .DeprecatedGetOriginAsURL())) {
     return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
         .DeprecatedGetOriginAsURL();
   }
@@ -723,8 +731,10 @@
   // the requesting origin is the NTP (chrome://new-tab-page).
   // Note that the embedding origin is later transformed to the DSE origin via
   // `GetCanonicalOriginOverride()`.
-  if (requesting_origin.host() == chrome::kChromeUINewTabPageHost &&
-      embedding_origin.host() == chrome::kChromeUINewTabHost) {
+  if (requesting_origin ==
+          GURL(chrome::kChromeUINewTabPageURL).DeprecatedGetOriginAsURL() &&
+      embedding_origin ==
+          GURL(chrome::kChromeUINewTabURL).DeprecatedGetOriginAsURL()) {
     return embedding_origin;
   }
 
@@ -733,8 +743,10 @@
   // Note that the embedding origin is later transformed to the DSE origin via
   // `GetCanonicalOriginOverride()`.
   if (requesting_origin == embedding_origin &&
-      (requesting_origin.host() == chrome::kChromeUIOmniboxPopupHost ||
-       requesting_origin.host() == chrome::kChromeUIContextualTasksHost)) {
+      (requesting_origin ==
+           GURL(chrome::kChromeUIOmniboxPopupURL).DeprecatedGetOriginAsURL() ||
+       requesting_origin == GURL(chrome::kChromeUIContextualTasksURL)
+                                .DeprecatedGetOriginAsURL())) {
     return embedding_origin;
   }
 
Loading diff…

Original Bug Report

reported by [email protected]

Permission Spoofing/Bypass via Missing Scheme Check in ChromePermissionsClient

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

Overview: A logic flaw in ChromePermissionsClient’s origin override logic fails to verify the chrome:// scheme for specific WebUI hosts like contextual-tasks and omnibox-popup.top-chrome. An attacker who can direct a user to an HTTPS version of these domains (e.g., via DNS spoofing or local network registration) will have their origin rewritten to the Google Default Search Engine (DSE) origin. This allows the attacker to silently inherit the user’s previously granted permissions for Google services, such as Geolocation or Camera.

Affected files:

  • chrome/browser/permissions/chrome_permissions_client.cc

Estimated timestamp from git blame: 2026-01-29

Description

There is a potential vulnerability in ChromePermissionsClient::GetCanonicalOriginOverride and CanBypassEmbeddingOriginCheck (chrome/browser/permissions/chrome_permissions_client.cc) where the origin’s scheme is not verified when checking for specific Chrome WebUI hosts.

At lines 695-697 in GetCanonicalOriginOverride, the code checks if the requesting origin matches specific WebUI hosts to determine if it should be rewritten to the user’s Default Search Engine (DSE) base URL (typically https://www.google.com/):

if (requesting_origin == embedding_origin &&
    (requesting_origin.host() == chrome::kChromeUIOmniboxPopupHost ||
     requesting_origin.host() == chrome::kChromeUIContextualTasksHost)) {
  return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
      .DeprecatedGetOriginAsURL();
}

Crucially, this check only verifies the .host() property ("contextual-tasks" or "omnibox-popup.top-chrome") and lacks a scheme check (e.g., requesting_origin.SchemeIs(content::kChromeUIScheme)).

If an attacker can control DNS resolution for these single-label domains on a local or enterprise network and serve a trusted HTTPS certificate, navigating the user to https://contextual-tasks/ will satisfy this condition. Because high-privilege permissions like Geolocation, Camera, and Microphone use WebsiteSettingsInfo::TOP_ORIGIN_ONLY_SCOPE, the HostContentSettingsMap ignores the embedding origin during lookup. The attacker’s page will silently inherit any permissions the user has already granted to Google, bypassing all permission prompts.

Potential Attack Steps

Note: These are suggested steps based on static analysis; our tooling agent does not currently have the capability to run a live proof-of-concept.

  1. DNS Control: An attacker on a local network (e.g., via rogue DHCP, DNS poisoning, or mDNS) configures the hostname contextual-tasks to resolve to an attacker-controlled IP address.
  2. HTTPS Setup: The attacker sets up an HTTPS server and provisions a certificate for contextual-tasks. This could be trusted via an enterprise root CA, or the attacker might rely on the user clicking through an SSL warning.
  3. Victim Navigation: The attacker lures the victim to navigate to https://contextual-tasks/ in Chrome.
  4. Permission Request: The attacker’s webpage executes JavaScript to request a sensitive permission, such as navigator.geolocation.getCurrentPosition(...).
  5. Origin Rewrite: The permission request enters the browser process. PermissionManager::RequestPermissions calls PermissionUtil::GetCanonicalOrigin, which triggers ChromePermissionsClient::GetCanonicalOriginOverride. The host matches "contextual-tasks", so the origin is rewritten to the Google DSE origin (e.g., https://www.google.com/).
  6. Silent Grant: PermissionContextBase checks existing grants using the rewritten origin. Because permissions like Geolocation use TOP_ORIGIN_ONLY_SCOPE, the browser finds the user’s existing grant for Google and silently allows the request, returning sensitive data to the attacker’s page without a prompt.

Suggested Fix

Update ChromePermissionsClient::GetCanonicalOriginOverride, ChromePermissionsClient::CanBypassEmbeddingOriginCheck, and ChromePermissionsClient::GetEmbeddingOriginOverride to explicitly verify that both requesting_origin and embedding_origin are using the chrome:// scheme before performing host comparisons.

For example, in GetCanonicalOriginOverride:

if (requesting_origin.SchemeIs(content::kChromeUIScheme) &&
    requesting_origin == embedding_origin &&
    (requesting_origin.host() == chrome::kChromeUIOmniboxPopupHost ||
     requesting_origin.host() == chrome::kChromeUIContextualTasksHost)) {
  return GURL(UIThreadSearchTermsData().GoogleBaseURLValue())
      .DeprecatedGetOriginAsURL();
}

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