CVE-2026-11229
Overview
Files Changed
chrome/browser/ui/webui/policy/policy_test_ui_browsertest.ccchrome/browser/ui/webui/policy/policy_ui_handler.ccios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm
Patch
From 7b6ba28a2a8ea3abd1cbe60ccea6f964f0cdb142 Mon Sep 17 00:00:00 2001
From: Dzmitry Radchuk <radchuk@google.com>
Date: Thu, 16 Apr 2026 04:22:48 -0700
Subject: [PATCH] Ensure chrome://policy restartBrowser message ignored when not supported
It was possible to go to chrome://policy and in the dev tools send
the restartBrowser message to set test policies even if the policy test
page was disabled and/or unavailable because both pages share the same
handler.
Bug: 482713603
Change-Id: I5a5a777be4c5e5083f9a12f1c8240b5cbb0ccca8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7764606
Reviewed-by: Yann Dago <ydago@chromium.org>
Auto-Submit: Dzmitry Radchuk <radchuk@google.com>
Reviewed-by: Gauthier Ambard <gambard@chromium.org>
Commit-Queue: Gauthier Ambard <gambard@chromium.org>
Reviewed-by: Owen Min <zmin@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1615744}
---
diff --git a/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc b/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
index 651b791..36a9768 100644
--- a/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
@@ -316,6 +316,36 @@
};
IN_PROC_BROWSER_TEST_P(PolicyTestHandlerTest,
+ HandleRestartBrowserNotSupported) {
+ // Ensure chrome://policy/test not supported.
+ policy::ScopedManagementServiceOverrideForTesting profile_management(
+ policy::ManagementServiceFactory::GetForProfile(GetProfile()),
+ policy::EnterpriseManagementAuthority::CLOUD);
+ std::unique_ptr<PolicyUIHandler> handler = SetUpHandler();
+ const std::string jsonString =
+ R"([
+ {"level": 0,"scope": 0,"source": 0, "namespace": "chrome",
+ "name": "AutofillAddressEnabled","value": false},
+ {"level": 1,"scope": 1,"source": 2, "namespace": "chrome",
+ "name": "CloudReportingEnabled","value": true}
+ ])";
+
+ // Open chrome://policy
+ ASSERT_TRUE(
+ content::NavigateToURL(web_contents(), GURL(chrome::kChromeUIPolicyURL)));
+ RestartBrowser(jsonString);
+
+ base::RunLoop().RunUntilIdle();
+
+ // Check policies not applied to preference
+ PrefService* prefs = g_browser_process->local_state();
+ std::string pref_value =
+ prefs->GetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup);
+ EXPECT_TRUE(pref_value.empty());
+}
+
+
+IN_PROC_BROWSER_TEST_P(PolicyTestHandlerTest,
HandleSetLocalTestPoliciesNotSupported) {
// Ensure chrome://policy/test not supported.
policy::ScopedManagementServiceOverrideForTesting profile_management(
@@ -655,6 +685,9 @@
RestartBrowser(jsonString);
handler.reset();
+
+ // Restart the browser.
+ chrome::AttemptRestart();
}
IN_PROC_BROWSER_TEST_P(PolicyTestHandlerTestDisabledByPolicy,
diff --git a/chrome/browser/ui/webui/policy/policy_ui_handler.cc b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
index 8426032..5a3318e1 100644
--- a/chrome/browser/ui/webui/policy/policy_ui_handler.cc
+++ b/chrome/browser/ui/webui/policy/policy_ui_handler.cc
@@ -429,6 +429,10 @@
}
void PolicyUIHandler::RestartBrowser(const std::string& policies) {
+ if (!PolicyUI::ShouldLoadTestPage(&*profile_)) {
+ return;
+ }
+
// Set policies to preference
PrefService* prefs = g_browser_process->local_state();
prefs->SetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup,
diff --git a/ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm b/ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm
index 337c52f..4caa1b7 100644
--- a/ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm
+++ b/ios/chrome/browser/webui/ui_bundled/policy/policy_ui_handler.mm
@@ -317,6 +317,10 @@
}
void PolicyUIHandler::RestartBrowser(const std::string& policies) {
+ if (!PolicyUI::ShouldLoadTestPage(&*profile_)) {
+ return;
+ }
+
// Set policies to preference
PrefService* prefs = GetApplicationContext()->GetLocalState();
prefs->SetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup,
Regression Test / PoC
diff --git a/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc b/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
index 651b791..36a9768 100644
--- a/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
+++ b/chrome/browser/ui/webui/policy/policy_test_ui_browsertest.cc
@@ -316,6 +316,36 @@
};
IN_PROC_BROWSER_TEST_P(PolicyTestHandlerTest,
+ HandleRestartBrowserNotSupported) {
+ // Ensure chrome://policy/test not supported.
+ policy::ScopedManagementServiceOverrideForTesting profile_management(
+ policy::ManagementServiceFactory::GetForProfile(GetProfile()),
+ policy::EnterpriseManagementAuthority::CLOUD);
+ std::unique_ptr<PolicyUIHandler> handler = SetUpHandler();
+ const std::string jsonString =
+ R"([
+ {"level": 0,"scope": 0,"source": 0, "namespace": "chrome",
+ "name": "AutofillAddressEnabled","value": false},
+ {"level": 1,"scope": 1,"source": 2, "namespace": "chrome",
+ "name": "CloudReportingEnabled","value": true}
+ ])";
+
+ // Open chrome://policy
+ ASSERT_TRUE(
+ content::NavigateToURL(web_contents(), GURL(chrome::kChromeUIPolicyURL)));
+ RestartBrowser(jsonString);
+
+ base::RunLoop().RunUntilIdle();
+
+ // Check policies not applied to preference
+ PrefService* prefs = g_browser_process->local_state();
+ std::string pref_value =
+ prefs->GetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup);
+ EXPECT_TRUE(pref_value.empty());
+}
+
+
+IN_PROC_BROWSER_TEST_P(PolicyTestHandlerTest,
HandleSetLocalTestPoliciesNotSupported) {
// Ensure chrome://policy/test not supported.
policy::ScopedManagementServiceOverrideForTesting profile_management(
@@ -655,6 +685,9 @@
RestartBrowser(jsonString);
handler.reset();
+
+ // Restart the browser.
+ chrome::AttemptRestart();
}
IN_PROC_BROWSER_TEST_P(PolicyTestHandlerTestDisabledByPolicy,
Original Bug Report
WebUI restart message applies local test policies when the policy test page should be disabled
Report description
WebUI restart message applies local test policies when the policy test page should be disabled
Bug location
Where do you want to report your vulnerability?
Chrome VRP – Report security issues affecting the Chrome browser. See program rules
Which URL (or repository) have you found the vulnerability in?
https://chromium.googlesource.com/chromium/src/
The problem
Please describe the technical details of the vulnerability
1. technical details
Chromium exposes a policy inspection WebUI at chrome://policy, and a separate test page at chrome://policy/test that is only intended to be available when policy testing is explicitly enabled.
The test page availability is guarded by PolicyUI::ShouldLoadTestPage, which checks both a testing preference and whether the profile is cloud managed:
This behavior represents a regression of the intended restrictions that were previously addressed in https://issues.chromium.org/issues/338248595.
// chrome/browser/ui/webui/policy/policy_ui.cc
// static
bool PolicyUI::ShouldLoadTestPage(Profile* profile) {
// Test page should only load if testing is enabled.
if (!policy::utils::IsPolicyTestingEnabled(profile->GetPrefs(),
chrome::GetChannel())) {
return false;
}
// The test page is not allowed if the profile is cloud managed unless
// we are already using the test policies.
if (policy::ManagementServiceFactory::GetForProfile(profile)
->HasManagementAuthority(
policy::EnterpriseManagementAuthority::CLOUD) &&
!profile->GetProfilePolicyConnector()->IsUsingLocalTestPolicyProvider()) {
return false;
}
return true;
}
At startup, ChromeBrowserPolicyConnector::MaybeApplyLocalTestPolicies reads a JSON string from a local pref and, if present, activates the local test policy provider and loads that JSON as policies:
// chrome/browser/policy/chrome_browser_policy_connector.cc
void ChromeBrowserPolicyConnector::MaybeApplyLocalTestPolicies(
PrefService* local_state) {
// Early return if the policy test page is disabled by any policy. This is
// done because that policy is a profile level policy and we have not yet
// loaded any profile to access its prefs.
const auto& chrome_policies =
GetPolicyService()->GetPolicies(policy::PolicyNamespace(
policy::PolicyDomain::POLICY_DOMAIN_CHROME, std::string()));
if (auto* policy_test_page_enabled = chrome_policies.GetValue(
policy::key::kPolicyTestPageEnabled, base::Value::Type::BOOLEAN);
policy_test_page_enabled && !policy_test_page_enabled->GetBool()) {
return;
}
std::string policies_to_apply =
local_state->GetString(policy_prefs::kLocalTestPoliciesForNextStartup);
if (policies_to_apply.empty()) {
return;
}
LocalTestPolicyProvider* test_provider =
local_test_provider_for_testing_ ? static_cast<LocalTestPolicyProvider*>(
local_test_provider_for_testing_)
: local_test_provider_.get();
test_provider->set_active(true);
GetPolicyService()->UseLocalTestPolicyProvider(test_provider);
test_provider->LoadJsonPolicies(policies_to_apply);
local_state->ClearPref(policy_prefs::kLocalTestPoliciesForNextStartup);
}
The local test provider itself is created on supported channels (for example, Dev/Canary and debug builds) without consulting per‑profile management state:
// components/policy/core/common/local_test_policy_provider.cc
// static
std::unique_ptr<LocalTestPolicyProvider>
LocalTestPolicyProvider::CreateIfAllowed(version_info::Channel channel) {
if (utils::IsPolicyTestingEnabled(/*pref_service=*/nullptr, channel)) {
return base::WrapUnique(new LocalTestPolicyProvider());
}
return nullptr;
}
On the WebUI side, both chrome://policy and chrome://policy/test share the same PolicyUIHandler. Among other messages, this handler exposes a "restartBrowser" message that accepts a JSON string and stores it into the pref inspected at startup:
// chrome/browser/ui/webui/policy/policy_ui_handler.cc
void PolicyUIHandler::HandleRestartBrowser(const base::ListValue& args) {
CHECK(args.size() == 2);
const std::string& policies = args[1].GetString();
// Set policies to preference
PrefService* prefs = g_browser_process->local_state();
prefs->SetString(policy::policy_prefs::kLocalTestPoliciesForNextStartup,
policies);
// Restart browser
chrome::AttemptRestart();
}
Unlike the setLocalTestPolicies and revertLocalTestPolicies handlers, HandleRestartBrowser does not check PolicyUI::ShouldLoadTestPage before accepting the JSON payload. As a result:
- In builds where the local test policy provider exists (for example, Dev/Canary or debug builds), and
- In profiles where
PolicyUI::ShouldLoadTestPage(profile)would returnfalse(for example, cloud‑managed profiles that are not already using local test policies),
any JavaScript running in the chrome://policy WebUI can call the "restartBrowser" message and cause arbitrary JSON to be written into kLocalTestPoliciesForNextStartup. On the next startup, that JSON is loaded via MaybeApplyLocalTestPolicies and applied as active policies through the local test provider, even though the test page itself should not be available.
2. vulnerability reproduction
The attached JavaScript helper under web/policy_js_bypass/policy_restart_test.js demonstrates the behavior in a realistic way using a Dev‑channel Chrome build:
- The script is designed to be pasted into the DevTools console on
chrome://policy. - It optionally loads a JSON payload from a remote URL or uses an inline JSON array of policy entries (for example, setting
CloudReportingEnabledtofalse). - It validates that the payload is parseable as JSON and then calls:
chrome.send('restartBrowser', [callbackId, jsonText]);
Conceptual reproduction steps using a Dev build:
- Start a Dev‑channel Chrome build where local test policy provider support is compiled in.
- Open
chrome://policyand then open DevTools (Console tab). - Copy the contents of
web/policy_js_bypass/policy_restart_test.jsinto the console, optionally adjusting the JSON payload if needed, and execute it. - Observe console logging indicating that a payload is chosen and the
"restartBrowser"message is being sent. - The browser process exits and restarts automatically.
- After restart, open
chrome://policyagain and search forCloudReportingEnabled(or other values from the JSON payload). The policy is now present with the value defined in the JSON, sourced from the local test provider.
In a profile configuration where PolicyUI::ShouldLoadTestPage(profile) would return false (for example, a cloud‑managed profile that does not already use local test policies), the same "restartBrowser" path would still accept and store the JSON payload from chrome://policy. On restart, MaybeApplyLocalTestPolicies applies those values as active policies, even though the test page is not supposed to be usable for injecting local test policies.
Impact analysis
-
Who can exploit it: Any local user or script that can execute JavaScript in the
chrome://policyWebUI of a build where the local test policy provider is enabled (for example, Dev/Canary or debug builds). This includes users manually pasting code into the DevTools console or higher‑privileged browser automation running in the same profile. -
What they gain: In environments where the policy test page is intended to be unavailable (for example, certain cloud‑managed profiles), the user can still inject a JSON payload through the
"restartBrowser"WebUI message and cause those values to be applied as local test policies on the next startup. This allows local policy values (such asCloudReportingEnabledor other supported keys) to be overridden through a test‑only mechanism even when the test page should be disabled, affecting how the browser interprets and enforces policies for that profile. -
Security significance: The issue does not provide remote code execution or cross‑user compromise, and it does not bypass sandboxing. Its significance lies in weakening the separation between normal policy evaluation and test‑only policy mechanisms: policy values can be altered via a test helper path from
chrome://policyeven when the environment is configured so that the policy test page should not be usable. This can change how certain enterprise policy checks behave for that local profile, depending on which policies are supplied in the JSON payload.
The cause
What version of Chrome have you found the security issue in?
146.0.7666.1/stable
Is the security issue related to a crash?
No, it is not related to a crash.
Choose the type of vulnerability
Permissions Bypass
How would you like to be publicly acknowledged for your report?
Povcfe of Tencent Security Xuanwu Lab