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 <[email protected]> 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 <[email protected]> Commit-Queue: Martin Kreichgauer <[email protected]> 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