Introduce ModuleBlacklistCacheUpdater
This class is responsible to keep the module blacklist cache
updated when the ThirdPartyModulesBlocking feature is enabled.
Bug: 846953
Change-Id: Ib8a99ac0352702daec6b79d15324c2e7787d4d1a
Reviewed-on: https://chromium-review.googlesource.com/1055893
Commit-Queue: Patrick Monette <[email protected]>
Reviewed-by: Scott Violet <[email protected]>
Reviewed-by: Bernhard Bauer <[email protected]>
Reviewed-by: Robert Kaplow <[email protected]>
Reviewed-by: Greg Thompson <[email protected]>
Cr-Commit-Position: refs/heads/master@{#564721}
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index ac2a296..64715ed 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -3048,6 +3048,7 @@
if (is_chrome_branded) {
deps += [
":conflicts_module_list_proto",
+ "//chrome_elf:sha1",
"//chrome_elf:third_party_shared_defines",
"//google_update",
]
@@ -3059,6 +3060,8 @@
"conflicts/incompatible_applications_updater_win.h",
"conflicts/installed_applications_win.cc",
"conflicts/installed_applications_win.h",
+ "conflicts/module_blacklist_cache_updater_win.cc",
+ "conflicts/module_blacklist_cache_updater_win.h",
"conflicts/module_blacklist_cache_util_win.cc",
"conflicts/module_blacklist_cache_util_win.h",
"conflicts/module_list_filter_win.cc",
diff --git a/chrome/browser/chrome_browser_main_win.cc b/chrome/browser/chrome_browser_main_win.cc
index 8ff0d2d..1271a646 100644
--- a/chrome/browser/chrome_browser_main_win.cc
+++ b/chrome/browser/chrome_browser_main_win.cc
@@ -10,7 +10,7 @@
#include <windows.h>
#include <algorithm>
-#include <memory>
+#include <utility>
#include "base/base_switches.h"
#include "base/bind_helpers.h"
@@ -21,7 +21,6 @@
#include "base/files/file_util.h"
#include "base/i18n/rtl.h"
#include "base/location.h"
-#include "base/macros.h"
#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
@@ -88,6 +87,10 @@
#include "ui/gfx/switches.h"
#include "ui/strings/grit/app_locale_settings.h"
+#if defined(GOOGLE_CHROME_BUILD)
+#include "chrome/browser/conflicts/third_party_conflicts_manager_win.h"
+#endif
+
namespace {
typedef HRESULT (STDAPICALLTYPE* RegisterApplicationRestartProc)(
@@ -513,6 +516,23 @@
void ChromeBrowserMainPartsWin::PostProfileInit() {
ChromeBrowserMainParts::PostProfileInit();
+#if defined(GOOGLE_CHROME_BUILD)
+ // Explicitly disable the third-party modules blocking.
+ //
+ // Because the blocking code lives in chrome_elf, it is not possible to check
+ // the feature (via the FeatureList API) or the policy to control whether it
+ // is enabled or not.
+ //
+ // What truly controls if the blocking is enabled is the presence of the
+ // module blacklist cache file. This means that to disable the feature, the
+ // cache must be deleted and the browser relaunched.
+ if (!base::FeatureList::IsEnabled(features::kModuleDatabase) ||
+ !ThirdPartyConflictsManager::IsThirdPartyBlockingPolicyEnabled() ||
+ !base::FeatureList::IsEnabled(features::kThirdPartyModulesBlocking)) {
+ ThirdPartyConflictsManager::DisableThirdPartyModuleBlocking();
+ }
+#endif
+
// Create the module database and hook up the in-process module watcher. This
// needs to be done before any child processes are initialized as the
// ModuleDatabase is an endpoint for IPC from child processes.
diff --git a/chrome/browser/conflicts/module_blacklist_cache_updater_win.cc b/chrome/browser/conflicts/module_blacklist_cache_updater_win.cc
new file mode 100644
index 0000000..209f86f
--- /dev/null
+++ b/chrome/browser/conflicts/module_blacklist_cache_updater_win.cc
@@ -0,0 +1,269 @@
+// 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/module_blacklist_cache_updater_win.h"
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/feature_list.h"
+#include "base/files/file_util.h"
+#include "base/i18n/case_conversion.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/path_service.h"
+#include "base/sequenced_task_runner.h"
+#include "base/sha1.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/task_runner_util.h"
+#include "base/task_scheduler/post_task.h"
+#include "base/time/time.h"
+#include "chrome/browser/conflicts/module_blacklist_cache_util_win.h"
+#include "chrome/browser/conflicts/module_database_win.h"
+#include "chrome/browser/conflicts/module_info_util_win.h"
+#include "chrome/browser/conflicts/module_list_filter_win.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/chrome_paths.h"
+
+#if !defined(OFFICIAL_BUILD)
+#include "base/base_paths.h"
+#endif
+
+namespace {
+
+// The maximum number of modules allowed in the cache. This keeps the cache
+// from growing indefinitely.
+// Note: This value is tied to the "ModuleBlacklistCache.ModuleCount" histogram.
+// Rename the histogram if this value is ever changed.
+static constexpr size_t kMaxModuleCount = 5000u;
+
+// The maximum amount of time a stale entry is kept in the cache before it is
+// deleted.
+static constexpr base::TimeDelta kMaxEntryAge = base::TimeDelta::FromDays(180);
+
+// Updates the module blacklist cache asynchronously on a background sequence
+// and return a CacheUpdateResult value.
+ModuleBlacklistCacheUpdater::CacheUpdateResult UpdateModuleBlacklistCache(
+ const base::FilePath& module_blacklist_cache_path,
+ const ModuleListFilter& module_list_filter,
+ const std::vector<third_party_dlls::PackedListModule>&
+ newly_blacklisted_modules,
+ const std::vector<third_party_dlls::PackedListModule>& blocked_modules,
+ size_t max_module_count,
+ uint32_t min_time_date_stamp) {
+ ModuleBlacklistCacheUpdater::CacheUpdateResult result;
+
+ // Read the existing cache.
+ third_party_dlls::PackedListMetadata metadata;
+ std::vector<third_party_dlls::PackedListModule> blacklisted_modules;
+ ReadResult read_result =
+ ReadModuleBlacklistCache(module_blacklist_cache_path, &metadata,
+ &blacklisted_modules, &result.old_md5_digest);
+ UMA_HISTOGRAM_ENUMERATION("ModuleBlacklistCache.ReadResult", read_result);
+
+ // Update the existing data with |newly_blacklisted_modules| and
+ // |blocked_modules|.
+ UpdateModuleBlacklistCacheData(
+ module_list_filter, newly_blacklisted_modules, blocked_modules,
+ max_module_count, min_time_date_stamp, &metadata, &blacklisted_modules);
+ // Note: This histogram is tied to the current value of kMaxModuleCount.
+ // Rename the histogram if that value is ever changed.
+ UMA_HISTOGRAM_CUSTOM_COUNTS("ModuleBlacklistCache.ModuleCount",
+ blacklisted_modules.size(), 1, kMaxModuleCount,
+ 50);
+
+ // Then write the updated cache to disk.
+ bool write_result =
+ WriteModuleBlacklistCache(module_blacklist_cache_path, metadata,
+ blacklisted_modules, &result.new_md5_digest);
+ UMA_HISTOGRAM_BOOLEAN("ModuleBlacklistCache.WriteResult", write_result);
+
+ return result;
+}
+
+} // namespace
+
+// static
+constexpr base::TimeDelta ModuleBlacklistCacheUpdater::kUpdateTimerDuration;
+
+ModuleBlacklistCacheUpdater::ModuleBlacklistCacheUpdater(
+ const CertificateInfo& exe_certificate_info,
+ const ModuleListFilter& module_list_filter,
+ OnCacheUpdatedCallback on_cache_updated_callback)
+ : exe_certificate_info_(exe_certificate_info),
+ module_list_filter_(module_list_filter),
+ on_cache_updated_callback_(std::move(on_cache_updated_callback)),
+ background_sequence_(base::CreateSequencedTaskRunnerWithTraits(
+ {base::MayBlock(), base::TaskPriority::BACKGROUND,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN})),
+ // The use of base::Unretained() is safe here because the callback can
+ // only be invoked while |module_load_attempt_log_listener_| is alive.
+ module_load_attempt_log_listener_(
+ base::BindRepeating(&ModuleBlacklistCacheUpdater::OnNewModulesBlocked,
+ base::Unretained(this))),
+ timer_(FROM_HERE,
+ kUpdateTimerDuration,
+ base::Bind(&ModuleBlacklistCacheUpdater::OnTimerExpired,
+ base::Unretained(this)),
+ false /* is_repeating */),
+ weak_ptr_factory_(this) {}
+
+ModuleBlacklistCacheUpdater::~ModuleBlacklistCacheUpdater() = default;
+
+// static
+bool ModuleBlacklistCacheUpdater::IsThirdPartyModuleBlockingEnabled() {
+ // The ThirdPartyConflictsManager can exist even if the blocking is disabled
+ // because that class also controls the warning of incompatible applications.
+ return ModuleDatabase::GetInstance() &&
+ ModuleDatabase::GetInstance()->third_party_conflicts_manager() &&
+ base::FeatureList::IsEnabled(features::kThirdPartyModulesBlocking);
+}
+
+// static
+base::FilePath ModuleBlacklistCacheUpdater::GetModuleBlacklistCachePath() {
+ base::FilePath user_data_dir;
+ if (!base::PathService::Get(chrome::DIR_USER_DATA, &user_data_dir))
+ return base::FilePath();
+
+ // Using FilePath::StringType + operator because the constants contains the
+ // path separator.
+ return base::FilePath(user_data_dir.value() + third_party_dlls::kFileSubdir +
+ third_party_dlls::kBlFileName);
+}
+
+// static
+void ModuleBlacklistCacheUpdater::DeleteModuleBlacklistCache() {
+ bool delete_result =
+ base::DeleteFile(GetModuleBlacklistCachePath(), false /* recursive */);
+ UMA_HISTOGRAM_BOOLEAN("ModuleBlacklistCache.DeleteResult", delete_result);
+}
+
+void ModuleBlacklistCacheUpdater::OnNewModuleFound(
+ const ModuleInfoKey& module_key,
+ const ModuleInfoData& module_data) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ // Only consider loaded modules that are not IMEs. Shell extensions are still
+ // blocked.
+ static constexpr uint32_t kModuleTypesBitmask =
+ ModuleInfoData::kTypeLoadedModule | ModuleInfoData::kTypeIme;
+ if ((module_data.module_types & kModuleTypesBitmask) !=
+ ModuleInfoData::kTypeLoadedModule) {
+ return;
+ }
+
+ // Explicitly whitelist modules whose signing cert's Subject field matches the
+ // one in the current executable. No attempt is made to check the validity of
+ // module signatures or of signing certs.
+ if (exe_certificate_info_.type != CertificateType::NO_CERTIFICATE &&
+ exe_certificate_info_.subject ==
+ module_data.inspection_result->certificate_info.subject) {
+ return;
+ }
+
+ // Never block a module seemingly signed by Microsoft. Again, no attempt is
+ // made to check the validity of the certificate.
+ if (IsMicrosoftModule(
+ module_data.inspection_result->certificate_info.subject)) {
+ return;
+ }
+
+// For developer builds only, whitelist modules in the same directory as the
+// executable.
+#if !defined(OFFICIAL_BUILD)
+ base::FilePath exe_path;
+ if (base::PathService::Get(base::DIR_EXE, &exe_path) &&
+ exe_path.DirName().IsParent(module_key.module_path)) {
+ return;
+ }
+#endif
+
+ // Skip modules whitelisted by the Module List component.
+ if (module_list_filter_.IsWhitelisted(module_key, module_data))
+ return;
+
+ // Some blacklisted modules are allowed to load.
+ std::unique_ptr<chrome::conflicts::BlacklistAction> blacklist_action =
+ module_list_filter_.IsBlacklisted(module_key, module_data);
+ if (blacklist_action && blacklist_action->allow_load())
+ return;
+
+ // Insert the blacklisted module.
+ newly_blacklisted_modules_.emplace_back();
+ third_party_dlls::PackedListModule& module =
+ newly_blacklisted_modules_.back();
+
+ // Hash the basename.
+ const std::string module_basename = base::UTF16ToUTF8(
+ base::i18n::ToLower(module_key.module_path.BaseName().value()));
+ base::SHA1HashBytes(reinterpret_cast<const uint8_t*>(module_basename.data()),
+ module_basename.length(), module.basename_hash);
+
+ // Hash the code id.
+ const std::string module_code_id = GenerateCodeId(module_key);
+ base::SHA1HashBytes(reinterpret_cast<const uint8_t*>(module_code_id.data()),
+ module_code_id.length(), module.code_id_hash);
+
+ module.time_date_stamp = CalculateTimeDateStamp(base::Time::Now());
+}
+
+void ModuleBlacklistCacheUpdater::OnModuleDatabaseIdle() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ StartModuleBlacklistCacheUpdate();
+}
+
+void ModuleBlacklistCacheUpdater::OnNewModulesBlocked(
+ std::vector<third_party_dlls::PackedListModule>&& blocked_modules) {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ blocked_modules_.insert(blocked_modules_.begin(),
+ std::make_move_iterator(blocked_modules.begin()),
+ std::make_move_iterator(blocked_modules.end()));
+
+ // Start the timer.
+ timer_.Reset();
+}
+
+void ModuleBlacklistCacheUpdater::OnTimerExpired() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ StartModuleBlacklistCacheUpdate();
+}
+
+void ModuleBlacklistCacheUpdater::StartModuleBlacklistCacheUpdate() {
+ DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+
+ timer_.Stop();
+
+ base::FilePath cache_file_path = GetModuleBlacklistCachePath();
+ if (cache_file_path.empty())
+ return;
+
+ // Calculate the minimum time date stamp.
+ uint32_t min_time_date_stamp =
+ CalculateTimeDateStamp(base::Time::Now() - kMaxEntryAge);
+
+ // Update the module blacklist cache on a background sequence.
+ //
+ // |module_list_filter_| is safe to pass by const-ref because it is owned by
+ // the ThirdPartyConflictsManager instance, which is never freed, and the only
+ // method called on it is const.
+ base::PostTaskAndReplyWithResult(
+ background_sequence_.get(), FROM_HERE,
+ base::BindOnce(&UpdateModuleBlacklistCache, cache_file_path,
+ base::ConstRef(module_list_filter_),
+ std::move(newly_blacklisted_modules_),
+ std::move(blocked_modules_), kMaxModuleCount,
+ min_time_date_stamp),
+ base::BindOnce(
+ &ModuleBlacklistCacheUpdater::OnModuleBlacklistCacheUpdated,
+ weak_ptr_factory_.GetWeakPtr()));
+}
+
+void ModuleBlacklistCacheUpdater::OnModuleBlacklistCacheUpdated(
+ const CacheUpdateResult& result) {
+ on_cache_updated_callback_.Run(result);
+}
diff --git a/chrome/browser/conflicts/module_blacklist_cache_updater_win.h b/chrome/browser/conflicts/module_blacklist_cache_updater_win.h
new file mode 100644
index 0000000..4a98af3
--- /dev/null
+++ b/chrome/browser/conflicts/module_blacklist_cache_updater_win.h
@@ -0,0 +1,124 @@
+// 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.
+
+#ifndef CHROME_BROWSER_CONFLICTS_MODULE_BLACKLIST_CACHE_UPDATER_WIN_H_
+#define CHROME_BROWSER_CONFLICTS_MODULE_BLACKLIST_CACHE_UPDATER_WIN_H_
+
+#include <vector>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/md5.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/weak_ptr.h"
+#include "base/sequence_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "chrome/browser/conflicts/module_database_observer_win.h"
+#include "chrome/browser/conflicts/module_load_attempt_log_listener_win.h"
+#include "chrome/browser/conflicts/proto/module_list.pb.h"
+#include "chrome_elf/third_party_dlls/packed_list_format.h"
+
+class ModuleListFilter;
+struct CertificateInfo;
+
+namespace base {
+class SequencedTaskRunner;
+}
+
+// This class is responsible for maintaining the module blacklist cache, which
+// is used by chrome_elf.dll to determine which module to block from loading
+// into the process.
+//
+// Two things can happen that requires an update to the cache:
+// 1. The Module Database becomes idle and this class identified new
+// blacklisted modules. They must be added to the cache.
+// 2. The module load attempt log was drained and contained blocked entries.
+// Their timestamp in the cache must be updated.
+//
+// To coalesce these events and reduce the number of updates, a timer is started
+// when the load attempt log is drained. Once expired, an update is triggered
+// unless one was already done because of newly blacklisted modules.
+class ModuleBlacklistCacheUpdater : public ModuleDatabaseObserver {
+ public:
+ struct CacheUpdateResult {
+ base::MD5Digest old_md5_digest;
+ base::MD5Digest new_md5_digest;
+ };
+ using OnCacheUpdatedCallback =
+ base::RepeatingCallback<void(const CacheUpdateResult&)>;
+
+ // The amount of time the timer will run before triggering an update of the
+ // cache.
+ static constexpr base::TimeDelta kUpdateTimerDuration =
+ base::TimeDelta::FromMinutes(2);
+
+ // Creates an instance of the updater. The callback will be invoked every time
+ // the cache is updated.
+ // The |exe_certificate_info| & |module_list_filter| must outlive the lifetime
+ // of this class.
+ ModuleBlacklistCacheUpdater(const CertificateInfo& exe_certificate_info,
+ const ModuleListFilter& module_list_filter,
+ OnCacheUpdatedCallback on_cache_updated_callback);
+ ~ModuleBlacklistCacheUpdater() override;
+
+ // Returns true if the blocking of third-party modules is enabled. The return
+ // value will not change throughout the lifetime of the process.
+ static bool IsThirdPartyModuleBlockingEnabled();
+
+ // Returns the path to the module blacklist cache.
+ static base::FilePath GetModuleBlacklistCachePath();
+
+ // Deletes the module blacklist cache. This disables the blocking of third-
+ // party modules for the next browser launch.
+ static void DeleteModuleBlacklistCache();
+
+ // ModuleDatabaseObserver:
+ void OnNewModuleFound(const ModuleInfoKey& module_key,
+ const ModuleInfoData& module_data) override;
+ void OnModuleDatabaseIdle() override;
+
+ // Callback for |module_load_attempt_log_listener_|;
+ void OnNewModulesBlocked(
+ std::vector<third_party_dlls::PackedListModule>&& blocked_modules);
+
+ private:
+ void OnTimerExpired();
+
+ // Posts the task to update the cache on |background_sequence_|.
+ void StartModuleBlacklistCacheUpdate();
+
+ // Invoked on the sequence that owns this instance when the cache is updated.
+ void OnModuleBlacklistCacheUpdated(const CacheUpdateResult& result);
+
+ const CertificateInfo& exe_certificate_info_;
+ const ModuleListFilter& module_list_filter_;
+
+ OnCacheUpdatedCallback on_cache_updated_callback_;
+
+ // The sequence on which the module blacklist cache file is updated.
+ scoped_refptr<base::SequencedTaskRunner> background_sequence_;
+
+ // Temporarily holds newly blacklisted modules before they are added to the
+ // module blacklist cache.
+ std::vector<third_party_dlls::PackedListModule> newly_blacklisted_modules_;
+
+ ModuleLoadAttemptLogListener module_load_attempt_log_listener_;
+
+ // Temporarily holds modules that were blocked from loading into the browser
+ // until they are used to update the cache.
+ std::vector<third_party_dlls::PackedListModule> blocked_modules_;
+
+ // Ensures that the cache is updated when new blocked modules arrives even if
+ // OnModuleDatabaseIdle() is never called again.
+ base::Timer timer_;
+
+ SEQUENCE_CHECKER(sequence_checker_);
+
+ base::WeakPtrFactory<ModuleBlacklistCacheUpdater> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleBlacklistCacheUpdater);
+};
+
+#endif // CHROME_BROWSER_CONFLICTS_MODULE_BLACKLIST_CACHE_UPDATER_WIN_H_
diff --git a/chrome/browser/conflicts/module_blacklist_cache_updater_win_unittest.cc b/chrome/browser/conflicts/module_blacklist_cache_updater_win_unittest.cc
new file mode 100644
index 0000000..66393c3
--- /dev/null
+++ b/chrome/browser/conflicts/module_blacklist_cache_updater_win_unittest.cc
@@ -0,0 +1,329 @@
+// 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/module_blacklist_cache_updater_win.h"
+
+#include <windows.h>
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind_helpers.h"
+#include "base/files/file_util.h"
+#include "base/i18n/case_conversion.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/sha1.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/scoped_path_override.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/pe_image.h"
+#include "base/win/registry.h"
+#include "chrome/browser/conflicts/module_blacklist_cache_util_win.h"
+#include "chrome/browser/conflicts/module_info_win.h"
+#include "chrome/browser/conflicts/module_list_filter_win.h"
+#include "chrome/common/chrome_paths.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Stubs an empty whitelist and blacklist.
+class StubModuleListFilter : public ModuleListFilter {
+ public:
+ StubModuleListFilter() = default;
+ ~StubModuleListFilter() override = default;
+
+ bool IsWhitelisted(base::StringPiece basename_hash,
+ base::StringPiece code_id_hash) const override {
+ return false;
+ }
+
+ std::unique_ptr<chrome::conflicts::BlacklistAction> IsBlacklisted(
+ const ModuleInfoKey& module_key,
+ const ModuleInfoData& module_data) const override {
+ return nullptr;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StubModuleListFilter);
+};
+
+constexpr base::FilePath::CharType kCertificatePath[] =
+ FILE_PATH_LITERAL("CertificatePath");
+constexpr base::FilePath::CharType kCertificateSubject[] =
+ FILE_PATH_LITERAL("CertificateSubject");
+
+constexpr base::FilePath::CharType kDllPath1[] =
+ FILE_PATH_LITERAL("c:\\path\\to\\module.dll");
+constexpr base::FilePath::CharType kDllPath2[] =
+ FILE_PATH_LITERAL("c:\\some\\shellextension.dll");
+
+// Returns a new ModuleInfoData marked as loaded into the process but otherwise
+// empty.
+ModuleInfoData CreateLoadedModuleInfoData() {
+ ModuleInfoData module_data;
+ module_data.module_types |= ModuleInfoData::kTypeLoadedModule;
+ module_data.inspection_result = std::make_unique<ModuleInspectionResult>();
+ return module_data;
+}
+
+// Returns a new ModuleInfoData marked as loaded into the process with a
+// CertificateInfo that matches kCertificateSubject.
+ModuleInfoData CreateSignedLoadedModuleInfoData() {
+ ModuleInfoData module_data = CreateLoadedModuleInfoData();
+
+ module_data.inspection_result->certificate_info.type =
+ CertificateType::CERTIFICATE_IN_FILE;
+ module_data.inspection_result->certificate_info.path =
+ base::FilePath(kCertificatePath);
+ module_data.inspection_result->certificate_info.subject = kCertificateSubject;
+
+ return module_data;
+}
+
+void GetModulePath(HMODULE module_handle, base::FilePath* module_path) {
+ base::FilePath result;
+
+ wchar_t buffer[MAX_PATH];
+ DWORD length = ::GetModuleFileName(module_handle, buffer, MAX_PATH);
+ ASSERT_NE(length, 0U);
+ ASSERT_LT(length, static_cast<DWORD>(MAX_PATH));
+
+ *module_path = base::FilePath(buffer);
+}
+
+} // namespace
+
+class ModuleBlacklistCacheUpdaterTest : public testing::Test {
+ protected:
+ ModuleBlacklistCacheUpdaterTest()
+ : dll1_(kDllPath1),
+ dll2_(kDllPath2),
+ scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
+ user_data_dir_override_(chrome::DIR_USER_DATA),
+ module_blacklist_cache_path_(
+ ModuleBlacklistCacheUpdater::GetModuleBlacklistCachePath()) {
+ exe_certificate_info_.type = CertificateType::CERTIFICATE_IN_FILE;
+ exe_certificate_info_.path = base::FilePath(kCertificatePath);
+ exe_certificate_info_.subject = kCertificateSubject;
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(base::CreateDirectory(module_blacklist_cache_path().DirName()));
+ ASSERT_NO_FATAL_FAILURE(
+ registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
+ }
+
+ std::unique_ptr<ModuleBlacklistCacheUpdater>
+ CreateModuleBlacklistCacheUpdater() {
+ return std::make_unique<ModuleBlacklistCacheUpdater>(
+ exe_certificate_info_, module_list_filter_,
+ base::BindRepeating(
+ &ModuleBlacklistCacheUpdaterTest::OnModuleBlacklistCacheUpdated,
+ base::Unretained(this)));
+ }
+
+ void RunUntilIdle() { scoped_task_environment_.RunUntilIdle(); }
+ void FastForwardBy(base::TimeDelta delta) {
+ scoped_task_environment_.FastForwardBy(delta);
+ // The expired timer callback posts a task to update the cache. Wait for it
+ // to finish.
+ scoped_task_environment_.RunUntilIdle();
+ }
+
+ base::FilePath& module_blacklist_cache_path() {
+ return module_blacklist_cache_path_;
+ }
+
+ bool on_cache_updated_callback_invoked() {
+ return on_cache_updated_callback_invoked_;
+ }
+
+ const base::FilePath dll1_;
+ const base::FilePath dll2_;
+
+ private:
+ void OnModuleBlacklistCacheUpdated(
+ const ModuleBlacklistCacheUpdater::CacheUpdateResult& result) {
+ on_cache_updated_callback_invoked_ = true;
+ }
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ registry_util::RegistryOverrideManager registry_override_manager_;
+ base::ScopedPathOverride user_data_dir_override_;
+
+ CertificateInfo exe_certificate_info_;
+ StubModuleListFilter module_list_filter_;
+
+ base::FilePath module_blacklist_cache_path_;
+
+ bool on_cache_updated_callback_invoked_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleBlacklistCacheUpdaterTest);
+};
+
+TEST_F(ModuleBlacklistCacheUpdaterTest, OneThirdPartyModule) {
+ EXPECT_FALSE(base::PathExists(module_blacklist_cache_path()));
+
+ auto module_blacklist_cache_updater = CreateModuleBlacklistCacheUpdater();
+
+ // Simulate some arbitrary module loading into the process.
+ module_blacklist_cache_updater->OnNewModuleFound(
+ ModuleInfoKey(dll1_, 0, 0, 0), CreateLoadedModuleInfoData());
+ module_blacklist_cache_updater->OnModuleDatabaseIdle();
+
+ RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
+ EXPECT_TRUE(on_cache_updated_callback_invoked());
+
+ // Check the cache.
+ third_party_dlls::PackedListMetadata metadata;
+ std::vector<third_party_dlls::PackedListModule> blacklisted_modules;
+ base::MD5Digest md5_digest;
+ EXPECT_EQ(ReadResult::kSuccess,
+ ReadModuleBlacklistCache(module_blacklist_cache_path(), &metadata,
+ &blacklisted_modules, &md5_digest));
+
+ EXPECT_EQ(1u, blacklisted_modules.size());
+}
+
+TEST_F(ModuleBlacklistCacheUpdaterTest, IgnoreMicrosoftModules) {
+ EXPECT_FALSE(base::PathExists(module_blacklist_cache_path()));
+
+ // base::RunLoop run_loop;
+ auto module_blacklist_cache_updater = CreateModuleBlacklistCacheUpdater();
+
+ // Simulate a Microsoft module loading into the process.
+ base::win::PEImage kernel32_image(::GetModuleHandle(L"kernel32.dll"));
+ ASSERT_TRUE(kernel32_image.module());
+
+ base::FilePath module_path;
+ ASSERT_NO_FATAL_FAILURE(GetModulePath(kernel32_image.module(), &module_path));
+ ASSERT_FALSE(module_path.empty());
+ uint32_t module_size =
+ kernel32_image.GetNTHeaders()->OptionalHeader.SizeOfImage;
+ uint32_t time_date_stamp =
+ kernel32_image.GetNTHeaders()->FileHeader.TimeDateStamp;
+
+ ModuleInfoKey module_key(module_path, module_size, time_date_stamp, 0);
+ ModuleInfoData module_data = CreateLoadedModuleInfoData();
+ module_data.inspection_result = InspectModule(StringMapping(), module_key);
+
+ module_blacklist_cache_updater->OnNewModuleFound(module_key, module_data);
+ module_blacklist_cache_updater->OnModuleDatabaseIdle();
+
+ RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
+ EXPECT_TRUE(on_cache_updated_callback_invoked());
+
+ // Check the cache.
+ third_party_dlls::PackedListMetadata metadata;
+ std::vector<third_party_dlls::PackedListModule> blacklisted_modules;
+ base::MD5Digest md5_digest;
+ EXPECT_EQ(ReadResult::kSuccess,
+ ReadModuleBlacklistCache(module_blacklist_cache_path(), &metadata,
+ &blacklisted_modules, &md5_digest));
+
+ EXPECT_EQ(0u, blacklisted_modules.size());
+}
+
+// Tests that modules with a matching certificate subject are whitelisted.
+TEST_F(ModuleBlacklistCacheUpdaterTest, WhitelistMatchingCertificateSubject) {
+ EXPECT_FALSE(base::PathExists(module_blacklist_cache_path()));
+
+ auto module_blacklist_cache_updater = CreateModuleBlacklistCacheUpdater();
+
+ // Simulate the module loading into the process.
+ module_blacklist_cache_updater->OnNewModuleFound(
+ ModuleInfoKey(dll1_, 0, 0, 0), CreateSignedLoadedModuleInfoData());
+ module_blacklist_cache_updater->OnModuleDatabaseIdle();
+
+ RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
+ EXPECT_TRUE(on_cache_updated_callback_invoked());
+
+ // Check the cache.
+ third_party_dlls::PackedListMetadata metadata;
+ std::vector<third_party_dlls::PackedListModule> blacklisted_modules;
+ base::MD5Digest md5_digest;
+ EXPECT_EQ(ReadResult::kSuccess,
+ ReadModuleBlacklistCache(module_blacklist_cache_path(), &metadata,
+ &blacklisted_modules, &md5_digest));
+
+ EXPECT_EQ(0u, blacklisted_modules.size());
+}
+
+// Make sure IMEs are allowed while shell extensions are blacklisted.
+TEST_F(ModuleBlacklistCacheUpdaterTest, RegisteredModules) {
+ EXPECT_FALSE(base::PathExists(module_blacklist_cache_path()));
+
+ auto module_blacklist_cache_updater = CreateModuleBlacklistCacheUpdater();
+
+ // Set the respective bit for registered modules.
+ ModuleInfoKey module_key1(dll1_, 123u, 456u, 0);
+ ModuleInfoData module_data1 = CreateLoadedModuleInfoData();
+ module_data1.module_types |= ModuleInfoData::kTypeIme;
+
+ ModuleInfoKey module_key2(dll2_, 456u, 789u, 0);
+ ModuleInfoData module_data2 = CreateLoadedModuleInfoData();
+ module_data2.module_types |= ModuleInfoData::kTypeShellExtension;
+
+ // Simulate the modules loading into the process.
+ module_blacklist_cache_updater->OnNewModuleFound(module_key1, module_data1);
+ module_blacklist_cache_updater->OnNewModuleFound(module_key2, module_data2);
+ module_blacklist_cache_updater->OnModuleDatabaseIdle();
+
+ RunUntilIdle();
+ EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
+ EXPECT_TRUE(on_cache_updated_callback_invoked());
+
+ // Check the cache.
+ third_party_dlls::PackedListMetadata metadata;
+ std::vector<third_party_dlls::PackedListModule> blacklisted_modules;
+ base::MD5Digest md5_digest;
+ EXPECT_EQ(ReadResult::kSuccess,
+ ReadModuleBlacklistCache(module_blacklist_cache_path(), &metadata,
+ &blacklisted_modules, &md5_digest));
+
+ // Make sure the only blacklisted module is the shell extension.
+ ASSERT_EQ(1u, blacklisted_modules.size());
+
+ third_party_dlls::PackedListModule expected;
+ const std::string module_basename = base::UTF16ToUTF8(
+ base::i18n::ToLower(module_key2.module_path.BaseName().value()));
+ base::SHA1HashBytes(reinterpret_cast<const uint8_t*>(module_basename.data()),
+ module_basename.length(), expected.basename_hash);
+ const std::string module_code_id = GenerateCodeId(module_key2);
+ base::SHA1HashBytes(reinterpret_cast<const uint8_t*>(module_code_id.data()),
+ module_code_id.length(), expected.code_id_hash);
+
+ EXPECT_TRUE(internal::ModuleEqual()(expected, blacklisted_modules[0]));
+}
+
+// This tests that if a new blocked load attempt arrives, an update will still
+// be triggered even if the Module Database never goes idle afterwards.
+TEST_F(ModuleBlacklistCacheUpdaterTest, NewModulesBlockedOnly) {
+ EXPECT_FALSE(base::PathExists(module_blacklist_cache_path()));
+
+ auto module_blacklist_cache_updater = CreateModuleBlacklistCacheUpdater();
+
+ // Simulate a new blocked load attempt.
+ std::vector<third_party_dlls::PackedListModule> blocked_modules;
+ const third_party_dlls::PackedListModule module = {
+ {}, {}, 123456u,
+ };
+ blocked_modules.push_back(module);
+ module_blacklist_cache_updater->OnNewModulesBlocked(
+ std::move(blocked_modules));
+
+ FastForwardBy(ModuleBlacklistCacheUpdater::kUpdateTimerDuration);
+ EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
+ EXPECT_TRUE(on_cache_updated_callback_invoked());
+}
diff --git a/chrome/browser/conflicts/module_database_win.cc b/chrome/browser/conflicts/module_database_win.cc
index 83980e3..c8ae33a 100644
--- a/chrome/browser/conflicts/module_database_win.cc
+++ b/chrome/browser/conflicts/module_database_win.cc
@@ -18,9 +18,6 @@
#include "chrome/browser/browser_process.h"
#include "chrome/browser/conflicts/third_party_conflicts_manager_win.h"
#include "chrome/common/chrome_features.h"
-#include "chrome/common/pref_names.h"
-#include "components/prefs/pref_registry_simple.h"
-#include "components/prefs/pref_service.h"
#endif
namespace {
@@ -179,15 +176,6 @@
module_inspector_.IncreaseInspectionPriority();
}
-#if defined(GOOGLE_CHROME_BUILD)
-// static
-void ModuleDatabase::RegisterLocalStatePrefs(PrefRegistrySimple* registry) {
- // Register the pref used to disable the Incompatible Applications warning
- // using group policy.
- registry->RegisterBooleanPref(prefs::kThirdPartyBlockingEnabled, true);
-}
-#endif
-
// static
uint32_t ModuleDatabase::ProcessTypeToBit(content::ProcessType process_type) {
uint32_t bit_index =
@@ -277,17 +265,10 @@
#if defined(GOOGLE_CHROME_BUILD)
void ModuleDatabase::MaybeInitializeThirdPartyConflictsManager() {
- // Early exit if disabled via group policy.
- const PrefService::Preference* third_party_blocking_enabled_pref =
- g_browser_process->local_state()->FindPreference(
- prefs::kThirdPartyBlockingEnabled);
- if (third_party_blocking_enabled_pref->IsManaged() &&
- !third_party_blocking_enabled_pref->GetValue()->GetBool())
- return;
-
- if (base::FeatureList::IsEnabled(
- features::kIncompatibleApplicationsWarning) &&
- base::win::GetVersion() >= base::win::VERSION_WIN10) {
+ if (base::win::GetVersion() >= base::win::VERSION_WIN10 &&
+ ThirdPartyConflictsManager::IsThirdPartyBlockingPolicyEnabled() &&
+ base::FeatureList::IsEnabled(
+ features::kIncompatibleApplicationsWarning)) {
third_party_conflicts_manager_ =
std::make_unique<ThirdPartyConflictsManager>(this);
AddObserver(third_party_conflicts_manager_.get());
diff --git a/chrome/browser/conflicts/module_database_win.h b/chrome/browser/conflicts/module_database_win.h
index 62fb8c7..f4303c1 100644
--- a/chrome/browser/conflicts/module_database_win.h
+++ b/chrome/browser/conflicts/module_database_win.h
@@ -21,7 +21,6 @@
class ModuleDatabaseObserver;
#if defined(GOOGLE_CHROME_BUILD)
-class PrefRegistrySimple;
class ThirdPartyConflictsManager;
#endif
@@ -115,8 +114,6 @@
void IncreaseInspectionPriority();
#if defined(GOOGLE_CHROME_BUILD)
- static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
-
// Accessor for the third party conflicts manager. This is exposed so that the
// manager can be wired up to the ThirdPartyModuleListComponentInstaller.
// Returns null if the tracking of incompatible applications is disabled.
@@ -174,7 +171,8 @@
#if defined(GOOGLE_CHROME_BUILD)
// Initializes the ThirdPartyConflictsManager, which controls the warning of
- // incompatible applications that injects into Chrome.
+ // incompatible applications that injects into Chrome and the blocking of
+ // third-party modules.
// The manager is not initialized if it is disabled via a base::Feature or a
// group policy. Note that it is also not initialized on Windows version
// 8.1 and less.
diff --git a/chrome/browser/conflicts/module_info_util_win.cc b/chrome/browser/conflicts/module_info_util_win.cc
index 4656ef1..4b029cf4 100644
--- a/chrome/browser/conflicts/module_info_util_win.cc
+++ b/chrome/browser/conflicts/module_info_util_win.cc
@@ -252,6 +252,11 @@
certificate_info->subject = subject;
}
+bool IsMicrosoftModule(base::StringPiece16 subject) {
+ static constexpr wchar_t kMicrosoft[] = L"Microsoft ";
+ return subject.starts_with(kMicrosoft);
+}
+
StringMapping GetEnvironmentVariablesMapping(
const std::vector<base::string16>& environment_variables) {
std::unique_ptr<base::Environment> environment(base::Environment::Create());
diff --git a/chrome/browser/conflicts/module_info_util_win.h b/chrome/browser/conflicts/module_info_util_win.h
index 68d6121c..642e4bc 100644
--- a/chrome/browser/conflicts/module_info_util_win.h
+++ b/chrome/browser/conflicts/module_info_util_win.h
@@ -10,6 +10,7 @@
#include "base/files/file_path.h"
#include "base/strings/string16.h"
+#include "base/strings/string_piece.h"
// A format string for generating paths to COM class in-proc server keys under
// HKEY_CLASSES_ROOT.
@@ -47,6 +48,13 @@
void GetCertificateInfo(const base::FilePath& file,
CertificateInfo* certificate_info);
+// Returns true if the signer name begins with "Microsoft ". Signatures are
+// typically "Microsoft Corporation" or "Microsoft Windows", but others may
+// exist.
+// Note: This is not a secure check to validate the owner of a certificate. It
+// simply does string comparison on the subject name.
+bool IsMicrosoftModule(base::StringPiece16 subject);
+
// Returns a mapping of the value of an environment variable to its name.
// Removes any existing trailing backslash in the values.
//
diff --git a/chrome/browser/conflicts/module_info_win.cc b/chrome/browser/conflicts/module_info_win.cc
index 4f9c4f8c9..3ba8257 100644
--- a/chrome/browser/conflicts/module_info_win.cc
+++ b/chrome/browser/conflicts/module_info_win.cc
@@ -15,6 +15,7 @@
#include "base/i18n/case_conversion.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
namespace {
@@ -102,6 +103,11 @@
return inspection_result;
}
+std::string GenerateCodeId(const ModuleInfoKey& module_key) {
+ return base::StringPrintf("%08X%x", module_key.module_time_date_stamp,
+ module_key.module_size);
+}
+
namespace internal {
void NormalizeInspectionResult(ModuleInspectionResult* inspection_result) {
diff --git a/chrome/browser/conflicts/module_info_win.h b/chrome/browser/conflicts/module_info_win.h
index 6f1ef91..c63a4de 100644
--- a/chrome/browser/conflicts/module_info_win.h
+++ b/chrome/browser/conflicts/module_info_win.h
@@ -6,6 +6,7 @@
#define CHROME_BROWSER_CONFLICTS_MODULE_INFO_WIN_H_
#include <memory>
+#include <string>
#include "base/files/file_path.h"
#include "chrome/browser/conflicts/module_info_util_win.h"
@@ -113,6 +114,9 @@
const StringMapping& env_variable_mapping,
const ModuleInfoKey& module_key);
+// Generate the code id of a module.
+std::string GenerateCodeId(const ModuleInfoKey& module_key);
+
namespace internal {
// Normalizes the information already contained in |inspection_result|. In
diff --git a/chrome/browser/conflicts/module_info_win_unittest.cc b/chrome/browser/conflicts/module_info_win_unittest.cc
index fc8e83d9..e1c4487a 100644
--- a/chrome/browser/conflicts/module_info_win_unittest.cc
+++ b/chrome/browser/conflicts/module_info_win_unittest.cc
@@ -50,6 +50,12 @@
inspection_result->certificate_info.subject.c_str());
}
+TEST(ModuleInfoTest, GenerateCodeId) {
+ static const char kExpected[] = "00000BADf00d";
+ ModuleInfoKey module_key = {base::FilePath(), 0xf00d, 0xbad, 1};
+ EXPECT_STREQ(kExpected, GenerateCodeId(module_key).c_str());
+}
+
TEST(ModuleInfoTest, NormalizeInspectionResult) {
ModuleInspectionResult test_case;
test_case.location = L"%variable%\\PATH\\TO\\file.txt";
diff --git a/chrome/browser/conflicts/module_list_filter_win.cc b/chrome/browser/conflicts/module_list_filter_win.cc
index cf9f824..184e35b 100644
--- a/chrome/browser/conflicts/module_list_filter_win.cc
+++ b/chrome/browser/conflicts/module_list_filter_win.cc
@@ -11,17 +11,11 @@
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/sha1.h"
-#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/conflicts/module_info_win.h"
namespace {
-std::string GenerateCodeId(const ModuleInfoKey& module_key) {
- return base::StringPrintf("%08X%x", module_key.module_time_date_stamp,
- module_key.module_size);
-}
-
bool MatchesModuleGroup(const chrome::conflicts::ModuleGroup& module_group,
base::StringPiece module_basename_hash,
base::StringPiece module_code_id_hash) {
diff --git a/chrome/browser/conflicts/third_party_blocking_browsertest.cc b/chrome/browser/conflicts/third_party_blocking_browsertest.cc
new file mode 100644
index 0000000..7b8dc1f
--- /dev/null
+++ b/chrome/browser/conflicts/third_party_blocking_browsertest.cc
@@ -0,0 +1,207 @@
+// 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 "base/base_paths.h"
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path_watcher.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/scoped_native_library.h"
+#include "base/test/scoped_feature_list.h"
+#include "base/test/test_reg_util_win.h"
+#include "base/win/windows_version.h"
+#include "chrome/browser/conflicts/incompatible_applications_updater_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_database_win.h"
+#include "chrome/browser/conflicts/proto/module_list.pb.h"
+#include "chrome/browser/conflicts/third_party_conflicts_manager_win.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome_elf/third_party_dlls/packed_list_format.h"
+
+namespace {
+
+// This classes watches the module blacklist cache directory to detect when the
+// cache file is created.
+class ModuleBlacklistCacheObserver {
+ public:
+ ModuleBlacklistCacheObserver(
+ const base::FilePath& module_blacklist_cache_path)
+ : module_blacklist_cache_path_(module_blacklist_cache_path) {}
+
+ bool StartWatching() {
+ return file_path_watcher_.Watch(
+ module_blacklist_cache_path_.DirName(), false,
+ base::Bind(
+ &ModuleBlacklistCacheObserver::OnModuleBlacklistCachePathChanged,
+ base::Unretained(this)));
+ }
+
+ void WaitForModuleBlacklistCacheCreated() {
+ if (module_blacklist_cache_created_)
+ return;
+
+ base::RunLoop run_loop;
+ run_loop_quit_closure_ = run_loop.QuitClosure();
+ run_loop.Run();
+ }
+
+ private:
+ void OnModuleBlacklistCachePathChanged(const base::FilePath& path,
+ bool error) {
+ if (!base::PathExists(module_blacklist_cache_path_))
+ return;
+
+ module_blacklist_cache_created_ = true;
+
+ if (run_loop_quit_closure_)
+ std::move(run_loop_quit_closure_).Run();
+ }
+
+ // Needed to watch a file on main thread.
+ base::ScopedAllowBlockingForTesting scoped_allow_blocking_;
+
+ // The path to the module_blacklist_cache.
+ base::FilePath module_blacklist_cache_path_;
+
+ base::FilePathWatcher file_path_watcher_;
+
+ // Remembers if the cache was created in case the callback is invoked before
+ // WaitForModuleBlacklistCacheCreated() was called.
+ bool module_blacklist_cache_created_ = false;
+
+ base::Closure run_loop_quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(ModuleBlacklistCacheObserver);
+};
+
+class ThirdPartyBlockingBrowserTest : public InProcessBrowserTest {
+ protected:
+ ThirdPartyBlockingBrowserTest() = default;
+
+ ~ThirdPartyBlockingBrowserTest() override = default;
+
+ // InProcessBrowserTest:
+ void SetUp() override {
+ scoped_feature_list_.InitWithFeatures(
+ // Enabled features.
+ {features::kModuleDatabase, features::kIncompatibleApplicationsWarning,
+ features::kThirdPartyModulesBlocking},
+ // Disabled features.
+ {});
+
+ ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
+
+ InProcessBrowserTest::SetUp();
+ }
+
+ // Creates a copy of a test DLL into a temp directory that will act as the
+ // third-party module and return its path. It can't be located in the output
+ // directory because modules in the same directory as chrome.exe are
+ // whitelisted in non-official builds.
+ void CreateThirdPartyModule(base::FilePath* third_party_module_path) {
+ base::FilePath test_dll_path;
+ ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &test_dll_path));
+ test_dll_path =
+ test_dll_path.Append(FILE_PATH_LITERAL("conflicts_dll.dll"));
+ *third_party_module_path = scoped_temp_dir_.GetPath().Append(
+ FILE_PATH_LITERAL("third_party_module.dll"));
+ base::ScopedAllowBlockingForTesting scoped_allow_blocking;
+ ASSERT_TRUE(base::CopyFile(test_dll_path, *third_party_module_path));
+ }
+
+ // Creates an empty serialized ModuleList proto in the module list component
+ // directory and returns its path.
+ void CreateModuleList(base::FilePath* module_list_path) {
+ chrome::conflicts::ModuleList module_list;
+ // Include an empty blacklist and whitelist.
+ module_list.mutable_blacklist();
+ module_list.mutable_whitelist();
+
+ std::string contents;
+ ASSERT_TRUE(module_list.SerializeToString(&contents));
+
+ // Put the module list beside the module blacklist cache.
+ *module_list_path =
+ ModuleBlacklistCacheUpdater::GetModuleBlacklistCachePath()
+ .DirName()
+ .Append(FILE_PATH_LITERAL("ModuleList.bin"));
+
+ base::ScopedAllowBlockingForTesting scoped_allow_blocking;
+ ASSERT_TRUE(base::CreateDirectory(module_list_path->DirName()));
+ ASSERT_EQ(static_cast<int>(contents.size()),
+ base::WriteFile(*module_list_path, contents.data(),
+ static_cast<int>(contents.size())));
+ }
+
+ // Enables the features to activate the ModuleBlacklistCacheUpdater.
+ base::test::ScopedFeatureList scoped_feature_list_;
+
+ // Temp directory where the third-party module is located.
+ base::ScopedTempDir scoped_temp_dir_;
+
+ DISALLOW_COPY_AND_ASSIGN(ThirdPartyBlockingBrowserTest);
+};
+
+} // namespace
+
+// This is an integration test for the blocking of third-party modules.
+//
+// This test makes sure that all the different classes interact together
+// correctly to produce a valid module blacklist cache.
+//
+// Note: This doesn't test that the modules are actually blocked on the next
+// browser launch.
+IN_PROC_BROWSER_TEST_F(ThirdPartyBlockingBrowserTest,
+ CreateModuleBlacklistCache) {
+ if (base::win::GetVersion() < base::win::VERSION_WIN10)
+ return;
+
+ base::FilePath module_list_path;
+ ASSERT_NO_FATAL_FAILURE(CreateModuleList(&module_list_path));
+ ASSERT_FALSE(module_list_path.empty());
+
+ ModuleDatabase* module_database = ModuleDatabase::GetInstance();
+
+ // Speed up the test.
+ module_database->IncreaseInspectionPriority();
+
+ base::FilePath module_blacklist_cache_path =
+ ModuleBlacklistCacheUpdater::GetModuleBlacklistCachePath();
+ ASSERT_FALSE(module_blacklist_cache_path.empty());
+
+ // Create the observer early so the change is guaranteed to be observed.
+ ModuleBlacklistCacheObserver module_blacklist_cache_observer(
+ module_blacklist_cache_path);
+ ASSERT_TRUE(module_blacklist_cache_observer.StartWatching());
+
+ // Simulate the download of the module list component.
+ module_database->third_party_conflicts_manager()->LoadModuleList(
+ module_list_path);
+
+ // Injects the third-party DLL into the process.
+ base::FilePath third_party_module_path;
+ ASSERT_NO_FATAL_FAILURE(CreateThirdPartyModule(&third_party_module_path));
+ ASSERT_FALSE(third_party_module_path.empty());
+
+ base::ScopedNativeLibrary dll(third_party_module_path);
+ ASSERT_TRUE(dll.is_valid());
+
+ // Now the module blacklist cache will eventually be created.
+ module_blacklist_cache_observer.WaitForModuleBlacklistCacheCreated();
+
+ // Now check that the third-party DLL was added to the module blacklist cache.
+ third_party_dlls::PackedListMetadata metadata;
+ std::vector<third_party_dlls::PackedListModule> blacklisted_modules;
+ base::MD5Digest md5_digest;
+ ASSERT_EQ(ReadResult::kSuccess,
+ ReadModuleBlacklistCache(module_blacklist_cache_path, &metadata,
+ &blacklisted_modules, &md5_digest));
+
+ EXPECT_EQ(1u, blacklisted_modules.size());
+}
diff --git a/chrome/browser/conflicts/third_party_conflicts_manager_win.cc b/chrome/browser/conflicts/third_party_conflicts_manager_win.cc
index 3a0d8a08e..fe7ffc1 100644
--- a/chrome/browser/conflicts/third_party_conflicts_manager_win.cc
+++ b/chrome/browser/conflicts/third_party_conflicts_manager_win.cc
@@ -4,18 +4,28 @@
#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/feature_list.h"
#include "base/location.h"
+#include "base/metrics/histogram_macros.h"
#include "base/path_service.h"
#include "base/task_scheduler/post_task.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_database_win.h"
#include "chrome/browser/conflicts/module_info_util_win.h"
#include "chrome/browser/conflicts/module_list_filter_win.h"
+#include "chrome/common/chrome_features.h"
+#include "chrome/common/pref_names.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_thread.h"
namespace {
@@ -58,6 +68,46 @@
ThirdPartyConflictsManager::~ThirdPartyConflictsManager() = default;
+// static
+void ThirdPartyConflictsManager::RegisterLocalStatePrefs(
+ PrefRegistrySimple* registry) {
+ // Register the pref used to disable the Incompatible Applications warning and
+ // the blocking of third-party modules using group policy. Enabled by default.
+ registry->RegisterBooleanPref(prefs::kThirdPartyBlockingEnabled, true);
+
+ // 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
+bool ThirdPartyConflictsManager::IsThirdPartyBlockingPolicyEnabled() {
+ const PrefService::Preference* third_party_blocking_enabled_pref =
+ g_browser_process->local_state()->FindPreference(
+ prefs::kThirdPartyBlockingEnabled);
+ return !third_party_blocking_enabled_pref->IsManaged() ||
+ third_party_blocking_enabled_pref->GetValue()->GetBool();
+}
+
+// static
+void ThirdPartyConflictsManager::DisableThirdPartyModuleBlocking() {
+ 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.
+ base::PostTaskWithTraits(
+ FROM_HERE,
+ {base::TaskPriority::BACKGROUND,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN, base::MayBlock()},
+ 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);
+}
+
void ThirdPartyConflictsManager::OnModuleDatabaseIdle() {
if (on_module_database_idle_called_)
return;
@@ -96,6 +146,9 @@
if (module_list_filter_ && installed_applications_)
InitializeIncompatibleApplicationsUpdater();
+
+ if (module_list_filter_)
+ MaybeInitializeModuleBlacklistCacheUpdater();
}
void ThirdPartyConflictsManager::OnModuleListFilterCreated(
@@ -115,6 +168,9 @@
if (exe_certificate_info_ && installed_applications_)
InitializeIncompatibleApplicationsUpdater();
+
+ if (exe_certificate_info_)
+ MaybeInitializeModuleBlacklistCacheUpdater();
}
void ThirdPartyConflictsManager::OnInstalledApplicationsCreated(
@@ -136,3 +192,48 @@
*installed_applications_);
module_database_->AddObserver(incompatible_applications_updater_.get());
}
+
+void ThirdPartyConflictsManager::MaybeInitializeModuleBlacklistCacheUpdater() {
+ DCHECK(exe_certificate_info_);
+ DCHECK(module_list_filter_);
+
+ if (!base::FeatureList::IsEnabled(features::kThirdPartyModulesBlocking))
+ return;
+
+ // Create the instance. It is safe to use base::Unretained() since the
+ // callback is not invoked when the updater is freed.
+ module_blacklist_cache_updater_ =
+ std::make_unique<ModuleBlacklistCacheUpdater>(
+ *exe_certificate_info_, *module_list_filter_,
+ base::BindRepeating(
+ &ThirdPartyConflictsManager::OnModuleBlacklistCacheUpdated,
+ base::Unretained(this)));
+ module_database_->AddObserver(module_blacklist_cache_updater_.get());
+}
+
+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)));
+}
diff --git a/chrome/browser/conflicts/third_party_conflicts_manager_win.h b/chrome/browser/conflicts/third_party_conflicts_manager_win.h
index 92f53cb..6ece75d 100644
--- a/chrome/browser/conflicts/third_party_conflicts_manager_win.h
+++ b/chrome/browser/conflicts/third_party_conflicts_manager_win.h
@@ -9,13 +9,15 @@
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
+#include "chrome/browser/conflicts/module_blacklist_cache_updater_win.h"
#include "chrome/browser/conflicts/module_database_observer_win.h"
-struct CertificateInfo;
+class IncompatibleApplicationsUpdater;
class InstalledApplications;
class ModuleDatabase;
class ModuleListFilter;
-class IncompatibleApplicationsUpdater;
+class PrefRegistrySimple;
+struct CertificateInfo;
namespace base {
class FilePath;
@@ -28,6 +30,17 @@
explicit ThirdPartyConflictsManager(ModuleDatabase* module_database);
~ThirdPartyConflictsManager() override;
+ static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);
+
+ // Returns true if the ThirdPartyBlocking policy is enabled. This can only
+ // return false if it is disabled via admin policy.
+ static bool IsThirdPartyBlockingPolicyEnabled();
+
+ // Explicitely disables the third-party module blocking feature. This is
+ // needed because simply turning off the feature using either the Feature List
+ // API or via group policy is not sufficient.
+ static void DisableThirdPartyModuleBlocking();
+
// ModuleDatabaseObserver:
void OnModuleDatabaseIdle() override;
@@ -52,6 +65,15 @@
// installed_applications_ are available.
void InitializeIncompatibleApplicationsUpdater();
+ // Initializes |module_blacklist_cache_updater_| if third-party module
+ // blocking is enabled.
+ void MaybeInitializeModuleBlacklistCacheUpdater();
+
+ // Checks if the |old_md5_digest| matches the expected one from the Local
+ // State file, and updates it to |new_md5_digest|.
+ void OnModuleBlacklistCacheUpdated(
+ const ModuleBlacklistCacheUpdater::CacheUpdateResult& result);
+
ModuleDatabase* module_database_;
// Indicates if the initial Module List has been received. Used to prevent the
@@ -76,6 +98,10 @@
std::unique_ptr<IncompatibleApplicationsUpdater>
incompatible_applications_updater_;
+ // Maintains the module blacklist cache. This member is only initialized when
+ // the ThirdPartyModuleBlocking feature is enabled.
+ std::unique_ptr<ModuleBlacklistCacheUpdater> module_blacklist_cache_updater_;
+
base::WeakPtrFactory<ThirdPartyConflictsManager> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(ThirdPartyConflictsManager);
diff --git a/chrome/browser/conflicts/third_party_metrics_recorder_win.cc b/chrome/browser/conflicts/third_party_metrics_recorder_win.cc
index 398ac0c..cd773e41 100644
--- a/chrome/browser/conflicts/third_party_metrics_recorder_win.cc
+++ b/chrome/browser/conflicts/third_party_metrics_recorder_win.cc
@@ -10,25 +10,16 @@
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
-#include "base/strings/string_util.h"
+#include "base/strings/string_piece.h"
+#include "chrome/browser/conflicts/module_info_util_win.h"
#include "chrome/browser/conflicts/module_info_win.h"
namespace {
// Returns true if the module is signed by Google.
-bool IsGoogleModule(const ModuleInfoData& module_data) {
+bool IsGoogleModule(base::StringPiece16 subject) {
static const wchar_t kGoogle[] = L"Google Inc";
- return module_data.inspection_result->certificate_info.subject == kGoogle;
-}
-
-// Returns true if the signer name begins with "Microsoft ". Signatures are
-// typically "Microsoft Corporation" or "Microsoft Windows", but others may
-// exist.
-bool IsMicrosoftModule(const ModuleInfoData& module_data) {
- static const wchar_t kMicrosoft[] = L"Microsoft ";
- return base::StartsWith(
- module_data.inspection_result->certificate_info.subject, kMicrosoft,
- base::CompareCase::SENSITIVE);
+ return subject == kGoogle;
}
} // namespace
@@ -49,9 +40,10 @@
if (certificate_info.type == CertificateType::CERTIFICATE_IN_CATALOG)
++catalog_module_count_;
- if (IsMicrosoftModule(module_data)) {
+ base::StringPiece16 certificate_subject = certificate_info.subject;
+ if (IsMicrosoftModule(certificate_subject)) {
++microsoft_module_count_;
- } else if (IsGoogleModule(module_data)) {
+ } else if (IsGoogleModule(certificate_subject)) {
// No need to count these explicitly.
} else {
// Count modules that are neither signed by Google nor Microsoft.
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index d3b6716f..f576ea66 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -280,7 +280,7 @@
#include "chrome/browser/component_updater/sw_reporter_installer_win.h"
#if defined(GOOGLE_CHROME_BUILD)
#include "chrome/browser/conflicts/incompatible_applications_updater_win.h"
-#include "chrome/browser/conflicts/module_database_win.h"
+#include "chrome/browser/conflicts/third_party_conflicts_manager_win.h"
#endif // defined(GOOGLE_CHROME_BUILD)
#include "chrome/browser/safe_browsing/chrome_cleaner/settings_resetter_win.h"
#include "chrome/browser/safe_browsing/settings_reset_prompt/settings_reset_prompt_prefs_manager.h"
@@ -469,7 +469,7 @@
password_manager::PasswordManager::RegisterLocalPrefs(registry);
#if defined(GOOGLE_CHROME_BUILD)
IncompatibleApplicationsUpdater::RegisterLocalStatePrefs(registry);
- ModuleDatabase::RegisterLocalStatePrefs(registry);
+ ThirdPartyConflictsManager::RegisterLocalStatePrefs(registry);
#endif // defined(GOOGLE_CHROME_BUILD)
#endif
diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc
index 3e6264a..f8cf5fbd 100644
--- a/chrome/browser/prefs/chrome_pref_service_factory.cc
+++ b/chrome/browser/prefs/chrome_pref_service_factory.cc
@@ -99,9 +99,9 @@
#endif // OS_WIN
// These preferences must be kept in sync with the TrackedPreference enum in
-// tools/metrics/histograms/histograms.xml. To add a new preference, append it
-// to the array and add a corresponding value to the histogram enum. Each
-// tracked preference must be given a unique reporting ID.
+// tools/metrics/histograms/enums.xml. To add a new preference, append it to the
+// array and add a corresponding value to the histogram enum. Each tracked
+// preference must be given a unique reporting ID.
// See CleanupDeprecatedTrackedPreferences() in pref_hash_filter.cc to remove a
// deprecated tracked preference.
const prefs::TrackedPreferenceMetadata kTrackedPrefs[] = {
@@ -178,6 +178,11 @@
#endif // defined(OS_WIN)
{29, prefs::kMediaStorageIdSalt, EnforcementLevel::ENFORCE_ON_LOAD,
PrefTrackingStrategy::ATOMIC, ValueType::IMPERSONAL},
+#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
+ {30, prefs::kModuleBlacklistCacheMD5Digest,
+ EnforcementLevel::ENFORCE_ON_LOAD, PrefTrackingStrategy::ATOMIC,
+ ValueType::IMPERSONAL},
+#endif
// See note at top, new items added here also need to be added to
// histograms.xml's TrackedPreference enum.
diff --git a/chrome/common/chrome_features.cc b/chrome/common/chrome_features.cc
index 56fb94d..837b9fd 100644
--- a/chrome/common/chrome_features.cc
+++ b/chrome/common/chrome_features.cc
@@ -136,6 +136,13 @@
base::FEATURE_ENABLED_BY_DEFAULT};
#endif
+#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
+// Enables the blocking of third-party modules. This feature depends on
+// ModuleDatabase and IncompatibleApplicationsWarning.
+const base::Feature kThirdPartyModulesBlocking{
+ "ThirdPartyModulesBlocking", base::FEATURE_DISABLED_BY_DEFAULT};
+#endif
+
#if (defined(OS_LINUX) && !defined(OS_CHROMEOS)) || defined(OS_MACOSX)
// Enables the dual certificate verification trial feature.
// https://crbug.com/649026
diff --git a/chrome/common/chrome_features.h b/chrome/common/chrome_features.h
index 804d071a..19527da 100644
--- a/chrome/common/chrome_features.h
+++ b/chrome/common/chrome_features.h
@@ -302,6 +302,10 @@
extern const base::Feature kTabMetricsLogging;
#endif
+#if defined(OS_WIN) && defined(GOOGLE_CHROME_BUILD)
+extern const base::Feature kThirdPartyModulesBlocking;
+#endif
+
extern const base::Feature kTopSitesFromSiteEngagement;
extern const base::Feature kUseGoogleLocalNtp;
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 476f01cb..b4a9b32d 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -2427,6 +2427,11 @@
// for the Incompatible Applications Warning feature.
const char kIncompatibleApplications[] = "incompatible_applications";
+// Contains the MD5 digest of the current module blacklist cache. Used to detect
+// external tampering.
+const char kModuleBlacklistCacheMD5Digest[] =
+ "module_blacklist_cache_md5_digest";
+
// Acts as a cache to remember problematic programs through restarts. Used for
// the Incompatible Applications Warning feature.
// Note: Deprecated. Renamed to kIncompatibleApplications.
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 2023f16..23eb6f7 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -852,6 +852,7 @@
#if defined(GOOGLE_CHROME_BUILD)
extern const char kIncompatibleApplications[];
+extern const char kModuleBlacklistCacheMD5Digest[];
extern const char kProblematicPrograms[];
extern const char kThirdPartyBlockingEnabled[];
#endif // defined(GOOGLE_CHROME_BUILD)
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 0d36baf..9ce45e5 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -1952,8 +1952,10 @@
data_deps += [ "../../device/vr:openvr_mock" ]
if (is_chrome_branded) {
- sources +=
- [ "../browser/conflicts/incompatible_applications_browsertest.cc" ]
+ sources += [
+ "../browser/conflicts/incompatible_applications_browsertest.cc",
+ "../browser/conflicts/third_party_blocking_browsertest.cc",
+ ]
deps += [ ":conflicts_dll" ]
}
} else { # Not Windows.
@@ -4251,6 +4253,7 @@
sources += [
"../browser/conflicts/incompatible_applications_updater_win_unittest.cc",
"../browser/conflicts/installed_applications_win_unittest.cc",
+ "../browser/conflicts/module_blacklist_cache_updater_win_unittest.cc",
"../browser/conflicts/module_blacklist_cache_util_win_unittest.cc",
"../browser/conflicts/module_list_filter_win_unittest.cc",
"../browser/conflicts/module_load_attempt_log_listener_win_unittest.cc",