Low watch CVSS 6.5 chrome Authorization Error 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in StorageAccessAPI
DescriptionIncorrect authorization in StorageAccessAPI
ComponentStorageAccessAPI
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker519254827
Fix commita3c67583da14 (chromium/src) +36/-3
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-06-02 (per vendor advisory)
Fix landed2026-07-20 (public git)
Disclosed2026-08-25
Patch gap36 days — fix public in source before the advisory

Background

`requestStorageAccessFor` (rsaFor) / top-level storage access
A Storage Access API variant that lets a top-level site request cookie access on behalf of an embedded cross-site resource; the browser decides it in TopLevelStorageAccessPermissionContext.
`net::SchemefulSite`
Chromium’s “site” (scheme plus registrable domain) used to decide whether two origins are same-site or cross-site; storage-access grants only make sense across a cross-site boundary.
Credentialless frame
An iframe that loads without the user’s existing credentials (cookies) in an ephemeral context, so it must not be able to obtain the embedding user’s stored state.
`bad_message::ReceivedBadMessage()`
The browser-side mechanism that terminates a renderer process that sent an IPC a well-behaved renderer never would, used as a hardening response to compromised renderers.

Root Cause Analysis

In top_level_storage_access_permission_context.cc, the permission-decision routine (its name is not shown in the hunk; the regression test drives it through the DecidePermissionSync helper) took request_data->requesting_origin and request_data->embedding_origin from the renderer’s request and checked user gesture, transient activation and origin validity, but never rejected a request where both origins are the same site. It computed embedding_site and requesting_site only later, for the First-Party Sets metadata lookup. Per the commit message, well-behaved renderers never send same-site requests, but a compromised renderer could, and the browser would grant the permission; the commit message says that permission could then be read from a credentialless frame, crossing a boundary the browser was supposed to enforce.

The fix moves the SchemefulSite computation before the gesture/validity checks and adds an early branch: if requesting_site == embedding_site, it calls bad_message::ReceivedBadMessage(rfh->GetProcess(), TLSAPC_INVALID_PERMISSION_REQUEST_CONTEXT) and runs the callback with PermissionStatus::DENIED before returning. This tightens browser-side validation so the cross-site precondition is enforced in the trusted process rather than assumed from the renderer.

Key insight
The browser trusted the renderer to only send cross-site rsaFor permission requests and never checked it; the fix enforces the cross-site condition in the browser, denying the request and killing the offending renderer.

Attack Path

  1. Attacker controls a renderer The attacker must first have a compromised renderer process (e.g. via a separate renderer bug); per the commit message, ordinary web content cannot send this request.
  2. Forged same-site rsaFor request The compromised renderer sends a top-level storage access permission request where requesting_origin and embedding_origin are the same site, while satisfying the user-gesture and transient-activation checks.
  3. Browser grants the permission Pre-patch, TopLevelStorageAccessPermissionContext had no same-site check, so the request proceeded through the normal decision flow and could be granted.
  4. Grant read from a credentialless frame The resulting permission could then be read from a credentialless frame, per the commit message, letting that frame obtain access it should not have (the exact downstream use is inferred).

Impact Assessment

A compromised renderer could obtain a top-level storage access grant for a same-site pair that the browser should never issue, and use it to weaken the isolation of credentialless frames, i.e. to regain access to credentials/storage they are designed not to have (precise data exposed is inferred). This is a browser-process authorization gap, reachable only after renderer compromise, and it does not itself demonstrate code execution or a sandbox escape. The advisory states “Incorrect authorization in StorageAccessAPI” and reports no in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
modified
TEST_F
chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
modified
TopLevelStorageAccessPermissionContextAPIWithFirstPartySetsTest
chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
modified

Files Changed

  • chrome/browser/top_level_storage_access_api/BUILD.gn
  • chrome/browser/bad_message.h
  • tools/metrics/histograms/metadata/stability/enums.xml
  • chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
  • chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc

Audit Directions

  • Renderer-asserted relationships in permission contexts
    Review other storage-access and permission contexts (e.g. the sibling Storage Access API context, which already has SAGPC_INVALID_PERMISSION_REQUEST_CONTEXT) for conditions such as cross-site, top-level, or frame type that are assumed from renderer behavior instead of checked against RenderFrameHost state in the browser.
  • Credentialless and anonymous contexts
    Check whether permission grants, content settings or storage keys keyed only by origin or site can be read from credentialless iframes, fenced frames or other partitioned contexts that should be excluded.
  • Order of trust checks
    Ensure origin/site sanity checks on renderer-supplied requesting_origin/embedding_origin run before any grant, prompt or asynchronous lookup, and that they are compared against browser-known frame origins.
From a3c67583da1402dba1ddd1242a9417f227e6e5b2 Mon Sep 17 00:00:00 2001
From: Chris Fredrickson <[email protected]>
Date: Mon, 20 Jul 2026 07:57:52 -0700
Subject: [PATCH] Kill compromised same-site renderers in rsaFor permission context

Same-site permissions requests are never sent by well-behaved renderers,
since there is no privacy boundary to enforce there (generally
speaking). However, a compromised renderer could request (and get)
permission, and then read permission from a credentialless frame,
because the browser process did not properly enforce the cross-site
condition.

Fixed: 519254827
Change-Id: I232efae5ceda718ef0db01071cef1eae482b0afa
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8110978
Reviewed-by: Lei Zhang <[email protected]>
Reviewed-by: Alexei Svitkine <[email protected]>
Auto-Submit: Chris Fredrickson <[email protected]>
Commit-Queue: Alexei Svitkine <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1664684}
---
diff --git a/chrome/browser/bad_message.h b/chrome/browser/bad_message.h
index 220ecd6..32c12993 100644
--- a/chrome/browser/bad_message.h
+++ b/chrome/browser/bad_message.h
@@ -34,6 +34,7 @@
   RFH_INVALID_WEB_FRAME_URL = 11,
   PVM_PRINT_FENCED_FRAME = 12,
   SAGPC_INVALID_PERMISSION_REQUEST_CONTEXT = 13,
+  TLSAPC_INVALID_PERMISSION_REQUEST_CONTEXT = 14,
 
   // Please add new elements here. The naming convention is abbreviated class
   // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the
diff --git a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
index aa49836..8122509e 100644
--- a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
+++ b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context.cc
@@ -10,6 +10,7 @@
 #include "base/functional/bind.h"
 #include "base/metrics/histogram_functions.h"
 #include "base/notreached.h"
+#include "chrome/browser/bad_message.h"
 #include "chrome/browser/content_settings/host_content_settings_map_factory.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service.h"
 #include "chrome/browser/first_party_sets/first_party_sets_policy_service_factory.h"
@@ -36,6 +37,7 @@
 #include "third_party/blink/public/common/features.h"
 #include "third_party/blink/public/common/features_generated.h"
 #include "third_party/blink/public/mojom/devtools/console_message.mojom-shared.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom-shared.h"
 
 namespace {
 
@@ -95,6 +97,22 @@
     return;
   }
 
+  net::SchemefulSite embedding_site(request_data->embedding_origin);
+  net::SchemefulSite requesting_site(request_data->requesting_origin);
+
+  if (requesting_site == embedding_site) {
+    // Well-behaved renderers don't send same-site permissions requests, since
+    // there is no privacy boundary within a site. This must be a compromised
+    // renderer.
+    bad_message::ReceivedBadMessage(
+        rfh->GetProcess(), bad_message::BadMessageReason::
+                               TLSAPC_INVALID_PERMISSION_REQUEST_CONTEXT);
+    std::move(callback).Run(content::PermissionResult(
+        blink::mojom::PermissionStatus::DENIED,
+        content::PermissionStatusSource::UNSPECIFIED));
+    return;
+  }
+
   if (!request_data->user_gesture || !rfh->HasTransientUserActivation() ||
       !request_data->requesting_origin.is_valid() ||
       !request_data->embedding_origin.is_valid()) {
@@ -111,9 +129,6 @@
     return;
   }
 
-  net::SchemefulSite embedding_site(request_data->embedding_origin);
-  net::SchemefulSite requesting_site(request_data->requesting_origin);
-
   first_party_sets::FirstPartySetsPolicyServiceFactory::GetForBrowserContext(
       browser_context())
       ->ComputeFirstPartySetMetadata(
diff --git a/tools/metrics/histograms/metadata/stability/enums.xml b/tools/metrics/histograms/metadata/stability/enums.xml
index b6a79ff..2e12749 100644
--- a/tools/metrics/histograms/metadata/stability/enums.xml
+++ b/tools/metrics/histograms/metadata/stability/enums.xml
@@ -131,6 +131,7 @@
   <int value="11" label="RFH_INVALID_WEB_FRAME_URL"/>
   <int value="12" label="PVM_PRINT_FENCED_FRAME"/>
   <int value="13" label="SAGPC_INVALID_PERMISSION_REQUEST_CONTEXT"/>
+  <int value="14" label="TLSAPC_INVALID_PERMISSION_REQUEST_CONTEXT"/>
 </enum>
 
 <enum name="BadMessageReasonContent">
diff --git a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
index c451452..2af14b8 100644
--- a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
+++ b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
@@ -24,6 +24,7 @@
 #include "content/public/browser/permission_result.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
@@ -233,6 +234,20 @@
           .status);
 }
 
+TEST_F(TopLevelStorageAccessPermissionContextTest, SameSiteDisallowed) {
+  TopLevelStorageAccessPermissionContext permission_context(profile());
+  NavigateAndCommit(GetTopLevelURL());
+
+  EXPECT_EQ(
+      DecidePermissionSync(&permission_context, /*user_gesture=*/true,
+                           GetDummyEmbeddingUrl(), GetDummyEmbeddingUrl()),
+      PermissionStatus::DENIED);
+
+  EXPECT_EQ(1, static_cast<content::MockRenderProcessHost*>(
+                   web_contents()->GetPrimaryMainFrame()->GetProcess())
+                   ->bad_msg_count());
+}
+
 class TopLevelStorageAccessPermissionContextAPIWithFirstPartySetsTest
     : public TopLevelStorageAccessPermissionContextTest {
  public:
diff --git a/chrome/browser/top_level_storage_access_api/BUILD.gn b/chrome/browser/top_level_storage_access_api/BUILD.gn
index 73a7a6de..fb5019a 100644
--- a/chrome/browser/top_level_storage_access_api/BUILD.gn
+++ b/chrome/browser/top_level_storage_access_api/BUILD.gn
@@ -15,6 +15,7 @@
   ]
 
   deps = [
+    "//chrome/browser:bad_message",
     "//chrome/browser/content_settings:content_settings_factory",
     "//chrome/browser/first_party_sets",
     "//chrome/common",
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
index c451452..2af14b8 100644
--- a/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
+++ b/chrome/browser/top_level_storage_access_api/top_level_storage_access_permission_context_unittest.cc
@@ -24,6 +24,7 @@
 #include "content/public/browser/permission_result.h"
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/content_features.h"
+#include "content/public/test/mock_render_process_host.h"
 #include "content/public/test/navigation_simulator.h"
 #include "content/public/test/test_renderer_host.h"
 #include "content/public/test/web_contents_tester.h"
@@ -233,6 +234,20 @@
           .status);
 }
 
+TEST_F(TopLevelStorageAccessPermissionContextTest, SameSiteDisallowed) {
+  TopLevelStorageAccessPermissionContext permission_context(profile());
+  NavigateAndCommit(GetTopLevelURL());
+
+  EXPECT_EQ(
+      DecidePermissionSync(&permission_context, /*user_gesture=*/true,
+                           GetDummyEmbeddingUrl(), GetDummyEmbeddingUrl()),
+      PermissionStatus::DENIED);
+
+  EXPECT_EQ(1, static_cast<content::MockRenderProcessHost*>(
+                   web_contents()->GetPrimaryMainFrame()->GetProcess())
+                   ->bad_msg_count());
+}
+
 class TopLevelStorageAccessPermissionContextAPIWithFirstPartySetsTest
     : public TopLevelStorageAccessPermissionContextTest {
  public:
Loading diff…

Original Bug Report

The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.