Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in FedCM
DescriptionImproper input validation in FedCM
ComponentFedCM
Bug ClassLogic Error
Tracker514016678
Fix commit562273008c5e (chromium/src) +82/-4
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Background

FedCM
Chromium’s browser-mediated Federated Credential Management API that lets sites request identity tokens from identity providers without third-party cookies.
IdP Registration API
a FedCM sub-feature (gated by FedCmIdPRegistration) where a browser-registered identity provider is used instead of an explicitly supplied config_url.
`from_idp_registration_api`
a boolean on blink::mojom::IdentityProviderConfig, sent from the renderer, signaling that a get-request should resolve against a registered IdP rather than a configured URL.
`ReportBadMessage`
a Mojo mechanism the browser process uses to reject malformed IPC and terminate the offending renderer for sending out-of-contract input.

Root Cause Analysis

The StartTokenRequest() handler in content/browser/webid/request_service.cc accepted renderer-supplied IdentityProviderConfig values and forwarded them into request creation without validating the from_idp_registration_api flag against its required invariants. A compromised or malicious renderer could set from_idp_registration_api to true even when the FedCmIdPRegistration feature was disabled, or set it true while also supplying a non-empty config_url, a combination the registration flow is not designed to handle. The browser process trusted this cross-process input as if it had been constrained by the renderer-side implementation, violating the rule that the privileged process must independently validate all Mojo input.

The fix iterates each provider and enforces two conditions: when the feature is disabled it sanitizes the flag back to false (tolerating benign test-config mismatches), and when the feature is enabled but config_url is non-empty it calls receivers_.ReportBadMessage("config_url must be empty for registered providers.") and aborts. This re-establishes the invariant that from_idp_registration_api implies both an enabled feature and an empty config_url before any request object is built.

Key insight
The single mistake was trusting the renderer-controlled from_idp_registration_api flag without re-validating it in the browser process; the fix adds an explicit browser-side check that resets the flag when the feature is off and rejects the message when a registered provider carries a config_url.

Attack Path

  1. Compromised renderer An attacker who controls a renderer process (e.g., via a prior bug) crafts a FedCM get-request through the FederatedRequest Mojo interface.
  2. Forge registration flag The attacker sets provider->config->from_idp_registration_api = true on an IdentityProviderConfig regardless of feature state or config_url contents.
  3. Bypass renderer constraints Because the browser’s StartTokenRequest() did not re-check the flag, the inconsistent config (registration requested with a populated config_url, or with the feature disabled) reached request construction.
  4. Enter unintended code path The registered-IdP handling proceeds on input it was never designed to accept, driving FedCM logic into an out-of-contract state.

Impact Assessment

An attacker who already controls a renderer can push malformed FedCM registration input past the browser-process trust boundary, exercising the IdP-registration code path under conditions it does not guard against. The gain is confined to logic/state confusion within the browser-side FedCM RequestService, consistent with the medium severity and “improper input validation” classification, and no memory-corruption primitive is evidenced by the diff. The precondition is a renderer able to send arbitrary blink::mojom FedCM messages.

Changed Functions

FunctionChangeNotes
TEST_F
content/browser/webid/request_registry_unittest.cc
modified
for
content/browser/webid/request_service.cc
modified
if
content/browser/webid/request_service.cc
modified

Files Changed

  • content/browser/webid/request_registry_unittest.cc
  • content/browser/webid/request_service.cc
  • content/browser/webid/request_unittest.cc
  • third_party/blink/web_tests/VirtualTestSuites
  • third_party/blink/web_tests/virtual/fedcm-register/README.md

Audit Directions

  • Renderer-supplied Mojo flags
    Audit other blink::mojom boolean and mode flags (like from_idp_registration_api) that select privileged code paths to confirm the browser process re-validates them rather than trusting renderer enforcement.
  • Feature-gated inputs
    Wherever a base::Feature gate exists only on the renderer side, verify the browser independently checks IsXEnabled() before honoring input that assumes the feature, and decide deliberately between sanitize-and-continue and ReportBadMessage.
  • Cross-field invariants
    Look for cases where one field’s value constrains another (here, a set registration flag requires an empty config_url) and ensure those combined constraints are validated together at the trust boundary.
From 562273008c5efc49a07d8adbb66a4cb50ee98dd8 Mon Sep 17 00:00:00 2001
From: Nicolás Peña <[email protected]>
Date: Wed, 12 Aug 2026 17:25:27 -0700
Subject: [PATCH] [FedCM] Validate from_idp_registration_api in StartTokenRequest()

This CL adds security checks to ensure that from_idp_registration_api is
only set to true when the FedCmIdPRegistration feature is enabled and
the config_url is empty. If these conditions are violated, a bad message
is reported. Unit tests are added to verify this behavior.

Fixed: 514016678
Change-Id: Ica99d5522f0ed77c48928cfb27c16872ff764f02
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8221783
Auto-Submit: Nicolás Peña <[email protected]>
Reviewed-by: Philip Rogers <[email protected]>
Commit-Queue: Nicolás Peña <[email protected]>
Reviewed-by: Yi Gu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1678418}
---

diff --git a/content/browser/webid/request_registry_unittest.cc b/content/browser/webid/request_registry_unittest.cc
index 793eb498..e7448f5 100644
--- a/content/browser/webid/request_registry_unittest.cc
+++ b/content/browser/webid/request_registry_unittest.cc
@@ -39,6 +39,8 @@
 
 namespace content::webid {
 
+namespace {
+
 using ::testing::_;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -46,8 +48,7 @@
 using ApiPermissionStatus =
     FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
 using blink::mojom::RegisterIdpStatus;
-
-namespace {
+using MediationRequirement = ::password_manager::CredentialMediationRequirement;
 
 constexpr char kIdpUrl[] = "https://idp.example/";
 
@@ -489,4 +490,61 @@
   run_loop.Run();
 }
 
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// when the feature is disabled resolves with a normal error instead of a bad
+// message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationDisabled) {
+  // Feature is disabled by default.
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  EXPECT_CALL(*mock_permission_delegate_, RemoveIdpSigninStatusObserver(_))
+      .WillOnce(Return());
+
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  base::RunLoop run_loop;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(),
+      base::BindLambdaForTesting(
+          [&run_loop](
+              blink::mojom::FederatedRequestService::StartTokenRequestResult
+                  result) {
+            EXPECT_FALSE(result.has_value());
+            EXPECT_EQ(blink::mojom::RequestTokenStatus::kError,
+                      result.error()->status);
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+}
+
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// and a non-empty config_url triggers a Bad Message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationNonEmptyUrl) {
+  feature_list_.InitAndEnableFeature(features::kFedCmIdPRegistration);
+
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  provider->config->config_url = GURL(kIdpUrl);
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  mojo::test::BadMessageObserver bad_message_observer;
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(), base::DoNothing());
+
+  EXPECT_EQ("config_url must be empty for registered providers.",
+            bad_message_observer.WaitForBadMessage());
+}
+
 }  // namespace content::webid
diff --git a/content/browser/webid/request_service.cc b/content/browser/webid/request_service.cc
index 8a0aefa..5a9109d 100644
--- a/content/browser/webid/request_service.cc
+++ b/content/browser/webid/request_service.cc
@@ -197,6 +197,24 @@
     return;
   }
 
+  for (auto& provider : idp_get_params[0]->providers) {
+    if (provider->config->from_idp_registration_api) {
+      if (!IsIdPRegistrationEnabled()) {
+        // In layout and web platform tests, features with "status: test" (like
+        // FedCmIdPRegistration) are enabled by default on the renderer-side,
+        // but the corresponding browser-side base::Feature may be disabled.
+        // To prevent mismatches in benign test configurations from terminating
+        // the renderer, we sanitize/reset from_idp_registration_api to false
+        // instead of reporting a bad message.
+        provider->config->from_idp_registration_api = false;
+      } else if (!provider->config->config_url.is_empty()) {
+        receivers_.ReportBadMessage(
+            "config_url must be empty for registered providers.");
+        return;
+      }
+    }
+  }
+
   RenderFrameHost& rfh = render_frame_host();
   auto new_request = std::make_unique<Request>(&rfh, *this);
   new_request->BindReceiver(std::move(request_receiver));
diff --git a/content/browser/webid/request_unittest.cc b/content/browser/webid/request_unittest.cc
index 0359465..775d244 100644
--- a/content/browser/webid/request_unittest.cc
+++ b/content/browser/webid/request_unittest.cc
@@ -2185,6 +2185,7 @@
   config.idp_info[idp_config_url].well_known = {
       {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
   RequestParameters requestParameters = kDefaultRequestParameters;
+  requestParameters.identity_providers[0].provider = "";
   requestParameters.identity_providers[0].from_idp_registration_api = true;
 
   // Need to simulate there is actually a registered IdP, or the call will fail.
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index ed4b2f9a..3738572 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1156,7 +1156,7 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--enable-features=FedCmIdPregistration"
+      "--enable-features=FedCmIdPRegistration"
     ],
     "owners": [
       "[email protected]",
diff --git a/third_party/blink/web_tests/virtual/fedcm-register/README.md b/third_party/blink/web_tests/virtual/fedcm-register/README.md
index 8a5b56c..8d5646d 100644
--- a/third_party/blink/web_tests/virtual/fedcm-register/README.md
+++ b/third_party/blink/web_tests/virtual/fedcm-register/README.md
@@ -1,5 +1,6 @@
 # FedCmRegister
+
 This suite runs the tests in wpt/credential-management/fedcm-register/ with
-`--enable-features=FedCmIdPregistration`.
+`--enable-features=FedCmIdPRegistration`.
 
 See crbug.com/40252825.
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/webid/request_registry_unittest.cc b/content/browser/webid/request_registry_unittest.cc
index 793eb498..e7448f5 100644
--- a/content/browser/webid/request_registry_unittest.cc
+++ b/content/browser/webid/request_registry_unittest.cc
@@ -39,6 +39,8 @@
 
 namespace content::webid {
 
+namespace {
+
 using ::testing::_;
 using ::testing::NiceMock;
 using ::testing::Return;
@@ -46,8 +48,7 @@
 using ApiPermissionStatus =
     FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
 using blink::mojom::RegisterIdpStatus;
-
-namespace {
+using MediationRequirement = ::password_manager::CredentialMediationRequirement;
 
 constexpr char kIdpUrl[] = "https://idp.example/";
 
@@ -489,4 +490,61 @@
   run_loop.Run();
 }
 
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// when the feature is disabled resolves with a normal error instead of a bad
+// message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationDisabled) {
+  // Feature is disabled by default.
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  EXPECT_CALL(*mock_permission_delegate_, RemoveIdpSigninStatusObserver(_))
+      .WillOnce(Return());
+
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  base::RunLoop run_loop;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(),
+      base::BindLambdaForTesting(
+          [&run_loop](
+              blink::mojom::FederatedRequestService::StartTokenRequestResult
+                  result) {
+            EXPECT_FALSE(result.has_value());
+            EXPECT_EQ(blink::mojom::RequestTokenStatus::kError,
+                      result.error()->status);
+            run_loop.Quit();
+          }));
+  run_loop.Run();
+}
+
+// Test that calling StartTokenRequest with from_idp_registration_api as true
+// and a non-empty config_url triggers a Bad Message.
+TEST_F(RequestRegistryTest, StartTokenRequestFromIdpRegistrationNonEmptyUrl) {
+  feature_list_.InitAndEnableFeature(features::kFedCmIdPRegistration);
+
+  std::vector<blink::mojom::IdentityProviderGetParametersPtr> idp_get_params;
+  auto get_params = blink::mojom::IdentityProviderGetParameters::New();
+  auto provider = blink::mojom::IdentityProviderRequestOptions::New();
+  provider->config = blink::mojom::IdentityProviderConfig::New();
+  provider->config->from_idp_registration_api = true;
+  provider->config->config_url = GURL(kIdpUrl);
+  get_params->providers.push_back(std::move(provider));
+  idp_get_params.push_back(std::move(get_params));
+
+  mojo::test::BadMessageObserver bad_message_observer;
+  mojo::Remote<blink::mojom::FederatedRequest> request_remote;
+  request_service_remote_->StartTokenRequest(
+      std::move(idp_get_params), MediationRequirement::kOptional,
+      request_remote.BindNewPipeAndPassReceiver(), base::DoNothing());
+
+  EXPECT_EQ("config_url must be empty for registered providers.",
+            bad_message_observer.WaitForBadMessage());
+}
+
 }  // namespace content::webid
diff --git a/content/browser/webid/request_unittest.cc b/content/browser/webid/request_unittest.cc
index 0359465..775d244 100644
--- a/content/browser/webid/request_unittest.cc
+++ b/content/browser/webid/request_unittest.cc
@@ -2185,6 +2185,7 @@
   config.idp_info[idp_config_url].well_known = {
       {kWellKnownMismatchConfigUrl}, {ParseStatus::kSuccess, net::HTTP_OK}};
   RequestParameters requestParameters = kDefaultRequestParameters;
+  requestParameters.identity_providers[0].provider = "";
   requestParameters.identity_providers[0].from_idp_registration_api = true;
 
   // Need to simulate there is actually a registered IdP, or the call will fail.
diff --git a/third_party/blink/web_tests/VirtualTestSuites b/third_party/blink/web_tests/VirtualTestSuites
index ed4b2f9a..3738572 100644
--- a/third_party/blink/web_tests/VirtualTestSuites
+++ b/third_party/blink/web_tests/VirtualTestSuites
@@ -1156,7 +1156,7 @@
     ],
     "exclusive_tests": "ALL",
     "args": [
-      "--enable-features=FedCmIdPregistration"
+      "--enable-features=FedCmIdPRegistration"
     ],
     "owners": [
       "[email protected]",
diff --git a/third_party/blink/web_tests/virtual/fedcm-register/README.md b/third_party/blink/web_tests/virtual/fedcm-register/README.md
index 8a5b56c..8d5646d 100644
--- a/third_party/blink/web_tests/virtual/fedcm-register/README.md
+++ b/third_party/blink/web_tests/virtual/fedcm-register/README.md
@@ -1,5 +1,6 @@
 # FedCmRegister
+
 This suite runs the tests in wpt/credential-management/fedcm-register/ with
-`--enable-features=FedCmIdPregistration`.
+`--enable-features=FedCmIdPRegistration`.
 
 See crbug.com/40252825.
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.