Low CVSS 6.5 chrome Policy Bypass 🔧 Commit mapped

Overview

Low
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in WebView
DescriptionInsufficient policy enforcement in WebView
ComponentWebView
Bug ClassPolicy Bypass
Tracker517335150
Fix commit35ef18f1aad2 (chromium/src) +131/-29
CWECWE-602
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-05-28 (per vendor advisory)
Fix landed2026-06-09 (public git)
Disclosed2026-07-29
Patch gap50 days — fix public in source before the advisory

Changed Functions

FunctionChangeNotes
if
android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
modified
if
android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
modified
if
android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
modified

Files Changed

  • android_webview/java/src/org/chromium/android_webview/common/AwSupervisedUserUrlClassifierDelegate.java
  • android_webview/java/src/org/chromium/android_webview/common/PlatformServiceBridge.java
  • android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
  • android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
  • android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
From 35ef18f1aad291729f59c9c7acb7af92996c2513 Mon Sep 17 00:00:00 2001
From: Chidera Olibie <colibie@google.com>
Date: Tue, 09 Jun 2026 08:55:02 -0700
Subject: [PATCH] Only update cached SB/RCB preference if non-null result received

This CL updates Safe Browsing and Restricted Content Blocking config
helpers to only update their cached preferences (and log associated
histograms) when GMS queries return a non-null result (i.e. not on
timeout or error).

It also updates corresponding histograms.xml to document this behavior,
and adds test cases to verify it.

TAG=agy
CONV=151e4036-1bf7-4756-a458-17bb9a74d65f

Bug: 508162988, 517335150
Change-Id: I03aa75a1043647a696b0fdc479af55184f92fef1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7899996
Auto-Submit: Chidera Olibie <colibie@google.com>
Reviewed-by: Nate Fischer <ntfschr@chromium.org>
Reviewed-by: Daniel Rubery <drubery@chromium.org>
Reviewed-by: Michael van Ouwerkerk <mvanouwerkerk@chromium.org>
Commit-Queue: Daniel Rubery <drubery@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1644001}
---

diff --git a/android_webview/java/src/org/chromium/android_webview/common/AwSupervisedUserUrlClassifierDelegate.java b/android_webview/java/src/org/chromium/android_webview/common/AwSupervisedUserUrlClassifierDelegate.java
index 2e86f943..ca7066bb2 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/AwSupervisedUserUrlClassifierDelegate.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/AwSupervisedUserUrlClassifierDelegate.java
@@ -6,6 +6,7 @@
 
 import org.chromium.base.Callback;
 import org.chromium.build.annotations.NullMarked;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.url.GURL;
 
 /**
@@ -35,5 +36,5 @@
      * <p>callback.onResult(false) - indicates the user does not require restricted content
      * blocking. callback.onResult(true) - indicates the user requires restricted content blocking.
      */
-    void needsRestrictedContentBlocking(final Callback<Boolean> callback);
+    void needsRestrictedContentBlocking(final Callback<@Nullable Boolean> callback);
 }
diff --git a/android_webview/java/src/org/chromium/android_webview/common/PlatformServiceBridge.java b/android_webview/java/src/org/chromium/android_webview/common/PlatformServiceBridge.java
index df35c5a..0c829bb1 100644
--- a/android_webview/java/src/org/chromium/android_webview/common/PlatformServiceBridge.java
+++ b/android_webview/java/src/org/chromium/android_webview/common/PlatformServiceBridge.java
@@ -63,7 +63,7 @@
     }
 
     // Overriding implementations may call "callback" asynchronously, on any thread.
-    public void querySafeBrowsingUserConsent(final Callback<Boolean> callback) {
+    public void querySafeBrowsingUserConsent(final Callback<@Nullable Boolean> callback) {
         // User opt-in preference depends on a SafetyNet API. In purely upstream builds (which don't
         // communicate with GMS), assume the user has not opted in.
         callback.onResult(false);
diff --git a/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java b/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
index f64b87f4..d95a5eb 100644
--- a/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
+++ b/android_webview/java/src/org/chromium/android_webview/safe_browsing/AwSafeBrowsingConfigHelper.java
@@ -77,14 +77,15 @@
     }
 
     public static void maybeEnableSafeBrowsingFromGms() {
-        Callback<Boolean> cb =
+        Callback<@Nullable Boolean> cb =
                 verifyAppsValue -> {
-                    ThreadUtils.postOnUiThread(
-                            () -> {
-                                AwSafeBrowsingConfigHelperJni.get()
-                                        .setSafeBrowsingUserOptIn(
-                                                Boolean.TRUE.equals(verifyAppsValue));
-                            });
+                    if (verifyAppsValue != null) {
+                        ThreadUtils.postOnUiThread(
+                                () -> {
+                                    AwSafeBrowsingConfigHelperJni.get()
+                                            .setSafeBrowsingUserOptIn(verifyAppsValue);
+                                });
+                    }
                 };
         PlatformServiceBridge.getInstance().querySafeBrowsingUserConsent(cb);
     }
diff --git a/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java b/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
index cf2bffa..43025df 100644
--- a/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
+++ b/android_webview/java/src/org/chromium/android_webview/supervised_user/AwSupervisedUserUrlClassifier.java
@@ -74,11 +74,13 @@
     public void checkIfNeedRestrictedContentBlocking() {
         mDelegate.needsRestrictedContentBlocking(
                 result -> {
-                    ThreadUtils.postOnUiThread(
-                            () -> {
-                                AwSupervisedUserUrlClassifierJni.get()
-                                        .setUserRequiresUrlChecks(result);
-                            });
+                    if (result != null) {
+                        ThreadUtils.postOnUiThread(
+                                () -> {
+                                    AwSupervisedUserUrlClassifierJni.get()
+                                            .setUserRequiresUrlChecks(result);
+                                });
+                    }
                 });
     }
 
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
index 9dc78d71..577147b 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
@@ -50,6 +50,7 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.content_public.browser.MessagePayload;
@@ -420,13 +421,15 @@
 
     private static class OnProgressChangedClient extends TestAwContentsClient {
         private final CallbackHelper mCallbackHelper = new CallbackHelper();
+        private int mLastProgress = -1;
 
         @Override
         public void onProgressChanged(int progress) {
             super.onProgressChanged(progress);
-            if (progress == 100) {
+            if (progress == 100 && mLastProgress != 100) {
                 mCallbackHelper.notifyCalled();
             }
+            mLastProgress = progress;
         }
 
         public void waitForFullLoad() throws TimeoutException {
@@ -462,7 +465,7 @@
         // works.
         private final Executor mExecutor = new BackgroundThreadExecutor("TEST_BACKGROUND_THREAD");
         private final CallbackHelper mNeedsRestrictionHelper = new CallbackHelper();
-        private boolean mNeedsRestrictionResponse;
+        private @Nullable Boolean mNeedsRestrictionResponse;
         private static final Set RESTRICTED_CONTENT_BLOCKLIST =
                 Set.of(MATURE_SITE_PATH, MATURE_SITE_IFRAME_PATH);
 
@@ -477,7 +480,7 @@
         }
 
         @Override
-        public void needsRestrictedContentBlocking(final Callback<Boolean> callback) {
+        public void needsRestrictedContentBlocking(final Callback<@Nullable Boolean> callback) {
             mExecutor.execute(
                     () -> {
                         callback.onResult(mNeedsRestrictionResponse);
@@ -485,7 +488,7 @@
                     });
         }
 
-        public void setNeedsRestrictedContentBlockingResponse(boolean value) {
+        public void setNeedsRestrictedContentBlockingResponse(@Nullable Boolean value) {
             mNeedsRestrictionResponse = value;
         }
 
@@ -507,7 +510,7 @@
         }
     }
 
-    private void resetNeedsRestriction(boolean value) throws Exception {
+    private void resetNeedsRestriction(@Nullable Boolean value) throws Exception {
         mDelegate.setNeedsRestrictedContentBlockingResponse(value);
         int count = mDelegate.getNeedsRestrictionHelper().getCallCount();
         AwSupervisedUserUrlClassifier classifier = AwSupervisedUserUrlClassifier.getInstance();
@@ -516,4 +519,49 @@
         classifier.checkIfNeedRestrictedContentBlocking();
         mDelegate.getNeedsRestrictionHelper().waitForCallback(count);
     }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testRestrictedContentBlockingNullDoesNotUpdateCache() throws Throwable {
+        String embeddedUrl = setUpWebPage(MATURE_SITE_IFRAME_PATH, MATURE_SITE_IFRAME_TITLE, null);
+        String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, embeddedUrl);
+
+        // Start with restriction enabled (setUp sets it to true, but let's be explicit)
+        resetNeedsRestriction(true);
+        loadUrl(requestUrl);
+        assertPageTitle(BLOCKED_SITE_TITLE);
+
+        // Now set restriction response to null (simulating timeout/error)
+        // It should NOT update the cache, so restriction should remain enabled (mature pages
+        // blocked)
+        // We also check that the histogram is NOT recorded.
+        try (HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords(
+                                "Android.WebView.RestrictedContentBlocking.ApiCallMatchesDiskCache")
+                        .build()) {
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
index 9dc78d71..577147b 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/AwSupervisedUserTest.java
@@ -50,6 +50,7 @@
 import org.chromium.base.test.util.Criteria;
 import org.chromium.base.test.util.CriteriaHelper;
 import org.chromium.base.test.util.Feature;
+import org.chromium.base.test.util.HistogramWatcher;
 import org.chromium.build.annotations.NullMarked;
 import org.chromium.build.annotations.Nullable;
 import org.chromium.content_public.browser.MessagePayload;
@@ -420,13 +421,15 @@
 
     private static class OnProgressChangedClient extends TestAwContentsClient {
         private final CallbackHelper mCallbackHelper = new CallbackHelper();
+        private int mLastProgress = -1;
 
         @Override
         public void onProgressChanged(int progress) {
             super.onProgressChanged(progress);
-            if (progress == 100) {
+            if (progress == 100 && mLastProgress != 100) {
                 mCallbackHelper.notifyCalled();
             }
+            mLastProgress = progress;
         }
 
         public void waitForFullLoad() throws TimeoutException {
@@ -462,7 +465,7 @@
         // works.
         private final Executor mExecutor = new BackgroundThreadExecutor("TEST_BACKGROUND_THREAD");
         private final CallbackHelper mNeedsRestrictionHelper = new CallbackHelper();
-        private boolean mNeedsRestrictionResponse;
+        private @Nullable Boolean mNeedsRestrictionResponse;
         private static final Set RESTRICTED_CONTENT_BLOCKLIST =
                 Set.of(MATURE_SITE_PATH, MATURE_SITE_IFRAME_PATH);
 
@@ -477,7 +480,7 @@
         }
 
         @Override
-        public void needsRestrictedContentBlocking(final Callback<Boolean> callback) {
+        public void needsRestrictedContentBlocking(final Callback<@Nullable Boolean> callback) {
             mExecutor.execute(
                     () -> {
                         callback.onResult(mNeedsRestrictionResponse);
@@ -485,7 +488,7 @@
                     });
         }
 
-        public void setNeedsRestrictedContentBlockingResponse(boolean value) {
+        public void setNeedsRestrictedContentBlockingResponse(@Nullable Boolean value) {
             mNeedsRestrictionResponse = value;
         }
 
@@ -507,7 +510,7 @@
         }
     }
 
-    private void resetNeedsRestriction(boolean value) throws Exception {
+    private void resetNeedsRestriction(@Nullable Boolean value) throws Exception {
         mDelegate.setNeedsRestrictedContentBlockingResponse(value);
         int count = mDelegate.getNeedsRestrictionHelper().getCallCount();
         AwSupervisedUserUrlClassifier classifier = AwSupervisedUserUrlClassifier.getInstance();
@@ -516,4 +519,49 @@
         classifier.checkIfNeedRestrictedContentBlocking();
         mDelegate.getNeedsRestrictionHelper().waitForCallback(count);
     }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testRestrictedContentBlockingNullDoesNotUpdateCache() throws Throwable {
+        String embeddedUrl = setUpWebPage(MATURE_SITE_IFRAME_PATH, MATURE_SITE_IFRAME_TITLE, null);
+        String requestUrl = setUpWebPage(MATURE_SITE_PATH, MATURE_SITE_TITLE, embeddedUrl);
+
+        // Start with restriction enabled (setUp sets it to true, but let's be explicit)
+        resetNeedsRestriction(true);
+        loadUrl(requestUrl);
+        assertPageTitle(BLOCKED_SITE_TITLE);
+
+        // Now set restriction response to null (simulating timeout/error)
+        // It should NOT update the cache, so restriction should remain enabled (mature pages
+        // blocked)
+        // We also check that the histogram is NOT recorded.
+        try (HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords(
+                                "Android.WebView.RestrictedContentBlocking.ApiCallMatchesDiskCache")
+                        .build()) {
+            resetNeedsRestriction(null);
+            loadUrl(requestUrl);
+            assertPageTitle(BLOCKED_SITE_TITLE);
+        }
+
+        // Now set restriction to false
+        resetNeedsRestriction(false);
+        loadUrl(requestUrl);
+        assertPageTitle(MATURE_SITE_TITLE);
+
+        // Now set restriction response to null again
+        // It should NOT update the cache, so restriction should remain disabled (mature pages
+        // allowed)
+        try (HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords(
+                                "Android.WebView.RestrictedContentBlocking.ApiCallMatchesDiskCache")
+                        .build()) {
+            resetNeedsRestriction(null);
+            loadUrl(requestUrl);
+            assertPageTitle(MATURE_SITE_TITLE);
+        }
+    }
 }
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java
index 84c84447..dab5556 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/SafeBrowsingTest.java
@@ -56,6 +56,7 @@
 import org.chromium.base.test.util.DoNotBatch;
 import org.chromium.base.test.util.Feature;
 import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.build.annotations.Nullable;
 import org.chromium.components.safe_browsing.SafeBrowsingApiBridge;
 import org.chromium.components.safe_browsing.SafeBrowsingApiHandler;
 import org.chromium.net.test.EmbeddedTestServer;
@@ -183,8 +184,8 @@
      * A fake PlatformServiceBridge that allows tests to make safe browsing requests without GMS.
      */
     private static class MockPlatformServiceBridge extends PlatformServiceBridge {
-        private Callback<Boolean> mCallback;
-        private Boolean mConsent;
+        private Callback<@Nullable Boolean> mCallback;
+        private @Nullable Boolean mConsent;
 
         @Override
         public boolean canUseGms() {
@@ -192,7 +193,7 @@
         }
 
         @Override
-        public void querySafeBrowsingUserConsent(Callback<Boolean> callback) {
+        public void querySafeBrowsingUserConsent(Callback<@Nullable Boolean> callback) {
             mCallback = callback;
             if (mConsent != null) {
                 callback.onResult(mConsent);
@@ -201,7 +202,7 @@
 
         public void setConsent(Boolean consent) {
             mConsent = consent;
-            if (mCallback != null && consent != null) {
+            if (mCallback != null) {
                 mCallback.onResult(consent);
             }
         }
@@ -1281,4 +1282,49 @@
             Assert.assertFalse(AwSafeBrowsingConfigHelper.getSafeBrowsingUserOptInForTesting());
         }
     }
+
+    @Test
+    @SmallTest
+    @Feature({"AndroidWebView"})
+    public void testSafeBrowsingUserOptInNullDoesNotUpdateCache() throws Throwable {
+        MockPlatformServiceBridge bridge =
+                (MockPlatformServiceBridge) PlatformServiceBridge.getInstance();
+
+        // Set initial consent to true in pref
+        AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptInForTesting(true);
+
+        bridge.setConsent(null); // Don't return immediately
+
+        AwSafeBrowsingConfigHelper.maybeEnableSafeBrowsingFromGms();
+
+        // Now trigger callback with null (simulating timeout/error)
+        // We expect that the cache is NOT updated (stays true)
+        // And we expect NO histogram record for ApiCallMatchesDiskCache
+        try (HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords("SafeBrowsing.WebView.GmsOptIn.ApiCallMatchesDiskCache")
+                        .build()) {
+            bridge.setConsent(null);
+            // Waits for posted task from callback to complete (if any)
+            // and verifies pref is still true
+            Assert.assertTrue(AwSafeBrowsingConfigHelper.getSafeBrowsingUserOptInForTesting());
+        }
+
+        // Set initial consent to false in pref
+        AwSafeBrowsingConfigHelper.setSafeBrowsingUserOptInForTesting(false);
+
+        bridge.setConsent(null); // Reset
+
+        AwSafeBrowsingConfigHelper.maybeEnableSafeBrowsingFromGms();
+
+        // Trigger callback with null again
+        // We expect that the cache is NOT updated (stays false)
+        try (HistogramWatcher watcher =
+                HistogramWatcher.newBuilder()
+                        .expectNoRecords("SafeBrowsing.WebView.GmsOptIn.ApiCallMatchesDiskCache")
+                        .build()) {
+            bridge.setConsent(null);
+            Assert.assertFalse(AwSafeBrowsingConfigHelper.getSafeBrowsingUserOptInForTesting());
+        }
+    }
 }
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.