chromium/chrome/chrome_elf/third_party_dlls/packed_list_file_unittest.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/chrome_elf/third_party_dlls/packed_list_file.h"

#include <windows.h>

#include <string>
#include <utility>

#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/pe_image.h"
#include "chrome/chrome_elf/nt_registry/nt_registry.h"
#include "chrome/chrome_elf/sha1/sha1.h"
#include "chrome/chrome_elf/third_party_dlls/packed_list_format.h"
#include "chrome/install_static/install_util.h"
#include "chrome/install_static/user_data_dir.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace third_party_dlls {
namespace {

constexpr wchar_t kTestBlFileName[] = L"blfile";
constexpr DWORD kPageSize = 4096;

void RegRedirect(nt::ROOT_KEY key,
                 registry_util::RegistryOverrideManager* rom) {
  ASSERT_NE(key, nt::AUTO);
  HKEY root = (key == nt::HKCU ? HKEY_CURRENT_USER : HKEY_LOCAL_MACHINE);
  std::wstring temp;

  ASSERT_NO_FATAL_FAILURE(rom->OverrideRegistry(root, &temp));
  ASSERT_TRUE(nt::SetTestingOverride(key, temp));
}

void CancelRegRedirect(nt::ROOT_KEY key) {
  ASSERT_NE(key, nt::AUTO);
  ASSERT_TRUE(nt::SetTestingOverride(key, std::wstring()));
}

bool CreateRegistryKeyValue(const std::wstring& full_file_path) {
  base::win::RegKey key;
  if (key.Create(HKEY_CURRENT_USER,
                 install_static::GetRegistryPath()
                     .append(kThirdPartyRegKeyName)
                     .c_str(),
                 KEY_WRITE) != ERROR_SUCCESS ||
      !key.Valid()) {
    return false;
  }
  if (key.WriteValue(kBlFilePathRegValue, full_file_path.c_str()) !=
      ERROR_SUCCESS)
    return false;

  return true;
}

struct TestModule {
  std::string basename;
  DWORD timedatestamp;
  DWORD imagesize;
};

bool GetTestModules(std::vector<TestModule>* test_modules,
                    std::vector<PackedListModule>* packed_modules) {
  // Test binaries in system32/syswow64.
  // Define them in hash order so that the resulting array is ordered!
  // ole32 = 65 6e 16..., gdi32 = 91 7a e5..., crypt32 = ce ab 70...
  static constexpr const wchar_t* kTestBins[] = {L"ole32.dll", L"gdi32.dll",
                                                 L"gdi32.dll", L"crypt32.dll"};

  // Get test data from system binaries.
  base::FilePath path;
  char buffer[kPageSize];
  PIMAGE_NT_HEADERS nt_headers = nullptr;
  test_modules->clear();
  packed_modules->clear();

  for (const wchar_t* test_bin : kTestBins) {
    if (!base::PathService::Get(base::DIR_SYSTEM, &path))
      return false;
    path = path.Append(test_bin);
    base::File binary(path, base::File::FLAG_READ | base::File::FLAG_OPEN);
    if (!binary.IsValid())
      return false;
    if (binary.Read(0, &buffer[0], kPageSize) != kPageSize)
      return false;
    base::win::PEImage pe_image(buffer);
    if (!pe_image.VerifyMagic())
      return false;
    nt_headers = pe_image.GetNTHeaders();

    // Save the module info for tests.
    TestModule test_module;
    test_module.basename = base::WideToASCII(test_bin);
    test_module.timedatestamp = nt_headers->FileHeader.TimeDateStamp;
    test_module.imagesize = nt_headers->OptionalHeader.SizeOfImage;
    test_modules->push_back(test_module);

    // SHA1 hash the two strings, and copy them into the module array.
    PackedListModule packed_module;
    packed_module.code_id_hash = elf_sha1::SHA1HashString(
        GetFingerprintString(test_module.timedatestamp, test_module.imagesize));
    packed_module.basename_hash =
        elf_sha1::SHA1HashString(test_module.basename);
    packed_modules->push_back(packed_module);
  }

  return true;
}

//------------------------------------------------------------------------------
// ThirdPartyFileTest class
//------------------------------------------------------------------------------

class ThirdPartyFileTest : public testing::Test {
 public:
  ThirdPartyFileTest(const ThirdPartyFileTest&) = delete;
  ThirdPartyFileTest& operator=(const ThirdPartyFileTest&) = delete;

 protected:
  ThirdPartyFileTest() = default;

  void SetUp() override {
    ASSERT_TRUE(GetTestModules(&test_array_, &test_packed_array_));

    // Setup temp test dir.
    ASSERT_TRUE(scoped_temp_dir_.CreateUniqueTempDir());

    // Store full path to test file (without creating it yet).
    base::FilePath path = scoped_temp_dir_.GetPath();
    path = path.Append(kTestBlFileName);
    bl_test_file_path_ = std::move(path.value());

    // Override the file paths in the live code for testing.
    OverrideFilePathForTesting(bl_test_file_path_);
  }

  void TearDown() override { DeinitFromFile(); }

  void CreateTestFile() {
    base::File file(base::FilePath(bl_test_file_path_),
                    base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
                        base::File::FLAG_WIN_SHARE_DELETE |
                        base::File::FLAG_DELETE_ON_CLOSE);
    ASSERT_TRUE(file.IsValid());

    // Write content {metadata}{array_of_modules}.
    PackedListMetadata meta = {
        kInitialVersion, static_cast<uint32_t>(test_packed_array_.size())};
    ASSERT_EQ(file.Write(0, reinterpret_cast<const char*>(&meta), sizeof(meta)),
              static_cast<int>(sizeof(meta)));
    int size =
        static_cast<int>(test_packed_array_.size() * sizeof(PackedListModule));
    ASSERT_EQ(
        file.Write(sizeof(PackedListMetadata),
                   reinterpret_cast<const char*>(test_packed_array_.data()),
                   size),
        size);

    // Leave file handle open for DELETE_ON_CLOSE.
    bl_file_ = std::move(file);
  }

  const std::wstring& GetBlTestFilePath() { return bl_test_file_path_; }

  base::File* GetBlFile() { return &bl_file_; }

  const std::vector<TestModule>& GetTestArray() { return test_array_; }

 private:
  base::ScopedTempDir scoped_temp_dir_;
  base::File bl_file_;
  std::wstring bl_test_file_path_;
  std::vector<TestModule> test_array_;
  std::vector<PackedListModule> test_packed_array_;
};

//------------------------------------------------------------------------------
// Third-party file tests
//------------------------------------------------------------------------------

// Test successful initialization and module lookup.
TEST_F(ThirdPartyFileTest, Success) {
  // Create blocklist data file.
  ASSERT_NO_FATAL_FAILURE(CreateTestFile());

  // Init.
  ASSERT_EQ(InitFromFile(), ThirdPartyStatus::kSuccess);

  // Test matching.
  for (const auto& test_module : GetTestArray()) {
    elf_sha1::Digest name_hash = elf_sha1::SHA1HashString(test_module.basename);
    elf_sha1::Digest fingerprint_hash = elf_sha1::SHA1HashString(
        GetFingerprintString(test_module.timedatestamp, test_module.imagesize));
    EXPECT_TRUE(IsModuleListed(name_hash, fingerprint_hash));
  }

  // Test a failure to match.
  elf_sha1::Digest name_hash = elf_sha1::SHA1HashString("booya.dll");
  elf_sha1::Digest fingerprint_hash =
      elf_sha1::SHA1HashString(GetFingerprintString(0x12345678, 1337));
  EXPECT_FALSE(IsModuleListed(name_hash, fingerprint_hash));
}

// Test successful initialization with no packed files.
TEST_F(ThirdPartyFileTest, NoFiles) {
  // kFileNotFound is a non-fatal status code.
  ASSERT_EQ(InitFromFile(), ThirdPartyStatus::kFileNotFound);

  elf_sha1::Digest name_hash = elf_sha1::SHA1HashString("booya.dll");
  elf_sha1::Digest fingerprint_hash =
      elf_sha1::SHA1HashString(GetFingerprintString(0x12345678, 1337));
  EXPECT_FALSE(IsModuleListed(name_hash, fingerprint_hash));
}

TEST_F(ThirdPartyFileTest, CorruptFile) {
  ASSERT_NO_FATAL_FAILURE(CreateTestFile());

  base::File* file = GetBlFile();
  ASSERT_TRUE(file->IsValid());

  // 1) Not enough data for array size
  PackedListMetadata meta = {kCurrent, static_cast<uint32_t>(50)};
  ASSERT_EQ(file->Write(0, reinterpret_cast<const char*>(&meta), sizeof(meta)),
            static_cast<int>(sizeof(meta)));
  EXPECT_EQ(InitFromFile(), ThirdPartyStatus::kFileArrayReadFailure);

  // 2) Corrupt data or just unsupported metadata version.
  meta = {kUnsupported, static_cast<uint32_t>(50)};
  ASSERT_EQ(file->Write(0, reinterpret_cast<const char*>(&meta), sizeof(meta)),
            static_cast<int>(sizeof(meta)));
  EXPECT_EQ(InitFromFile(), ThirdPartyStatus::kFileInvalidFormatVersion);

  // 3) Not enough data for metadata.
  meta = {kCurrent, static_cast<uint32_t>(10)};
  ASSERT_EQ(
      file->Write(0, reinterpret_cast<const char*>(&meta), sizeof(meta) / 2),
      static_cast<int>(sizeof(meta) / 2));
  ASSERT_TRUE(file->SetLength(sizeof(meta) / 2));
  EXPECT_EQ(InitFromFile(), ThirdPartyStatus::kFileMetadataReadFailure);
}

// Test successful initialization, getting the file path from registry.
TEST_F(ThirdPartyFileTest, SuccessFromRegistry) {
  // 1. Enable reg override for test net.
  registry_util::RegistryOverrideManager override_manager;
  ASSERT_NO_FATAL_FAILURE(RegRedirect(nt::HKCU, &override_manager));

  // 2. Add a sample ThirdParty subkey and value, which would be created by
  //    chrome.dll.
  ASSERT_TRUE(CreateRegistryKeyValue(GetBlTestFilePath()));

  // 3. Drop a blocklist to the expected path.
  ASSERT_NO_FATAL_FAILURE(CreateTestFile());

  // Clear override file path so that initialization goes to registry.
  OverrideFilePathForTesting(std::wstring());

  // 4. Run the test.
  EXPECT_EQ(InitFromFile(), ThirdPartyStatus::kSuccess);

  // 5. Disable reg override.
  ASSERT_NO_FATAL_FAILURE(CancelRegRedirect(nt::HKCU));
}

}  // namespace
}  // namespace third_party_dlls