chromium/chrome/browser/safe_browsing/incident_reporting/environment_data_collection_win.cc

// Copyright 2014 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/browser/safe_browsing/incident_reporting/environment_data_collection_win.h"

#include <stdint.h>

#include <array>
#include <memory>
#include <set>
#include <string>

#include "base/enterprise_util.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "chrome/browser/install_verification/win/module_info.h"
#include "chrome/browser/install_verification/win/module_verification_common.h"
#include "chrome/browser/net/service_providers_win.h"
#include "chrome/browser/safe_browsing/download_protection/path_sanitizer.h"
#include "chrome/browser/safe_browsing/incident_reporting/module_integrity_verifier_win.h"
#include "chrome/common/safe_browsing/binary_feature_extractor.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "components/variations/variations_associated_data.h"

namespace safe_browsing {

namespace {

const REGSAM kKeyReadNoNotify = (KEY_READ) & ~(KEY_NOTIFY);

// The modules on which we will run VerifyModule.
constexpr std::array<const wchar_t*, 3> kModulesToVerify = {{
    L"chrome.dll",
    L"chrome_elf.dll",
    L"ntdll.dll",
}};

// The registry keys to collect data from.
const std::array<RegistryKeyInfo, 1> kRegKeysToCollect = {{
    {HKEY_CURRENT_USER, L"Software\\CSAStats"},
}};

// Helper function to convert HKEYs to strings.
std::wstring HKEYToString(HKEY key) {
  DCHECK_EQ(HKEY_CURRENT_USER, key);
  return L"HKEY_CURRENT_USER";
}

// Helper function to extract data from a single registry key (and all of its
// subkeys), recursively.
void CollectRegistryDataForKey(
    const base::win::RegKey& key,
    ClientIncidentReport_EnvironmentData_OS_RegistryKey* key_pb) {
  using RegistryValueProto =
      ClientIncidentReport_EnvironmentData_OS_RegistryValue;
  using RegistryKeyProto = ClientIncidentReport_EnvironmentData_OS_RegistryKey;

  DWORD num_subkeys = 0;
  DWORD max_subkey_name_len = 0;
  DWORD num_values = 0;
  DWORD max_value_name_len = 0;
  DWORD max_value_len = 0;
  LONG result = RegQueryInfoKey(
      key.Handle(), NULL, NULL, NULL, &num_subkeys, &max_subkey_name_len, NULL,
      &num_values, &max_value_name_len, &max_value_len, NULL, NULL);

  DWORD max_name_len = std::max(max_subkey_name_len, max_value_name_len) + 1;
  std::vector<wchar_t> name_buffer(max_name_len);
  // Read the values.
  if (num_values != 0) {
    std::vector<uint8_t> value_buffer(max_value_len != 0 ? max_value_len : 1);
    DWORD name_size = 0;
    DWORD value_type = REG_NONE;
    DWORD value_size = 0;

    std::wstring name_str;
    RegistryValueProto* registry_value;
    for (DWORD i = 0; i < num_values;) {
      name_size = static_cast<DWORD>(name_buffer.size());
      value_size = static_cast<DWORD>(value_buffer.size());
      result = ::RegEnumValue(key.Handle(), i, &name_buffer[0], &name_size,
                              NULL, &value_type, &value_buffer[0], &value_size);
      switch (result) {
        case ERROR_NO_MORE_ITEMS:
          i = num_values;
          break;
        case ERROR_SUCCESS:
          registry_value = key_pb->add_value();
          if (name_size) {
            name_str.assign(&name_buffer[0], name_size);
            registry_value->set_name(base::WideToUTF8(name_str));
          }
          if (value_size) {
            registry_value->set_type(value_type);
            registry_value->set_data(&value_buffer[0], value_size);
          }
          ++i;
          break;
        case ERROR_MORE_DATA:
          if (value_size > value_buffer.size())
            value_buffer.resize(value_size);
          // |name_size| does not include space for the terminating NULL.
          if (name_size + 1 > name_buffer.size())
            name_buffer.resize(name_size + 1);
          break;
        default:
          break;
      }
    }
  }

  // Read the subkeys.
  if (num_subkeys != 0) {
    DWORD name_size = 0;
    std::vector<std::wstring> subkey_names;

    // Get the names of them.
    for (DWORD i = 0; i < num_subkeys;) {
      name_size = static_cast<DWORD>(name_buffer.size());
      result = RegEnumKeyEx(key.Handle(), i, &name_buffer[0], &name_size, NULL,
                            NULL, NULL, NULL);
      switch (result) {
        case ERROR_NO_MORE_ITEMS:
          num_subkeys = i;
          break;
        case ERROR_SUCCESS:
          subkey_names.push_back(std::wstring(&name_buffer[0], name_size));
          ++i;
          break;
        case ERROR_MORE_DATA:
          name_buffer.resize(name_size + 1);
          break;
        default:
          break;
      }
    }

    // Extract the data (if possible).
    base::win::RegKey subkey;
    for (const auto& subkey_name : subkey_names) {
      if (subkey.Open(key.Handle(), subkey_name.c_str(), kKeyReadNoNotify) ==
          ERROR_SUCCESS) {
        RegistryKeyProto* subkey_pb = key_pb->add_key();
        subkey_pb->set_name(base::WideToUTF8(subkey_name));
        CollectRegistryDataForKey(subkey, subkey_pb);
      }
    }
  }
}

}  // namespace

bool CollectDlls(ClientIncidentReport_EnvironmentData_Process* process) {
  // Retrieve the module list.
  std::set<ModuleInfo> loaded_modules;
  if (!GetLoadedModules(&loaded_modules))
    return false;

  // Sanitize path of each module and add it to the incident report along with
  // its headers.
  PathSanitizer path_sanitizer;
  scoped_refptr<BinaryFeatureExtractor> feature_extractor(
      new BinaryFeatureExtractor());
  for (const auto& module : loaded_modules) {
    base::FilePath dll_path(module.name);
    base::FilePath sanitized_path(dll_path);
    path_sanitizer.StripHomeDirectory(&sanitized_path);

    ClientIncidentReport_EnvironmentData_Process_Dll* dll = process->add_dll();
    dll->set_path(
        base::UTF16ToUTF8(base::i18n::ToLower(sanitized_path.AsUTF16Unsafe())));
    dll->set_base_address(module.base_address);
    dll->set_length(module.size);
    // TODO(grt): Consider skipping this for valid system modules.
    if (!feature_extractor->ExtractImageFeatures(
            dll_path,
            BinaryFeatureExtractor::kOmitExports,
            dll->mutable_image_headers(),
            nullptr /* signed_data */)) {
      dll->clear_image_headers();
    }
  }

  return true;
}

void RecordLspFeature(ClientIncidentReport_EnvironmentData_Process* process) {
  WinsockLayeredServiceProviderList lsp_list;
  GetWinsockLayeredServiceProviders(&lsp_list);

  // For each LSP, we extract and sanitize the path.
  PathSanitizer path_sanitizer;
  std::set<std::wstring> lsp_paths;
  for (size_t i = 0; i < lsp_list.size(); ++i) {
    auto expanded_path =
        base::win::ExpandEnvironmentVariables(lsp_list[i].path);
    base::FilePath lsp_path(expanded_path.value_or(lsp_list[i].path));
    path_sanitizer.StripHomeDirectory(&lsp_path);
    lsp_paths.insert(
        base::UTF16ToWide(base::i18n::ToLower(lsp_path.AsUTF16Unsafe())));
  }

  // Look for a match between LSPs and loaded dlls.
  for (int i = 0; i < process->dll_size(); ++i) {
    if (lsp_paths.count(base::UTF8ToWide(process->dll(i).path()))) {
      process->mutable_dll(i)
          ->add_feature(ClientIncidentReport_EnvironmentData_Process_Dll::LSP);
    }
  }
}

void CollectModuleVerificationData(
    base::span<const wchar_t* const> modules_to_verify,
    ClientIncidentReport_EnvironmentData_Process* process) {
#if !defined(_WIN64)
  using ModuleState = ClientIncidentReport_EnvironmentData_Process_ModuleState;

  for (const wchar_t* const module_name : modules_to_verify) {
    auto module_state = std::make_unique<ModuleState>();

    int num_bytes_different = 0;
    VerifyModule(module_name, module_state.get(), &num_bytes_different);

    if (module_state->modified_state() == ModuleState::MODULE_STATE_UNMODIFIED)
      continue;

    process->mutable_module_state()->AddAllocated(module_state.release());
  }
#endif  // _WIN64
}

void CollectRegistryData(
    base::span<const RegistryKeyInfo> keys_to_collect,
    google::protobuf::RepeatedPtrField<
        ClientIncidentReport_EnvironmentData_OS_RegistryKey>* key_data) {
  using RegistryKeyProto = ClientIncidentReport_EnvironmentData_OS_RegistryKey;
  for (const RegistryKeyInfo& key_info : keys_to_collect) {
    base::win::RegKey reg_key(key_info.rootkey, key_info.subkey,
                              kKeyReadNoNotify);
    if (reg_key.Valid()) {
      RegistryKeyProto* regkey_pb = key_data->Add();
      std::wstring rootkey_name = HKEYToString(key_info.rootkey);
      rootkey_name += L"\\";
      rootkey_name += key_info.subkey;
      regkey_pb->set_name(base::WideToUTF8(rootkey_name));
      CollectRegistryDataForKey(reg_key, regkey_pb);
    }
  }
}

void CollectDomainEnrollmentData(
    ClientIncidentReport_EnvironmentData_OS* os_data) {
  os_data->set_is_enrolled_to_domain(base::IsEnterpriseDevice());
}

void CollectPlatformProcessData(
    ClientIncidentReport_EnvironmentData_Process* process) {
  CollectDlls(process);
  RecordLspFeature(process);
  CollectModuleVerificationData(kModulesToVerify, process);
}

void CollectPlatformOSData(ClientIncidentReport_EnvironmentData_OS* os_data) {
  CollectRegistryData(kRegKeysToCollect, os_data->mutable_registry_key());
  CollectDomainEnrollmentData(os_data);
}
}  // namespace safe_browsing