Chrome · Web Authentication
CVE-2026-87561
Logic Error in Web Authentication
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Files Changed
content/browser/webauth/authenticator_impl_unittest.cccontent/browser/webauth/webauth_request_security_checker_impl.cc
Patch
From 7f59498d7097ca3905b4401463347e139bc905c5 Mon Sep 17 00:00:00 2001
From: Martin Kreichgauer <martinkr@google.com>
Date: Fri, 31 Jul 2026 14:20:44 -0700
Subject: [PATCH] webauthn: gate remoteDesktopClientOverride for all caller types
ValidateDomainAndRelyingPartyID returned early for callers whose
origin/RP-ID validation is delegated to the embedder (e.g.
chrome-extension:// origins) without first evaluating
OriginMayUseRemoteDesktopClientOverride against the caller. As a result,
such a caller could supply the remoteDesktopClientOverride extension and
have its origin written into the browser-generated clientDataJSON
regardless of whether the caller origin was on the device-level
allowlist.
In practice, OverrideCallerOriginAndRelyingPartyIdValidation severely
limits the set of origins an extension could claim by doing this, but
nevertheless it is a good idea to tighten this up.
Hoist the policy check to the start of the function so that it runs
before every early-return path, and replace the now redundant inline
check with a value_or() for the relying-party-origin assignment.
BUG=499217288
TAG=agy
CONV=b7e1564f-5289-478d-bba4-48195f9673f6
Change-Id: I4a423db7677e3abd875219621daec6ca9b95c884
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8140023
Reviewed-by: Ken Buchanan <kenrb@chromium.org>
Commit-Queue: Martin Kreichgauer <martinkr@google.com>
Cr-Commit-Position: refs/heads/main@{#1672106}
---
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 5e9f2feb..6ef94c5 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -3249,6 +3249,40 @@
}
}
+// A Chrome extension should not be authorized to use the
+// remoteDesktopClientOverride request extension.
+TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
+ ExtensionCallerOrigin) {
+ static const std::string kExtensionOrigin =
+ base::StrCat({kExtensionScheme, "://abcdefg"});
+ test_client_.GetTestWebAuthenticationDelegate()->permit_extensions = true;
+
+ {
+ PublicKeyCredentialCreationOptionsPtr options =
+ GetTestPublicKeyCredentialCreationOptions();
+ options->relying_party.id = kExampleRpId;
+ options->remote_desktop_client_override = RemoteDesktopClientOverride::New(
+ url::Origin::Create(GURL(kExampleOrigin)), true);
+ EXPECT_EQ(
+ AuthenticatorMakeCredential(std::move(options)).status,
+ AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+ }
+
+ {
+ PublicKeyCredentialRequestOptionsPtr options =
+ GetTestPublicKeyCredentialRequestOptions();
+ options->relying_party_id = kExampleRpId;
+ options->extensions->remote_desktop_client_override =
+ RemoteDesktopClientOverride::New(
+ url::Origin::Create(GURL(kExampleOrigin)), true);
+ ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
+ options->allow_credentials[0].id, kExtensionOrigin));
+ EXPECT_EQ(
+ AuthenticatorGetAssertion(std::move(options)).status,
+ AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+ }
+}
+
TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
GetAssertionImmediateMediation) {
// Verify that an authorized origin may not use the extension with immediate
diff --git a/content/browser/webauth/webauth_request_security_checker_impl.cc b/content/browser/webauth/webauth_request_security_checker_impl.cc
index 8e49065..a40d72b 100644
--- a/content/browser/webauth/webauth_request_security_checker_impl.cc
+++ b/content/browser/webauth/webauth_request_security_checker_impl.cc
@@ -159,6 +159,23 @@
RequestType request_type,
const std::optional<url::Origin>& remote_desktop_client_override_origin,
base::OnceCallback<void(blink::mojom::AuthenticatorStatus)> callback) {
+ if (remote_desktop_client_override_origin.has_value()) {
+ // SECURITY: `remote_desktop_client_override_origin` comes from the renderer
+ // process and should not be trusted by default. We only allow its use when
+ // the `caller_origin` is explicitly allowlisted through device level
+ // enterprise policy.
+ if (!GetContentClient()
+ ->browser()
+ ->GetWebAuthenticationDelegate()
+ ->OriginMayUseRemoteDesktopClientOverride(
+ render_frame_host_->GetBrowserContext(), caller_origin)) {
+ std::move(callback).Run(
+ blink::mojom::AuthenticatorStatus::
+ REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+ return nullptr;
+ }
+ }
+
#if !BUILDFLAG(IS_ANDROID)
// Extensions are not supported on Android.
if (GetContentClient()
@@ -188,24 +205,8 @@
return nullptr;
}
- url::Origin relying_party_origin = caller_origin;
- if (remote_desktop_client_override_origin.has_value()) {
- // SECURITY: `remote_desktop_client_override_origin` comes from the renderer
- // process and should not be trusted by default. We only allow its use when
- // the `caller_origin` is explicitly allowlisted through device level
- // enterprise policy.
- if (!GetContentClient()
- ->browser()
- ->GetWebAuthenticationDelegate()
- ->OriginMayUseRemoteDesktopClientOverride(
- render_frame_host_->GetBrowserContext(), caller_origin)) {
- std::move(callback).Run(
- blink::mojom::AuthenticatorStatus::
- REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
- return nullptr;
- }
- relying_party_origin = remote_desktop_client_override_origin.value();
- }
+ url::Origin relying_party_origin =
+ remote_desktop_client_override_origin.value_or(caller_origin);
if (webauthn::OriginIsAllowedToClaimRelyingPartyId(relying_party_id,
relying_party_origin)) {
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/content/browser/webauth/authenticator_impl_unittest.cc b/content/browser/webauth/authenticator_impl_unittest.cc
index 5e9f2feb..6ef94c5 100644
--- a/content/browser/webauth/authenticator_impl_unittest.cc
+++ b/content/browser/webauth/authenticator_impl_unittest.cc
@@ -3249,6 +3249,40 @@
}
}
+// A Chrome extension should not be authorized to use the
+// remoteDesktopClientOverride request extension.
+TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
+ ExtensionCallerOrigin) {
+ static const std::string kExtensionOrigin =
+ base::StrCat({kExtensionScheme, "://abcdefg"});
+ test_client_.GetTestWebAuthenticationDelegate()->permit_extensions = true;
+
+ {
+ PublicKeyCredentialCreationOptionsPtr options =
+ GetTestPublicKeyCredentialCreationOptions();
+ options->relying_party.id = kExampleRpId;
+ options->remote_desktop_client_override = RemoteDesktopClientOverride::New(
+ url::Origin::Create(GURL(kExampleOrigin)), true);
+ EXPECT_EQ(
+ AuthenticatorMakeCredential(std::move(options)).status,
+ AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+ }
+
+ {
+ PublicKeyCredentialRequestOptionsPtr options =
+ GetTestPublicKeyCredentialRequestOptions();
+ options->relying_party_id = kExampleRpId;
+ options->extensions->remote_desktop_client_override =
+ RemoteDesktopClientOverride::New(
+ url::Origin::Create(GURL(kExampleOrigin)), true);
+ ASSERT_TRUE(virtual_device_factory_->mutable_state()->InjectRegistration(
+ options->allow_credentials[0].id, kExtensionOrigin));
+ EXPECT_EQ(
+ AuthenticatorGetAssertion(std::move(options)).status,
+ AuthenticatorStatus::REMOTE_DESKTOP_CLIENT_OVERRIDE_NOT_AUTHORIZED);
+ }
+}
+
TEST_F(AuthenticatorImplRemoteDesktopClientOverrideTest,
GetAssertionImmediateMediation) {
// Verify that an authorized origin may not use the extension with immediate
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