Medium chrome Logic Error 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper privilege management in Regional Capabilities
DescriptionImproper privilege management in Regional Capabilities
ComponentRegional Capabilities
Bug ClassLogic Error
Tracker513607252
Fix commit2e421e49757b (chromium/src) +254/-35
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
GetChoiceDataFromProfile
chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
modified
if
chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
modified

Files Changed

  • chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
  • chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
From 2e421e49757bf4231412a3f46d756df41df71272 Mon Sep 17 00:00:00 2001
From: Nicolas Dossou-Gbete <dgn@chromium.org>
Date: Fri, 03 Jul 2026 09:21:44 -0700
Subject: [PATCH] Reland "Avoid propagating extension or policy choice across profiles"

This is a reland of commit 1c6acb95514ada701e107ff963b8689281503d71

Fixes the test-specific compilation failure in ChromeForTesting builds,
un-guarding one of the test helper functions that the new tests start
using in CFT builds.

Original change's description:
> Avoid propagating extension or policy choice across profiles
>
> When capturing default search engine (DSE) choice data to propagate to
> forked profiles, skip the propagation for the cases where the active DSE
> is not set by the user (e.g. managed by extension or policies, or
> fallback)
>
> Adds the dedicated UMA histogram
> `Search.ChoiceDebug.PropagatedDataOutcome` to track the impact of this
> change.
>
> Bug: 513607252
> Change-Id: I86c1fdfb6cbe392e4cd7bb8d48dc179f5b07fcd8
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7986107
> Commit-Queue: Nicolas Dossou-Gbété <dgn@chromium.org>
> Reviewed-by: David Roger <droger@chromium.org>
> Reviewed-by: James Lee <ljjlee@google.com>
> Auto-Submit: Nicolas Dossou-Gbété <dgn@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1656537}

Bug: 513607252
Change-Id: I1b6cb0ec5fef5d6a1960ffb876a15064febad439
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8034248
Reviewed-by: James Lee <ljjlee@google.com>
Auto-Submit: Nicolas Dossou-Gbété <dgn@chromium.org>
Reviewed-by: David Roger <droger@chromium.org>
Commit-Queue: David Roger <droger@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1656620}
---

diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
index ad63ef3f..072359d2 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc
@@ -292,49 +292,82 @@
 }
 
 // static
-search_engines::ChoiceData
+std::optional<search_engines::ChoiceData>
 SearchEngineChoiceDialogService::GetChoiceDataFromProfile(Profile& profile) {
   PrefService* pref_service = profile.GetPrefs();
   TemplateURLService* template_url_service =
       TemplateURLServiceFactory::GetForProfile(&profile);
   CHECK(template_url_service);
+
+  switch (template_url_service->default_search_provider_source()) {
+    case DefaultSearchManager::FROM_FALLBACK:
+      base::UmaHistogramEnumeration(
+          "Search.ChoiceDebug.PropagatedDataOutcome",
+          CurrentDefaultPropagationOutcome::kSkippedIsFallback);
+      return std::nullopt;
+
+    case DefaultSearchManager::FROM_EXTENSION:
+      base::UmaHistogramEnumeration(
+          "Search.ChoiceDebug.PropagatedDataOutcome",
+          CurrentDefaultPropagationOutcome::kSkippedIsExtension);
+      return std::nullopt;
+
+    case DefaultSearchManager::FROM_POLICY:
+    case DefaultSearchManager::FROM_POLICY_RECOMMENDED:
+      base::UmaHistogramEnumeration(
+          "Search.ChoiceDebug.PropagatedDataOutcome",
+          CurrentDefaultPropagationOutcome::kSkippedDueToPolicies);
+      return std::nullopt;
+
+    case DefaultSearchManager::FROM_USER:
+      break;  // Current default eligible for propagation.
+  }
+
+  CHECK(template_url_service->GetDefaultSearchProvider());
   const TemplateURLData& default_search_engine =
       template_url_service->GetDefaultSearchProvider()->data();
 
-  return {.timestamp = pref_service->GetInt64(
-              prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
-          .chrome_version = pref_service->GetString(
-              prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
-          .default_search_engine = default_search_engine};
+  base::UmaHistogramEnumeration(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      CurrentDefaultPropagationOutcome::kPropagatedCurrentDefault);
+
+  return search_engines::ChoiceData{
+      .timestamp = pref_service->GetInt64(
+          prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp),
+      .chrome_version = pref_service->GetString(
+          prefs::kDefaultSearchProviderChoiceScreenCompletionVersion),
+      .default_search_engine = default_search_engine};
 }
 
 // static
 void SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
     Profile& profile,
-    const search_engines::ChoiceData& choice_data) {
-  PrefService* pref_service = profile.GetPrefs();
-  if (choice_data.timestamp != 0) {
-    pref_service->SetInt64(
-        prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
-        choice_data.timestamp);
-  }
-
-  if (!choice_data.chrome_version.empty()) {
-    pref_service->SetString(
-        prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
-        choice_data.chrome_version);
+    const std::optional<search_engines::ChoiceData>& choice_data) {
+  if (!choice_data.has_value()) {
+    return;
   }
 
   const TemplateURLData& default_search_engine =
-      choice_data.default_search_engine;
-  if (!default_search_engine.keyword().empty() &&
-      !default_search_engine.url().empty()) {
-    TemplateURLService* template_url_service =
-        TemplateURLServiceFactory::GetForProfile(&profile);
-    CHECK(template_url_service);
-    TemplateURL template_url(default_search_engine);
-    template_url_service->SetUserSelectedDefaultSearchProvider(&template_url);
+      choice_data->default_search_engine;
+
+  PrefService* pref_service = profile.GetPrefs();
+  if (choice_data->timestamp != 0) {
+    pref_service->SetInt64(
+        prefs::kDefaultSearchProviderChoiceScreenCompletionTimestamp,
+        choice_data->timestamp);
   }
+
+  if (!choice_data->chrome_version.empty()) {
+    pref_service->SetString(
+        prefs::kDefaultSearchProviderChoiceScreenCompletionVersion,
+        choice_data->chrome_version);
+  }
+
+  TemplateURLService* template_url_service =
+      TemplateURLServiceFactory::GetForProfile(&profile);
+  CHECK(template_url_service);
+  TemplateURL template_url(default_search_engine);
+  template_url_service->SetUserSelectedDefaultSearchProvider(&template_url);
 }
 
 TemplateURL::TemplateURLVector
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
index 6b83b61..9b9a9482 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
@@ -5,6 +5,7 @@
 #ifndef CHROME_BROWSER_SEARCH_ENGINE_CHOICE_SEARCH_ENGINE_CHOICE_DIALOG_SERVICE_H_
 #define CHROME_BROWSER_SEARCH_ENGINE_CHOICE_SEARCH_ENGINE_CHOICE_DIALOG_SERVICE_H_
 
+#include <optional>
 #include <string>
 
 #include "base/containers/flat_map.h"
@@ -152,12 +153,28 @@
   static void SetDialogDisabledForTests(bool dialog_disabled);
 
   // Returns a copy of the `ChoiceData` specific to `profile`.
-  static search_engines::ChoiceData GetChoiceDataFromProfile(Profile& profile);
+  // LINT.IfChange(CurrentDefaultPropagationOutcome)
+  enum class CurrentDefaultPropagationOutcome {
+    kPropagatedCurrentDefault = 0,
+    kSkippedDueToPolicies = 1,
+    kSkippedIsFallback = 2,
+    kSkippedIsExtension = 3,
+    kMaxValue = kSkippedIsExtension,
+  };
+  // LINT.ThenChange(/tools/metrics/histograms/metadata/search/enums.xml:CurrentDefaultPropagationOutcome)
+
+  // Returns a copy of the `ChoiceData` specific to `profile`, or `std::nullopt`
+  // if there is no default search engine to propagate. This function ignores
+  // any extension-provided default search engine to capture the underlying user
+  // choice (or system default), and skips propagation entirely if the default
+  // search is managed by an enterprise policy.
+  static std::optional<search_engines::ChoiceData> GetChoiceDataFromProfile(
+      Profile& profile);
 
   // Updates `profile` with the values from `choice_data`.
   static void UpdateProfileFromChoiceData(
       Profile& profile,
-      const search_engines::ChoiceData& choice_data);
+      const std::optional<search_engines::ChoiceData>& choice_data);
 
  private:
   friend class SearchEngineChoiceDialogServiceFactory;
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index c24152a..bf9330a5 100644
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
index c24152a..bf9330a5 100644
--- a/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
+++ b/chrome/browser/search_engine_choice/search_engine_choice_dialog_service_unittest.cc
@@ -19,6 +19,7 @@
 #include "chrome/browser/search_engine_choice/search_engine_choice_service_factory.h"
 #include "chrome/browser/search_engines/template_url_service_factory.h"
 #include "chrome/common/webui_url_constants.h"
+#include "chrome/test/base/search_test_utils.h"
 #include "chrome/test/base/testing_browser_process.h"
 #include "chrome/test/base/testing_profile_manager.h"
 #include "components/country_codes/country_codes.h"
@@ -31,9 +32,11 @@
 #include "components/search_engines/search_engines_pref_names.h"
 #include "components/search_engines/search_engines_switches.h"
 #include "components/search_engines/search_engines_test_util.h"
+#include "components/search_engines/template_url_data_util.h"
 #include "components/search_engines/template_url_prepopulate_data.h"
 #include "components/search_engines/template_url_service.h"
 #include "components/signin/public/base/signin_switches.h"
+#include "components/sync_preferences/testing_pref_service_syncable.h"
 #include "content/public/test/browser_task_environment.h"
 #include "testing/gmock/include/gmock/gmock.h"
 #include "testing/gtest/include/gtest/gtest.h"
@@ -63,7 +66,6 @@
 
 constexpr CountryId kBelgiumCountryId("BE");
 
-#if !BUILDFLAG(CHROME_FOR_TESTING)
 void SetUserSelectedDefaultSearchProvider(
     TemplateURLService* template_url_service,
     bool created_by_policy) {
@@ -89,6 +91,7 @@
   template_url_service->SetUserSelectedDefaultSearchProvider(template_url);
 }
 
+#if !BUILDFLAG(CHROME_FOR_TESTING)
 struct TestParam {
   std::string test_suffix;
   std::optional<regional_capabilities::SearchEngineCountryListOverride>
@@ -549,3 +552,141 @@
   ASSERT_EQ(search_engine_choice_dialog_service, nullptr);
 }
 #endif  // !BUILDFLAG(CHROME_FOR_TESTING)
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfilePropagatesCurrentDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // Set a user-selected default search provider to ensure the source is
+  // FROM_USER.
+  SetUserSelectedDefaultSearchProvider(source_template_url_service,
+                                       /*created_by_policy=*/false);
+
+  const TemplateURL* underlying_default =
+      source_template_url_service->GetDefaultSearchProvider();
+  ASSERT_TRUE(underlying_default);
+  const std::string underlying_default_url = underlying_default->url();
+
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  ASSERT_TRUE(choice_data.has_value());
+  EXPECT_EQ(choice_data->default_search_engine.url(), underlying_default_url);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kPropagatedCurrentDefault,
+      1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileIgnoresExtensionProvidedDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // Install an extension-provided default search engine in the source profile.
+  std::unique_ptr<TemplateURLData> extension_data =
+      GenerateDummyTemplateURLData("extension");
+  source_template_url_service->Add(std::make_unique<TemplateURL>(
+      *extension_data, TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION,
+      "extension_id", base::Time(), /*wants_to_be_default_engine=*/true));
+  SetExtensionDefaultSearchInPrefs(profile()->GetTestingPrefService(),
+                                   *extension_data);
+  ASSERT_TRUE(
+      source_template_url_service->IsExtensionControlledDefaultSearch());
+
+  // The choice data captured for propagation to a new profile should be absent
+  // for extensions under the new logic.
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  EXPECT_EQ(choice_data, std::nullopt);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kSkippedIsExtension,
+      1);
+
+  // Propagating absent choice data should leave the destination profile's
+  // default search engine unchanged from its own default.
+  TestingProfile* destination_profile =
+      profile_manager()->CreateTestingProfile("Profile 2");
+  TemplateURLServiceFactory::GetInstance()->SetTestingFactory(
+      destination_profile,
+      base::BindRepeating(&TemplateURLServiceFactory::BuildInstanceFor));
+  TemplateURLService* destination_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(destination_profile);
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      destination_template_url_service);
+
+  const std::string destination_default_url =
+      destination_template_url_service->GetDefaultSearchProvider()->url();
+
+  SearchEngineChoiceDialogService::UpdateProfileFromChoiceData(
+      *destination_profile, choice_data);
+  EXPECT_FALSE(
+      destination_template_url_service->IsExtensionControlledDefaultSearch());
+  EXPECT_EQ(destination_template_url_service->GetDefaultSearchProvider()->url(),
+            destination_default_url);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileSkipsIfNoUnderlyingDefault) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  // When no user choice is made, a fresh profile uses the fallback search
+  // engine, which skips under the new FROM_FALLBACK policy.
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  EXPECT_EQ(choice_data, std::nullopt);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kSkippedIsFallback,
+      1);
+}
+
+TEST_F(SearchEngineChoiceDialogServiceTest,
+       GetChoiceDataFromProfileSkipsIfManagedByPolicy) {
+  base::HistogramTester histogram_tester;
+  TemplateURLService* source_template_url_service =
+      TemplateURLServiceFactory::GetForProfile(profile());
+  search_test_utils::WaitForTemplateURLServiceToLoad(
+      source_template_url_service);
+
+  TemplateURLData data;
+  data.SetShortName(u"policy");
+  data.SetKeyword(u"policy");
+  data.SetURL("https://policy/url?bar={searchTerms}");
+  profile()->GetTestingPrefService()->SetManagedPref(
+      DefaultSearchManager::kDefaultSearchProviderDataPrefName,
+      TemplateURLDataToDictionary(data));
+
+  ASSERT_TRUE(source_template_url_service->is_default_search_managed());
+
+  std::optional<search_engines::ChoiceData> choice_data =
+      SearchEngineChoiceDialogService::GetChoiceDataFromProfile(*profile());
+  EXPECT_EQ(choice_data, std::nullopt);
+
+  histogram_tester.ExpectUniqueSample(
+      "Search.ChoiceDebug.PropagatedDataOutcome",
+      SearchEngineChoiceDialogService::CurrentDefaultPropagationOutcome::
+          kSkippedDueToPolicies,
+      1);
+
+  // Clear for subsequent tests.
+  profile()->GetTestingPrefService()->RemoveManagedPref(
+      DefaultSearchManager::kDefaultSearchProviderDataPrefName);
+}
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.