chromium/chrome/services/util_win/processor_metrics.cc

// Copyright 2020 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/services/util_win/processor_metrics.h"

#include <objbase.h>

#include <sysinfoapi.h>
#include <wbemidl.h>
#include <winbase.h>
#include <wrl/client.h>

#include <string_view>

#include "base/metrics/histogram_functions.h"
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/com_init_util.h"
#include "base/win/scoped_bstr.h"
#include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_variant.h"
#include "base/win/windows_version.h"
#include "base/win/wmi.h"
#include "chrome/services/util_win/public/mojom/util_win.mojom.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver.h"

using base::win::ScopedBstr;
using base::win::ScopedVariant;
using Microsoft::WRL::ComPtr;

namespace {

HRESULT GetClassObject(ComPtr<IWbemClassObject> class_object,
                       const wchar_t* const name,
                       ScopedVariant* variant) {
  return class_object->Get(name, 0, variant->Receive(), 0, 0);
}

void RecordHypervStatusFromWMI(const ComPtr<IWbemServices>& services) {
  static constexpr wchar_t kHypervPresent[] = L"HypervisorPresent";
  static constexpr wchar_t kQueryProcessor[] =
      L"SELECT HypervisorPresent FROM Win32_ComputerSystem";

  ComPtr<IEnumWbemClassObject> enumerator_computer_system;
  HRESULT hr =
      services->ExecQuery(ScopedBstr(L"WQL").Get(), ScopedBstr(kQueryProcessor).Get(),
                          WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                          nullptr, &enumerator_computer_system);
  if (FAILED(hr) || !enumerator_computer_system.Get())
    return;

  ComPtr<IWbemClassObject> class_object;
  ULONG items_returned = 0;
  hr = enumerator_computer_system->Next(WBEM_INFINITE, 1, &class_object,
                                        &items_returned);
  if (FAILED(hr) || !items_returned)
    return;

  ScopedVariant hyperv_present;
  hr = GetClassObject(class_object, kHypervPresent, &hyperv_present);
  if (SUCCEEDED(hr) && hyperv_present.type() == VT_BOOL) {
    base::UmaHistogramBoolean("Windows.HypervPresent",
                              V_BOOL(hyperv_present.ptr()));
  }
}

void RecordProcessorMetricsFromWMI(const ComPtr<IWbemServices>& services) {
  static constexpr wchar_t kProcessorFamily[] = L"Family";
  static constexpr wchar_t kProcessorVirtualizationFirmwareEnabled[] =
      L"VirtualizationFirmwareEnabled";
  static constexpr wchar_t kQueryProcessor[] =
      L"SELECT Family,VirtualizationFirmwareEnabled FROM Win32_Processor";

  ComPtr<IEnumWbemClassObject> enumerator_processor;
  HRESULT hr =
      services->ExecQuery(ScopedBstr(L"WQL").Get(), ScopedBstr(kQueryProcessor).Get(),
                          WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
                          nullptr, &enumerator_processor);
  if (FAILED(hr) || !enumerator_processor.Get())
    return;

  ComPtr<IWbemClassObject> class_object;
  ULONG items_returned = 0;
  hr = enumerator_processor->Next(WBEM_INFINITE, 1, &class_object,
                                  &items_returned);
  if (FAILED(hr) || !items_returned)
    return;

  ScopedVariant processor_family;
  hr = GetClassObject(class_object, kProcessorFamily, &processor_family);
  if (SUCCEEDED(hr) && processor_family.type() == VT_I4) {
    base::UmaHistogramSparse("Windows.ProcessorFamily",
                             V_I4(processor_family.ptr()));
  }

  ScopedVariant enabled;
  hr = GetClassObject(class_object, kProcessorVirtualizationFirmwareEnabled,
                      &enabled);
  if (SUCCEEDED(hr) && enabled.type() == VT_BOOL) {
    base::UmaHistogramBoolean("Windows.ProcessorVirtualizationFirmwareEnabled",
                              V_BOOL(enabled.ptr()));
  }
}

// TODO(crbug.com/40152192) Can be removed once CET support is stable.
void RecordCetAvailability() {
  bool available = false;
  auto is_user_cet_available_in_environment =
      reinterpret_cast<decltype(&IsUserCetAvailableInEnvironment)>(
          ::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"),
                           "IsUserCetAvailableInEnvironment"));

  if (is_user_cet_available_in_environment) {
    available = is_user_cet_available_in_environment(
        USER_CET_ENVIRONMENT_WIN32_PROCESS);
  }
  base::UmaHistogramBoolean("Windows.CetAvailable", available);

  if (available) {
    PROCESS_MITIGATION_USER_SHADOW_STACK_POLICY policy = {0};
    if (::GetProcessMitigationPolicy(GetCurrentProcess(),
                                     ProcessUserShadowStackPolicy, &policy,
                                     sizeof(policy))) {
      base::UmaHistogramBoolean("Windows.CetEnabled",
                                policy.EnableUserShadowStack);
    }
  }
}

void RecordEnclaveAvailabilityInternal(std::string_view type,
                                       DWORD enclave_type) {
  // This API does not appear to be exported from kernel32.dll on
  // Windows 10.0.10240.
  static auto is_enclave_type_supported_func =
      reinterpret_cast<decltype(&IsEnclaveTypeSupported)>(::GetProcAddress(
          ::GetModuleHandleW(L"kernel32.dll"), "IsEnclaveTypeSupported"));

  bool is_supported = false;

  if (is_enclave_type_supported_func) {
    is_supported = is_enclave_type_supported_func(enclave_type);
  }

  base::UmaHistogramBoolean(
      base::StrCat({"Windows.Enclave.", type, ".Available"}), is_supported);
}

void RecordEnclaveAvailability() {
  RecordEnclaveAvailabilityInternal("SGX", ENCLAVE_TYPE_SGX);
  RecordEnclaveAvailabilityInternal("SGX2", ENCLAVE_TYPE_SGX2);
  RecordEnclaveAvailabilityInternal("VBS", ENCLAVE_TYPE_VBS);
  RecordEnclaveAvailabilityInternal("VBSBasic", ENCLAVE_TYPE_VBS_BASIC);
}

void RecordProcessorMetrics() {
  // These metrics do not require a WMI connection.
  RecordCetAvailability();
  RecordEnclaveAvailability();

  {
    base::ScopedBlockingCall scoped_blocking_call(
        FROM_HERE, base::BlockingType::MAY_BLOCK);
    ComPtr<IWbemServices> wmi_services;
    if (!base::win::CreateLocalWmiConnection(true, &wmi_services)) {
      return;
    }
    RecordProcessorMetricsFromWMI(wmi_services);
    RecordHypervStatusFromWMI(wmi_services);
  }
}

}  // namespace

void RecordProcessorMetricsForTesting() {
  RecordProcessorMetrics();
}

ProcessorMetricsImpl::ProcessorMetricsImpl(
    mojo::PendingReceiver<chrome::mojom::ProcessorMetrics> receiver)
    : receiver_(this, std::move(receiver)) {}

ProcessorMetricsImpl::~ProcessorMetricsImpl() = default;

void ProcessorMetricsImpl::RecordProcessorMetrics(
    RecordProcessorMetricsCallback callback) {
  // TODO(sebmarchand): Check if we should move the ScopedCOMInitializer to the
  // ProcessorMetrics class.
  base::win::ScopedCOMInitializer scoped_com_initializer;
  ::RecordProcessorMetrics();
  std::move(callback).Run();
}