blob: c3dfab2cf2453375c3d6392f6bae1f13141846e7 [file] [log] [blame]
// 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 "chrome/install_static/install_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
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_properties |= ModuleInfoData::kPropertyLoadedModule;
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);
}
// Returns true if the cache path registry key value exists.
bool RegistryKeyExists() {
base::win::RegKey reg_key(HKEY_CURRENT_USER,
install_static::GetRegistryPath()
.append(third_party_dlls::kThirdPartyRegKeyName)
.c_str(),
KEY_READ);
return reg_key.HasValue(third_party_dlls::kBlFilePathRegValue);
}
} // namespace
class ModuleBlacklistCacheUpdaterTest : public testing::Test,
public ModuleDatabaseEventSource {
protected:
ModuleBlacklistCacheUpdaterTest()
: dll1_(kDllPath1),
dll2_(kDllPath2),
scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::MOCK_TIME),
user_data_dir_override_(chrome::DIR_USER_DATA),
module_list_filter_(CreateModuleListFilter()),
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>(
this, exe_certificate_info_, module_list_filter_,
initial_blacklisted_modules_,
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_;
}
// ModuleDatabaseEventSource:
void AddObserver(ModuleDatabaseObserver* observer) override {}
void RemoveObserver(ModuleDatabaseObserver* observer) override {}
const base::FilePath dll1_;
const base::FilePath dll2_;
private:
scoped_refptr<ModuleListFilter> CreateModuleListFilter() {
chrome::conflicts::ModuleList module_list;
// Include an empty blacklist and whitelist.
module_list.mutable_blacklist();
module_list.mutable_whitelist();
// Serialize the module list to the user data directory.
base::FilePath module_list_path;
if (!base::PathService::Get(chrome::DIR_USER_DATA, &module_list_path))
return nullptr;
module_list_path =
module_list_path.Append(FILE_PATH_LITERAL("ModuleList.bin"));
std::string contents;
if (!module_list.SerializeToString(&contents) ||
base::WriteFile(module_list_path, contents.data(),
static_cast<int>(contents.size())) !=
static_cast<int>(contents.size())) {
return nullptr;
}
auto module_list_filter = base::MakeRefCounted<ModuleListFilter>();
if (!module_list_filter->Initialize(module_list_path))
return nullptr;
return module_list_filter;
}
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_;
scoped_refptr<ModuleListFilter> module_list_filter_;
std::vector<third_party_dlls::PackedListModule> initial_blacklisted_modules_;
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.
ModuleInfoKey module_key(dll1_, 0, 0, 0);
module_blacklist_cache_updater->OnNewModuleFound(
module_key, CreateLoadedModuleInfoData());
module_blacklist_cache_updater->OnModuleDatabaseIdle();
RunUntilIdle();
EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
EXPECT_TRUE(on_cache_updated_callback_invoked());
EXPECT_TRUE(RegistryKeyExists());
// 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());
ASSERT_EQ(
ModuleBlacklistCacheUpdater::ModuleBlockingDecision::kDisallowedImplicit,
module_blacklist_cache_updater->GetModuleBlockingState(module_key)
.blocking_decision);
}
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());
EXPECT_TRUE(RegistryKeyExists());
// 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());
ASSERT_EQ(
ModuleBlacklistCacheUpdater::ModuleBlockingDecision::kAllowedMicrosoft,
module_blacklist_cache_updater->GetModuleBlockingState(module_key)
.blocking_decision);
}
// 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.
ModuleInfoKey module_key(dll1_, 0, 0, 0);
module_blacklist_cache_updater->OnNewModuleFound(
module_key, CreateSignedLoadedModuleInfoData());
module_blacklist_cache_updater->OnModuleDatabaseIdle();
RunUntilIdle();
EXPECT_TRUE(base::PathExists(module_blacklist_cache_path()));
EXPECT_TRUE(on_cache_updated_callback_invoked());
EXPECT_TRUE(RegistryKeyExists());
// 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());
ASSERT_EQ(ModuleBlacklistCacheUpdater::ModuleBlockingDecision::
kAllowedSameCertificate,
module_blacklist_cache_updater->GetModuleBlockingState(module_key)
.blocking_decision);
}
// 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_properties |= ModuleInfoData::kPropertyIme;
ModuleInfoKey module_key2(dll2_, 456u, 789u, 1);
ModuleInfoData module_data2 = CreateLoadedModuleInfoData();
module_data2.module_properties |= ModuleInfoData::kPropertyShellExtension;
// 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());
EXPECT_TRUE(RegistryKeyExists());
// 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());
ASSERT_EQ(ModuleBlacklistCacheUpdater::ModuleBlockingDecision::kAllowedIME,
module_blacklist_cache_updater->GetModuleBlockingState(module_key1)
.blocking_decision);
ASSERT_EQ(
ModuleBlacklistCacheUpdater::ModuleBlockingDecision::kDisallowedImplicit,
module_blacklist_cache_updater->GetModuleBlockingState(module_key2)
.blocking_decision);
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]));
}