Chrome · Regional Capabilities
CVE-2026-79226
Logic Error in Regional Capabilities
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
GetChoiceDataFromProfilechrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc |
modified | |
ifchrome/browser/search_engine_choice/search_engine_choice_dialog_service.cc |
modified |
Files Changed
chrome/browser/search_engine_choice/search_engine_choice_dialog_service.ccchrome/browser/search_engine_choice/search_engine_choice_dialog_service.h
Patch
From 2e421e49757bf4231412a3f46d756df41df71272 Mon Sep 17 00:00:00 2001 From: Nicolas Dossou-Gbete <[email protected]> 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é <[email protected]> > Reviewed-by: David Roger <[email protected]> > Reviewed-by: James Lee <[email protected]> > Auto-Submit: Nicolas Dossou-Gbété <[email protected]> > 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 <[email protected]> Auto-Submit: Nicolas Dossou-Gbété <[email protected]> Reviewed-by: David Roger <[email protected]> Commit-Queue: David Roger <[email protected]> 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.
References
On This Page