0

[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:
Zonghan Xu
2025-03-20 13:10:51 -07:00
committed by Chromium LUCI CQ
parent 5d92196daa
commit 5c4a5d12a0
16 changed files with 834 additions and 250 deletions

@ -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",
]

@ -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

@ -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";
}
}

@ -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;

@ -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