CVE-2026-14142
Overview
Files Changed
chrome/browser/extensions/extension_loading_browsertest.ccchrome/browser/extensions/extension_url_overrides_unittest.ccchrome/browser/extensions/service_worker_registration_apitest.ccchrome/browser/ui/extensions/settings_overridden_params_providers_unittest.ccchrome/common/extensions/permissions/chrome_permission_message_provider.cc
Patch
From 81cc594d570cb7c59cc0eb50584f12577e0f0834 Mon Sep 17 00:00:00 2001 From: Tim Judkins <[email protected]> Date: Tue, 19 May 2026 16:56:30 -0700 Subject: [PATCH] [Extensions] Remove old NTP permission upgrade code This code was intended to be removed a long time ago and only existed to allow for extensions using the old New Tab Page permission to update to using the newer way of doing that with chrome_url_overrides. This CL removes the old code and adjusts the associated test to ensure an extension doing this now would be disabled for a permission increase. It also updates a few tests which were previously skipping the permission dialog on initial install of a NTP override extension and then updating that extension, to instead grant the permission so that the extension isn't disabled for a "permission increase" on update. Fixed: 514073460 Change-Id: I6a53074c13f68271074b1543d20daf2102a4aca1 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7856958 Commit-Queue: Tim <[email protected]> Reviewed-by: Devlin Cronin <[email protected]> Cr-Commit-Position: refs/heads/main@{#1633202} --- diff --git a/chrome/browser/extensions/extension_loading_browsertest.cc b/chrome/browser/extensions/extension_loading_browsertest.cc index d509ba6..4c914697 100644 --- a/chrome/browser/extensions/extension_loading_browsertest.cc +++ b/chrome/browser/extensions/extension_loading_browsertest.cc @@ -23,8 +23,10 @@ #include "content/public/browser/devtools_agent_host.h" #include "content/public/test/browser_test.h" #include "content/public/test/browser_test_utils.h" +#include "extensions/browser/disable_reason.h" #include "extensions/browser/event_router.h" #include "extensions/browser/extension_host.h" +#include "extensions/browser/extension_prefs.h" #include "extensions/browser/extension_registry.h" #include "extensions/browser/process_manager.h" #include "extensions/common/manifest_handlers/background_info.h" @@ -73,8 +75,8 @@ extension_dir.WriteFile(FILE_PATH_LITERAL("newtab.html"), "<h1>Overridden New Tab Page</h1>"); - const Extension* new_tab_extension = - InstallExtension(extension_dir.Pack(), 1 /*new install*/); + const Extension* new_tab_extension = InstallExtensionWithPermissionsGranted( + extension_dir.Pack(), 1 /*new install*/); ASSERT_TRUE(new_tab_extension); // Visit the New Tab Page to get a renderer using the extension into history. @@ -109,7 +111,7 @@ } IN_PROC_BROWSER_TEST_F(ExtensionLoadingTest, - UpgradeAddingNewTabPagePermissionNoPrompt) { + UpgradeAddingNewTabPagePermissionDisablesExtension) { ASSERT_TRUE(embedded_test_server()->Start()); TestExtensionDir extension_dir; @@ -149,19 +151,22 @@ extension_dir.WriteManifest( base::StringPrintf(kManifestTemplate, 2, kNtpOverrideString)); - // Upgrade the extension, ensure that the upgrade 'worked' in the sense that - // the extension is still present and not disabled and that it now has the - // new API permission. - // TODO(robertshield): Update this once most of the population is on M62+ - // and adding NTP permissions implies a permission upgrade. - new_tab_extension = UpdateExtension( - new_tab_extension->id(), extension_dir.Pack(), 0 /*expected upgrade*/); + std::string extension_id = new_tab_extension->id(); + + // Upgrade the extension, and ensure that adding NTP permissions implies a + // permission upgrade, so the extension is disabled. + UpdateExtension(extension_id, extension_dir.Pack(), -1 /*expected upgrade*/); + + ExtensionRegistry* registry = ExtensionRegistry::Get(profile()); + new_tab_extension = registry->disabled_extensions().GetByID(extension_id); ASSERT_NE(nullptr, new_tab_extension); EXPECT_TRUE(new_tab_extension->permissions_data()->HasAPIPermission( mojom::APIPermissionID::kNewTabPageOverride)); EXPECT_THAT(new_tab_extension->version().components(), testing::ElementsAre(2)); + EXPECT_TRUE(ExtensionPrefs::Get(profile())->HasDisableReason( + extension_id, disable_reason::DISABLE_PERMISSIONS_INCREASE)); } // Tests the behavior described in http://crbug.com/41201916. diff --git a/chrome/browser/extensions/extension_url_overrides_unittest.cc b/chrome/browser/extensions/extension_url_overrides_unittest.cc index fac625b4..2f9c15d 100644 --- a/chrome/browser/extensions/extension_url_overrides_unittest.cc +++ b/chrome/browser/extensions/extension_url_overrides_unittest.cc @@ -26,6 +26,7 @@ #include "extensions/browser/extension_registrar.h" #include "extensions/browser/extension_system.h" #include "extensions/browser/mock_external_provider.h" +#include "extensions/browser/permissions/permissions_updater.h" #include "extensions/browser/test_extension_registry_observer.h" #include "extensions/buildflags/buildflags.h" #include "extensions/common/api/chrome_url_overrides.h" @@ -365,6 +366,7 @@ std::move(chrome_url_overrides)) .Build(); + PermissionsUpdater(profile_.get()).GrantActivePermissions(extension.get()); registrar()->AddExtension(extension.get()); EXPECT_EQ(extension, ExtensionUrlOverrides::GetExtensionControllingURL( diff --git a/chrome/browser/extensions/service_worker_registration_apitest.cc b/chrome/browser/extensions/service_worker_registration_apitest.cc index cd8962fad..4192d299 100644 --- a/chrome/browser/extensions/service_worker_registration_apitest.cc +++ b/chrome/browser/extensions/service_worker_registration_apitest.cc @@ -470,7 +470,7 @@ const Extension* extension = nullptr; { ExtensionTestMessageListener listener("v1 ready"); - extension = InstallExtension(crx_v1, 1); + extension = InstallExtensionWithPermissionsGranted(crx_v1, 1); ASSERT_TRUE(listener.WaitUntilSatisfied()); } @@ -1011,7 +1011,8 @@ const Extension* extension_v1 = nullptr; { ExtensionTestMessageListener v1_install_listener_("v1 installed"); - extension_v1 = InstallExtension(crx_v1_path, /*expected_change=*/1); + extension_v1 = InstallExtensionWithPermissionsGranted( + crx_v1_path, /*expected_change=*/1); SCOPED_TRACE("waiting for version 1 of the extension to install"); ASSERT_TRUE(v1_install_listener_.WaitUntilSatisfied()); ASSERT_EQ("1", extension_v1->version().GetString()); diff --git a/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc b/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc index 8dd6191..f2bc0e2 100644 --- a/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc +++ b/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc @@ -15,6 +15,7 @@ #include "chrome/test/base/search_test_utils.h" #include "components/search_engines/template_url_service.h" #include "extensions/browser/extension_registrar.h" +#include "extensions/browser/permissions/permissions_updater.h" #include "extensions/browser/ui_util.h" #include "extensions/common/extension_builder.h" @@ -54,6 +55,8 @@ std::move(chrome_url_overrides)) .Build(); + extensions::PermissionsUpdater(profile()).GrantActivePermissions( + extension.get()); registrar()->AddExtension(extension); EXPECT_EQ(extension, ExtensionUrlOverrides::GetExtensionControllingURL( chrome::ChromeUINewTabURLAsGURL(), profile())); diff --git a/chrome/common/extensions/permissions/chrome_permission_message_provider.cc b/chrome/common/extensions/permissions/chrome_permission_message_provider.cc index fe27182..0a834b1 100644 --- a/chrome/common/extensions/permissions/chrome_permission_message_provider.cc +++ b/chrome/common/extensions/permissions/chrome_permission_message_provider.cc @@ -186,13 +186,6 @@ if (requested_permissions.ShouldWarnAllHosts()) potential_total_ids.insert(APIPermissionID::kHostsAll); - // For M62, we added a new permission ID for new tab page overrides. Consider - // the addition of this permission to not result in a privilege increase for - // the time being. - // TODO(robertshield): Remove this once most of the population is on M62+ - granted_ids.erase(APIPermissionID::kNewTabPageOverride); - potential_total_ids.erase(APIPermissionID::kNewTabPageOverride); - // If all the IDs were already there, it's not a privilege increase. if (granted_ids.Includes(potential_total_ids)) return false;
Regression Test / PoC
diff --git a/chrome/browser/extensions/extension_loading_browsertest.cc b/chrome/browser/extensions/extension_loading_browsertest.cc
index d509ba6..4c914697 100644
--- a/chrome/browser/extensions/extension_loading_browsertest.cc
+++ b/chrome/browser/extensions/extension_loading_browsertest.cc
@@ -23,8 +23,10 @@
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
+#include "extensions/browser/disable_reason.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_host.h"
+#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/process_manager.h"
#include "extensions/common/manifest_handlers/background_info.h"
@@ -73,8 +75,8 @@
extension_dir.WriteFile(FILE_PATH_LITERAL("newtab.html"),
"<h1>Overridden New Tab Page</h1>");
- const Extension* new_tab_extension =
- InstallExtension(extension_dir.Pack(), 1 /*new install*/);
+ const Extension* new_tab_extension = InstallExtensionWithPermissionsGranted(
+ extension_dir.Pack(), 1 /*new install*/);
ASSERT_TRUE(new_tab_extension);
// Visit the New Tab Page to get a renderer using the extension into history.
@@ -109,7 +111,7 @@
}
IN_PROC_BROWSER_TEST_F(ExtensionLoadingTest,
- UpgradeAddingNewTabPagePermissionNoPrompt) {
+ UpgradeAddingNewTabPagePermissionDisablesExtension) {
ASSERT_TRUE(embedded_test_server()->Start());
TestExtensionDir extension_dir;
@@ -149,19 +151,22 @@
extension_dir.WriteManifest(
base::StringPrintf(kManifestTemplate, 2, kNtpOverrideString));
- // Upgrade the extension, ensure that the upgrade 'worked' in the sense that
- // the extension is still present and not disabled and that it now has the
- // new API permission.
- // TODO(robertshield): Update this once most of the population is on M62+
- // and adding NTP permissions implies a permission upgrade.
- new_tab_extension = UpdateExtension(
- new_tab_extension->id(), extension_dir.Pack(), 0 /*expected upgrade*/);
+ std::string extension_id = new_tab_extension->id();
+
+ // Upgrade the extension, and ensure that adding NTP permissions implies a
+ // permission upgrade, so the extension is disabled.
+ UpdateExtension(extension_id, extension_dir.Pack(), -1 /*expected upgrade*/);
+
+ ExtensionRegistry* registry = ExtensionRegistry::Get(profile());
+ new_tab_extension = registry->disabled_extensions().GetByID(extension_id);
ASSERT_NE(nullptr, new_tab_extension);
EXPECT_TRUE(new_tab_extension->permissions_data()->HasAPIPermission(
mojom::APIPermissionID::kNewTabPageOverride));
EXPECT_THAT(new_tab_extension->version().components(),
testing::ElementsAre(2));
+ EXPECT_TRUE(ExtensionPrefs::Get(profile())->HasDisableReason(
+ extension_id, disable_reason::DISABLE_PERMISSIONS_INCREASE));
}
// Tests the behavior described in http://crbug.com/41201916.
diff --git a/chrome/browser/extensions/extension_url_overrides_unittest.cc b/chrome/browser/extensions/extension_url_overrides_unittest.cc
index fac625b4..2f9c15d 100644
--- a/chrome/browser/extensions/extension_url_overrides_unittest.cc
+++ b/chrome/browser/extensions/extension_url_overrides_unittest.cc
@@ -26,6 +26,7 @@
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/mock_external_provider.h"
+#include "extensions/browser/permissions/permissions_updater.h"
#include "extensions/browser/test_extension_registry_observer.h"
#include "extensions/buildflags/buildflags.h"
#include "extensions/common/api/chrome_url_overrides.h"
@@ -365,6 +366,7 @@
std::move(chrome_url_overrides))
.Build();
+ PermissionsUpdater(profile_.get()).GrantActivePermissions(extension.get());
registrar()->AddExtension(extension.get());
EXPECT_EQ(extension,
ExtensionUrlOverrides::GetExtensionControllingURL(
diff --git a/chrome/browser/extensions/service_worker_registration_apitest.cc b/chrome/browser/extensions/service_worker_registration_apitest.cc
index cd8962fad..4192d299 100644
--- a/chrome/browser/extensions/service_worker_registration_apitest.cc
+++ b/chrome/browser/extensions/service_worker_registration_apitest.cc
@@ -470,7 +470,7 @@
const Extension* extension = nullptr;
{
ExtensionTestMessageListener listener("v1 ready");
- extension = InstallExtension(crx_v1, 1);
+ extension = InstallExtensionWithPermissionsGranted(crx_v1, 1);
ASSERT_TRUE(listener.WaitUntilSatisfied());
}
@@ -1011,7 +1011,8 @@
const Extension* extension_v1 = nullptr;
{
ExtensionTestMessageListener v1_install_listener_("v1 installed");
- extension_v1 = InstallExtension(crx_v1_path, /*expected_change=*/1);
+ extension_v1 = InstallExtensionWithPermissionsGranted(
+ crx_v1_path, /*expected_change=*/1);
SCOPED_TRACE("waiting for version 1 of the extension to install");
ASSERT_TRUE(v1_install_listener_.WaitUntilSatisfied());
ASSERT_EQ("1", extension_v1->version().GetString());
diff --git a/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc b/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc
index 8dd6191..f2bc0e2 100644
--- a/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc
+++ b/chrome/browser/ui/extensions/settings_overridden_params_providers_unittest.cc
@@ -15,6 +15,7 @@
#include "chrome/test/base/search_test_utils.h"
#include "components/search_engines/template_url_service.h"
#include "extensions/browser/extension_registrar.h"
+#include "extensions/browser/permissions/permissions_updater.h"
#include "extensions/browser/ui_util.h"
#include "extensions/common/extension_builder.h"
@@ -54,6 +55,8 @@
std::move(chrome_url_overrides))
.Build();
+ extensions::PermissionsUpdater(profile()).GrantActivePermissions(
+ extension.get());
registrar()->AddExtension(extension);
EXPECT_EQ(extension, ExtensionUrlOverrides::GetExtensionControllingURL(
chrome::ChromeUINewTabURLAsGURL(), profile()));
Original Bug Report
Potential bypass of New Tab Page override warning via Web Store API logic error
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A logic error in the extension permission system allows a compromised Web Store renderer to suppress the mandatory New Tab Page override warning. By providing a spoofed manifest that omits the override, an attacker can trick the user into approving an installation that silently hijacks the New Tab Page. This is enabled by stale code in the permission provider that explicitly ignores New Tab Page override permissions during verification.
Affected files:
chrome/common/extensions/permissions/chrome_permission_message_provider.ccchrome/browser/extensions/api/webstore_private/webstore_private_api.ccextensions/browser/crx_installer.ccextensions/browser/install_approval.ccchrome/browser/ui/extensions/settings_api_bubble_helpers.cc
Estimated timestamp from git blame: 2017-07-19
Summary
A potential vulnerability exists in the Chrome extension installation flow where a compromised renderer process hosting the chromewebstore.google.com origin can suppress the ‘Replace the page you see when opening a new tab’ warning. This occurs because the browser process fails to correctly validate the actual permissions of a downloaded CRX against the manifest approved by the user, specifically regarding New Tab Page (NTP) overrides.
Technical Details
The Chrome Web Store installation flow uses the webstorePrivate.beginInstallWithManifest3 API. The vulnerability involves the following sequence of events:
- Spoofed Prompt: A compromised renderer calls the API with a spoofed manifest that omits the
chrome_url_overrideskey. The browser creates a dummy extension from this manifest to show the installation prompt. Since the key is missing, the NTP override warning is suppressed. - Loose Verification: Upon user approval, the browser creates an
extensions::InstallApprovalwithmanifest_check_levelset tokLoose. - Validation Bypass: After downloading the real CRX (which contains the NTP override),
extensions::CrxInstaller::AllowInstall(extensions/browser/crx_installer.cc) detects the manifest mismatch and callsIsPrivilegeIncreaseto verify if the new manifest requires more permissions than approved. - Root Cause: In
chrome/common/extensions/permissions/chrome_permission_message_provider.cc, theIsAPIOrManifestPrivilegeIncreasefunction contains a stale workaround (Lines 193-194):By erasing// For M62, we added a new permission ID for new tab page overrides. Consider // the addition of this permission to not result in a privilege increase for // the time being. granted_ids.erase(APIPermissionID::kNewTabPageOverride); potential_total_ids.erase(APIPermissionID::kNewTabPageOverride);kNewTabPageOverridefrom the comparison sets, the browser incorrectly concludes that the addition of an NTP override is not a privilege increase.
Impact
An attacker can silently hijack a user’s New Tab Page. This is particularly effective on Linux, where the post-install ‘Extension Settings Overridden’ notification is explicitly disabled (g_ntp_post_install_ui_enabled = false in chrome/browser/ui/extensions/settings_api_bubble_helpers.cc), making the override entirely transparent to the user.
Potential Steps to Reproduce
- Compromise a renderer process with access to
https://chromewebstore.google.com/*. - Call
chrome.webstorePrivate.beginInstallWithManifest3with the ID of a legitimate NTP-overriding extension, but use a manifest string that omits thechrome_url_overridesfield. - Observe that the resulting install prompt does not warn the user about the NTP override.
- After user approval, the extension is installed and the NTP is successfully hijacked.
Recommended Fix
Remove the stale logic in chrome/common/extensions/permissions/chrome_permission_message_provider.cc that erases APIPermissionID::kNewTabPageOverride from the granted_ids and potential_total_ids sets. This will ensure that NTP overrides are treated as a privilege increase if they were not present in the user-approved manifest.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.