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