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 WebView
DescriptionInsufficient validation of untrusted input in WebView
ComponentWebView
Bug ClassLogic Error
Tracker495834228
Fix commit43ea7494cc63 (chromium/src) +105/-56
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
if
android_webview/browser/aw_cookie_access_policy.cc
modified
switch
android_webview/browser/aw_cookie_access_policy.cc
modified
AwCookieAccessPolicyTableTest
android_webview/browser/aw_cookie_access_policy_unittest.cc
modified
TEST_P
android_webview/browser/aw_cookie_access_policy_unittest.cc
modified

Files Changed

  • android_webview/browser/aw_cookie_access_policy.cc
  • android_webview/browser/aw_cookie_access_policy.h
  • android_webview/browser/aw_cookie_access_policy_unittest.cc
  • android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
From 43ea7494cc63fa4352ca38ba95b21f3c910ee88f Mon Sep 17 00:00:00 2001
From: Chris Fredrickson <[email protected]>
Date: Mon, 13 Apr 2026 13:03:23 -0700
Subject: [PATCH] [SAA] Disallow renderer-based 3PCD policy bypass

It's unclear to me if this CL will have any real effect, since SAA
permission autogrants were removed in https://crrev.com/c/7172818. (The
CL that added this logic, https://crrev.com/c/5083622, says that it has
no effect when no permissions are granted.) But we can at least clean up
the unused parameters anyway and make the code less misleading.

Fixed: 495834228
Change-Id: If32f57c5424e51669de76489641bc887af0c74be
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7726801
Reviewed-by: Richard Coles <[email protected]>
Commit-Queue: Richard Coles <[email protected]>
Auto-Submit: Chris Fredrickson <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1613921}
---

diff --git a/android_webview/browser/aw_cookie_access_policy.cc b/android_webview/browser/aw_cookie_access_policy.cc
index 3edaf5685..78e7078 100644
--- a/android_webview/browser/aw_cookie_access_policy.cc
+++ b/android_webview/browser/aw_cookie_access_policy.cc
@@ -68,22 +68,18 @@
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     base::optional_ref<const content::GlobalRenderFrameHostToken>
-        global_frame_token,
-    net::StorageAccessApiStatus storage_access_api_status) {
+        global_frame_token) {
   DCHECK_CURRENTLY_ON(BrowserThread::IO);
   bool third_party = GetShouldAcceptThirdPartyCookies(global_frame_token);
-  return CanAccessCookies(url, site_for_cookies, third_party,
-                          storage_access_api_status);
+  return CanAccessCookies(url, site_for_cookies, third_party);
 }
 
 PrivacySetting AwCookieAccessPolicy::CanAccessCookies(
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
-    bool accept_third_party_cookies,
-    net::StorageAccessApiStatus storage_access_api_status) {
+    bool accept_third_party_cookies) {
   return CanAccessCookies(url, site_for_cookies, accept_cookies_,
-                          accept_third_party_cookies,
-                          storage_access_api_status);
+                          accept_third_party_cookies);
 }
 
 // static
@@ -91,8 +87,7 @@
     const GURL& url,
     const net::SiteForCookies& site_for_cookies,
     bool accept_cookies,
-    bool accept_third_party_cookies,
-    net::StorageAccessApiStatus storage_access_api_status) {
+    bool accept_third_party_cookies) {
   if (!accept_cookies) {
     return PrivacySetting::kStateDisallowed;
   }
@@ -109,13 +104,6 @@
     return PrivacySetting::kStateAllowed;
   }
 
-  switch (storage_access_api_status) {
-    case net::StorageAccessApiStatus::kNone:
-      break;
-    case net::StorageAccessApiStatus::kAccessViaAPI:
-      return PrivacySetting::kStateAllowed;
-  }
-
   // Otherwise, block third-party cookies.
   bool should_allow_3pcs =
       net::StaticCookiePolicy(
diff --git a/android_webview/browser/aw_cookie_access_policy.h b/android_webview/browser/aw_cookie_access_policy.h
index 854da01..5b097c3 100644
--- a/android_webview/browser/aw_cookie_access_policy.h
+++ b/android_webview/browser/aw_cookie_access_policy.h
@@ -48,14 +48,12 @@
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       base::optional_ref<const content::GlobalRenderFrameHostToken>
-          global_frame_token,
-      net::StorageAccessApiStatus storage_access_api_status);
+          global_frame_token);
 
   net::NetworkDelegate::PrivacySetting CanAccessCookies(
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
-      bool accept_third_party_cookies,
-      net::StorageAccessApiStatus storage_access_api_status);
+      bool accept_third_party_cookies);
 
   // Static version that takes all policy values as parameters, allowing
   // callers to use pre-captured/latched values instead of dynamic lookups.
@@ -63,8 +61,7 @@
       const GURL& url,
       const net::SiteForCookies& site_for_cookies,
       bool accept_cookies,
-      bool accept_third_party_cookies,
-      net::StorageAccessApiStatus storage_access_api_status);
+      bool accept_third_party_cookies);
 
  private:
   friend class AwCookieAccessPolicyTest;
diff --git a/android_webview/browser/aw_cookie_access_policy_unittest.cc b/android_webview/browser/aw_cookie_access_policy_unittest.cc
new file mode 100644
index 0000000..318282df
--- /dev/null
+++ b/android_webview/browser/aw_cookie_access_policy_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "android_webview/browser/aw_cookie_access_policy.h"
+
+#include <string_view>
+
+#include "net/cookies/site_for_cookies.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace android_webview {
+
+using PrivacySetting = net::NetworkDelegate::PrivacySetting;
+
+struct CanAccessCookiesTestCase {
+  std::string_view url;
+  std::string_view site_for_cookies_url;
+  bool accept_cookies;
+  bool accept_third_party_cookies;
+  PrivacySetting expected;
+};
+
+class AwCookieAccessPolicyTableTest
+    : public testing::TestWithParam<CanAccessCookiesTestCase> {};
+
+TEST_P(AwCookieAccessPolicyTableTest, CanAccessCookies) {
+  const auto& test_case = GetParam();
+  GURL url(test_case.url);
+  net::SiteForCookies site_for_cookies =
+      net::SiteForCookies::FromUrl(GURL(test_case.site_for_cookies_url));
+
+  EXPECT_EQ(AwCookieAccessPolicy::CanAccessCookies(
+                url, site_for_cookies, test_case.accept_cookies,
+                test_case.accept_third_party_cookies),
+            test_case.expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    AwCookieAccessPolicyTableTest,
+    testing::Values(
+        // DisallowCookies
+        CanAccessCookiesTestCase{"https://example.com", "https://example.com",
+                                 /*accept_cookies=*/false,
+                                 /*accept_third_party_cookies=*/false,
+                                 PrivacySetting::kStateDisallowed},
+        CanAccessCookiesTestCase{"https://example.com", "https://example.com",
+                                 /*accept_cookies=*/false,
+                                 /*accept_third_party_cookies=*/true,
+                                 PrivacySetting::kStateDisallowed},
+
+        // AllowThirdPartyCookies
+        CanAccessCookiesTestCase{"https://other.com", "https://example.com",
+                                 /*accept_cookies=*/true,
+                                 /*accept_third_party_cookies=*/true,
+                                 PrivacySetting::kStateAllowed},
+
+        // FileSchemeAllowed
+        CanAccessCookiesTestCase{
+            "file:///path/to/file.html", "file:///path/to/file.html",
+            /*accept_cookies=*/true, /*accept_third_party_cookies=*/false,
+            PrivacySetting::kStateAllowed},
+
+        // BlockThirdPartyCookies: First-party
+        CanAccessCookiesTestCase{"https://example.com", "https://example.com",
+                                 /*accept_cookies=*/true,
+                                 /*accept_third_party_cookies=*/false,
+                                 PrivacySetting::kStateAllowed},
+
+        // BlockThirdPartyCookies: Third-party
+        CanAccessCookiesTestCase{
+            "https://other.com", "https://example.com",
+            /*accept_cookies=*/true, /*accept_third_party_cookies=*/false,
+            PrivacySetting::kPartitionedStateAllowedOnly}));
+
+}  // namespace android_webview
diff --git a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
index 97da2b3..d7b9d83 100644
--- a/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
+++ b/android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc
@@ -45,18 +45,16 @@
       const net::SiteForCookies& site_for_cookies,
       base::WeakPtr<AwProxyingRestrictedCookieManager>
           aw_restricted_cookie_manager,
-      mojo::PendingRemote<network::mojom::CookieChangeListener> client_listener,
-      net::StorageAccessApiStatus storage_access_api_status)
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/android_webview/browser/aw_cookie_access_policy_unittest.cc b/android_webview/browser/aw_cookie_access_policy_unittest.cc
new file mode 100644
index 0000000..318282df
--- /dev/null
+++ b/android_webview/browser/aw_cookie_access_policy_unittest.cc
@@ -0,0 +1,78 @@
+// Copyright 2026 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "android_webview/browser/aw_cookie_access_policy.h"
+
+#include <string_view>
+
+#include "net/cookies/site_for_cookies.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace android_webview {
+
+using PrivacySetting = net::NetworkDelegate::PrivacySetting;
+
+struct CanAccessCookiesTestCase {
+  std::string_view url;
+  std::string_view site_for_cookies_url;
+  bool accept_cookies;
+  bool accept_third_party_cookies;
+  PrivacySetting expected;
+};
+
+class AwCookieAccessPolicyTableTest
+    : public testing::TestWithParam<CanAccessCookiesTestCase> {};
+
+TEST_P(AwCookieAccessPolicyTableTest, CanAccessCookies) {
+  const auto& test_case = GetParam();
+  GURL url(test_case.url);
+  net::SiteForCookies site_for_cookies =
+      net::SiteForCookies::FromUrl(GURL(test_case.site_for_cookies_url));
+
+  EXPECT_EQ(AwCookieAccessPolicy::CanAccessCookies(
+                url, site_for_cookies, test_case.accept_cookies,
+                test_case.accept_third_party_cookies),
+            test_case.expected);
+}
+
+INSTANTIATE_TEST_SUITE_P(
+    All,
+    AwCookieAccessPolicyTableTest,
+    testing::Values(
+        // DisallowCookies
+        CanAccessCookiesTestCase{"https://example.com", "https://example.com",
+                                 /*accept_cookies=*/false,
+                                 /*accept_third_party_cookies=*/false,
+                                 PrivacySetting::kStateDisallowed},
+        CanAccessCookiesTestCase{"https://example.com", "https://example.com",
+                                 /*accept_cookies=*/false,
+                                 /*accept_third_party_cookies=*/true,
+                                 PrivacySetting::kStateDisallowed},
+
+        // AllowThirdPartyCookies
+        CanAccessCookiesTestCase{"https://other.com", "https://example.com",
+                                 /*accept_cookies=*/true,
+                                 /*accept_third_party_cookies=*/true,
+                                 PrivacySetting::kStateAllowed},
+
+        // FileSchemeAllowed
+        CanAccessCookiesTestCase{
+            "file:///path/to/file.html", "file:///path/to/file.html",
+            /*accept_cookies=*/true, /*accept_third_party_cookies=*/false,
+            PrivacySetting::kStateAllowed},
+
+        // BlockThirdPartyCookies: First-party
+        CanAccessCookiesTestCase{"https://example.com", "https://example.com",
+                                 /*accept_cookies=*/true,
+                                 /*accept_third_party_cookies=*/false,
+                                 PrivacySetting::kStateAllowed},
+
+        // BlockThirdPartyCookies: Third-party
+        CanAccessCookiesTestCase{
+            "https://other.com", "https://example.com",
+            /*accept_cookies=*/true, /*accept_third_party_cookies=*/false,
+            PrivacySetting::kPartitionedStateAllowedOnly}));
+
+}  // namespace android_webview
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 5ff4fb89..c9523a33 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -839,6 +839,7 @@
     "../browser/aw_content_browser_client_content_restriction_throttles_unittest.cc",
     "../browser/aw_content_browser_client_unittest.cc",
     "../browser/aw_contents_client_bridge_unittest.cc",
+    "../browser/aw_cookie_access_policy_unittest.cc",
     "../browser/aw_crash_keys_unittest.cc",
     "../browser/aw_field_trials_unittest.cc",
     "../browser/aw_origin_matched_header_unittest.cc",
Loading diff…

Original Bug Report

reported by [email protected]

Potential bypass of WebView third-party cookie blocking via StorageAccessApiStatus

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

Overview: A compromised renderer can bypass Android WebView’s third-party cookie blocking policy by spoofing the StorageAccessApiStatus::kAccessViaAPI flag in Mojo requests. This forces AwCookieAccessPolicy to unconditionally allow cookie access, granting a third-party iframe access to its unpartitioned first-party cookies.

Affected files:

  • android_webview/browser/aw_cookie_access_policy.cc
  • android_webview/browser/network_service/aw_proxying_restricted_cookie_manager.cc

Estimated timestamp from git blame: 2025-01-17

A compromised renderer successfully bypasses Android WebView’s third-party cookie blocking (setAcceptThirdPartyCookies(false)) by supplying net::StorageAccessApiStatus::kAccessViaAPI over Mojo, allowing cross-site tracking.

Technical Details

Initial WebView proxy architecture and Mojo routing for RestrictedCookieManager and URLLoaderFactory are validated. The WebView network context defaults to allowing cookies, relying entirely on the browser-process proxy (AwProxyingRestrictedCookieManager and AwProxyingURLLoaderFactory) for policy enforcement.

Standard IPC payload delivery is executed by the compromised renderer, sending cookie read/write requests or subresource loads with the storage_access_api_status field manipulated.

The vulnerability materializes directly in AwCookieAccessPolicy::CanAccessCookies (android_webview/browser/aw_cookie_access_policy.cc). When the spoofed kAccessViaAPI flag is evaluated, the code performs the following:

switch (storage_access_api_status) {
  case net::StorageAccessApiStatus::kNone:
    break;
  case net::StorageAccessApiStatus::kAccessViaAPI:
    return PrivacySetting::kStateAllowed;
}

// Otherwise, block third-party cookies.
bool should_allow_3pcs = ...

The switch statement immediately returns PrivacySetting::kStateAllowed. This unconditionally bypasses the subsequent should_allow_3pcs check. Consequently, the proxy computes disable_3pcs as false and forwards the request to the network service, which allows the unpartitioned cookie access.

A parallel bypass exists for network requests via URLLoaderFactory::CreateLoaderAndStart. In AwProxyingURLLoaderFactory::ConstructOptions, hasStorageAccess evaluates to true, preventing the proxy from applying the network::mojom::kURLLoadOptionBlockThirdPartyCookies option.

Potential Exploitation Steps

(Note: These are potential steps as the AI agent cannot execute code.)

  1. Standard setup: An Android app embeds a WebView, calls setAcceptThirdPartyCookies(false), and loads a third-party iframe.
  2. Renderer compromise is achieved within the third-party iframe.
  3. Standard proxy interaction applied: The compromised renderer issues a RestrictedCookieManager::GetAllForUrl or URLLoaderFactory::CreateLoaderAndStart Mojo request, setting storage_access_api_status to kAccessViaAPI.
  4. The proxy evaluates CanAccessCookies, hits the kAccessViaAPI switch case, and immediately returns kStateAllowed, finalizing the privacy bypass.

Suggested Fix

Android WebView currently hardcodes the Storage Access API permission to DENIED. Therefore, AwCookieAccessPolicy::CanAccessCookies should not treat kAccessViaAPI as an unconditional allow. The kAccessViaAPI case should either be removed (falling through to the standard 3PC checks) or explicitly treated as kStateDisallowed until the Storage Access API is fully and safely supported in WebView.

Evaluated with Chrome root at commit: 0eb4855bda702feaaa8b899336664f97e3df88b8


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. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker