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 <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.
References
On This Page