// Copyright 2019 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/browser/enterprise/signals/device_info_fetcher_win.h"
#include <windows.h>
// SECURITY_WIN32 must be defined in order to get
// EXTENDED_NAME_FORMAT enumeration.
#define SECURITY_WIN32 1
#include <security.h>
#undef SECURITY_WIN32
#include <shobjidl.h>
#include <DSRole.h>
#include <iphlpapi.h>
#include <powersetting.h>
#include <propsys.h>
#include <wincred.h>
#include "base/files/file_path.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "base/win/wmi.h"
#include "chrome/browser/enterprise/signals/signals_common.h"
#include "net/base/network_interfaces.h"
namespace enterprise_signals {
namespace {
constexpr wchar_t kSecureBootRegPath[] =
L"SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State";
constexpr wchar_t kSecureBootRegKey[] = L"UEFISecureBootEnabled";
// Possible results of the "System.Volume.BitLockerProtection" shell property.
// These values are undocumented but were directly validated on a Windows 10
// machine. See the comment above the GetDiskEncryption() method.
// The values in this enum should be kep in sync with the analogous definiotion
// in the native app implementation.
enum class BitLockerStatus {
// Encryption is on, and the volume is unlocked
kOn = 1,
// Encryption is off
kOff = 2,
// Encryption is in progress
kEncryptionInProgress = 3,
// Decryption is in progress
kDecryptionInProgress = 4,
// Encryption is on, but temporarily suspended
kSuspended = 5,
// Encryption is on, and the volume is locked
kLocked = 6,
};
// Retrieves the computer serial number from WMI.
std::string GetSerialNumber() {
base::win::WmiComputerSystemInfo sys_info =
base::win::WmiComputerSystemInfo::Get();
return base::WideToUTF8(sys_info.serial_number());
}
// Retrieves the FQDN of the computer and if this fails reverts to the hostname
// as known to the net subsystem.
std::string GetComputerName() {
DWORD size = 1024;
std::wstring result_wstr(size, L'\0');
if (::GetComputerNameExW(ComputerNameDnsFullyQualified, &result_wstr[0],
&size)) {
std::string result;
if (base::WideToUTF8(result_wstr.data(), size, &result)) {
return result;
}
}
return net::GetHostName();
}
// Retrieves the state of the screen locking feature from the screen saver
// settings.
std::optional<bool> GetScreenLockStatus() {
std::optional<bool> status;
BOOL value = FALSE;
if (::SystemParametersInfo(SPI_GETSCREENSAVESECURE, 0, &value, 0))
status = static_cast<bool>(value);
return status;
}
// Checks if locking is enabled at the currently active power scheme.
std::optional<bool> GetConsoleLockStatus() {
std::optional<bool> status;
SYSTEM_POWER_STATUS sps;
// https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-getsystempowerstatus
// Retrieves the power status of the system. The status indicates whether the
// system is running on AC or DC power.
if (!::GetSystemPowerStatus(&sps))
return status;
LPGUID p_active_policy = nullptr;
// https://docs.microsoft.com/en-us/windows/desktop/api/powersetting/nf-powersetting-powergetactivescheme
// Retrieves the active power scheme and returns a GUID that identifies the
// scheme.
if (::PowerGetActiveScheme(nullptr, &p_active_policy) == ERROR_SUCCESS) {
constexpr GUID kConsoleLock = {
0x0E796BDB,
0x100D,
0x47D6,
{0xA2, 0xD5, 0xF7, 0xD2, 0xDA, 0xA5, 0x1F, 0x51}};
const GUID active_policy = *p_active_policy;
::LocalFree(p_active_policy);
auto const power_read_current_value_func =
sps.ACLineStatus != 0U ? &PowerReadACValue : &PowerReadDCValue;
ULONG type;
DWORD value;
DWORD value_size = sizeof(value);
// https://docs.microsoft.com/en-us/windows/desktop/api/powersetting/nf-powersetting-powerreadacvalue
// Retrieves the AC/DC power value for the specified power setting.
// NO_SUBGROUP_GUID to retrieve the setting for the default power scheme.
// LPBYTE case is safe and is needed as the function expects generic byte
// array buffer regardless of the exact value read as it is a generic
// interface.
if (power_read_current_value_func(
nullptr, &active_policy, &NO_SUBGROUP_GUID, &kConsoleLock, &type,
reinterpret_cast<LPBYTE>(&value), &value_size) == ERROR_SUCCESS) {
status = value != 0U;
}
}
return status;
}
// Gets cumulative screen locking policy based on the screen saver and console
// lock status.
SettingValue GetScreenlockSecured() {
const std::optional<bool> screen_lock_status = GetScreenLockStatus();
if (screen_lock_status.value_or(false))
return SettingValue::ENABLED;
const std::optional<bool> console_lock_status = GetConsoleLockStatus();
if (console_lock_status.value_or(false))
return SettingValue::ENABLED;
if (screen_lock_status.has_value() || console_lock_status.has_value()) {
return SettingValue::DISABLED;
}
return SettingValue::UNKNOWN;
}
// Returns the volume where the Windows OS is installed.
std::optional<std::wstring> GetOsVolume() {
std::optional<std::wstring> volume;
base::FilePath windows_dir;
if (base::PathService::Get(base::DIR_WINDOWS, &windows_dir) &&
windows_dir.IsAbsolute()) {
std::vector<std::wstring> components = windows_dir.GetComponents();
DCHECK(components.size());
volume = components[0];
}
return volume;
}
bool GetPropVariantAsInt64(PROPVARIANT variant, int64_t* out_value) {
switch (variant.vt) {
case VT_I2:
*out_value = variant.iVal;
return true;
case VT_UI2:
*out_value = variant.uiVal;
return true;
case VT_I4:
*out_value = variant.lVal;
return true;
case VT_UI4:
*out_value = variant.ulVal;
return true;
case VT_INT:
*out_value = variant.intVal;
return true;
case VT_UINT:
*out_value = variant.uintVal;
return true;
}
return false;
}
// The ideal solution to check the disk encryption (BitLocker) status is to
// use the WMI APIs (Win32_EncryptableVolume). However, only programs running
// with elevated priledges can access those APIs.
//
// Our alternative solution is based on the value of the undocumented (shell)
// property: "System.Volume.BitLockerProtection". That property is essentially
// an enum containing the current BitLocker status for a given volume. This
// approached was suggested here:
// https://stackoverflow.com/questions/41308245/detect-bitlocker-programmatically-from-c-sharp-without-admin/41310139
//
// Note that the link above doesn't give any explanation / meaning for the
// enum values, it simply says that 1, 3 or 5 means the disk is encrypted.
//
// I directly tested and validated this strategy on a Windows 10 machine.
// The values given in the BitLockerStatus enum contain the relevant values
// for the shell property. I also directly validated them.
SettingValue GetDiskEncrypted() {
// |volume| has to be a |wstring| because SHCreateItemFromParsingName() only
// accepts |PCWSTR| which is |wchar_t*|.
std::optional<std::wstring> volume = GetOsVolume();
if (!volume.has_value())
return SettingValue::UNKNOWN;
PROPERTYKEY key;
const HRESULT property_key_result =
PSGetPropertyKeyFromName(L"System.Volume.BitLockerProtection", &key);
if (FAILED(property_key_result))
return SettingValue::UNKNOWN;
Microsoft::WRL::ComPtr<IShellItem2> item;
const HRESULT create_item_result = SHCreateItemFromParsingName(
volume.value().c_str(), nullptr, IID_IShellItem2, &item);
if (FAILED(create_item_result) || !item)
return SettingValue::UNKNOWN;
PROPVARIANT prop_status;
const HRESULT property_result = item->GetProperty(key, &prop_status);
int64_t status;
if (FAILED(property_result) || !GetPropVariantAsInt64(prop_status, &status))
return SettingValue::UNKNOWN;
// Note that we are not considering BitLockerStatus::Suspended as ENABLED.
if (status == static_cast<int64_t>(BitLockerStatus::kOn) ||
status == static_cast<int64_t>(BitLockerStatus::kEncryptionInProgress) ||
status == static_cast<int64_t>(BitLockerStatus::kLocked)) {
return SettingValue::ENABLED;
}
return SettingValue::DISABLED;
}
std::vector<std::string> GetMacAddresses() {
std::vector<std::string> mac_addresses;
ULONG adapter_info_size = 0;
// Get the right buffer size in case of overflow
if (::GetAdaptersInfo(nullptr, &adapter_info_size) != ERROR_BUFFER_OVERFLOW ||
adapter_info_size == 0) {
return mac_addresses;
}
std::vector<byte> adapters(adapter_info_size);
if (::GetAdaptersInfo(reinterpret_cast<PIP_ADAPTER_INFO>(adapters.data()),
&adapter_info_size) != ERROR_SUCCESS) {
return mac_addresses;
}
// The returned value is not an array of IP_ADAPTER_INFO elements but a linked
// list of such
PIP_ADAPTER_INFO adapter =
reinterpret_cast<PIP_ADAPTER_INFO>(adapters.data());
while (adapter) {
if (adapter->AddressLength == 6) {
mac_addresses.push_back(
base::StringPrintf("%02X-%02X-%02X-%02X-%02X-%02X",
static_cast<unsigned int>(adapter->Address[0]),
static_cast<unsigned int>(adapter->Address[1]),
static_cast<unsigned int>(adapter->Address[2]),
static_cast<unsigned int>(adapter->Address[3]),
static_cast<unsigned int>(adapter->Address[4]),
static_cast<unsigned int>(adapter->Address[5])));
}
adapter = adapter->Next;
}
return mac_addresses;
}
std::optional<std::string> GetWindowsMachineDomain() {
if (!base::win::IsEnrolledToDomain())
return std::nullopt;
std::string domain;
::DSROLE_PRIMARY_DOMAIN_INFO_BASIC* info = nullptr;
if (::DsRoleGetPrimaryDomainInformation(nullptr,
::DsRolePrimaryDomainInfoBasic,
(PBYTE*)&info) == ERROR_SUCCESS) {
if (info->DomainNameFlat)
domain = base::WideToUTF8(info->DomainNameFlat);
::DsRoleFreeMemory(info);
}
return domain.empty() ? std::nullopt : std::make_optional(domain);
}
std::optional<std::string> GetWindowsUserDomain() {
WCHAR username[CREDUI_MAX_USERNAME_LENGTH + 1] = {};
DWORD username_length = sizeof(username);
if (!::GetUserNameExW(::NameSamCompatible, username, &username_length) ||
username_length <= 0) {
return std::nullopt;
}
// The string corresponds to DOMAIN\USERNAME. If there isn't a domain, the
// domain name is replaced by the name of the machine, so the function
// returns nothing in that case.
std::string username_str = base::WideToUTF8(username);
std::string domain = username_str.substr(0, username_str.find("\\"));
return domain == base::ToUpperASCII(GetComputerNameW())
? std::nullopt
: std::make_optional(domain);
}
std::string GetSecurityPatchLevel() {
base::win::OSInfo* gi = base::win::OSInfo::GetInstance();
return base::NumberToString(gi->version_number().patch);
}
SettingValue GetSecureBootEnabled() {
base::win::RegKey key;
auto result = key.Open(HKEY_LOCAL_MACHINE, kSecureBootRegPath,
KEY_QUERY_VALUE | KEY_WOW64_64KEY);
if (result != ERROR_SUCCESS || !key.Valid()) {
return SettingValue::UNKNOWN;
}
DWORD secure_boot_dw;
result = key.ReadValueDW(kSecureBootRegKey, &secure_boot_dw);
if (result != ERROR_SUCCESS) {
return SettingValue::UNKNOWN;
}
return secure_boot_dw == 1 ? SettingValue::ENABLED : SettingValue::DISABLED;
}
} // namespace
// static
std::unique_ptr<DeviceInfoFetcher> DeviceInfoFetcher::CreateInstanceInternal() {
return std::make_unique<DeviceInfoFetcherWin>();
}
DeviceInfoFetcherWin::DeviceInfoFetcherWin() = default;
DeviceInfoFetcherWin::~DeviceInfoFetcherWin() = default;
DeviceInfo DeviceInfoFetcherWin::Fetch() {
DeviceInfo device_info;
device_info.os_name = "windows";
device_info.os_version = base::SysInfo::OperatingSystemVersion();
device_info.security_patch_level = GetSecurityPatchLevel();
device_info.device_host_name = GetComputerName();
device_info.device_model = base::SysInfo::HardwareModelName();
device_info.serial_number = GetSerialNumber();
device_info.screen_lock_secured = GetScreenlockSecured();
device_info.disk_encrypted = GetDiskEncrypted();
device_info.mac_addresses = GetMacAddresses();
device_info.windows_machine_domain = GetWindowsMachineDomain();
device_info.windows_user_domain = GetWindowsUserDomain();
device_info.secure_boot_enabled = GetSecureBootEnabled();
return device_info;
}
} // namespace enterprise_signals