From: uazo Date: Thu, 28 Dec 2023 14:58:09 +0000 Subject: Chrome web store protection The amount of information sent is minimized without disabling the ability to install extensions (which is under user control) It became possible to activate the autoupdate of extensions by sending the minimum of values. Installation with the chrome web store is allowed, but the website by default cannot retrieve the list of installed extensions and their status (under flag) All http calls related to requesting and downloading updates are cookie-free, but the ip and the list of extensions are traceable, that information cannot be omitted. Auto-update is disabled by default: it is possible to activate it with chrome://extensions/ License: GPL-2.0-or-later - https://spdx.org/licenses/GPL-2.0-or-later.html --- chrome/app/extensions_strings.grdp | 6 +++ .../developer_private_api.cc | 37 ++++++++++++- .../developer_private/developer_private_api.h | 3 ++ .../webstore_private/webstore_private_api.cc | 15 +++++- chrome/browser/extensions/cws_info_service.cc | 1 + .../extensions/extension_system_impl.cc | 2 + .../chrome_extension_downloader_factory.cc | 1 - .../extensions/updater/extension_updater.cc | 40 ++++++++------ .../extensions/updater/extension_updater.h | 4 ++ .../extensions/webstore_install_helper.cc | 2 +- .../browser/extensions/webstore_installer.cc | 2 +- .../browser/resources/extensions/manager.html | 1 + .../browser/resources/extensions/manager.ts | 7 +++ .../browser/resources/extensions/service.ts | 5 ++ .../browser/resources/extensions/toolbar.html | 12 +++++ .../browser/resources/extensions/toolbar.ts | 20 +++++++ .../resources/webstore_app/manifest.json | 7 +-- .../ui/webui/extensions/extensions_ui.cc | 8 +++ .../chrome_update_query_params_delegate.cc | 13 ++--- .../extensions/api/developer_private.idl | 2 + .../update_client/update_query_params.cc | 7 ++- .../about_flags_cc/Webstore-protection.inc | 19 +++++++ .../browser/api/management/management_api.cc | 50 +++++++++++------ .../browser/updater/extension_downloader.cc | 53 +++++++------------ .../browser/updater/extension_downloader.h | 1 - .../browser/updater/manifest_fetch_data.cc | 8 +-- .../browser/updater/safe_manifest_parser.cc | 1 + extensions/common/extension_features.cc | 15 ++++++ extensions/common/extension_features.h | 4 ++ .../definitions/developer_private.d.ts | 4 +- 30 files changed, 252 insertions(+), 98 deletions(-) create mode 100644 cromite_flags/chrome/browser/about_flags_cc/Webstore-protection.inc diff --git a/chrome/app/extensions_strings.grdp b/chrome/app/extensions_strings.grdp --- a/chrome/app/extensions_strings.grdp +++ b/chrome/app/extensions_strings.grdp @@ -76,6 +76,12 @@ Developer mode + + Enable Auto-Update + + + Need restart + This extension is outdated and disabled by enterprise policy. It might become enabled automatically when a newer version is available. diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.cc b/chrome/browser/extensions/api/developer_private/developer_private_api.cc --- a/chrome/browser/extensions/api/developer_private/developer_private_api.cc +++ b/chrome/browser/extensions/api/developer_private/developer_private_api.cc @@ -127,6 +127,12 @@ #include "url/gurl.h" #include "url/origin.h" +#include "chrome/browser/about_flags.h" +#include "chrome/browser/browser_process.h" +#include "components/flags_ui/pref_service_flags_storage.h" +#include "components/flags_ui/feature_entry.h" +#include "components/flags_ui/flags_storage.h" + #if BUILDFLAG(ENABLE_SUPERVISED_USERS) #include "components/supervised_user/core/browser/supervised_user_preferences.h" #endif // BUILDFLAG(ENABLE_SUPERVISED_USERS) @@ -476,9 +482,32 @@ std::unique_ptr DeveloperPrivateAPI::CreateProfileInfo( info->can_load_unpacked = ExtensionManagementFactory::GetForBrowserContext(profile) ->HasAllowlistedExtension(); + info->is_extension_autoupdate_enabled = DeveloperPrivateAPI::IsExtensionAutoupdateEnabled(); return info; } +// static +bool DeveloperPrivateAPI::IsExtensionAutoupdateEnabled() { + const std::string enabled_entry = "enable-extension-autoupdate@1"; + flags_ui::PrefServiceFlagsStorage flags_storage( + g_browser_process->local_state()); + std::set entries = flags_storage.GetFlags(); + return entries.count(enabled_entry) > 0; +} + +// static +void DeveloperPrivateAPI::SetExtensionAutoupdateEnabled(bool enable) { + flags_ui::PrefServiceFlagsStorage flags_storage( + g_browser_process->local_state()); + if (enable) { + about_flags::SetFeatureEntryEnabled( + &flags_storage, "enable-extension-autoupdate@1", true); + } else { + about_flags::SetFeatureEntryEnabled( + &flags_storage, "enable-extension-autoupdate", false); + } +} + template <> void BrowserContextKeyedAPIFactory< DeveloperPrivateAPI>::DeclareFactoryDependencies() { @@ -908,6 +937,7 @@ ExtensionFunction::ResponseAction DeveloperPrivateAutoUpdateFunction::Run() { ExtensionUpdater::CheckParams params; params.fetch_priority = DownloadFetchPriority::kForeground; params.install_immediately = true; + params.user_initiated = true; params.callback = base::BindOnce(&DeveloperPrivateAutoUpdateFunction::OnComplete, this); updater->CheckNow(std::move(params)); @@ -1048,7 +1078,7 @@ DeveloperPrivateUpdateProfileConfigurationFunction::Run() { const developer::ProfileConfigurationUpdate& update = params->update; - if (update.in_developer_mode) { + if (update.in_developer_mode.has_value() && update.in_developer_mode) { Profile* profile = Profile::FromBrowserContext(browser_context()); CHECK(profile); #if BUILDFLAG(ENABLE_SUPERVISED_USERS) @@ -1060,6 +1090,11 @@ DeveloperPrivateUpdateProfileConfigurationFunction::Run() { util::SetDeveloperModeForProfile(profile, *update.in_developer_mode); } + if (update.is_extension_autoupdate_enabled.has_value()) { + DeveloperPrivateAPI::SetExtensionAutoupdateEnabled( + *update.is_extension_autoupdate_enabled); + } + return RespondNow(NoArguments()); } diff --git a/chrome/browser/extensions/api/developer_private/developer_private_api.h b/chrome/browser/extensions/api/developer_private/developer_private_api.h --- a/chrome/browser/extensions/api/developer_private/developer_private_api.h +++ b/chrome/browser/extensions/api/developer_private/developer_private_api.h @@ -233,6 +233,9 @@ class DeveloperPrivateAPI : public BrowserContextKeyedAPI, static std::unique_ptr CreateProfileInfo( Profile* profile); + static bool IsExtensionAutoupdateEnabled(); + static void SetExtensionAutoupdateEnabled(bool enable); + // Convenience method to get the DeveloperPrivateAPI for a profile. static DeveloperPrivateAPI* Get(content::BrowserContext* context); diff --git a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc --- a/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc +++ b/chrome/browser/extensions/api/webstore_private/webstore_private_api.cc @@ -60,6 +60,7 @@ #include "extensions/browser/extension_system.h" #include "extensions/browser/extension_util.h" #include "extensions/common/extension.h" +#include "extensions/common/extension_features.h" #include "extensions/common/manifest.h" #include "extensions/common/manifest_constants.h" #include "extensions/common/manifest_handlers/permissions_parser.h" @@ -230,12 +231,16 @@ WebstorePrivateApi::Delegate* test_delegate = nullptr; // there was previously stored data, or an empty string otherwise. The Set will // overwrite any previous login. std::string GetWebstoreLogin(Profile* profile) { + if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) + return std::string(); if (profile->GetPrefs()->HasPrefPath(kWebstoreLogin)) return profile->GetPrefs()->GetString(kWebstoreLogin); return std::string(); } void SetWebstoreLogin(Profile* profile, const std::string& login) { + if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) + return; profile->GetPrefs()->SetString(kWebstoreLogin, login); } @@ -245,6 +250,8 @@ void RecordWebstoreExtensionInstallResult(bool success) { api::webstore_private::ExtensionInstallStatus ConvertExtensionInstallStatusForAPI(ExtensionInstallStatus status) { + if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) + return api::webstore_private::ExtensionInstallStatus::kInstallable; switch (status) { case kCanRequest: return api::webstore_private::ExtensionInstallStatus::kCanRequest; @@ -1163,7 +1170,8 @@ ExtensionFunction::ResponseAction WebstorePrivateIsInIncognitoModeFunction::Run() { Profile* profile = Profile::FromBrowserContext(browser_context()); return RespondNow(ArgumentList(IsInIncognitoMode::Results::Create( - profile != profile->GetOriginalProfile()))); + base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore) + && profile != profile->GetOriginalProfile()))); } WebstorePrivateIsPendingCustodianApprovalFunction:: @@ -1262,11 +1270,14 @@ WebstorePrivateGetReferrerChainFunction::Run() { request.mutable_referrer_chain_options()->set_recent_navigations_to_collect( recent_navigations_to_collect); + std::string serialized_referrer_proto = request.SerializeAsString(); + if (!base::FeatureList::IsEnabled(extensions_features::kEnableExtensionManagementToChromeStore)) + serialized_referrer_proto = ""; // Base64 encode the request to avoid issues with base::Value rejecting // strings which are not valid UTF8. return RespondNow( ArgumentList(api::webstore_private::GetReferrerChain::Results::Create( - base::Base64Encode(request.SerializeAsString())))); + base::Base64Encode(serialized_referrer_proto)))); } WebstorePrivateGetExtensionStatusFunction:: diff --git a/chrome/browser/extensions/cws_info_service.cc b/chrome/browser/extensions/cws_info_service.cc --- a/chrome/browser/extensions/cws_info_service.cc +++ b/chrome/browser/extensions/cws_info_service.cc @@ -160,6 +160,7 @@ namespace extensions { BASE_FEATURE(kCWSInfoService, "CWSInfoService", base::FEATURE_ENABLED_BY_DEFAULT); +SET_CROMITE_FEATURE_DISABLED(kCWSInfoService); // Increase the frequency of periodic retrieval of extensions metadata from // CWS. This feature is used only for testing purposes. diff --git a/chrome/browser/extensions/extension_system_impl.cc b/chrome/browser/extensions/extension_system_impl.cc --- a/chrome/browser/extensions/extension_system_impl.cc +++ b/chrome/browser/extensions/extension_system_impl.cc @@ -56,6 +56,7 @@ #include "extensions/browser/updater/uninstall_ping_sender.h" #include "extensions/browser/user_script_manager.h" #include "extensions/common/constants.h" +#include "extensions/common/extension_features.h" #include "extensions/common/features/feature_channel.h" #include "extensions/common/manifest_url_handlers.h" #include "ui/message_center/public/cpp/notifier_id.h" @@ -83,6 +84,7 @@ UninstallPingSender::FilterResult ShouldSendUninstallPing( Profile* profile, const Extension* extension, UninstallReason reason) { + if ((true)) return UninstallPingSender::DO_NOT_SEND_PING; ExtensionManagement* extension_management = ExtensionManagementFactory::GetForBrowserContext(profile); if (extension && (extension->from_webstore() || diff --git a/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc b/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc --- a/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc +++ b/chrome/browser/extensions/updater/chrome_extension_downloader_factory.cc @@ -49,7 +49,6 @@ ChromeExtensionDownloaderFactory::CreateForURLLoaderFactory( manifest_query_params += "&testrequest=1"; } downloader->set_manifest_query_params(manifest_query_params); - downloader->set_ping_enabled_domain("google.com"); return downloader; } diff --git a/chrome/browser/extensions/updater/extension_updater.cc b/chrome/browser/extensions/updater/extension_updater.cc --- a/chrome/browser/extensions/updater/extension_updater.cc +++ b/chrome/browser/extensions/updater/extension_updater.cc @@ -43,6 +43,7 @@ #include "extensions/browser/updater/extension_update_data.h" #include "extensions/common/constants.h" #include "extensions/common/extension.h" +#include "extensions/common/extension_features.h" #include "extensions/common/extension_set.h" #include "extensions/common/extension_updater_uma.h" #include "extensions/common/extension_urls.h" @@ -197,6 +198,10 @@ void ExtensionUpdater::Start() { DCHECK(!weak_ptr_factory_.HasWeakPtrs()); DCHECK(registry_); alive_ = true; + if (first_start_) { + first_start_ = false; + CheckSoon(); + } // Check soon, and set up the first delayed check. if (!g_skip_scheduled_checks_for_tests) { if (g_should_immediately_update) @@ -329,7 +334,8 @@ void ExtensionUpdater::AddToDownloader( const Extension& extension = **extension_iter; const ExtensionId& extension_id = extension.id(); if (!Manifest::IsAutoUpdateableLocation(extension.location())) { - VLOG(2) << "Extension " << extension_id << " is not auto updateable"; + LOG(INFO) << "Extension " << extension_id << " is not auto updateable: " + << "location=" << extension.location(); continue; } // An extension might be overwritten by policy, and have its update url @@ -343,7 +349,7 @@ void ExtensionUpdater::AddToDownloader( continue; } - if (CanUseUpdateService(extension_id)) { + if ((false) && CanUseUpdateService(extension_id)) { update_check_params->update_info[extension_id] = ExtensionUpdateData(); } else if (AddExtensionToDownloader(extension, request_id, fetch_priority)) { @@ -381,6 +387,12 @@ bool ExtensionUpdater::AddExtensionToDownloader( } void ExtensionUpdater::CheckNow(CheckParams params) { + if (!params.user_initiated && + !base::FeatureList::IsEnabled( + extensions_features::kEnableExtensionAutoupdate)) { + LOG(INFO) << "Extensions autoupdate is disabled."; + return; + } if (params.ids.empty()) { // Checking all extensions. Cancel pending DoCheckSoon() call if there's // one, as it would be redundant. @@ -389,7 +401,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { int request_id = next_request_id_++; - VLOG(2) << "Starting update check " << request_id; + LOG(INFO) << "Starting extensions update check id: " << request_id; if (params.ids.empty()) NotifyStarted(); @@ -461,7 +473,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { << " is not a corrupt reinstall"; update_check_params.update_info[pending_id] = ExtensionUpdateData(); } else if (!Manifest::IsAutoUpdateableLocation(info->install_source())) { - VLOG(2) << "Extension " << pending_id << " is not auto updateable"; + LOG(INFO) << "Extension " << pending_id << " is not auto updateable"; continue; } // We have to mark high-priority extensions (such as policy-forced @@ -471,7 +483,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { // See https://crbug.com/904600 and https://crbug.com/965686. const bool is_high_priority_extension_pending = pending_extension_manager->HasHighPriorityPendingExtension(); - if (CanUseUpdateService(pending_id)) { + if ((false) && CanUseUpdateService(pending_id)) { update_check_params.update_info[pending_id].is_corrupt_reinstall = is_corrupt_reinstall; if (is_corrupt_reinstall) { @@ -492,6 +504,8 @@ void ExtensionUpdater::CheckNow(CheckParams params) { LOG(WARNING) << "Corrupt extension with id " << pending_id << " will be reinstalled with ExtensionDownloader."; } + LOG(INFO) << "Extension " << pending_id << " is auto updateable " + << "from " << info->update_url(); } else { InstallStageTracker::Get(profile_)->ReportFailure( pending_id, @@ -504,13 +518,6 @@ void ExtensionUpdater::CheckNow(CheckParams params) { AddToDownloader(®istry_->disabled_extensions(), pending_ids, request_id, params.fetch_priority, &update_check_params); ExtensionSet remotely_disabled_extensions; - for (auto extension : registry_->blocklisted_extensions()) { - if (blocklist_prefs::HasOmahaBlocklistState( - extension->id(), BitMapBlocklistState::BLOCKLISTED_MALWARE, - extension_prefs_)) { - remotely_disabled_extensions.Insert(extension); - } - } AddToDownloader(&remotely_disabled_extensions, pending_ids, request_id, params.fetch_priority, &update_check_params); } else { @@ -518,7 +525,7 @@ void ExtensionUpdater::CheckNow(CheckParams params) { const Extension* extension = registry_->GetExtensionById(id, ExtensionRegistry::EVERYTHING); if (extension) { - if (CanUseUpdateService(id)) { + if ((false) && CanUseUpdateService(id)) { update_check_params.update_info[id] = ExtensionUpdateData(); } else if (AddExtensionToDownloader(*extension, request_id, params.fetch_priority)) { @@ -659,7 +666,7 @@ void ExtensionUpdater::OnExtensionDownloadFinished( file.extension_id, InstallStageTracker::Stage::INSTALLING); UpdatePingData(file.extension_id, ping); - VLOG(2) << download_url << " written to " << file.path.value(); + LOG(INFO) << download_url << " written to " << file.path.value(); FetchedCRXFile fetched(file, file_ownership_passed, request_ids, std::move(callback)); @@ -670,6 +677,7 @@ void ExtensionUpdater::OnExtensionDownloadFinished( bool ExtensionUpdater::GetPingDataForExtension(const ExtensionId& id, DownloadPingData* ping_data) { + if ((true)) return false; DCHECK(alive_); ping_data->rollcall_days = CalculatePingDaysForExtension(extension_prefs_->LastPingDay(id)); @@ -768,7 +776,7 @@ bool ExtensionUpdater::CanUseUpdateService( void ExtensionUpdater::InstallCRXFile(FetchedCRXFile crx_file) { std::set request_ids; - VLOG(2) << "updating " << crx_file.info.extension_id << " with " + LOG(INFO) << "Updating " << crx_file.info.extension_id << " with " << crx_file.info.path.value(); // The ExtensionService is now responsible for cleaning up the temp file @@ -877,7 +885,7 @@ void ExtensionUpdater::NotifyIfFinished(int request_id) { InProgressCheck& request = requests_in_progress_[request_id]; if (!request.in_progress_ids.empty() || request.awaiting_update_service) return; // This request is not done yet. - VLOG(2) << "Finished update check " << request_id; + LOG(INFO) << "Finished update check id: " << request_id; if (!request.callback.is_null()) std::move(request.callback).Run(); requests_in_progress_.erase(request_id); diff --git a/chrome/browser/extensions/updater/extension_updater.h b/chrome/browser/extensions/updater/extension_updater.h --- a/chrome/browser/extensions/updater/extension_updater.h +++ b/chrome/browser/extensions/updater/extension_updater.h @@ -80,6 +80,8 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate { // right away. bool install_immediately = false; + bool user_initiated = false; + // An extension update check can be originated by a user or by a scheduled // task. When the value of |fetch_priority| is FOREGROUND, the update // request was initiated by a user. @@ -327,6 +329,8 @@ class ExtensionUpdater : public ExtensionDownloaderDelegate { base::TimeDelta frequency_; bool will_check_soon_ = false; + bool first_start_ = true; + raw_ptr extension_prefs_ = nullptr; raw_ptr prefs_ = nullptr; raw_ptr profile_ = nullptr; diff --git a/chrome/browser/extensions/webstore_install_helper.cc b/chrome/browser/extensions/webstore_install_helper.cc --- a/chrome/browser/extensions/webstore_install_helper.cc +++ b/chrome/browser/extensions/webstore_install_helper.cc @@ -46,7 +46,7 @@ void WebstoreInstallHelper::Start( data_decoder::DataDecoder::ParseJsonIsolated( manifest_, base::BindOnce(&WebstoreInstallHelper::OnJSONParsed, this)); - if (icon_url_.is_empty()) { + if ((true) || icon_url_.is_empty()) { icon_decode_complete_ = true; } else { // No existing |icon_fetcher_| to avoid unbalanced AddRef(). diff --git a/chrome/browser/extensions/webstore_installer.cc b/chrome/browser/extensions/webstore_installer.cc --- a/chrome/browser/extensions/webstore_installer.cc +++ b/chrome/browser/extensions/webstore_installer.cc @@ -624,7 +624,7 @@ void WebstoreInstaller::StartDownload( download_url_, render_process_host_id, render_frame_host->GetRoutingID(), traffic_annotation)); params->set_file_path(file); - if (controller.GetVisibleEntry()) { + if ((false) && controller.GetVisibleEntry()) { content::Referrer referrer = content::Referrer::SanitizeForRequest( download_url_, content::Referrer(controller.GetVisibleEntry()->GetURL(), diff --git a/chrome/browser/resources/extensions/manager.html b/chrome/browser/resources/extensions/manager.html --- a/chrome/browser/resources/extensions/manager.html +++ b/chrome/browser/resources/extensions/manager.html @@ -64,6 +64,7 @@ loadTimeData.getBoolean('inDevMode'), }, + isExtUpdateEnabled: { + type: Boolean, + value: () => loadTimeData.getBoolean('isExtUpdateEnabled'), + }, + showActivityLog: { type: Boolean, value: () => loadTimeData.getBoolean('showActivityLog'), @@ -223,6 +228,7 @@ export class ExtensionsManagerElement extends ExtensionsManagerElementBase { canLoadUnpacked: boolean; delegate: Service; inDevMode: boolean; + isExtUpdateEnabled: boolean; showActivityLog: boolean; enableEnhancedSiteControls: boolean; devModeControlledByPolicy: boolean; @@ -293,6 +299,7 @@ export class ExtensionsManagerElement extends ExtensionsManagerElementBase { profileInfo.isDeveloperModeControlledByPolicy; this.inDevMode = profileInfo.inDeveloperMode; this.canLoadUnpacked = profileInfo.canLoadUnpacked; + this.isExtUpdateEnabled = profileInfo.isExtensionAutoupdateEnabled; }; service.getProfileStateChangedTarget().addListener(onProfileStateChanged); service.getProfileConfiguration().then(onProfileStateChanged); diff --git a/chrome/browser/resources/extensions/service.ts b/chrome/browser/resources/extensions/service.ts --- a/chrome/browser/resources/extensions/service.ts +++ b/chrome/browser/resources/extensions/service.ts @@ -284,6 +284,11 @@ export class Service implements ServiceInterface { {inDeveloperMode: inDevMode}); } + setExtUpdateEnabled(enabled: boolean) { + chrome.developerPrivate.updateProfileConfiguration( + {isExtensionAutoupdateEnabled: enabled}); + } + loadUnpacked(): Promise { return this.loadUnpackedHelper_(); } diff --git a/chrome/browser/resources/extensions/toolbar.html b/chrome/browser/resources/extensions/toolbar.html --- a/chrome/browser/resources/extensions/toolbar.html +++ b/chrome/browser/resources/extensions/toolbar.html @@ -63,6 +63,11 @@ margin-inline-end: 16px; } + #need-update { + color: red; + margin-inline: 0px; + } + cr-toolbar { --cr-toolbar-center-basis: 680px; --cr-toolbar-field-max-width: var(--cr-toolbar-center-basis); @@ -88,6 +93,13 @@ checked="[[inDevMode]]" aria-labelledby="devModeLabel"> +
+ $i18n{toolbarExtensionUpdateEnabled} + + + + +