| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/conflicts/third_party_conflicts_manager_win.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/base_paths.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/feature_list.h" |
| #include "base/files/file_path.h" |
| #include "base/location.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/path_service.h" |
| #include "base/sequenced_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/post_task.h" |
| #include "base/task_runner.h" |
| #include "base/task_runner_util.h" |
| #include "base/win/registry.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/conflicts/incompatible_applications_updater_win.h" |
| #include "chrome/browser/conflicts/installed_applications_win.h" |
| #include "chrome/browser/conflicts/module_blacklist_cache_updater_win.h" |
| #include "chrome/browser/conflicts/module_blacklist_cache_util_win.h" |
| #include "chrome/browser/conflicts/module_info_util_win.h" |
| #include "chrome/browser/conflicts/module_info_win.h" |
| #include "chrome/browser/conflicts/module_list_filter_win.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/install_static/install_util.h" |
| #include "chrome_elf/third_party_dlls/packed_list_format.h" |
| #include "chrome_elf/third_party_dlls/status_codes.h" |
| #include "components/prefs/pref_registry_simple.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/browser/browser_thread.h" |
| |
| namespace { |
| |
| scoped_refptr<ModuleListFilter> CreateModuleListFilter( |
| const base::FilePath& module_list_path) { |
| auto module_list_filter = base::MakeRefCounted<ModuleListFilter>(); |
| |
| if (!module_list_filter->Initialize(module_list_path)) |
| return nullptr; |
| |
| return module_list_filter; |
| } |
| |
| std::unique_ptr<std::vector<third_party_dlls::PackedListModule>> |
| ReadInitialBlacklistedModules() { |
| base::FilePath path = |
| ModuleBlacklistCacheUpdater::GetModuleBlacklistCachePath(); |
| |
| third_party_dlls::PackedListMetadata metadata; |
| std::vector<third_party_dlls::PackedListModule> blacklisted_modules; |
| base::MD5Digest md5_digest; |
| ReadResult read_result = ReadModuleBlacklistCache( |
| path, &metadata, &blacklisted_modules, &md5_digest); |
| |
| // Return an empty vector on failure. |
| auto initial_blacklisted_modules = |
| std::make_unique<std::vector<third_party_dlls::PackedListModule>>(); |
| if (read_result == ReadResult::kSuccess) |
| *initial_blacklisted_modules = std::move(blacklisted_modules); |
| |
| return initial_blacklisted_modules; |
| } |
| |
| // Log the initialization status of the chrome_elf component that is responsible |
| // for blocking third-party DLLs. The status is stored in the |
| // kStatusCodesRegValue registry key during chrome_elf's initialization. |
| void LogChromeElfThirdPartyStatus() { |
| base::win::RegKey registry_key( |
| HKEY_CURRENT_USER, |
| base::StringPrintf(L"%ls%ls", install_static::GetRegistryPath().c_str(), |
| third_party_dlls::kThirdPartyRegKeyName) |
| .c_str(), |
| KEY_QUERY_VALUE); |
| |
| // Early return if the registry key can't be opened. |
| if (!registry_key.Valid()) |
| return; |
| |
| // Read the status code. Since the data is basically an array of integers, and |
| // resets every startups, a starting size of 128 should always be sufficient. |
| DWORD size = 128; |
| std::vector<uint8_t> buffer(size); |
| DWORD key_type; |
| DWORD result = registry_key.ReadValue(third_party_dlls::kStatusCodesRegValue, |
| buffer.data(), &size, &key_type); |
| |
| if (result == ERROR_MORE_DATA) { |
| buffer.resize(size); |
| result = registry_key.ReadValue(third_party_dlls::kStatusCodesRegValue, |
| buffer.data(), &size, &key_type); |
| } |
| |
| // Give up if it failed to retrieve the status codes. |
| if (result != ERROR_SUCCESS) |
| return; |
| |
| // The real size of the data is most probably smaller than the initial size. |
| buffer.resize(size); |
| |
| // Sanity check the type of data. |
| if (key_type != REG_BINARY) |
| return; |
| |
| std::vector<third_party_dlls::ThirdPartyStatus> status_array; |
| third_party_dlls::ConvertBufferToStatusCodes(buffer, &status_array); |
| |
| for (auto status : status_array) |
| UMA_HISTOGRAM_ENUMERATION("ChromeElf.ThirdPartyStatus", status); |
| } |
| |
| } // namespace |
| |
| ThirdPartyConflictsManager::ThirdPartyConflictsManager( |
| ModuleDatabaseEventSource* module_database_event_source) |
| : module_database_event_source_(module_database_event_source), |
| background_sequence_(base::CreateSequencedTaskRunnerWithTraits( |
| {base::TaskPriority::BEST_EFFORT, |
| base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, |
| base::MayBlock()})), |
| module_list_received_(false), |
| on_module_database_idle_called_(false), |
| initialization_forced_(false), |
| module_list_update_needed_(false), |
| component_update_service_observer_(this), |
| weak_ptr_factory_(this) { |
| LogChromeElfThirdPartyStatus(); |
| |
| module_database_event_source_->AddObserver(this); |
| |
| // Get the path to the current executable as it will be used to retrieve its |
| // associated CertificateInfo from the ModuleDatabase. This shouldn't fail, |
| // but it is assumed that without the path, the executable is not signed |
| // (hence an empty CertificateInfo). |
| if (!base::PathService::Get(base::FILE_EXE, &exe_path_)) |
| exe_certificate_info_ = std::make_unique<CertificateInfo>(); |
| } |
| |
| ThirdPartyConflictsManager::~ThirdPartyConflictsManager() { |
| if (!terminal_state_.has_value()) |
| SetTerminalState(State::kDestroyed); |
| module_database_event_source_->RemoveObserver(this); |
| } |
| |
| // static |
| void ThirdPartyConflictsManager::RegisterLocalStatePrefs( |
| PrefRegistrySimple* registry) { |
| // Register the pref that remembers the MD5 digest for the current module |
| // blacklist cache. The default value is an invalid MD5 digest. |
| registry->RegisterStringPref(prefs::kModuleBlacklistCacheMD5Digest, ""); |
| } |
| |
| // static |
| void ThirdPartyConflictsManager::DisableThirdPartyModuleBlocking( |
| base::TaskRunner* background_sequence) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Delete the module blacklist cache. Since the NtMapViewOfSection hook only |
| // blocks if the file is present, this will deactivate third-party modules |
| // blocking for the next browser launch. |
| background_sequence->PostTask( |
| FROM_HERE, |
| base::BindOnce(&ModuleBlacklistCacheUpdater::DeleteModuleBlacklistCache)); |
| |
| // Also clear the MD5 digest since there will no longer be a current module |
| // blacklist cache. |
| g_browser_process->local_state()->ClearPref( |
| prefs::kModuleBlacklistCacheMD5Digest); |
| } |
| |
| // static |
| void ThirdPartyConflictsManager::ShutdownAndDestroy( |
| std::unique_ptr<ThirdPartyConflictsManager> instance) { |
| DisableThirdPartyModuleBlocking(instance->background_sequence_.get()); |
| // |instance| is intentionally destroyed at the end of the function scope. |
| } |
| |
| void ThirdPartyConflictsManager::OnNewModuleFound( |
| const ModuleInfoKey& module_key, |
| const ModuleInfoData& module_data) { |
| // Keep looking for the CertificateInfo of the current executable as long as |
| // it wasn't found yet. |
| if (exe_certificate_info_) |
| return; |
| |
| DCHECK(!exe_path_.empty()); |
| |
| // The module represent the current executable only if the paths matches. |
| if (exe_path_ != module_key.module_path) |
| return; |
| |
| exe_certificate_info_ = std::make_unique<CertificateInfo>( |
| module_data.inspection_result->certificate_info); |
| |
| InitializeIfReady(); |
| } |
| |
| void ThirdPartyConflictsManager::OnModuleDatabaseIdle() { |
| if (on_module_database_idle_called_) |
| return; |
| |
| on_module_database_idle_called_ = true; |
| |
| // The InstalledApplications instance is only needed for the incompatible |
| // applications warning. |
| if (IncompatibleApplicationsUpdater::IsWarningEnabled()) { |
| base::PostTaskAndReplyWithResult( |
| background_sequence_.get(), FROM_HERE, base::BindOnce([]() { |
| return std::make_unique<InstalledApplications>(); |
| }), |
| base::BindOnce( |
| &ThirdPartyConflictsManager::OnInstalledApplicationsCreated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| // And the initial blacklisted modules are only needed for the third-party |
| // modules blocking. |
| if (base::FeatureList::IsEnabled(features::kThirdPartyModulesBlocking)) { |
| base::PostTaskAndReplyWithResult( |
| background_sequence_.get(), FROM_HERE, |
| base::BindOnce(&ReadInitialBlacklistedModules), |
| base::BindOnce( |
| &ThirdPartyConflictsManager::OnInitialBlacklistedModulesRead, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void ThirdPartyConflictsManager::OnModuleListComponentRegistered( |
| base::StringPiece component_id) { |
| DCHECK(module_list_component_id_.empty()); |
| module_list_component_id_ = component_id.as_string(); |
| |
| auto components = g_browser_process->component_updater()->GetComponents(); |
| auto iter = std::find_if(components.begin(), components.end(), |
| [this](const auto& component) { |
| return component.id == module_list_component_id_; |
| }); |
| DCHECK(iter != components.end()); |
| |
| if (iter->version == base::Version("0.0.0.0")) { |
| // The module list component is currently not installed. An update is |
| // required to initialize the ModuleListFilter. |
| module_list_update_needed_ = true; |
| |
| // The update is usually done automatically when the component update |
| // service decides to do it. But if the initialization was forced, the |
| // component update must also be triggered right now. |
| if (initialization_forced_) |
| ForceModuleListComponentUpdate(); |
| } |
| |
| // LoadModuleList() will be called if the version is not "0.0.0.0". |
| } |
| |
| void ThirdPartyConflictsManager::LoadModuleList(const base::FilePath& path) { |
| if (module_list_received_) |
| return; |
| |
| component_update_service_observer_.RemoveAll(); |
| |
| module_list_received_ = true; |
| |
| base::PostTaskAndReplyWithResult( |
| background_sequence_.get(), FROM_HERE, |
| base::BindOnce(&CreateModuleListFilter, path), |
| base::BindOnce(&ThirdPartyConflictsManager::OnModuleListFilterCreated, |
| weak_ptr_factory_.GetWeakPtr())); |
| } |
| |
| void ThirdPartyConflictsManager::ForceInitialization( |
| OnInitializationCompleteCallback on_initialization_complete_callback) { |
| on_initialization_complete_callback_ = |
| std::move(on_initialization_complete_callback); |
| |
| if (terminal_state_.has_value()) { |
| std::move(on_initialization_complete_callback_) |
| .Run(terminal_state_.value()); |
| return; |
| } |
| |
| // It doesn't make sense to do this twice. |
| if (initialization_forced_) |
| return; |
| initialization_forced_ = true; |
| |
| // Nothing to force if we already received a module list. |
| if (module_list_received_) |
| return; |
| |
| // Only force an update if it is needed, because the ModuleListFilter can be |
| // initialized with an older version of the Module List component. |
| if (module_list_update_needed_) |
| ForceModuleListComponentUpdate(); |
| } |
| |
| void ThirdPartyConflictsManager::OnEvent(Events event, |
| const std::string& component_id) { |
| DCHECK(!module_list_component_id_.empty()); |
| |
| // LoadModuleList() was already invoked. |
| if (module_list_received_) |
| return; |
| |
| // Only consider events for the module list component. |
| if (component_id != module_list_component_id_) |
| return; |
| |
| // There are 2 cases that are important. Either the component is being |
| // updated, or the component is not updated because there is no update |
| // available. |
| // |
| // For the first case, there is nothing to do because LoadModuleList() will |
| // eventually be called when the component is installed. |
| // |
| // For the second case, it means that the server is not offering any update |
| // right now, either because it is too busy, or there is an issue with the |
| // server-side component configuration. |
| // |
| // Note: |
| // The COMPONENT_NOT_UPDATED event can also be broadcasted when the component |
| // is already up-to-date. This is not the case here because this class only |
| // registers to the component updater service as an observer when the |
| // component version is 0.0.0.0 (aka not installed). |
| if (event == Events::COMPONENT_NOT_UPDATED) |
| SetTerminalState(State::kNoModuleListAvailableFailure); |
| } |
| |
| void ThirdPartyConflictsManager::OnModuleListFilterCreated( |
| scoped_refptr<ModuleListFilter> module_list_filter) { |
| module_list_filter_ = std::move(module_list_filter); |
| |
| // A valid |module_list_filter_| is critical to the blocking of third-party |
| // modules. By returning early here, the |incompatible_applications_updater_| |
| // instance never gets created, thus disabling the identification of |
| // incompatible applications. |
| if (!module_list_filter_) { |
| // Mark the module list as not received so that a new one may trigger the |
| // creation of a valid filter. |
| module_list_received_ = false; |
| SetTerminalState(State::kModuleListInvalidFailure); |
| return; |
| } |
| |
| module_list_update_needed_ = false; |
| |
| InitializeIfReady(); |
| } |
| |
| void ThirdPartyConflictsManager::OnInstalledApplicationsCreated( |
| std::unique_ptr<InstalledApplications> installed_applications) { |
| installed_applications_ = std::move(installed_applications); |
| |
| InitializeIfReady(); |
| } |
| |
| void ThirdPartyConflictsManager::OnInitialBlacklistedModulesRead( |
| std::unique_ptr<std::vector<third_party_dlls::PackedListModule>> |
| initial_blacklisted_modules) { |
| initial_blacklisted_modules_ = std::move(initial_blacklisted_modules); |
| |
| InitializeIfReady(); |
| } |
| |
| void ThirdPartyConflictsManager::InitializeIfReady() { |
| DCHECK(!terminal_state_.has_value()); |
| |
| // Check if this instance is ready to initialize. First look at dependencies |
| // that both features need. |
| if (!exe_certificate_info_ || !module_list_filter_) |
| return; |
| |
| // Then look at the dependency needed only for the |
| // IncompatibleApplicationsWarning feature. |
| if (IncompatibleApplicationsUpdater::IsWarningEnabled() && |
| !installed_applications_) { |
| return; |
| } |
| |
| // And the dependency needed only for the ThirdPartyModulesBlocking feature. |
| if (base::FeatureList::IsEnabled(features::kThirdPartyModulesBlocking) && |
| !initial_blacklisted_modules_) { |
| return; |
| } |
| |
| // Now both features are ready to be initialized. |
| if (base::FeatureList::IsEnabled(features::kThirdPartyModulesBlocking)) { |
| // It is safe to use base::Unretained() since the callback will not be |
| // invoked if the updater is freed. |
| module_blacklist_cache_updater_ = |
| std::make_unique<ModuleBlacklistCacheUpdater>( |
| module_database_event_source_, *exe_certificate_info_, |
| module_list_filter_, *initial_blacklisted_modules_, |
| base::BindRepeating( |
| &ThirdPartyConflictsManager::OnModuleBlacklistCacheUpdated, |
| base::Unretained(this))); |
| } |
| |
| // The |incompatible_applications_updater_| instance must be created last so |
| // that it is registered to the Module Database observer's API after the |
| // ModuleBlacklistCacheUpdater instance. This way, it knows about which |
| // modules were added to the module blacklist cache so that it's possible to |
| // not warn about them. |
| if (installed_applications_) { |
| incompatible_applications_updater_ = |
| std::make_unique<IncompatibleApplicationsUpdater>( |
| module_database_event_source_, *exe_certificate_info_, |
| module_list_filter_, *installed_applications_); |
| } |
| |
| if (!incompatible_applications_updater_) { |
| SetTerminalState(State::kBlockingInitialized); |
| } else if (!module_blacklist_cache_updater_) { |
| SetTerminalState(State::kWarningInitialized); |
| } else { |
| SetTerminalState(State::kWarningAndBlockingInitialized); |
| } |
| } |
| |
| void ThirdPartyConflictsManager::OnModuleBlacklistCacheUpdated( |
| const ModuleBlacklistCacheUpdater::CacheUpdateResult& result) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| |
| // Check that the MD5 digest of the old cache matches what was expected. Only |
| // used for reporting a metric. |
| const PrefService::Preference* preference = |
| g_browser_process->local_state()->FindPreference( |
| prefs::kModuleBlacklistCacheMD5Digest); |
| DCHECK(preference); |
| |
| // The first time this is executed, the pref doesn't yet hold a valid MD5 |
| // digest. |
| if (!preference->IsDefaultValue()) { |
| const std::string old_md5_string = |
| base::MD5DigestToBase16(result.old_md5_digest); |
| const std::string& current_md5_string = preference->GetValue()->GetString(); |
| UMA_HISTOGRAM_BOOLEAN("ModuleBlacklistCache.ExpectedMD5Digest", |
| old_md5_string == current_md5_string); |
| } |
| |
| // Set the expected MD5 digest for the next time the cache is updated. |
| g_browser_process->local_state()->Set( |
| prefs::kModuleBlacklistCacheMD5Digest, |
| base::Value(base::MD5DigestToBase16(result.new_md5_digest))); |
| } |
| |
| void ThirdPartyConflictsManager::ForceModuleListComponentUpdate() { |
| auto* component_update_service = g_browser_process->component_updater(); |
| |
| // Observe the component updater service to know the result of the update. |
| DCHECK(!component_update_service_observer_.IsObserving( |
| component_update_service)); |
| component_update_service_observer_.Add(component_update_service); |
| |
| component_update_service->MaybeThrottle(module_list_component_id_, |
| base::DoNothing()); |
| } |
| |
| void ThirdPartyConflictsManager::SetTerminalState(State terminal_state) { |
| DCHECK(!terminal_state_.has_value()); |
| terminal_state_ = terminal_state; |
| if (on_initialization_complete_callback_) |
| std::move(on_initialization_complete_callback_) |
| .Run(terminal_state_.value()); |
| } |