chromium/chrome/chrome_elf/third_party_dlls/packed_list_file.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.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

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

#include <windows.h>

#include <assert.h>
#include <stdio.h>

#include <algorithm>
#include <limits>

#include "chrome/chrome_elf/nt_registry/nt_registry.h"
#include "chrome/chrome_elf/third_party_dlls/packed_list_format.h"
#include "chrome/install_static/install_util.h"

namespace third_party_dlls {
namespace {

// No concern about concurrency control in chrome_elf.
bool g_initialized = false;

// This will hold a packed blocklist module array, read directly from a
// data file during InitFromFile().
PackedListModule* g_bl_module_array = nullptr;
size_t g_bl_module_array_size = 0;

// NOTE: this "global" is only initialized once on first access.
// NOTE: it is wrapped in a function to prevent exit-time dtors.
std::wstring& GetBlFilePath() {
  static std::wstring* const file_path = new std::wstring();
  return *file_path;
}

//------------------------------------------------------------------------------
// Private functions
//------------------------------------------------------------------------------

// Binary predicate compare function for use with
// std::equal_range/std::is_sorted. Must return TRUE if lhs < rhs.
bool HashBinaryPredicate(const PackedListModule& lhs,
                         const PackedListModule& rhs) {
  return lhs.basename_hash < rhs.basename_hash;
}

// Given a file opened for read, pull in the packed list.
ThirdPartyStatus ReadInArray(HANDLE file,
                             size_t* array_size,
                             PackedListModule** array_ptr) {
  PackedListMetadata metadata;
  DWORD bytes_read = 0;

  if (!::ReadFile(file, &metadata, sizeof(PackedListMetadata), &bytes_read,
                  FALSE) ||
      bytes_read != sizeof(PackedListMetadata)) {
    // If |bytes_read| is actually 0, then the file was empty.
    if (!bytes_read)
      return ThirdPartyStatus::kFileEmpty;
    return ThirdPartyStatus::kFileMetadataReadFailure;
  }

  // Careful of versioning.  For now, only support the latest version.
  if (metadata.version != PackedListVersion::kCurrent)
    return ThirdPartyStatus::kFileInvalidFormatVersion;

  *array_size = metadata.module_count;
  // Check for size 0.
  if (!*array_size)
    return ThirdPartyStatus::kFileArraySizeZero;

  // Sanity check the array fits in a DWORD.
  if (*array_size >
      (std::numeric_limits<DWORD>::max() / sizeof(PackedListModule))) {
    assert(false);
    return ThirdPartyStatus::kFileArrayTooBig;
  }

  DWORD buffer_size =
      static_cast<DWORD>(*array_size * sizeof(PackedListModule));
  *array_ptr = reinterpret_cast<PackedListModule*>(new uint8_t[buffer_size]);

  // Read in the array.
  // NOTE: Ignore the rest of the file - other data could be stored at the end.
  if (!::ReadFile(file, *array_ptr, buffer_size, &bytes_read, FALSE) ||
      bytes_read != buffer_size) {
    delete[] * array_ptr;
    *array_ptr = nullptr;
    *array_size = 0;
    return ThirdPartyStatus::kFileArrayReadFailure;
  }

  // Ensure array is sorted (as expected).
  if (!std::is_sorted(*array_ptr, *array_ptr + *array_size,
                      HashBinaryPredicate)) {
    delete[] * array_ptr;
    *array_ptr = nullptr;
    *array_size = 0;
    return ThirdPartyStatus::kFileArrayNotSorted;
  }

  return ThirdPartyStatus::kSuccess;
}

// Reads the path to the blocklist file from the registry into |file_path|.
//
// - Returns false if the value is not found, is not a REG_SZ, or is empty.
bool GetFilePathFromRegistry(std::wstring* file_path) {
  file_path->clear();
  HANDLE key_handle = nullptr;

  if (!nt::CreateRegKey(nt::HKCU,
                        install_static::GetRegistryPath()
                            .append(kThirdPartyRegKeyName)
                            .c_str(),
                        KEY_QUERY_VALUE, &key_handle)) {
    return false;
  }

  bool found = nt::QueryRegValueSZ(key_handle, kBlFilePathRegValue, file_path);
  nt::CloseRegKey(key_handle);

  return found && !file_path->empty();
}

// Open a packed data file.
ThirdPartyStatus OpenDataFile(HANDLE* file_handle) {
  *file_handle = INVALID_HANDLE_VALUE;
  std::wstring& file_path = GetBlFilePath();

  // The path may have been overridden for testing.
  if (file_path.empty() && !GetFilePathFromRegistry(&file_path))
    return ThirdPartyStatus::kFilePathNotFoundInRegistry;

  // See if file exists.  INVALID_HANDLE_VALUE alert!
  *file_handle =
      ::CreateFileW(file_path.c_str(), FILE_READ_DATA,
                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                    nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
  if (*file_handle == INVALID_HANDLE_VALUE) {
    switch (::GetLastError()) {
      case ERROR_FILE_NOT_FOUND:
      case ERROR_PATH_NOT_FOUND:
        return ThirdPartyStatus::kFileNotFound;
      case ERROR_ACCESS_DENIED:
        return ThirdPartyStatus::kFileAccessDenied;
      default:
        return ThirdPartyStatus::kFileUnexpectedFailure;
    }
  }

  return ThirdPartyStatus::kSuccess;
}

// Find the packed blocklist file and read in the array.
ThirdPartyStatus InitInternal() {
  HANDLE handle = INVALID_HANDLE_VALUE;
  ThirdPartyStatus status = OpenDataFile(&handle);
  if (status != ThirdPartyStatus::kSuccess)
    return status;

  status = ReadInArray(handle, &g_bl_module_array_size, &g_bl_module_array);
  ::CloseHandle(handle);

  return status;
}

}  // namespace

//------------------------------------------------------------------------------
// Public defines & functions
//------------------------------------------------------------------------------

bool IsModuleListed(const elf_sha1::Digest& basename_hash,
                    const elf_sha1::Digest& fingerprint_hash) {
  assert(g_initialized);

  if (!g_bl_module_array_size)
    return false;

  PackedListModule target = {};
  target.basename_hash = basename_hash;
  target.code_id_hash = fingerprint_hash;

  // Binary search for primary hash (basename).  There can be more than one
  // match.
  auto pair = std::equal_range(g_bl_module_array,
                               g_bl_module_array + g_bl_module_array_size,
                               target, HashBinaryPredicate);

  // Search for secondary hash.
  for (PackedListModule* i = pair.first; i != pair.second; ++i) {
    if (target.code_id_hash == i->code_id_hash)
      return true;
  }

  // No match.
  return false;
}

std::wstring GetBlFilePathUsed() {
  assert(g_initialized);
  return GetBlFilePath();
}

ThirdPartyStatus InitFromFile() {
  // Debug check: InitFromFile should not be called more than once.
  assert(!g_initialized);

  ThirdPartyStatus status = InitInternal();

  if (IsStatusCodeSuccessful(status))
    g_initialized = true;

  return status;
}

bool IsStatusCodeSuccessful(ThirdPartyStatus code) {
  if (code == ThirdPartyStatus::kSuccess ||
      code == ThirdPartyStatus::kFilePathNotFoundInRegistry ||
      code == ThirdPartyStatus::kFileNotFound ||
      code == ThirdPartyStatus::kFileEmpty ||
      code == ThirdPartyStatus::kFileArraySizeZero) {
    return true;
  }

  return false;
}

void DeinitFromFile() {
  if (!g_initialized)
    return;

  delete[] g_bl_module_array;
  g_bl_module_array = nullptr;
  g_bl_module_array_size = 0;
  GetBlFilePath().clear();

  g_initialized = false;
}

void OverrideFilePathForTesting(const std::wstring& new_bl_path) {
  GetBlFilePath().assign(new_bl_path);
}

}  // namespace third_party_dlls