chromium/chrome/browser/win/conflicts/third_party_blocking_browsertest.cc

// Copyright 2018 The Chromium Authors
// 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/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/scoped_native_library.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "chrome/browser/win/conflicts/module_blocklist_cache_updater.h"
#include "chrome/browser/win/conflicts/module_blocklist_cache_util.h"
#include "chrome/browser/win/conflicts/module_database.h"
#include "chrome/browser/win/conflicts/proto/module_list.pb.h"
#include "chrome/browser/win/conflicts/third_party_conflicts_manager.h"
#include "chrome/chrome_elf/third_party_dlls/packed_list_format.h"
#include "chrome/common/chrome_features.h"
#include "chrome/install_static/install_util.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"

namespace {

// This classes watches the third-party blocking key to detect when the path to
// the freshly created cache is written into the registry.
class ThirdPartyRegistryKeyObserver {
 public:
  ThirdPartyRegistryKeyObserver()
      : registry_key_(HKEY_CURRENT_USER,
                      GetRegistryKeyPath().c_str(),
                      KEY_CREATE_SUB_KEY | KEY_READ | KEY_NOTIFY) {}

  ThirdPartyRegistryKeyObserver(const ThirdPartyRegistryKeyObserver&) = delete;
  ThirdPartyRegistryKeyObserver& operator=(
      const ThirdPartyRegistryKeyObserver&) = delete;

  bool StartWatching() {
    return registry_key_.StartWatching(base::BindOnce(
        &ThirdPartyRegistryKeyObserver::OnChange, base::Unretained(this)));
  }

  void WaitForCachePathWritten() {
    if (path_written_)
      return;

    base::RunLoop run_loop;
    run_loop_quit_closure_ = run_loop.QuitClosure();
    run_loop.Run();
  }

  void OnChange() {
    if (!registry_key_.HasValue(third_party_dlls::kBlFilePathRegValue))
      return;

    path_written_ = true;

    if (run_loop_quit_closure_)
      std::move(run_loop_quit_closure_).Run();
  }

 private:
  std::wstring GetRegistryKeyPath() {
    return install_static::GetRegistryPath().append(
        third_party_dlls::kThirdPartyRegKeyName);
  }

  base::win::RegKey registry_key_;

  // Remembers if the path of the cache was written in the registry in case the
  // callback is invoked before WaitForCachePathWritten() was called.
  bool path_written_ = false;

  base::OnceClosure run_loop_quit_closure_;
};

// 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 blocklist and allowlist.
  module_list.mutable_blocklist();
  module_list.mutable_allowlist();

  std::string contents;
  ASSERT_TRUE(module_list.SerializeToString(&contents));

  // Put the module list beside the module blocklist cache.
  *module_list_path = ModuleBlocklistCacheUpdater::GetModuleBlocklistCachePath()
                          .DirName()
                          .Append(FILE_PATH_LITERAL("ModuleList.bin"));

  base::ScopedAllowBlockingForTesting scoped_allow_blocking;
  ASSERT_TRUE(base::CreateDirectory(module_list_path->DirName()));
  ASSERT_TRUE(base::WriteFile(*module_list_path, contents));
}

class ThirdPartyBlockingBrowserTest : public InProcessBrowserTest {
 public:
  ThirdPartyBlockingBrowserTest(const ThirdPartyBlockingBrowserTest&) = delete;
  ThirdPartyBlockingBrowserTest& operator=(
      const ThirdPartyBlockingBrowserTest&) = delete;

 protected:
  ThirdPartyBlockingBrowserTest() = default;
  ~ThirdPartyBlockingBrowserTest() override = default;

  // InProcessBrowserTest:
  void SetUp() override {
    scoped_feature_list_.InitAndEnableFeature(
        features::kThirdPartyModulesBlocking);

    ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));

    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
  // allowlisted 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));
  }

  // Enables the ThirdPartyModulesBlocking feature.
  base::test::ScopedFeatureList scoped_feature_list_;

  registry_util::RegistryOverrideManager registry_override_manager_;

  // Temp directory where the third-party module is located.
  base::ScopedTempDir scoped_temp_dir_;
};

}  // 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 blocklist cache and to write its path in
// the registry.
//
// Note: This doesn't test that the modules are actually blocked on the next
//       browser launch.
IN_PROC_BROWSER_TEST_F(ThirdPartyBlockingBrowserTest,
                       CreateModuleBlocklistCache) {
  // Create the observer early so the change is guaranteed to be observed.
  ThirdPartyRegistryKeyObserver third_party_registry_key_observer;
  ASSERT_TRUE(third_party_registry_key_observer.StartWatching());

  base::RunLoop run_loop;
  ModuleDatabase::GetTaskRunner()->PostTask(
      FROM_HERE,
      base::BindLambdaForTesting([quit_closure = run_loop.QuitClosure()]() {
        ModuleDatabase* module_database = ModuleDatabase::GetInstance();

        base::FilePath module_list_path;
        ASSERT_NO_FATAL_FAILURE(CreateModuleList(&module_list_path));
        ASSERT_FALSE(module_list_path.empty());

        // Simulate the download of the module list component.
        module_database->third_party_conflicts_manager()->LoadModuleList(
            module_list_path);

        quit_closure.Run();
      }));
  run_loop.Run();

  // 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::ScopedAllowBlockingForTesting scoped_allow_blocking;
  base::ScopedNativeLibrary dll(third_party_module_path);
  ASSERT_TRUE(dll.is_valid());

  // Now the module blocklist cache will eventually be created and its path
  // written in the registry.
  third_party_registry_key_observer.WaitForCachePathWritten();

  base::FilePath module_blocklist_cache_path =
      ModuleBlocklistCacheUpdater::GetModuleBlocklistCachePath();
  ASSERT_FALSE(module_blocklist_cache_path.empty());
  ASSERT_TRUE(base::PathExists(module_blocklist_cache_path));

  // Now check that the third-party DLL was added to the module blocklist cache.
  third_party_dlls::PackedListMetadata metadata;
  std::vector<third_party_dlls::PackedListModule> blocklisted_modules;
  base::MD5Digest md5_digest;
  ASSERT_EQ(ReadResult::kSuccess,
            ReadModuleBlocklistCache(module_blocklist_cache_path, &metadata,
                                     &blocklisted_modules, &md5_digest));

  EXPECT_GE(blocklisted_modules.size(), 1u);
}