chromium/chrome/browser/win/chrome_elf_init.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/win/chrome_elf_init.h"

#include <stddef.h>

#include "base/functional/bind.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/browser/browser_process.h"
#include "chrome/chrome_elf/blocklist_constants.h"
#include "chrome/chrome_elf/chrome_elf_constants.h"
#include "chrome/chrome_elf/dll_hash/dll_hash.h"
#include "chrome/chrome_elf/third_party_dlls/public_api.h"
#include "chrome/common/chrome_version.h"
#include "chrome/common/pref_names.h"
#include "chrome/install_static/install_util.h"
#include "components/prefs/pref_service.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "sandbox/policy/features.h"

const char kBrowserBlocklistTrialName[] = "BrowserBlocklist";
const char kBrowserBlocklistTrialDisabledGroupName[] = "NoBlocklist";

namespace {

// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
//   (a) existing enumerated constants should never be deleted or reordered, and
//   (b) new constants should only be appended in front of
//       BLOCKLIST_SETUP_EVENT_MAX.
enum BlocklistSetupEventType {
  // The blocklist beacon has placed to enable the browser blocklisting.
  BLOCKLIST_SETUP_ENABLED = 0,

  // The blocklist was successfully enabled.
  BLOCKLIST_SETUP_RAN_SUCCESSFULLY,

  // The blocklist setup code failed to execute.
  BLOCKLIST_SETUP_FAILED,

  // The blocklist thunk setup code failed. This is probably an indication
  // that something else patched that code first.
  BLOCKLIST_THUNK_SETUP_FAILED,

  // Deprecated. The blocklist interception code failed to execute.
  BLOCKLIST_INTERCEPTION_FAILED,

  // The blocklist was disabled for this run (after it failed too many times).
  BLOCKLIST_SETUP_DISABLED,

  // Always keep this at the end.
  BLOCKLIST_SETUP_EVENT_MAX,
};

void RecordBlocklistSetupEvent(BlocklistSetupEventType blocklist_setup_event) {
  base::UmaHistogramEnumeration("ChromeElf.Beacon.SetupStatus",
                                blocklist_setup_event,
                                BLOCKLIST_SETUP_EVENT_MAX);
}

std::wstring GetBeaconRegistryPath() {
  return install_static::GetRegistryPath().append(
      blocklist::kRegistryBeaconKeyName);
}

// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
//   (a) existing enumerated constants should never be deleted or reordered, and
//   (b) new constants should only be appended in front of EXTENSIONPOINT_MAX.
enum ExtensionPointEnableState {
  // Extension point mitigation disabled due to presence of legacy IME.
  EXTENSIONPOINT_DISABLED_IME,

  // Extension point mitigation enabled.
  EXTENSIONPOINT_ENABLED,

  // Always keep this at the end.
  EXTENSIONPOINT_MAX,
};

void RecordExtensionPointsEnableState(ExtensionPointEnableState enable_state) {
  base::UmaHistogramEnumeration("ChromeElf.ExtensionPoint.EnableState",
                                enable_state, EXTENSIONPOINT_MAX);
}

ExtensionPointEnableState GetExtensionPointsEnableState() {
  // Legacy IMEs can be detected as HKLs that have a file name.
  int list_size = GetKeyboardLayoutList(0, nullptr);
  if (list_size != 0) {
    std::vector<HKL> hkl_list(list_size);
    if (GetKeyboardLayoutList(list_size, hkl_list.data()) == list_size) {
      for (auto* hkl : hkl_list) {
        if (ImmGetIMEFileName(hkl, nullptr, 0) != 0)
          return EXTENSIONPOINT_DISABLED_IME;
      }
    }
  }
  return EXTENSIONPOINT_ENABLED;
}

bool IsBrowserLegacyExtensionPointsBlocked() {
  PrefService* local_state = g_browser_process->local_state();
  if (!local_state ||
      !local_state->HasPrefPath(prefs::kBlockBrowserLegacyExtensionPoints) ||
      !local_state->IsManagedPreference(
          prefs::kBlockBrowserLegacyExtensionPoints))
    return true;
  return local_state->GetBoolean(prefs::kBlockBrowserLegacyExtensionPoints);
}

}  // namespace

void InitializeChromeElf() {
  if (base::FieldTrialList::FindFullName(kBrowserBlocklistTrialName) ==
      kBrowserBlocklistTrialDisabledGroupName) {
    // Disable the blocklist for all future runs by removing the beacon.
    base::win::RegKey blocklist_registry_key(HKEY_CURRENT_USER);
    blocklist_registry_key.DeleteKey(GetBeaconRegistryPath().c_str());
  } else {
    BrowserBlocklistBeaconSetup();
  }

  // Make sure the registry key we read earlier in startup
  // sandbox::MITIGATION_EXTENSION_POINT_DISABLE is set properly in reg.
  // Note: the very existence of this key signals elf to not enable
  // this mitigation on browser next start.
  const std::wstring reg_path(install_static::GetRegistryPath().append(
      elf_sec::kRegBrowserExtensionPointKeyName));
  base::win::RegKey browser_extension_point_registry_key(
      HKEY_CURRENT_USER, reg_path.c_str(), KEY_READ);

  ExtensionPointEnableState extension_point_enable_state =
      GetExtensionPointsEnableState();
  RecordExtensionPointsEnableState(extension_point_enable_state);
  bool enable_extension_point_policy =
      (extension_point_enable_state == EXTENSIONPOINT_ENABLED) &&
      base::FeatureList::IsEnabled(
          sandbox::policy::features::kWinSboxDisableExtensionPoints) &&
      IsBrowserLegacyExtensionPointsBlocked();

  if (enable_extension_point_policy) {
    if (!browser_extension_point_registry_key.Valid()) {
      (void)browser_extension_point_registry_key.Create(
          HKEY_CURRENT_USER, reg_path.c_str(), KEY_WRITE);
    }
  } else {
    if (browser_extension_point_registry_key.Valid()) {
      browser_extension_point_registry_key.DeleteKey(L"");
    }
  }
}

void BrowserBlocklistBeaconSetup() {
  base::win::RegKey blocklist_registry_key(HKEY_CURRENT_USER,
                                           GetBeaconRegistryPath().c_str(),
                                           KEY_QUERY_VALUE | KEY_SET_VALUE);

  // No point in trying to continue if the registry key isn't valid.
  if (!blocklist_registry_key.Valid())
    return;

  // Record the results of the last blocklist setup.
  DWORD blocklist_state = blocklist::BLOCKLIST_STATE_MAX;
  blocklist_registry_key.ReadValueDW(blocklist::kBeaconState, &blocklist_state);

  if (blocklist_state == blocklist::BLOCKLIST_ENABLED) {
    // The blocklist setup didn't crash, so we report if it was enabled or not.
    if (IsThirdPartyInitialized()) {
      RecordBlocklistSetupEvent(BLOCKLIST_SETUP_RAN_SUCCESSFULLY);
    } else {
      // The only way for the blocklist to be enabled, but not fully
      // initialized is if the thunk setup failed. See blocklist.cc
      // for more details.
      RecordBlocklistSetupEvent(BLOCKLIST_THUNK_SETUP_FAILED);
    }

    // Regardless of if the blocklist was fully enabled or not, report how many
    // times we had to try to set it up.
    DWORD attempt_count = 0;
    blocklist_registry_key.ReadValueDW(blocklist::kBeaconAttemptCount,
                                       &attempt_count);
    base::UmaHistogramCounts100("ChromeElf.Beacon.RetryAttemptsBeforeSuccess",
                                attempt_count);
  } else if (blocklist_state == blocklist::BLOCKLIST_SETUP_FAILED) {
    // We can set the state to disabled without checking that the maximum number
    // of attempts was exceeded because blocklist.cc has already done this.
    RecordBlocklistSetupEvent(BLOCKLIST_SETUP_FAILED);
    blocklist_registry_key.WriteValue(blocklist::kBeaconState,
                                      blocklist::BLOCKLIST_DISABLED);
  } else if (blocklist_state == blocklist::BLOCKLIST_DISABLED) {
    RecordBlocklistSetupEvent(BLOCKLIST_SETUP_DISABLED);
  }

  // Find the last recorded blocklist version.
  std::wstring blocklist_version;
  blocklist_registry_key.ReadValue(blocklist::kBeaconVersion,
                                   &blocklist_version);

  if (blocklist_version != TEXT(CHROME_VERSION_STRING)) {
    // The blocklist hasn't been enabled for this version yet, so enable it
    // and reset the failure count to zero.
    LONG set_version = blocklist_registry_key.WriteValue(
        blocklist::kBeaconVersion,
        TEXT(CHROME_VERSION_STRING));

    LONG set_state = blocklist_registry_key.WriteValue(
        blocklist::kBeaconState,
        blocklist::BLOCKLIST_ENABLED);

    blocklist_registry_key.WriteValue(blocklist::kBeaconAttemptCount,
                                      static_cast<DWORD>(0));

    // Only report the blocklist as getting setup when both registry writes
    // succeed, since otherwise the blocklist wasn't properly setup.
    if (set_version == ERROR_SUCCESS && set_state == ERROR_SUCCESS)
      RecordBlocklistSetupEvent(BLOCKLIST_SETUP_ENABLED);
  }
}