[Signals Reporting] Add OsSignalsCollector
Implement the OsSignalsCollector class that should collect all device and OS user level signals for Chrome reports. Bug: 398829966 Change-Id: Ic0155c25ff9894326d2e7673a152e4900d3c1e06 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6287535 Commit-Queue: Zonghan Xu <xzonghan@chromium.org> Reviewed-by: Sébastien Lalancette <seblalancette@chromium.org> Cr-Commit-Position: refs/heads/main@{#1435634}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
5d92196daa
commit
5c4a5d12a0
chrome/browser/enterprise/signals
context_info_fetcher.ccdevice_info_fetcher_linux.ccdevice_info_fetcher_mac.mmdevice_info_fetcher_win.cc
components/device_signals/core/browser
@ -8,8 +8,6 @@
|
||||
#include <memory>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/threading/scoped_blocking_call.h"
|
||||
#include "build/build_config.h"
|
||||
@ -21,33 +19,13 @@
|
||||
#include "chrome/browser/policy/profile_policy_connector.h"
|
||||
#include "chrome/browser/profiles/profile.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "components/component_updater/pref_names.h"
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
#include "components/enterprise/browser/identifiers/profile_id_service.h"
|
||||
#include "components/policy/content/policy_blocklist_service.h"
|
||||
#include "components/version_info/version_info.h"
|
||||
#include "content/public/browser/site_isolation_policy.h"
|
||||
#include "device_management_backend.pb.h"
|
||||
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
#include "net/dns/public/resolv_reader.h"
|
||||
#include "net/dns/public/scoped_res_state.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/process/launch.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include <windows.h>
|
||||
|
||||
#include <netfw.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "net/dns/public/win_dns_system_settings.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS)
|
||||
#include "chromeos/dbus/constants/dbus_switches.h"
|
||||
@ -71,135 +49,6 @@ std::optional<std::string> GetEnterpriseProfileId(Profile* profile) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
const char** GetUfwConfigPath() {
|
||||
static const char* path = "/etc/ufw/ufw.conf";
|
||||
return &path;
|
||||
}
|
||||
|
||||
SettingValue GetUfwStatus() {
|
||||
base::FilePath path(*GetUfwConfigPath());
|
||||
std::string file_content;
|
||||
base::StringPairs values;
|
||||
|
||||
if (!base::PathExists(path) || !base::PathIsReadable(path) ||
|
||||
!base::ReadFileToString(path, &file_content)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
base::SplitStringIntoKeyValuePairs(file_content, '=', '\n', &values);
|
||||
auto is_ufw_enabled = std::ranges::find(
|
||||
values, "ENABLED", &std::pair<std::string, std::string>::first);
|
||||
if (is_ufw_enabled == values.end())
|
||||
return SettingValue::UNKNOWN;
|
||||
|
||||
if (is_ufw_enabled->second == "yes")
|
||||
return SettingValue::ENABLED;
|
||||
else if (is_ufw_enabled->second == "no")
|
||||
return SettingValue::DISABLED;
|
||||
else
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
SettingValue GetWinOSFirewall() {
|
||||
Microsoft::WRL::ComPtr<INetFwPolicy2> firewall_policy;
|
||||
HRESULT hr = CoCreateInstance(CLSID_NetFwPolicy2, nullptr, CLSCTX_ALL,
|
||||
IID_PPV_ARGS(&firewall_policy));
|
||||
if (FAILED(hr)) {
|
||||
DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
long profile_types = 0;
|
||||
hr = firewall_policy->get_CurrentProfileTypes(&profile_types);
|
||||
if (FAILED(hr))
|
||||
return SettingValue::UNKNOWN;
|
||||
|
||||
// The most restrictive active profile takes precedence.
|
||||
constexpr NET_FW_PROFILE_TYPE2 kProfileTypes[] = {
|
||||
NET_FW_PROFILE2_PUBLIC, NET_FW_PROFILE2_PRIVATE, NET_FW_PROFILE2_DOMAIN};
|
||||
for (size_t i = 0; i < std::size(kProfileTypes); ++i) {
|
||||
if ((profile_types & UNSAFE_TODO(kProfileTypes[i])) != 0) {
|
||||
VARIANT_BOOL enabled = VARIANT_TRUE;
|
||||
hr = firewall_policy->get_FirewallEnabled(UNSAFE_TODO(kProfileTypes[i]),
|
||||
&enabled);
|
||||
if (FAILED(hr))
|
||||
return SettingValue::UNKNOWN;
|
||||
if (enabled == VARIANT_TRUE)
|
||||
return SettingValue::ENABLED;
|
||||
else if (enabled == VARIANT_FALSE)
|
||||
return SettingValue::DISABLED;
|
||||
else
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
}
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
SettingValue GetMacOSFirewall() {
|
||||
if (base::mac::MacOSMajorVersion() < 15) {
|
||||
// There is no official Apple documentation on how to obtain the enabled
|
||||
// status of the firewall (System Preferences> Security & Privacy> Firewall)
|
||||
// prior to MacOS versions 15. Reading globalstate from com.apple.alf is the
|
||||
// closest way to get such an API in Chrome without delegating to
|
||||
// potentially unstable commands. Values of "globalstate":
|
||||
// 0 = de-activated
|
||||
// 1 = on for specific services
|
||||
// 2 = on for essential services
|
||||
// You can get 2 by, e.g., enabling the "Block all incoming connections"
|
||||
// firewall functionality.
|
||||
Boolean key_exists_with_valid_format = false;
|
||||
CFIndex globalstate = CFPreferencesGetAppIntegerValue(
|
||||
CFSTR("globalstate"), CFSTR("com.apple.alf"),
|
||||
&key_exists_with_valid_format);
|
||||
|
||||
if (!key_exists_with_valid_format) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
switch (globalstate) {
|
||||
case 0:
|
||||
return SettingValue::DISABLED;
|
||||
case 1:
|
||||
case 2:
|
||||
return SettingValue::ENABLED;
|
||||
default:
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// Based on this recommendation from Apple:
|
||||
// https://developer.apple.com/documentation/macos-release-notes/macos-15-release-notes/#Application-Firewall
|
||||
base::FilePath fw_util("/usr/libexec/ApplicationFirewall/socketfilterfw");
|
||||
if (!base::PathExists(fw_util)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
base::CommandLine command(fw_util);
|
||||
command.AppendSwitch("getglobalstate");
|
||||
std::string output;
|
||||
if (!base::GetAppOutput(command, &output)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
// State 1 is when the Firewall is simply enabled.
|
||||
// State 2 is when the Firewall is enabled and all incoming connections are
|
||||
// blocked.
|
||||
if (output.find("(State = 1)") != std::string::npos ||
|
||||
output.find("(State = 2)") != std::string::npos) {
|
||||
return SettingValue::ENABLED;
|
||||
}
|
||||
if (output.find("(State = 0)") != std::string::npos) {
|
||||
return SettingValue::DISABLED;
|
||||
}
|
||||
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS)
|
||||
SettingValue GetChromeosFirewall() {
|
||||
// The firewall is always enabled and can only be disabled in dev mode on
|
||||
@ -328,12 +177,8 @@ std::vector<std::string> ContextInfoFetcher::GetOnSecurityEventProviders() {
|
||||
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
|
||||
|
||||
SettingValue ContextInfoFetcher::GetOSFirewall() {
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
return GetUfwStatus();
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
return GetWinOSFirewall();
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
return GetMacOSFirewall();
|
||||
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
|
||||
return device_signals::GetOSFirewall();
|
||||
#elif BUILDFLAG(IS_CHROMEOS)
|
||||
return GetChromeosFirewall();
|
||||
#else
|
||||
@ -343,47 +188,21 @@ SettingValue ContextInfoFetcher::GetOSFirewall() {
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
ScopedUfwConfigPathForTesting::ScopedUfwConfigPathForTesting(const char* path)
|
||||
: initial_path_(*GetUfwConfigPath()) {
|
||||
*GetUfwConfigPath() = path;
|
||||
: initial_path_(*device_signals::GetUfwConfigPath()) {
|
||||
*device_signals::GetUfwConfigPath() = path;
|
||||
}
|
||||
|
||||
ScopedUfwConfigPathForTesting::~ScopedUfwConfigPathForTesting() {
|
||||
*GetUfwConfigPath() = initial_path_;
|
||||
*device_signals::GetUfwConfigPath() = initial_path_;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
std::vector<std::string> ContextInfoFetcher::GetDnsServers() {
|
||||
std::vector<std::string> dns_addresses;
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
std::unique_ptr<net::ScopedResState> res = net::ResolvReader().GetResState();
|
||||
if (res) {
|
||||
std::optional<std::vector<net::IPEndPoint>> nameservers =
|
||||
net::GetNameservers(res->state());
|
||||
if (nameservers) {
|
||||
// If any name server is 0.0.0.0, assume the configuration is invalid.
|
||||
for (const net::IPEndPoint& nameserver : nameservers.value()) {
|
||||
if (nameserver.address().IsZero())
|
||||
return std::vector<std::string>();
|
||||
else
|
||||
dns_addresses.push_back(nameserver.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
#elif BUILDFLAG(IS_WIN)
|
||||
std::optional<std::vector<net::IPEndPoint>> nameservers;
|
||||
base::expected<net::WinDnsSystemSettings, net::ReadWinSystemDnsSettingsError>
|
||||
settings = net::ReadWinSystemDnsSettings();
|
||||
if (settings.has_value()) {
|
||||
nameservers = settings->GetAllNameservers();
|
||||
}
|
||||
|
||||
if (nameservers.has_value()) {
|
||||
for (const net::IPEndPoint& nameserver : nameservers.value()) {
|
||||
dns_addresses.push_back(nameserver.ToString());
|
||||
}
|
||||
}
|
||||
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
|
||||
return device_signals::GetSystemDnsServers();
|
||||
#else
|
||||
return std::vector<std::string>();
|
||||
#endif
|
||||
return dns_addresses;
|
||||
}
|
||||
|
||||
} // namespace enterprise_signals
|
||||
|
@ -13,6 +13,7 @@
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
#include "components/device_signals/core/common/common_types.h"
|
||||
#include "components/device_signals/core/common/platform_utils.h"
|
||||
#include "net/base/network_interfaces.h"
|
||||
@ -44,10 +45,6 @@ std::string GetSecurityPatchLevel() {
|
||||
return base::StringPrintf("%d.%d.%d", major, minor, bugfix);
|
||||
}
|
||||
|
||||
std::string GetDeviceHostName() {
|
||||
return net::GetHostName();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
@ -64,7 +61,7 @@ DeviceInfo DeviceInfoFetcherLinux::Fetch() {
|
||||
device_info.os_name = "linux";
|
||||
device_info.os_version = GetOsVersion();
|
||||
device_info.security_patch_level = GetSecurityPatchLevel();
|
||||
device_info.device_host_name = GetDeviceHostName();
|
||||
device_info.device_host_name = device_signals::GetHostName();
|
||||
device_info.device_model = device_signals::GetDeviceModel();
|
||||
device_info.serial_number = device_signals::GetSerialNumber();
|
||||
device_info.screen_lock_secured = device_signals::GetScreenlockSecured();
|
||||
|
@ -5,6 +5,7 @@
|
||||
#include "chrome/browser/enterprise/signals/device_info_fetcher_mac.h"
|
||||
|
||||
#include "base/system/sys_info.h"
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
#include "components/device_signals/core/common/platform_utils.h"
|
||||
#include "net/base/network_interfaces.h"
|
||||
|
||||
@ -16,10 +17,6 @@ std::string GetOsVersion() {
|
||||
return base::SysInfo::OperatingSystemVersion();
|
||||
}
|
||||
|
||||
std::string GetDeviceHostName() {
|
||||
return net::GetHostName();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
@ -35,7 +32,7 @@ DeviceInfo DeviceInfoFetcherMac::Fetch() {
|
||||
DeviceInfo device_info;
|
||||
device_info.os_name = "macOS";
|
||||
device_info.os_version = GetOsVersion();
|
||||
device_info.device_host_name = GetDeviceHostName();
|
||||
device_info.device_host_name = device_signals::GetHostName();
|
||||
device_info.device_model = device_signals::GetDeviceModel();
|
||||
device_info.serial_number = device_signals::GetSerialNumber();
|
||||
device_info.screen_lock_secured = device_signals::GetScreenlockSecured();
|
||||
|
@ -2,39 +2,14 @@
|
||||
// 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 "base/strings/string_number_conversions.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 "base/win/wincred_shim.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "base/win/windows_version.h"
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
#include "components/device_signals/core/common/common_types.h"
|
||||
#include "components/device_signals/core/common/platform_utils.h"
|
||||
#include "components/policy/core/common/cloud/cloud_policy_util.h"
|
||||
#include "net/base/network_interfaces.h"
|
||||
|
||||
namespace enterprise_signals {
|
||||
|
||||
@ -46,24 +21,6 @@ std::string GetSecurityPatchLevel() {
|
||||
return base::NumberToString(gi->version_number().patch);
|
||||
}
|
||||
|
||||
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(policy::GetDeviceFqdn())
|
||||
? std::nullopt
|
||||
: std::make_optional(domain);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
@ -78,9 +35,13 @@ DeviceInfoFetcherWin::~DeviceInfoFetcherWin() = default;
|
||||
DeviceInfo DeviceInfoFetcherWin::Fetch() {
|
||||
DeviceInfo device_info;
|
||||
device_info.os_name = "windows";
|
||||
device_info.os_version = device_signals::GetOsVersion();
|
||||
// Using `SysInfo` instead of device_signals function because the clients of
|
||||
// this class expects the version to have the format of
|
||||
// "<Major>.<Minor>.<Build>", instead of the full version number returned by
|
||||
// platform_utils, which also includes <Revision>.
|
||||
device_info.os_version = base::SysInfo::OperatingSystemVersion();
|
||||
device_info.security_patch_level = GetSecurityPatchLevel();
|
||||
device_info.device_host_name = policy::GetDeviceFqdn();
|
||||
device_info.device_host_name = device_signals::GetHostName();
|
||||
device_info.device_model = device_signals::GetDeviceModel();
|
||||
device_info.serial_number = device_signals::GetSerialNumber();
|
||||
device_info.screen_lock_secured = device_signals::GetScreenlockSecured();
|
||||
@ -88,7 +49,7 @@ DeviceInfo DeviceInfoFetcherWin::Fetch() {
|
||||
device_info.mac_addresses = device_signals::GetMacAddresses();
|
||||
device_info.windows_machine_domain =
|
||||
device_signals::GetWindowsMachineDomain();
|
||||
device_info.windows_user_domain = GetWindowsUserDomain();
|
||||
device_info.windows_user_domain = device_signals::GetWindowsUserDomain();
|
||||
device_info.secure_boot_enabled = device_signals::GetSecureBootEnabled();
|
||||
|
||||
return device_info;
|
||||
|
@ -5,6 +5,7 @@
|
||||
static_library("browser") {
|
||||
public = [
|
||||
"base_signals_collector.h",
|
||||
"browser_utils.h",
|
||||
"file_system_signals_collector.h",
|
||||
"metrics_utils.h",
|
||||
"pref_names.h",
|
||||
@ -46,12 +47,29 @@ static_library("browser") {
|
||||
"//components/policy/core/common",
|
||||
"//components/prefs",
|
||||
"//components/signin/public/identity_manager",
|
||||
"//components/version_info",
|
||||
"//net",
|
||||
]
|
||||
|
||||
if (is_win || is_mac || is_linux) {
|
||||
public += [ "os_signals_collector.h" ]
|
||||
sources += [ "os_signals_collector.cc" ]
|
||||
}
|
||||
|
||||
if (is_win) {
|
||||
sources += [ "win/browser_utils_win.cc" ]
|
||||
|
||||
public_deps += [ "//components/device_signals/core/common/win" ]
|
||||
}
|
||||
|
||||
if (is_mac) {
|
||||
sources += [ "mac/browser_utils_mac.cc" ]
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
sources += [ "linux/browser_utils_linux.cc" ]
|
||||
}
|
||||
|
||||
if (is_win || is_mac) {
|
||||
public += [
|
||||
"agent_signals_collector.h",
|
||||
@ -116,6 +134,10 @@ source_set("unit_tests") {
|
||||
"//testing/gtest",
|
||||
]
|
||||
|
||||
if (is_win || is_mac || is_linux) {
|
||||
sources += [ "os_signals_collector_unittest.cc" ]
|
||||
}
|
||||
|
||||
if (is_win || is_mac) {
|
||||
sources += [
|
||||
"agent_signals_collector_unittest.cc",
|
||||
|
@ -2,6 +2,9 @@ include_rules = [
|
||||
"+components/keyed_service/core",
|
||||
"+components/signin/public",
|
||||
"+components/policy/core",
|
||||
"+components/policy/proto",
|
||||
"+components/prefs",
|
||||
"+components/version_info",
|
||||
"+services/data_decoder/public/cpp",
|
||||
"+net",
|
||||
]
|
||||
|
39
components/device_signals/core/browser/browser_utils.h
Normal file
39
components/device_signals/core/browser/browser_utils.h
Normal file
@ -0,0 +1,39 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_BROWSER_UTILS_H_
|
||||
#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_BROWSER_UTILS_H_
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "components/device_signals/core/common/common_types.h"
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
// Returns the hostname of the current machine.
|
||||
std::string GetHostName();
|
||||
|
||||
// Returns the hostname of the current machine.
|
||||
std::vector<std::string> GetSystemDnsServers();
|
||||
|
||||
// Returns the current state of the OS firewall.
|
||||
SettingValue GetOSFirewall();
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// Returns the path to the ufw configuration file.
|
||||
const char** GetUfwConfigPath();
|
||||
#endif // BUILDFLAG(IS_LINUX)
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Returns the domain of the current Windows user.
|
||||
std::optional<std::string> GetWindowsUserDomain();
|
||||
|
||||
// Returns the machine GUID of the current Windows machine.
|
||||
std::optional<std::string> GetMachineGuid();
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
} // namespace device_signals
|
||||
|
||||
#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_BROWSER_UTILS_H_
|
@ -0,0 +1,75 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/strings/string_split.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "components/device_signals/core/common/common_types.h"
|
||||
#include "components/device_signals/core/common/platform_utils.h"
|
||||
#include "net/base/network_interfaces.h"
|
||||
#include "net/dns/public/resolv_reader.h"
|
||||
#include "net/dns/public/scoped_res_state.h"
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
std::string GetHostName() {
|
||||
return net::GetHostName();
|
||||
}
|
||||
|
||||
std::vector<std::string> GetSystemDnsServers() {
|
||||
std::vector<std::string> dns_addresses;
|
||||
std::unique_ptr<net::ScopedResState> res = net::ResolvReader().GetResState();
|
||||
if (res) {
|
||||
std::optional<std::vector<net::IPEndPoint>> nameservers =
|
||||
net::GetNameservers(res->state());
|
||||
if (nameservers) {
|
||||
// If any name server is 0.0.0.0, assume the configuration is invalid.
|
||||
for (const net::IPEndPoint& nameserver : nameservers.value()) {
|
||||
if (nameserver.address().IsZero()) {
|
||||
return std::vector<std::string>();
|
||||
} else {
|
||||
dns_addresses.push_back(nameserver.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dns_addresses;
|
||||
}
|
||||
|
||||
SettingValue GetOSFirewall() {
|
||||
base::FilePath path(*GetUfwConfigPath());
|
||||
std::string file_content;
|
||||
base::StringPairs values;
|
||||
|
||||
if (!base::PathExists(path) || !base::PathIsReadable(path) ||
|
||||
!base::ReadFileToString(path, &file_content)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
base::SplitStringIntoKeyValuePairs(file_content, '=', '\n', &values);
|
||||
auto is_ufw_enabled = std::ranges::find(
|
||||
values, "ENABLED", &std::pair<std::string, std::string>::first);
|
||||
if (is_ufw_enabled == values.end()) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
if (is_ufw_enabled->second == "yes") {
|
||||
return SettingValue::ENABLED;
|
||||
} else if (is_ufw_enabled->second == "no") {
|
||||
return SettingValue::DISABLED;
|
||||
} else {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
const char** GetUfwConfigPath() {
|
||||
static const char* path = "/etc/ufw/ufw.conf";
|
||||
return &path;
|
||||
}
|
||||
|
||||
} // namespace device_signals
|
102
components/device_signals/core/browser/mac/browser_utils_mac.cc
Normal file
102
components/device_signals/core/browser/mac/browser_utils_mac.cc
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/process/launch.h"
|
||||
#include "net/base/network_interfaces.h"
|
||||
#include "net/dns/public/resolv_reader.h"
|
||||
#include "net/dns/public/scoped_res_state.h"
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
std::string GetHostName() {
|
||||
return net::GetHostName();
|
||||
}
|
||||
|
||||
std::vector<std::string> GetSystemDnsServers() {
|
||||
std::vector<std::string> dns_addresses;
|
||||
std::unique_ptr<net::ScopedResState> res = net::ResolvReader().GetResState();
|
||||
if (res) {
|
||||
std::optional<std::vector<net::IPEndPoint>> nameservers =
|
||||
net::GetNameservers(res->state());
|
||||
if (nameservers) {
|
||||
// If any name server is 0.0.0.0, assume the configuration is invalid.
|
||||
for (const net::IPEndPoint& nameserver : nameservers.value()) {
|
||||
if (nameserver.address().IsZero()) {
|
||||
return std::vector<std::string>();
|
||||
} else {
|
||||
dns_addresses.push_back(nameserver.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return dns_addresses;
|
||||
}
|
||||
|
||||
SettingValue GetOSFirewall() {
|
||||
if (base::mac::MacOSMajorVersion() < 15) {
|
||||
// There is no official Apple documentation on how to obtain the enabled
|
||||
// status of the firewall (System Preferences> Security & Privacy> Firewall)
|
||||
// prior to MacOS versions 15. Reading globalstate from com.apple.alf is the
|
||||
// closest way to get such an API in Chrome without delegating to
|
||||
// potentially unstable commands. Values of "globalstate":
|
||||
// 0 = de-activated
|
||||
// 1 = on for specific services
|
||||
// 2 = on for essential services
|
||||
// You can get 2 by, e.g., enabling the "Block all incoming connections"
|
||||
// firewall functionality.
|
||||
Boolean key_exists_with_valid_format = false;
|
||||
CFIndex globalstate = CFPreferencesGetAppIntegerValue(
|
||||
CFSTR("globalstate"), CFSTR("com.apple.alf"),
|
||||
&key_exists_with_valid_format);
|
||||
|
||||
if (!key_exists_with_valid_format) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
switch (globalstate) {
|
||||
case 0:
|
||||
return SettingValue::DISABLED;
|
||||
case 1:
|
||||
case 2:
|
||||
return SettingValue::ENABLED;
|
||||
default:
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
// Based on this recommendation from Apple:
|
||||
// https://developer.apple.com/documentation/macos-release-notes/macos-15-release-notes/#Application-Firewall
|
||||
base::FilePath fw_util("/usr/libexec/ApplicationFirewall/socketfilterfw");
|
||||
if (!base::PathExists(fw_util)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
base::CommandLine command(fw_util);
|
||||
command.AppendSwitch("getglobalstate");
|
||||
std::string output;
|
||||
if (!base::GetAppOutput(command, &output)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
// State 1 is when the Firewall is simply enabled.
|
||||
// State 2 is when the Firewall is enabled and all incoming connections are
|
||||
// blocked.
|
||||
if (output.find("(State = 1)") != std::string::npos ||
|
||||
output.find("(State = 2)") != std::string::npos) {
|
||||
return SettingValue::ENABLED;
|
||||
}
|
||||
if (output.find("(State = 0)") != std::string::npos) {
|
||||
return SettingValue::DISABLED;
|
||||
}
|
||||
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
} // namespace device_signals
|
@ -51,6 +51,8 @@ std::string GetHistogramVariant(SignalName signal_name) {
|
||||
return "SystemSettings";
|
||||
case SignalName::kAgent:
|
||||
return "Agent";
|
||||
case SignalName::kOsSignals:
|
||||
return "OsSignals";
|
||||
}
|
||||
}
|
||||
|
||||
|
153
components/device_signals/core/browser/os_signals_collector.cc
Normal file
153
components/device_signals/core/browser/os_signals_collector.cc
Normal file
@ -0,0 +1,153 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/device_signals/core/browser/os_signals_collector.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/check.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "base/threading/scoped_blocking_call.h"
|
||||
#include "components/device_signals/core/browser/browser_utils.h"
|
||||
#include "components/device_signals/core/browser/signals_types.h"
|
||||
#include "components/device_signals/core/browser/user_permission_service.h"
|
||||
#include "components/device_signals/core/common/common_types.h"
|
||||
#include "components/device_signals/core/common/platform_utils.h"
|
||||
#include "components/device_signals/core/common/signals_constants.h"
|
||||
#include "components/policy/core/common/cloud/cloud_policy_manager.h"
|
||||
#include "components/policy/core/common/cloud/cloud_policy_store.h"
|
||||
#include "components/policy/core/common/cloud/cloud_policy_util.h"
|
||||
#include "components/version_info/version_info.h"
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
namespace {
|
||||
|
||||
std::unique_ptr<OsSignalsResponse> AddAsyncOsSignals(
|
||||
UserPermission permission,
|
||||
const SignalsAggregationRequest& request,
|
||||
std::unique_ptr<OsSignalsResponse> os_signals_response) {
|
||||
{
|
||||
base::ScopedBlockingCall scoped_blocking_call(
|
||||
FROM_HERE, base::BlockingType::MAY_BLOCK);
|
||||
// PII signals requires user consent
|
||||
if (permission == UserPermission::kGranted) {
|
||||
os_signals_response->mac_addresses = device_signals::GetMacAddresses();
|
||||
os_signals_response->serial_number = device_signals::GetSerialNumber();
|
||||
os_signals_response->system_dns_servers =
|
||||
device_signals::GetSystemDnsServers();
|
||||
}
|
||||
|
||||
os_signals_response->disk_encryption = device_signals::GetDiskEncrypted();
|
||||
os_signals_response->os_firewall = device_signals::GetOSFirewall();
|
||||
}
|
||||
|
||||
return os_signals_response;
|
||||
}
|
||||
|
||||
void OnSignalsCollected(
|
||||
SignalsAggregationResponse& response,
|
||||
base::OnceClosure done_closure,
|
||||
std::unique_ptr<OsSignalsResponse> os_signals_response) {
|
||||
if (os_signals_response) {
|
||||
response.os_signals_response = std::move(*os_signals_response);
|
||||
}
|
||||
|
||||
std::move(done_closure).Run();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
OsSignalsCollector::OsSignalsCollector(
|
||||
policy::CloudPolicyManager* device_cloud_policy_manager)
|
||||
: BaseSignalsCollector({
|
||||
{SignalName::kOsSignals,
|
||||
base::BindRepeating(&OsSignalsCollector::GetOsSignals,
|
||||
base::Unretained(this))},
|
||||
}),
|
||||
device_cloud_policy_manager_(device_cloud_policy_manager) {}
|
||||
|
||||
OsSignalsCollector::~OsSignalsCollector() = default;
|
||||
|
||||
void OsSignalsCollector::GetOsSignals(UserPermission permission,
|
||||
const SignalsAggregationRequest& request,
|
||||
SignalsAggregationResponse& response,
|
||||
base::OnceClosure done_closure) {
|
||||
if (permission != UserPermission::kGranted &&
|
||||
permission != UserPermission::kMissingConsent) {
|
||||
std::move(done_closure).Run();
|
||||
return;
|
||||
}
|
||||
|
||||
auto signal_response = std::make_unique<OsSignalsResponse>();
|
||||
signal_response->operating_system = policy::GetOSPlatform();
|
||||
signal_response->os_version = device_signals::GetOsVersion();
|
||||
signal_response->browser_version = version_info::GetVersionNumber();
|
||||
signal_response->screen_lock_secured = device_signals::GetScreenlockSecured();
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
signal_response->secure_boot_mode = device_signals::GetSecureBootEnabled();
|
||||
signal_response->windows_machine_domain =
|
||||
device_signals::GetWindowsMachineDomain();
|
||||
signal_response->windows_user_domain = device_signals::GetWindowsUserDomain();
|
||||
signal_response->machine_guid = device_signals::GetMachineGuid();
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
// PII signals requires user consent
|
||||
if (permission == UserPermission::kGranted) {
|
||||
signal_response->display_name = policy::GetDeviceName();
|
||||
signal_response->hostname = device_signals::GetHostName();
|
||||
}
|
||||
|
||||
signal_response->device_enrollment_domain = TryGetEnrollmentDomain();
|
||||
|
||||
base::SysInfo::GetHardwareInfo(base::BindOnce(
|
||||
&OsSignalsCollector::OnHardwareInfoRetrieved, weak_factory_.GetWeakPtr(),
|
||||
permission, request, std::ref(response), std::move(signal_response),
|
||||
std::move(done_closure)));
|
||||
}
|
||||
|
||||
void OsSignalsCollector::OnHardwareInfoRetrieved(
|
||||
UserPermission permission,
|
||||
const SignalsAggregationRequest& request,
|
||||
SignalsAggregationResponse& response,
|
||||
std::unique_ptr<OsSignalsResponse> os_signals_response,
|
||||
base::OnceClosure done_closure,
|
||||
base::SysInfo::HardwareInfo hardware_info) {
|
||||
os_signals_response->device_manufacturer = hardware_info.manufacturer;
|
||||
os_signals_response->device_model = hardware_info.model;
|
||||
|
||||
auto add_async_os_signals_callback = base::BindOnce(
|
||||
&AddAsyncOsSignals, permission, request, std::move(os_signals_response));
|
||||
auto on_signals_collected_callback = base::BindOnce(
|
||||
&OnSignalsCollected, std::ref(response), std::move(done_closure));
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
base::ThreadPool::CreateCOMSTATaskRunner({base::MayBlock()})
|
||||
->PostTaskAndReplyWithResult(FROM_HERE,
|
||||
std::move(add_async_os_signals_callback),
|
||||
std::move(on_signals_collected_callback));
|
||||
#else
|
||||
base::ThreadPool::PostTaskAndReplyWithResult(
|
||||
FROM_HERE, {base::MayBlock()}, std::move(add_async_os_signals_callback),
|
||||
std::move(on_signals_collected_callback));
|
||||
#endif
|
||||
}
|
||||
|
||||
std::optional<std::string> OsSignalsCollector::TryGetEnrollmentDomain() {
|
||||
policy::CloudPolicyStore* store = nullptr;
|
||||
if (device_cloud_policy_manager_ && device_cloud_policy_manager_->core() &&
|
||||
device_cloud_policy_manager_->core()->store()) {
|
||||
store = device_cloud_policy_manager_->core()->store();
|
||||
}
|
||||
|
||||
if (store && store->has_policy()) {
|
||||
const auto* policy = store->policy();
|
||||
return policy->has_managed_by() ? policy->managed_by()
|
||||
: policy->display_domain();
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace device_signals
|
@ -0,0 +1,52 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_OS_SIGNALS_COLLECTOR_H_
|
||||
#define COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_OS_SIGNALS_COLLECTOR_H_
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/system/sys_info.h"
|
||||
#include "components/device_signals/core/browser/base_signals_collector.h"
|
||||
|
||||
namespace policy {
|
||||
class CloudPolicyManager;
|
||||
} // namespace policy
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
struct OsSignalsResponse;
|
||||
|
||||
class OsSignalsCollector : public BaseSignalsCollector {
|
||||
public:
|
||||
explicit OsSignalsCollector(
|
||||
policy::CloudPolicyManager* device_cloud_policy_manager);
|
||||
|
||||
~OsSignalsCollector() override;
|
||||
|
||||
OsSignalsCollector(const OsSignalsCollector&) = delete;
|
||||
OsSignalsCollector& operator=(const OsSignalsCollector&) = delete;
|
||||
|
||||
private:
|
||||
void GetOsSignals(UserPermission permission,
|
||||
const SignalsAggregationRequest& request,
|
||||
SignalsAggregationResponse& response,
|
||||
base::OnceClosure done_closure);
|
||||
|
||||
void OnHardwareInfoRetrieved(
|
||||
UserPermission permission,
|
||||
const SignalsAggregationRequest& request,
|
||||
SignalsAggregationResponse& response,
|
||||
std::unique_ptr<OsSignalsResponse> os_signals_response,
|
||||
base::OnceClosure done_closure,
|
||||
base::SysInfo::HardwareInfo hardware_info);
|
||||
|
||||
std::optional<std::string> TryGetEnrollmentDomain();
|
||||
|
||||
const raw_ptr<policy::CloudPolicyManager> device_cloud_policy_manager_;
|
||||
base::WeakPtrFactory<OsSignalsCollector> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace device_signals
|
||||
|
||||
#endif // COMPONENTS_DEVICE_SIGNALS_CORE_BROWSER_OS_SIGNALS_COLLECTOR_H_
|
@ -0,0 +1,167 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/device_signals/core/browser/os_signals_collector.h"
|
||||
|
||||
#include <array>
|
||||
#include <utility>
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/run_loop.h"
|
||||
#include "base/test/task_environment.h"
|
||||
#include "base/values.h"
|
||||
#include "components/device_signals/core/browser/signals_types.h"
|
||||
#include "components/device_signals/core/browser/user_permission_service.h"
|
||||
#include "components/device_signals/core/common/signals_constants.h"
|
||||
#include "components/policy/core/common/cloud/cloud_policy_util.h"
|
||||
#include "components/policy/core/common/cloud/mock_cloud_policy_manager.h"
|
||||
#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
|
||||
#include "components/policy/proto/device_management_backend.pb.h"
|
||||
#include "components/version_info/version_info.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
using testing::_;
|
||||
using testing::ContainerEq;
|
||||
using testing::Invoke;
|
||||
using testing::Return;
|
||||
using testing::StrictMock;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr char kFakeBrowserEnrollmentDomain[] = "fake.domain.google.com";
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
class OsSignalsCollectorTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
auto mock_browser_cloud_policy_store =
|
||||
std::make_unique<policy::MockCloudPolicyStore>();
|
||||
mock_browser_cloud_policy_store_ = mock_browser_cloud_policy_store.get();
|
||||
mock_browser_cloud_policy_manager_ =
|
||||
std::make_unique<policy::MockCloudPolicyManager>(
|
||||
std::move(mock_browser_cloud_policy_store),
|
||||
task_environment_.GetMainThreadTaskRunner());
|
||||
signal_collector_ = std::make_unique<OsSignalsCollector>(
|
||||
mock_browser_cloud_policy_manager_.get());
|
||||
}
|
||||
|
||||
void TearDown() override { mock_browser_cloud_policy_store_ = nullptr; }
|
||||
|
||||
void SetFakeBrowserPolicyData() {
|
||||
auto policy_data = std::make_unique<enterprise_management::PolicyData>();
|
||||
policy_data->set_managed_by(kFakeBrowserEnrollmentDomain);
|
||||
mock_browser_cloud_policy_store_->set_policy_data_for_testing(
|
||||
std::move(policy_data));
|
||||
}
|
||||
|
||||
// Helper function to check a subset of signals that should or should not be
|
||||
// collected based on permission. Not all signals are checked due to testing
|
||||
// limitation.
|
||||
void CheckSignalsCollected(OsSignalsResponse& response,
|
||||
bool can_collect_pii) {
|
||||
EXPECT_EQ(response.device_enrollment_domain, kFakeBrowserEnrollmentDomain);
|
||||
EXPECT_EQ(response.browser_version, version_info::GetVersionNumber());
|
||||
EXPECT_EQ(response.operating_system, policy::GetOSPlatform());
|
||||
|
||||
if (can_collect_pii) {
|
||||
EXPECT_EQ(response.display_name, policy::GetDeviceName());
|
||||
} else {
|
||||
EXPECT_FALSE(response.display_name);
|
||||
}
|
||||
}
|
||||
|
||||
base::test::TaskEnvironment task_environment_;
|
||||
std::unique_ptr<policy::MockCloudPolicyManager>
|
||||
mock_browser_cloud_policy_manager_;
|
||||
raw_ptr<policy::MockCloudPolicyStore> mock_browser_cloud_policy_store_;
|
||||
std::unique_ptr<OsSignalsCollector> signal_collector_;
|
||||
};
|
||||
|
||||
// Test that runs a sanity check on the set of signals supported by this
|
||||
// collector. Will need to be updated if new signals become supported.
|
||||
TEST_F(OsSignalsCollectorTest, SupportedOsSignalNames) {
|
||||
const std::array<SignalName, 1> supported_signals{{SignalName::kOsSignals}};
|
||||
|
||||
const auto names_set = signal_collector_->GetSupportedSignalNames();
|
||||
|
||||
EXPECT_EQ(names_set.size(), supported_signals.size());
|
||||
for (const auto& signal_name : supported_signals) {
|
||||
EXPECT_TRUE(names_set.find(signal_name) != names_set.end());
|
||||
}
|
||||
}
|
||||
|
||||
// Happy path test case for OS signals collection with full permission.
|
||||
TEST_F(OsSignalsCollectorTest, GetSignal_Success) {
|
||||
SetFakeBrowserPolicyData();
|
||||
|
||||
SignalName signal_name = SignalName::kOsSignals;
|
||||
SignalsAggregationRequest empty_request;
|
||||
SignalsAggregationResponse response;
|
||||
base::RunLoop run_loop;
|
||||
signal_collector_->GetSignal(signal_name, UserPermission::kGranted,
|
||||
empty_request, response, run_loop.QuitClosure());
|
||||
|
||||
run_loop.Run();
|
||||
|
||||
ASSERT_FALSE(response.top_level_error.has_value());
|
||||
ASSERT_TRUE(response.os_signals_response);
|
||||
CheckSignalsCollected(response.os_signals_response.value(),
|
||||
/*can_collect_pii=*/true);
|
||||
}
|
||||
|
||||
// Tests that an unsupported signal is marked as unsupported.
|
||||
TEST_F(OsSignalsCollectorTest, GetOsSignal_Unsupported) {
|
||||
SignalName signal_name = SignalName::kAntiVirus;
|
||||
SignalsAggregationRequest empty_request;
|
||||
SignalsAggregationResponse response;
|
||||
base::RunLoop run_loop;
|
||||
signal_collector_->GetSignal(signal_name, UserPermission::kGranted,
|
||||
empty_request, response, run_loop.QuitClosure());
|
||||
|
||||
run_loop.Run();
|
||||
|
||||
ASSERT_TRUE(response.top_level_error.has_value());
|
||||
EXPECT_EQ(response.top_level_error.value(),
|
||||
SignalCollectionError::kUnsupported);
|
||||
}
|
||||
|
||||
// Tests that signal collection is still complete even when consent is missing.
|
||||
TEST_F(OsSignalsCollectorTest, GetSignal_MissingConsent) {
|
||||
SetFakeBrowserPolicyData();
|
||||
|
||||
SignalName signal_name = SignalName::kOsSignals;
|
||||
SignalsAggregationRequest empty_request;
|
||||
SignalsAggregationResponse response;
|
||||
base::RunLoop run_loop;
|
||||
signal_collector_->GetSignal(signal_name, UserPermission::kMissingConsent,
|
||||
empty_request, response, run_loop.QuitClosure());
|
||||
|
||||
run_loop.Run();
|
||||
|
||||
ASSERT_FALSE(response.top_level_error.has_value());
|
||||
ASSERT_TRUE(response.os_signals_response);
|
||||
CheckSignalsCollected(response.os_signals_response.value(),
|
||||
/*can_collect_pii=*/false);
|
||||
}
|
||||
|
||||
// Tests that signal collection is halted if permission is not sufficient.
|
||||
TEST_F(OsSignalsCollectorTest, GetSignal_MissingUser) {
|
||||
SignalName signal_name = SignalName::kOsSignals;
|
||||
SignalsAggregationRequest empty_request;
|
||||
SignalsAggregationResponse response;
|
||||
base::RunLoop run_loop;
|
||||
signal_collector_->GetSignal(signal_name, UserPermission::kMissingUser,
|
||||
empty_request, response, run_loop.QuitClosure());
|
||||
|
||||
run_loop.Run();
|
||||
|
||||
ASSERT_FALSE(response.top_level_error.has_value());
|
||||
ASSERT_FALSE(response.os_signals_response);
|
||||
}
|
||||
|
||||
} // namespace device_signals
|
@ -104,6 +104,14 @@ SettingsResponse& SettingsResponse::operator=(const SettingsResponse&) =
|
||||
|
||||
SettingsResponse::~SettingsResponse() = default;
|
||||
|
||||
OsSignalsResponse::OsSignalsResponse() = default;
|
||||
OsSignalsResponse::OsSignalsResponse(const OsSignalsResponse&) = default;
|
||||
|
||||
OsSignalsResponse& OsSignalsResponse::operator=(const OsSignalsResponse&) =
|
||||
default;
|
||||
|
||||
OsSignalsResponse::~OsSignalsResponse() = default;
|
||||
|
||||
FileSystemInfoResponse::FileSystemInfoResponse() = default;
|
||||
FileSystemInfoResponse::FileSystemInfoResponse(const FileSystemInfoResponse&) =
|
||||
default;
|
||||
|
@ -37,7 +37,8 @@ enum class SignalName {
|
||||
kFileSystemInfo,
|
||||
kSystemSettings,
|
||||
kAgent,
|
||||
kMaxValue = kAgent
|
||||
kOsSignals,
|
||||
kMaxValue = kOsSignals
|
||||
};
|
||||
|
||||
// Superset of all signal collection errors that can occur, including top-level
|
||||
@ -185,6 +186,40 @@ struct SettingsResponse : BaseSignalResponse {
|
||||
std::vector<SettingsItem> settings_items{};
|
||||
};
|
||||
|
||||
struct OsSignalsResponse : BaseSignalResponse {
|
||||
OsSignalsResponse();
|
||||
|
||||
OsSignalsResponse(const OsSignalsResponse&);
|
||||
OsSignalsResponse& operator=(const OsSignalsResponse&);
|
||||
|
||||
~OsSignalsResponse() override;
|
||||
|
||||
// Common to all platforms
|
||||
std::optional<std::string> display_name = std::nullopt;
|
||||
std::string browser_version{};
|
||||
std::optional<std::string> device_enrollment_domain = std::nullopt;
|
||||
std::string device_manufacturer{};
|
||||
std::string device_model{};
|
||||
device_signals::SettingValue disk_encryption =
|
||||
device_signals::SettingValue::UNKNOWN;
|
||||
std::optional<std::string> hostname = std::nullopt;
|
||||
std::optional<std::vector<std::string>> mac_addresses = std::nullopt;
|
||||
std::string operating_system{};
|
||||
device_signals::SettingValue os_firewall =
|
||||
device_signals::SettingValue::UNKNOWN;
|
||||
std::string os_version{};
|
||||
device_signals::SettingValue screen_lock_secured =
|
||||
device_signals::SettingValue::UNKNOWN;
|
||||
std::optional<std::string> serial_number = std::nullopt;
|
||||
std::optional<std::vector<std::string>> system_dns_servers = std::nullopt;
|
||||
|
||||
// Windows specific
|
||||
std::optional<std::string> machine_guid = std::nullopt;
|
||||
std::optional<device_signals::SettingValue> secure_boot_mode = std::nullopt;
|
||||
std::optional<std::string> windows_machine_domain = std::nullopt;
|
||||
std::optional<std::string> windows_user_domain = std::nullopt;
|
||||
};
|
||||
|
||||
struct FileSystemInfoResponse : BaseSignalResponse {
|
||||
FileSystemInfoResponse();
|
||||
|
||||
@ -255,6 +290,7 @@ struct SignalsAggregationResponse {
|
||||
std::optional<HotfixSignalResponse> hotfix_signal_response = std::nullopt;
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
std::optional<SettingsResponse> settings_response = std::nullopt;
|
||||
std::optional<OsSignalsResponse> os_signals_response = std::nullopt;
|
||||
|
||||
std::optional<FileSystemInfoResponse> file_system_info_response =
|
||||
std::nullopt;
|
||||
|
151
components/device_signals/core/browser/win/browser_utils_win.cc
Normal file
151
components/device_signals/core/browser/win/browser_utils_win.cc
Normal file
@ -0,0 +1,151 @@
|
||||
// Copyright 2025 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "components/device_signals/core/browser/browser_utils.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 <winsock2.h>
|
||||
|
||||
#include <DSRole.h>
|
||||
#include <iphlpapi.h>
|
||||
#include <netfw.h>
|
||||
#include <powersetting.h>
|
||||
#include <propsys.h>
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/win/registry.h"
|
||||
#include "base/win/wincred_shim.h"
|
||||
#include "components/device_signals/core/common/common_types.h"
|
||||
#include "components/policy/core/common/cloud/cloud_policy_util.h"
|
||||
#include "net/base/network_interfaces.h"
|
||||
#include "net/dns/public/win_dns_system_settings.h"
|
||||
|
||||
namespace device_signals {
|
||||
|
||||
namespace {
|
||||
|
||||
// Registry for device ID.
|
||||
constexpr wchar_t kRegKeyCryptographyKey[] =
|
||||
L"SOFTWARE\\Microsoft\\Cryptography\\";
|
||||
constexpr wchar_t kRegValueMachineGuid[] = L"MachineGuid";
|
||||
|
||||
bool ReadRegistryString(const std::wstring& key_path,
|
||||
const std::wstring& name,
|
||||
REGSAM reg_view,
|
||||
std::string& value) {
|
||||
std::wstring data;
|
||||
const LONG result = base::win::RegKey(HKEY_LOCAL_MACHINE, key_path.c_str(),
|
||||
reg_view | KEY_READ)
|
||||
.ReadValue(name.c_str(), &data);
|
||||
if (result != ERROR_SUCCESS) {
|
||||
VLOG(1) << __func__ << ": failed to read registry: " << key_path << "@"
|
||||
<< name;
|
||||
return false;
|
||||
}
|
||||
value = base::SysWideToUTF8(data);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::string GetHostName() {
|
||||
return policy::GetDeviceFqdn();
|
||||
}
|
||||
|
||||
std::vector<std::string> GetSystemDnsServers() {
|
||||
std::vector<std::string> dns_addresses;
|
||||
std::optional<std::vector<net::IPEndPoint>> nameservers;
|
||||
base::expected<net::WinDnsSystemSettings, net::ReadWinSystemDnsSettingsError>
|
||||
settings = net::ReadWinSystemDnsSettings();
|
||||
if (settings.has_value()) {
|
||||
nameservers = settings->GetAllNameservers();
|
||||
}
|
||||
|
||||
if (nameservers.has_value()) {
|
||||
for (const net::IPEndPoint& nameserver : nameservers.value()) {
|
||||
dns_addresses.push_back(nameserver.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return dns_addresses;
|
||||
}
|
||||
|
||||
SettingValue GetOSFirewall() {
|
||||
Microsoft::WRL::ComPtr<INetFwPolicy2> firewall_policy;
|
||||
HRESULT hr = CoCreateInstance(CLSID_NetFwPolicy2, nullptr, CLSCTX_ALL,
|
||||
IID_PPV_ARGS(&firewall_policy));
|
||||
if (FAILED(hr)) {
|
||||
DLOG(ERROR) << logging::SystemErrorCodeToString(hr);
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
long profile_types = 0;
|
||||
hr = firewall_policy->get_CurrentProfileTypes(&profile_types);
|
||||
if (FAILED(hr)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
// The most restrictive active profile takes precedence.
|
||||
constexpr NET_FW_PROFILE_TYPE2 kProfileTypes[] = {
|
||||
NET_FW_PROFILE2_PUBLIC, NET_FW_PROFILE2_PRIVATE, NET_FW_PROFILE2_DOMAIN};
|
||||
for (size_t i = 0; i < std::size(kProfileTypes); ++i) {
|
||||
if ((profile_types & UNSAFE_TODO(kProfileTypes[i])) != 0) {
|
||||
VARIANT_BOOL enabled = VARIANT_TRUE;
|
||||
hr = firewall_policy->get_FirewallEnabled(UNSAFE_TODO(kProfileTypes[i]),
|
||||
&enabled);
|
||||
if (FAILED(hr)) {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
if (enabled == VARIANT_TRUE) {
|
||||
return SettingValue::ENABLED;
|
||||
} else if (enabled == VARIANT_FALSE) {
|
||||
return SettingValue::DISABLED;
|
||||
} else {
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
}
|
||||
}
|
||||
return SettingValue::UNKNOWN;
|
||||
}
|
||||
|
||||
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(GetHostName())
|
||||
? std::nullopt
|
||||
: std::make_optional(domain);
|
||||
}
|
||||
|
||||
std::optional<std::string> GetMachineGuid() {
|
||||
std::string machine_guid;
|
||||
if (!ReadRegistryString(kRegKeyCryptographyKey, kRegValueMachineGuid,
|
||||
KEY_READ | KEY_WOW64_64KEY, machine_guid)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return machine_guid;
|
||||
}
|
||||
|
||||
} // namespace device_signals
|
Reference in New Issue
Block a user