Chrome · WebView
CVE-2026-87522
Logic Error in WebView
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifandroid_webview/browser/aw_content_browser_client.cc |
modified |
Files Changed
android_webview/browser/BUILD.gnandroid_webview/browser/DEPSandroid_webview/browser/aw_content_browser_client.ccandroid_webview/browser/aw_content_browser_client.handroid_webview/common/aw_features.ccandroid_webview/common/aw_features.handroid_webview/javatests/DEPSandroid_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
Patch
From e84aaf8b1e016ff23ddc3e4d6e9e5d5e7ec6f35f Mon Sep 17 00:00:00 2001 From: Nate Fischer <[email protected]> Date: Tue, 04 Aug 2026 23:18:51 -0700 Subject: [PATCH] Block WebAuthn requests on pages with SSL errors AwContentBrowserClient inherits ContentBrowserClient::IsSecurityLevelAcceptableForWebAuthn which unconditionally returns true, allowing WebAuthn requests to proceed without considering TLS errors. This CL overrides IsSecurityLevelAcceptableForWebAuthn in AwContentBrowserClient to check the security level similar to what Chrome browser does. This adds an end-to-end test verifying WebAuthn is blocked on sites with SSL errors. TAG=agy CONV=c1aad737-b624-4e3e-8c12-a5dd34c64316 Fixed: 507219126 Test: run_webview_instrumentation_test_apk -f WebAuthnTest.* Change-Id: I977fe7ec62450f2ab6ae93458a2bd6d62213315e Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8178688 Auto-Submit: Nate Fischer <[email protected]> Reviewed-by: Rupert Wiser <[email protected]> Commit-Queue: Rupert Wiser <[email protected]> Cr-Commit-Position: refs/heads/main@{#1673924} --- diff --git a/android_webview/browser/BUILD.gn b/android_webview/browser/BUILD.gn index 09c77123..cec21723b 100644 --- a/android_webview/browser/BUILD.gn +++ b/android_webview/browser/BUILD.gn @@ -297,6 +297,7 @@ "//components/minidump_uploader", "//components/navigation_interception", "//components/network_hints/browser", + "//components/network_session_configurator/common", "//components/optimization_guide/core:bloomfilter", "//components/origin_matcher", "//components/origin_trials:browser", @@ -332,6 +333,8 @@ "//components/sampling_profiler:profile_params", "//components/security_interstitials/content:security_interstitial_page", "//components/security_interstitials/core", + "//components/security_state/content", + "//components/security_state/core", "//components/sensitive_content", "//components/services/heap_profiling/public/cpp", "//components/spellcheck:buildflags", diff --git a/android_webview/browser/DEPS b/android_webview/browser/DEPS index 8f171c20..1a571c2 100644 --- a/android_webview/browser/DEPS +++ b/android_webview/browser/DEPS @@ -52,6 +52,8 @@ "+components/safe_browsing/core/browser", "+components/safe_browsing/core/common", "+components/security_interstitials", + "+components/security_state/content", + "+components/security_state/core", "+components/sensitive_content", "+components/services/heap_profiling", "+components/spellcheck/browser", diff --git a/android_webview/browser/aw_content_browser_client.cc b/android_webview/browser/aw_content_browser_client.cc index 13dabf1a..eadf3d5 100644 --- a/android_webview/browser/aw_content_browser_client.cc +++ b/android_webview/browser/aw_content_browser_client.cc @@ -81,6 +81,7 @@ #include "components/embedder_support/user_agent_utils.h" #include "components/heap_profiling/in_process/heap_profiler_controller.h" #include "components/navigation_interception/intercept_navigation_delegate.h" +#include "components/network_session_configurator/common/network_switches.h" #include "components/page_load_metrics/browser/metrics_navigation_throttle.h" #include "components/page_load_metrics/browser/metrics_web_contents_observer.h" #include "components/policy/content/policy_blocklist_navigation_throttle.h" @@ -93,6 +94,8 @@ #include "components/safe_browsing/core/common/features.h" #include "components/safe_browsing/core/common/hashprefix_realtime/hash_realtime_utils.h" #include "components/sampling_profiler/process_type.h" +#include "components/security_state/content/content_utils.h" +#include "components/security_state/core/security_state.h" #include "components/url_matcher/url_matcher.h" #include "components/url_matcher/url_util.h" #include "components/user_prefs/user_prefs.h" @@ -125,6 +128,7 @@ #include "mojo/public/cpp/bindings/pending_receiver.h" #include "net/android/network_library.h" #include "net/base/features.h" +#include "net/base/url_util.h" #include "net/cookies/cookie_setting_override.h" #include "net/cookies/site_for_cookies.h" #include "net/http/http_util.h" @@ -583,6 +587,32 @@ } } +bool AwContentBrowserClient::IsSecurityLevelAcceptableForWebAuthn( + content::RenderFrameHost* rfh, + const url::Origin& caller_origin) { + if (!base::FeatureList::IsEnabled( + android_webview::features::kWebViewWebAuthnRequiresSecureOrigin)) { + return true; + } + content::WebContents* web_contents = + content::WebContents::FromRenderFrameHost(rfh); + if (!web_contents) { + return false; + } + if (net::IsLocalhost(caller_origin.GetURL())) { + return true; + } + auto state = security_state::GetVisibleSecurityState(web_contents); + if (!state) { + return false; + } + security_state::SecurityLevel security_level = + security_state::GetSecurityLevel(*state); + return security_level == security_state::SecurityLevel::SECURE || + base::CommandLine::ForCurrentProcess()->HasSwitch( + switches::kIgnoreCertificateErrors); +} + base::OnceClosure AwContentBrowserClient::SelectClientCertificate( content::BrowserContext* browser_context, int process_id, diff --git a/android_webview/browser/aw_content_browser_client.h b/android_webview/browser/aw_content_browser_client.h index f18b542f..a2adea2 100644 --- a/android_webview/browser/aw_content_browser_client.h +++ b/android_webview/browser/aw_content_browser_client.h @@ -108,6 +108,9 @@ bool strict_enforcement, base::OnceCallback<void(content::CertificateRequestResultType)> callback) override; + bool IsSecurityLevelAcceptableForWebAuthn( + content::RenderFrameHost* rfh, + const url::Origin& caller_origin) override; base::OnceClosure SelectClientCertificate( content::BrowserContext* browser_context, int process_id, diff --git a/android_webview/common/aw_features.cc b/android_webview/common/aw_features.cc index cdcdcd6..0433b98 100644 --- a/android_webview/common/aw_features.cc +++ b/android_webview/common/aw_features.cc @@ -406,6 +406,10 @@ // browser process startup in WebView. BASE_FEATURE(kWebViewWarmupNetworkService, base::FEATURE_DISABLED_BY_DEFAULT); +// When enabled, WebAuthn requests are blocked on pages with TLS/SSL errors. +BASE_FEATURE(kWebViewWebAuthnRequiresSecureOrigin, + base::FEATURE_ENABLED_BY_DEFAULT); + // Kill switch for reporting web performance metrics. BASE_FEATURE(kWebViewWebPerformanceMetricsReporting, base::FEATURE_ENABLED_BY_DEFAULT); diff --git a/android_webview/common/aw_features.h b/android_webview/common/aw_features.h index bf614a6..836493d 100644 --- a/android_webview/common/aw_features.h +++ b/android_webview/common/aw_features.h @@ -99,6 +99,7 @@ BASE_DECLARE_FEATURE(kWebViewUseWVLESForLayeredStudy); BASE_DECLARE_FEATURE(kWebViewVizDirectCompositorThreadIpcFrameSinkManager); BASE_DECLARE_FEATURE(kWebViewWarmupNetworkService); +BASE_DECLARE_FEATURE(kWebViewWebAuthnRequiresSecureOrigin); BASE_DECLARE_FEATURE(kWebViewWebPerformanceMetricsReporting); // Don't add new features to the end! Insert them in alphabetical order to // reduce conflicts. diff --git a/android_webview/javatests/DEPS b/android_webview/javatests/DEPS index 15d6e6f..43dac3b 100644 --- a/android_webview/javatests/DEPS +++ b/android_webview/javatests/DEPS @@ -9,6 +9,7 @@ "+components/policy/android/javatests", "+components/safe_browsing/android/java", "+components/variations/android/java", + "+components/webauthn/android/java/src/org/chromium/components/webauthn", "-content/public/android/java", "+content/public/android/java/src/org/chromium/content_public", diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java index 3477bc0..2448307 100644 --- a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java +++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java @@ -4,8 +4,17 @@ package org.chromium.android_webview.test; +import android.app.PendingIntent; +import android.net.Uri; +import android.os.Build; +import android.os.ResultReceiver; + +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; + import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -22,8 +31,14 @@
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
index 3477bc0..2448307 100644
--- a/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
+++ b/android_webview/javatests/src/org/chromium/android_webview/test/WebAuthnTest.java
@@ -4,8 +4,17 @@
package org.chromium.android_webview.test;
+import android.app.PendingIntent;
+import android.net.Uri;
+import android.os.Build;
+import android.os.ResultReceiver;
+
+import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.android.gms.tasks.OnSuccessListener;
+
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -22,8 +31,14 @@
import org.chromium.base.test.util.Features.DisableFeatures;
import org.chromium.base.test.util.Features.EnableFeatures;
import org.chromium.base.test.util.HistogramWatcher;
+import org.chromium.blink.mojom.PublicKeyCredentialRequestOptions;
+import org.chromium.components.webauthn.AuthenticationContextProvider;
+import org.chromium.components.webauthn.Fido2ApiCallHelper;
import org.chromium.components.webauthn.WebauthnMode;
+import org.chromium.components.webauthn.cred_man.CredManSupportProvider;
import org.chromium.content_public.common.ContentSwitches;
+import org.chromium.net.test.EmbeddedTestServer;
+import org.chromium.net.test.ServerCertificate;
import org.chromium.net.test.util.TestWebServer;
import org.chromium.url.GURL;
@@ -32,6 +47,23 @@
@CommandLineFlags.Add({ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
@Batch(Batch.PER_CLASS)
public class WebAuthnTest {
+ private static class TestFido2ApiCallHelper extends Fido2ApiCallHelper {
+ public boolean mGetAssertionCalled;
+
+ @Override
+ public void invokeFido2GetAssertion(
+ AuthenticationContextProvider authenticationContextProvider,
+ PublicKeyCredentialRequestOptions options,
+ Uri uri,
+ byte[] clientDataHash,
+ ResultReceiver resultReceiver,
+ OnSuccessListener<PendingIntent> successCallback,
+ OnFailureListener failureCallback) {
+ mGetAssertionCalled = true;
+ failureCallback.onFailure(new Exception("MOCK_FIDO2_API_INVOKED"));
+ }
+ }
+
@Rule public AwActivityTestRule mActivityTestRule = new AwActivityTestRule();
private TestAwContentsClient mContentsClient;
@@ -158,6 +190,69 @@
histogramWatcher.assertExpected();
}
+ @Test
+ @SmallTest
+ @Feature({"AndroidWebView"})
+ public void testWebAuthnBlockedOnSslError() throws Throwable {
+ CredManSupportProvider.setupForTesting(Build.VERSION_CODES.TIRAMISU, false);
+ TestFido2ApiCallHelper testHelper = new TestFido2ApiCallHelper();
+ Fido2ApiCallHelper.overrideInstanceForTesting(testHelper);
+
+ EmbeddedTestServer testServer =
+ EmbeddedTestServer.createAndStartHTTPSServer(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ ServerCertificate.CERT_MISMATCHED_NAME);
+ try {
+ mAwSettings.setJavaScriptEnabled(true);
+ mAwSettings.setWebauthnSupport(WebauthnMode.APP);
+ mContentsClient.setAllowSslError(true);
+
+ final String pageUrl =
+ testServer.getURLWithHostName(
+ "a.test", "/android_webview/test/data/hello_world.html");
+ mActivityTestRule.loadUrlSync(
+ mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl);
+
+ mActivityTestRule.executeJavaScriptAndWaitForResult(
+ mAwContents,
+ mContentsClient,
+ """
+ window.webauthnResult = 'PENDING';
+ navigator.credentials.get({
+ publicKey: {
+ challenge: new Uint8Array([1, 2, 3, 4]),
+ timeout: 500,
+ rpId: 'a.test'
+ }
+ }).then(
+ () => { window.webauthnResult = 'SUCCESS'; },
+ (e) => { window.webauthnResult = e.name + ': ' + e.message; }
+ );
+ """);
+
+ AwActivityTestRule.pollInstrumentationThread(
+ () -> {
+ String val =
+ mActivityTestRule.executeJavaScriptAndWaitForResult(
+ mAwContents, mContentsClient, "window.webauthnResult");
+ return !"\"PENDING\"".equals(val) && !"null".equals(val);
+ });
+
+ String result =
+ mActivityTestRule.executeJavaScriptAndWaitForResult(
+ mAwContents, mContentsClient, "window.webauthnResult");
+
+ Assert.assertFalse(
+ "FIDO2 API should not be invoked for pages with SSL errors",
+ testHelper.mGetAssertionCalled);
+ Assert.assertTrue(
+ "WebAuthn should be blocked due to SSL error, but got: " + result,
+ result.contains("NotAllowedError") && result.contains("certificate errors"));
+ } finally {
+ testServer.stopAndDestroyServer();
+ }
+ }
+
private static boolean isSecureDomain(GURL url) {
if ("https".equals(url.getScheme())) {
return true;
diff --git a/android_webview/test/BUILD.gn b/android_webview/test/BUILD.gn
index 1d748689..69b320f 100644
--- a/android_webview/test/BUILD.gn
+++ b/android_webview/test/BUILD.gn
@@ -276,9 +276,8 @@
android_library("webview_compose_javatests") {
testonly = true
enable_compose = true
- sources = [
- "../javatests/src/org/chromium/android_webview/test/AwComposeTest.kt",
- ]
+ sources =
+ [ "../javatests/src/org/chromium/android_webview/test/AwComposeTest.kt" ]
deps = [
":base_webview_instrumentation_java",
":base_webview_instrumentation_test_java",
@@ -309,6 +308,7 @@
deps = [
":webview_compose_javatests",
+ "$google_play_services_package:google_play_services_tasks_java",
"//android_webview:common_aidl_java",
"//android_webview:common_origin_trials_java",
"//android_webview:common_variations_java",
@@ -384,6 +384,7 @@
"//third_party/androidx_javascriptengine:javascriptengine_java",
"//third_party/blink/public:blink_headers_java",
"//third_party/blink/public/common:common_java",
+ "//third_party/blink/public/mojom:authenticator_mojo_bindings_java",
"//third_party/blink/public/mojom:mojom_platform_java",
"//third_party/blink/public/mojom:web_feature_mojo_bindings_java",
"//third_party/google-truth:google_truth_java",
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.
References
On This Page