blob: 639ee4a093943f30a00982af8532752a5751ce09 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service.h"
#include <sstream>
#include <vector>
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/containers/flat_set.h"
#include "base/files/file_util.h"
#include "base/i18n/time_formatting.h"
#include "base/json/values_util.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_tokenizer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/threading/sequence_bound.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "chrome/browser/enterprise/browser_management/management_service_factory.h"
#include "chrome/browser/extensions/extension_management.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/managed_installation_mode.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/safe_browsing/extension_telemetry/cookies_get_all_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/cookies_get_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/declarative_net_request_action_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/declarative_net_request_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_js_callstacks.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_signal.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_config_manager.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_file_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_persister.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_service_factory.h"
#include "chrome/browser/safe_browsing/extension_telemetry/extension_telemetry_uploader.h"
#include "chrome/browser/safe_browsing/extension_telemetry/potential_password_theft_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/remote_host_contacted_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/search_hijacking_detector.h"
#include "chrome/browser/safe_browsing/extension_telemetry/tabs_api_signal_processor.h"
#include "chrome/browser/safe_browsing/extension_telemetry/tabs_execute_script_signal_processor.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/common/pref_names.h"
#include "components/enterprise/buildflags/buildflags.h"
#include "components/omnibox/browser/autocomplete_match.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "components/safe_browsing/core/browser/sync/safe_browsing_primary_account_token_fetcher.h"
#include "components/safe_browsing/core/browser/sync/sync_utils.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/safe_browsing/core/common/proto/csd.pb.h"
#include "components/safe_browsing/core/common/safe_browsing_prefs.h"
#include "components/search_engines/template_url_service.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/browser/blocklist_extension_prefs.h"
#include "extensions/browser/blocklist_state.h"
#include "extensions/browser/disable_reason.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/install_prefs_helper.h"
#include "extensions/browser/path_util.h"
#include "extensions/common/file_util.h"
#include "extensions/common/mojom/manifest.mojom-shared.h"
#include "extensions/common/switches.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
#include "chrome/browser/enterprise/connectors/connectors_service.h"
#include "chrome/browser/enterprise/connectors/reporting/extension_telemetry_event_router.h"
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
namespace safe_browsing {
namespace {
using ::extensions::ExtensionManagement;
using ::extensions::ManagedInstallationMode;
using ::extensions::mojom::ManifestLocation;
using ::google::protobuf::RepeatedPtrField;
using ExtensionInfo =
::safe_browsing::ExtensionTelemetryReportRequest_ExtensionInfo;
using OffstoreExtensionVerdict =
::safe_browsing::ExtensionTelemetryReportResponse_OffstoreExtensionVerdict;
using SearchHijackingSignal =
::safe_browsing::ExtensionTelemetryReportRequest_SearchHijackingSignal;
// The ExtensionTelemetryService saves offstore extensions file data such as
// filenames and hashes in Prefs. This information is stored in the following
// dictionary format:
// {
// ...
// kExtensionTelemetryFileData : {
// <extension_id_1> : {
// "last_processed_timestamp" : <timestamp>,
// "file_data" : {
// <file_path_1> : <file_hash_1>,
// <file_path_2> : <file_hash_2>,
// ...
// <manifest.json> : <file_contents>
// }
// },
// <extension_id_2> : {
// "last_processed_timestamp" : <timestamp>,
// "file_data" : {
// <file_path_1> : <file_hash_1>,
// <file_path_2> : <file_hash_2>,
// ...
// <manifest.json> : <file_contents>
// }
// },
// ...
// },
// ...
// }
constexpr char kFileDataProcessTimestampPref[] = "last_processed_timestamp";
constexpr char kFileDataDictPref[] = "file_data";
constexpr char kManifestFile[] = "manifest.json";
// Specifies the default for `num_checks_per_upload_interval_`
// NOTE: With a value of 1, the telemetry service will perform checks at the
// upload interval itself and will only write a report to disk if the upload
// fails.
constexpr int kNumChecksPerUploadInterval = 1;
// Specifies the upload interval for ESB telemetry reports.
base::TimeDelta kUploadIntervalSeconds = base::Seconds(3600);
// Delay before the Telemetry Service checks its last upload time.
base::TimeDelta kStartupUploadCheckDelaySeconds = base::Seconds(15);
// Interval for extension telemetry to start another off-store extension
// file data collection process.
base::TimeDelta kOffstoreFileDataCollectionIntervalSeconds =
base::Seconds(7200);
// Initial delay for extension telemetry to start collecting
// off-store extension file data.
base::TimeDelta kOffstoreFileDataCollectionStartupDelaySeconds =
base::Seconds(300);
// Limit the off-store file data collection duration.
base::TimeDelta kOffstoreFileDataCollectionDurationLimitSeconds =
base::Seconds(60);
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
// Interval for generating and send extension telemetry for enterprise
base::TimeDelta kExtensionTelemetryEnterpriseReportingIntervalSeconds =
base::Seconds(300);
#endif
void RecordWhenFileWasPersisted(bool persisted_at_write_interval) {
base::UmaHistogramBoolean(
"SafeBrowsing.ExtensionTelemetry.FilePersistedAtWriteInterval",
persisted_at_write_interval);
}
void RecordSignalUploadSize(size_t size, ExtensionSignalType signal_type) {
switch (signal_type) {
case ExtensionSignalType::kTabsExecuteScript:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.TabsExecuteScript", size);
break;
case ExtensionSignalType::kRemoteHostContacted:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.RemoteHostContacted",
size);
break;
case ExtensionSignalType::kCookiesGetAll:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.CookiesGetAll", size);
break;
case ExtensionSignalType::kPotentialPasswordTheft:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.PotentialPasswordTheft",
size);
break;
case ExtensionSignalType::kCookiesGet:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.CookiesGet", size);
break;
case ExtensionSignalType::kDeclarativeNetRequest:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.DeclarativeNetRequest",
size);
break;
case ExtensionSignalType::kTabsApi:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize.TabsApi", size);
break;
case ExtensionSignalType::kDeclarativeNetRequestAction:
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.UploadSize."
"DeclarativeNetRequestAction",
size);
break;
case ExtensionSignalType::kPasswordReuse:
// This signal is used to construct the potential password theft signal and
// is not itself included in a telemetry report.
default:
break;
}
}
void RecordNumOffstoreExtensions(int num_extensions) {
base::UmaHistogramCounts100(
"SafeBrowsing.ExtensionTelemetry.FileData.NumOffstoreExtensions",
num_extensions);
}
void RecordCollectionDuration(base::TimeDelta duration) {
base::UmaHistogramMediumTimes(
"SafeBrowsing.ExtensionTelemetry.FileData.CollectionDuration", duration);
}
void RecordSignalTypeForEnterprise(ExtensionSignalType signal_type) {
base::UmaHistogramEnumeration(
"SafeBrowsing.ExtensionTelemetry.Enterprise.Signals.SignalType",
signal_type);
}
void RecordEnterpriseReportSize(size_t size) {
base::UmaHistogramCounts1M(
"SafeBrowsing.ExtensionTelemetry.Enterprise.ReportSize", size);
}
static_assert(extensions::Manifest::NUM_LOAD_TYPES == 10,
"ExtensionTelemetryReportRequest::ExtensionInfo::Type "
"needs to match extensions::Manifest::Type.");
ExtensionInfo::Type GetType(extensions::Manifest::Type type) {
switch (type) {
case extensions::Manifest::TYPE_UNKNOWN:
return ExtensionInfo::UNKNOWN_TYPE;
case extensions::Manifest::TYPE_EXTENSION:
return ExtensionInfo::EXTENSION;
case extensions::Manifest::TYPE_THEME:
return ExtensionInfo::THEME;
case extensions::Manifest::TYPE_USER_SCRIPT:
return ExtensionInfo::USER_SCRIPT;
case extensions::Manifest::TYPE_HOSTED_APP:
return ExtensionInfo::HOSTED_APP;
case extensions::Manifest::TYPE_LEGACY_PACKAGED_APP:
return ExtensionInfo::LEGACY_PACKAGED_APP;
case extensions::Manifest::TYPE_PLATFORM_APP:
return ExtensionInfo::PLATFORM_APP;
case extensions::Manifest::TYPE_SHARED_MODULE:
return ExtensionInfo::SHARED_MODULE;
case extensions::Manifest::TYPE_LOGIN_SCREEN_EXTENSION:
return ExtensionInfo::LOGIN_SCREEN_EXTENSION;
case extensions::Manifest::TYPE_CHROMEOS_SYSTEM_EXTENSION:
// TODO(mgawad): introduce new CHROMEOS_SYSTEM_EXTENSION type.
return ExtensionInfo::EXTENSION;
default:
return ExtensionInfo::UNKNOWN_TYPE;
}
}
static_assert(static_cast<int>(ManifestLocation::kMaxValue) ==
static_cast<int>(ExtensionInfo::EXTERNAL_COMPONENT),
"ExtensionTelemetryReportRequest::ExtensionInfo::InstallLocation "
"needs to match extensions::mojom::ManifestLocation.");
ExtensionInfo::InstallLocation GetInstallLocation(ManifestLocation location) {
switch (location) {
case ManifestLocation::kInvalidLocation:
return ExtensionInfo::UNKNOWN_LOCATION;
case ManifestLocation::kInternal:
return ExtensionInfo::INTERNAL;
case ManifestLocation::kExternalPref:
return ExtensionInfo::EXTERNAL_PREF;
case ManifestLocation::kExternalRegistry:
return ExtensionInfo::EXTERNAL_REGISTRY;
case ManifestLocation::kUnpacked:
return ExtensionInfo::UNPACKED;
case ManifestLocation::kComponent:
return ExtensionInfo::COMPONENT;
case ManifestLocation::kExternalPrefDownload:
return ExtensionInfo::EXTERNAL_PREF_DOWNLOAD;
case ManifestLocation::kExternalPolicyDownload:
return ExtensionInfo::EXTERNAL_POLICY_DOWNLOAD;
case ManifestLocation::kCommandLine:
return ExtensionInfo::COMMAND_LINE;
case ManifestLocation::kExternalPolicy:
return ExtensionInfo::EXTERNAL_POLICY;
case ManifestLocation::kExternalComponent:
return ExtensionInfo::EXTERNAL_COMPONENT;
}
return ExtensionInfo::UNKNOWN_LOCATION;
}
// Converts a policy::ManagementAuthorityTrustworthiness to
// ExtensionTelemetryReportRequest::ManagementAuthority.
ExtensionTelemetryReportRequest::ManagementAuthority GetManagementAuthority(
policy::ManagementAuthorityTrustworthiness
management_authority_trustworthiness) {
switch (management_authority_trustworthiness) {
case policy::ManagementAuthorityTrustworthiness::NONE:
return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_NONE;
case policy::ManagementAuthorityTrustworthiness::LOW:
return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_LOW;
case policy::ManagementAuthorityTrustworthiness::TRUSTED:
return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_TRUSTED;
case policy::ManagementAuthorityTrustworthiness::FULLY_TRUSTED:
return ExtensionTelemetryReportRequest::
MANAGEMENT_AUTHORITY_FULLY_TRUSTED;
default:
return ExtensionTelemetryReportRequest::MANAGEMENT_AUTHORITY_UNSPECIFIED;
}
}
ExtensionInfo::InstallationPolicy
ExtensionManagementInstallationModeToExtensionInfoInstallationPolicy(
const ManagedInstallationMode& installation_mode) {
switch (installation_mode) {
case ManagedInstallationMode::kAllowed:
return ExtensionInfo::INSTALLATION_ALLOWED;
case ManagedInstallationMode::kBlocked:
return ExtensionInfo::INSTALLATION_BLOCKED;
case ManagedInstallationMode::kForced:
return ExtensionInfo::INSTALLATION_FORCED;
case ManagedInstallationMode::kRecommended:
return ExtensionInfo::INSTALLATION_RECOMMENDED;
case ManagedInstallationMode::kRemoved:
return ExtensionInfo::INSTALLATION_REMOVED;
default:
return ExtensionInfo::NO_POLICY;
}
}
ExtensionInfo::BlocklistState BitMapBlocklistStateToExtensionInfoBlocklistState(
const extensions::BitMapBlocklistState& state) {
switch (state) {
case extensions::BitMapBlocklistState::NOT_BLOCKLISTED:
return ExtensionInfo::NOT_BLOCKLISTED;
case extensions::BitMapBlocklistState::BLOCKLISTED_MALWARE:
return ExtensionInfo::BLOCKLISTED_MALWARE;
case extensions::BitMapBlocklistState::BLOCKLISTED_SECURITY_VULNERABILITY:
return ExtensionInfo::BLOCKLISTED_SECURITY_VULNERABILITY;
case extensions::BitMapBlocklistState::BLOCKLISTED_CWS_POLICY_VIOLATION:
return ExtensionInfo::BLOCKLISTED_CWS_POLICY_VIOLATION;
case extensions::BitMapBlocklistState::BLOCKLISTED_POTENTIALLY_UNWANTED:
return ExtensionInfo::BLOCKLISTED_POTENTIALLY_UNWANTED;
default:
return ExtensionInfo::BLOCKLISTED_UNKNOWN;
}
}
ExtensionInfo::BlocklistState GetBlocklistState(
const extensions::ExtensionId extension_id,
extensions::ExtensionPrefs* extension_prefs) {
extensions::BitMapBlocklistState state =
extensions::blocklist_prefs::GetExtensionBlocklistState(extension_id,
extension_prefs);
return BitMapBlocklistStateToExtensionInfoBlocklistState(state);
}
ExtensionInfo::BlocklistState GetExtensionTelemetryServiceBlocklistState(
const extensions::ExtensionId extension_id,
extensions::ExtensionPrefs* extension_prefs) {
extensions::BitMapBlocklistState state =
extensions::blocklist_prefs::GetExtensionTelemetryServiceBlocklistState(
extension_id, extension_prefs);
return BitMapBlocklistStateToExtensionInfoBlocklistState(state);
}
extensions::BlocklistState ConvertTelemetryResponseVerdictToBlocklistState(
OffstoreExtensionVerdict::OffstoreExtensionVerdictType type) {
switch (type) {
case OffstoreExtensionVerdict::NONE:
return extensions::BlocklistState::NOT_BLOCKLISTED;
case OffstoreExtensionVerdict::MALWARE:
return extensions::BlocklistState::BLOCKLISTED_MALWARE;
default:
return extensions::BlocklistState::BLOCKLISTED_UNKNOWN;
}
}
// Checks for extensions specified in the -load-extension commandline switch.
// Creates and returns a set of extension objects for such extensions.
// NOTE: These extensions are not installed - the information is only collected
// for telemetry purposes.
// This function is executed on a separate thread from the UI thread since it
// involves reading the manifest files from disk.
extensions::ExtensionSet CollectCommandLineExtensionInfo() {
extensions::ExtensionSet commandline_extensions;
// If there are no commandline extensions, return an empty set.
base::CommandLine* cmdline(base::CommandLine::ForCurrentProcess());
if (!cmdline || !cmdline->HasSwitch(extensions::switches::kLoadExtension)) {
return commandline_extensions;
}
// Otherwise, create an extension object for each extension specified in the
// commandline. Note that the extension is not installed.
base::CommandLine::StringType path_list =
cmdline->GetSwitchValueNative(extensions::switches::kLoadExtension);
base::StringTokenizerT<base::CommandLine::StringType,
base::CommandLine::StringType::const_iterator>
t(path_list, FILE_PATH_LITERAL(","));
while (t.GetNext()) {
auto tmp_path = extensions::path_util::ResolveHomeDirectory(
base::FilePath(t.token_piece()));
auto extension_path = base::MakeAbsoluteFilePath(tmp_path);
std::string error;
// Use default creation flags. Since we are not installing the extension,
// it doesn't really mattter.
int flags = extensions::Extension::FOLLOW_SYMLINKS_ANYWHERE |
extensions::Extension::ALLOW_FILE_ACCESS |
extensions::Extension::REQUIRE_MODERN_MANIFEST_VERSION;
scoped_refptr<extensions::Extension> extension =
extensions::file_util::LoadExtension(
extension_path, ManifestLocation::kCommandLine, flags, &error);
if (error.empty()) {
commandline_extensions.Insert(extension);
}
}
return commandline_extensions;
}
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
// Retrieves the ExtensionTelemetryEventRouter associated with the profile.
enterprise_connectors::ExtensionTelemetryEventRouter*
GetExtensionTelemetryEventRouter(Profile* profile) {
return enterprise_connectors::ExtensionTelemetryEventRouter::Get(profile);
}
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
// Returns true if the signal type should be collected for enterprise telemetry.
bool CollectForEnterprise(ExtensionSignalType type) {
return type == ExtensionSignalType::kCookiesGet ||
type == ExtensionSignalType::kCookiesGetAll ||
type == ExtensionSignalType::kRemoteHostContacted ||
type == ExtensionSignalType::kTabsApi;
}
} // namespace
// Adds extension installation mode and managed status to extension telemetry
// reports.
BASE_FEATURE(kExtensionTelemetryIncludePolicyData,
"SafeBrowsingExtensionTelemetryIncludePolicyData",
base::FEATURE_ENABLED_BY_DEFAULT);
ExtensionTelemetryService::~ExtensionTelemetryService() = default;
// static
ExtensionTelemetryService* ExtensionTelemetryService::Get(Profile* profile) {
return ExtensionTelemetryServiceFactory::GetInstance()->GetForProfile(
profile);
}
ExtensionTelemetryService::ExtensionTelemetryService(
Profile* profile,
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory)
: profile_(profile),
extension_registry_(extensions::ExtensionRegistry::Get(profile)),
extension_prefs_(extensions::ExtensionPrefs::Get(profile)),
offstore_file_data_collection_duration_limit_(
kOffstoreFileDataCollectionDurationLimitSeconds),
current_reporting_interval_(kUploadIntervalSeconds),
url_loader_factory_(url_loader_factory),
num_checks_per_upload_interval_(kNumChecksPerUploadInterval) {
// Register for SB preference change notifications.
pref_service_ = profile_->GetPrefs();
pref_change_registrar_.Init(pref_service_);
pref_change_registrar_.Add(
prefs::kSafeBrowsingEnhanced,
base::BindRepeating(&ExtensionTelemetryService::OnESBPrefChanged,
base::Unretained(this)));
// Set initial enable/disable state for ESB.
SetEnabledForESB(IsEnhancedProtectionEnabled(*pref_service_));
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
// Register for enterprise policy changes.
auto* connector_service =
enterprise_connectors::ConnectorsServiceFactory::GetForBrowserContext(
profile);
connector_service->ObserveTelemetryReporting(base::BindRepeating(
&ExtensionTelemetryService::OnEnterprisePolicyChanged,
base::Unretained(this)));
// Set initial enable/disable state for enterprise.
SetEnabledForEnterprise(
GetExtensionTelemetryEventRouter(profile_)->IsPolicyEnabled());
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
}
void ExtensionTelemetryService::RecordSignalType(
ExtensionSignalType signal_type) {
base::UmaHistogramEnumeration(
"SafeBrowsing.ExtensionTelemetry.Signals.SignalType", signal_type);
}
void ExtensionTelemetryService::RecordSignalDiscarded(
ExtensionSignalType signal_type) {
base::UmaHistogramEnumeration(
"SafeBrowsing.ExtensionTelemetry.Signals.Discarded", signal_type);
}
void ExtensionTelemetryService::OnESBPrefChanged() {
SetEnabledForESB(IsEnhancedProtectionEnabled(*pref_service_));
}
void ExtensionTelemetryService::OnEnterprisePolicyChanged() {
if (is_shutdown_) {
return;
}
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
SetEnabledForEnterprise(
GetExtensionTelemetryEventRouter(profile_)->IsPolicyEnabled());
#else
SetEnabledForEnterprise(false);
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
}
// Telemetry features for ESB include:
// - ESB signals
// - Search hijacking signal
// - Off-store data collection
// - Command line extensions file data
// - Telemetry Configuration
// - Persister
void ExtensionTelemetryService::SetEnabledForESB(bool enable) {
// Make call idempotent.
if (esb_enabled_ == enable) {
return;
}
esb_enabled_ = enable;
if (esb_enabled_) {
SetUpSignalProcessorsAndSubscribersForESB();
SetUpOffstoreFileDataCollection();
if (base::FeatureList::IsEnabled(
kExtensionTelemetrySearchHijackingSignal)) {
auto* template_url_service =
TemplateURLServiceFactory::GetForProfile(profile_);
search_hijacking_detector_ = std::make_unique<SearchHijackingDetector>(
pref_service_, template_url_service);
search_hijacking_detector_->SetHeuristicCheckInterval(base::Seconds(
kExtensionTelemetrySearchHijackingSignalHeuristicCheckIntervalSeconds
.Get()));
search_hijacking_detector_->SetHeuristicThreshold(
kExtensionTelemetrySearchHijackingSignalHeuristicThreshold.Get());
}
// File data for Command Line extensions.
if (base::FeatureList::IsEnabled(
kExtensionTelemetryFileDataForCommandLineExtensions)) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::BindOnce(CollectCommandLineExtensionInfo),
base::BindOnce(
&ExtensionTelemetryService::OnCommandLineExtensionsInfoCollected,
weak_factory_.GetWeakPtr()));
}
// Telemetry Configuration
if (base::FeatureList::IsEnabled(kExtensionTelemetryConfiguration)) {
config_manager_ =
std::make_unique<ExtensionTelemetryConfigManager>(pref_service_);
config_manager_->LoadConfig();
}
if (current_reporting_interval_.is_positive()) {
int max_files_supported =
ExtensionTelemetryPersister::MaxFilesSupported();
int checks_per_interval =
std::min(max_files_supported, num_checks_per_upload_interval_);
// Configure persister for the maximum number of reports to be persisted
// on disk. This number is the sum of reports written at every
// write interval plus an additional allocation (3) for files written at
// browser/profile shutdown.
int max_reports_to_persist =
std::min(checks_per_interval + 3, max_files_supported);
// Instantiate persister which is used to read/write telemetry reports to
// disk and start timer for sending periodic telemetry reports.
persister_ = base::SequenceBound<ExtensionTelemetryPersister>(
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}),
max_reports_to_persist, profile_->GetPath());
persister_.AsyncCall(&ExtensionTelemetryPersister::PersisterInit);
timer_.Start(FROM_HERE, current_reporting_interval_ / checks_per_interval,
this, &ExtensionTelemetryService::PersistOrUploadData);
}
// Post this task with a delay to avoid running right at Chrome startup
// when a lot of other startup tasks are running.
base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ExtensionTelemetryService::StartUploadCheck,
weak_factory_.GetWeakPtr()),
kStartupUploadCheckDelaySeconds);
} else {
// Stop timer for periodic telemetry reports.
timer_.Stop();
// Clear all data stored by the service.
extension_store_.clear();
// Destruct signal subscribers.
signal_subscribers_.clear();
// Destruct signal processors.
signal_processors_.clear();
// Destruct search hijacking detector.
if (search_hijacking_detector_) {
// Clear all persisted data.
search_hijacking_detector_->ClearAllDataFromPrefs();
search_hijacking_detector_.reset();
}
// Delete persisted files.
if (!persister_.is_null()) {
persister_.AsyncCall(&ExtensionTelemetryPersister::ClearPersistedFiles);
}
StopOffstoreFileDataCollection();
}
}
// Telemetry features for enterprise include:
// - Enterprise signals
// - Off-store data collection
void ExtensionTelemetryService::SetEnabledForEnterprise(bool enable) {
// Make call idempotent.
if (enterprise_enabled_ == enable) {
return;
}
enterprise_enabled_ = enable;
if (enterprise_enabled_) {
SetUpSignalProcessorsAndSubscribersForEnterprise();
SetUpOffstoreFileDataCollection();
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
enterprise_timer_.Start(
FROM_HERE, kExtensionTelemetryEnterpriseReportingIntervalSeconds, this,
&ExtensionTelemetryService::CreateAndSendEnterpriseReport);
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
} else {
// Stop enterprise timer for periodic telemetry reports.
enterprise_timer_.Stop();
// Clear all enterprise data stored by the service.
enterprise_extension_store_.clear();
// Destruct signal subscribers.
enterprise_signal_subscribers_.clear();
// Destruct signal processors.
enterprise_signal_processors_.clear();
StopOffstoreFileDataCollection();
}
}
bool ExtensionTelemetryService::enabled() const {
return esb_enabled_ || enterprise_enabled_;
}
void ExtensionTelemetryService::Shutdown() {
is_shutdown_ = true;
if (esb_enabled_ && SignalDataPresent() && !persister_.is_null()) {
// Saving data to disk.
active_report_ = CreateReport();
std::string write_string;
active_report_->SerializeToString(&write_string);
persister_.AsyncCall(&ExtensionTelemetryPersister::WriteReport)
.WithArgs(std::move(write_string),
ExtensionTelemetryPersister::WriteReportTrigger::kAtShutdown);
RecordWhenFileWasPersisted(/*persisted_at_write_interval=*/false);
}
timer_.Stop();
enterprise_timer_.Stop();
pref_change_registrar_.RemoveAll();
StopOffstoreFileDataCollection();
}
bool ExtensionTelemetryService::SignalDataPresent() const {
return (extension_store_.size() > 0);
}
bool ExtensionTelemetryService::IsSignalEnabled(
const extensions::ExtensionId& extension_id,
ExtensionSignalType signal_type) const {
return config_manager_->IsSignalEnabled(extension_id, signal_type);
}
void ExtensionTelemetryService::AddSignal(
std::unique_ptr<ExtensionSignal> signal) {
ExtensionSignalType signal_type = signal->GetType();
if (esb_enabled_) {
RecordSignalType(signal_type);
AddSignalHelper(*signal, extension_store_, signal_subscribers_);
}
if (enterprise_enabled_ && CollectForEnterprise(signal_type)) {
RecordSignalTypeForEnterprise(signal_type);
AddSignalHelper(*signal, enterprise_extension_store_,
enterprise_signal_subscribers_);
}
}
void ExtensionTelemetryService::OnOmniboxSearch(
const AutocompleteMatch& match) {
if (search_hijacking_detector_) {
search_hijacking_detector_->OnOmniboxSearch(match);
}
}
void ExtensionTelemetryService::AddSignalHelper(
const ExtensionSignal& signal,
ExtensionStore& store,
SignalSubscribers& subscribers) {
ExtensionSignalType signal_type = signal.GetType();
DCHECK(base::Contains(subscribers, signal_type));
if (!store.contains(signal.extension_id())) {
// This is the first signal triggered by this extension since the last
// time a report was generated for it. Store its information.
// Note: The extension information is cached at signal addition time
// instead of being calculated at report generation. This approach handles
// the case where the extension is uninstalled after generating the signal
// but before a report is generated. The extension information is also
// cleared after each telemetry report is sent to keep the data fresh.
const extensions::Extension* extension =
extension_registry_->GetInstalledExtension(signal.extension_id());
// Do a sanity check on the returned extension object and abort if it is
// invalid.
if (!extension) {
RecordSignalDiscarded(signal_type);
return;
}
store.emplace(signal.extension_id(), GetExtensionInfoForReport(*extension));
}
for (safe_browsing::ExtensionSignalProcessor* processor :
subscribers[signal_type]) {
processor->ProcessSignal(signal);
}
}
std::unique_ptr<ExtensionTelemetryReportRequest>
ExtensionTelemetryService::CreateReportWithCommonFieldsPopulated() {
auto telemetry_report_pb =
std::make_unique<ExtensionTelemetryReportRequest>();
telemetry_report_pb->set_developer_mode_enabled(
profile_->GetPrefs()->GetBoolean(prefs::kExtensionsUIDeveloperMode));
telemetry_report_pb->set_creation_timestamp_msec(
base::Time::Now().InMillisecondsSinceUnixEpoch());
if (search_hijacking_detector_) {
std::unique_ptr<SearchHijackingSignal> signal =
search_hijacking_detector_->GetSignalForReport();
if (signal) {
telemetry_report_pb->set_allocated_search_hijacking_signal(
signal.release());
}
}
// Only collect if `is_shutdown_` is false, since BrowserContextHelper can be
// destroyed already and cause a crash on ChromeOS.
// TODO(crbug.com/367327319): Investigate keyed service dependency order to
// guarantee BrowserContextHelper lifetime during shutdown.
if (base::FeatureList::IsEnabled(kExtensionTelemetryIncludePolicyData) &&
!is_shutdown_) {
// The highest level of ManagementAuthorityTrustworthiness of either
// platform or browser are taken into account.
policy::ManagementAuthorityTrustworthiness platform_trustworthiness =
policy::ManagementServiceFactory::GetForPlatform()
->GetManagementAuthorityTrustworthiness();
policy::ManagementAuthorityTrustworthiness browser_trustworthiness =
policy::ManagementServiceFactory::GetForProfile(profile_)
->GetManagementAuthorityTrustworthiness();
policy::ManagementAuthorityTrustworthiness highest_trustworthiness =
std::max(platform_trustworthiness, browser_trustworthiness);
telemetry_report_pb->set_management_authority(
GetManagementAuthority(highest_trustworthiness));
}
return telemetry_report_pb;
}
void ExtensionTelemetryService::CreateAndUploadReport() {
if (!esb_enabled_) {
return;
}
active_report_ = CreateReport();
if (!active_report_) {
return;
}
auto upload_data = std::make_unique<std::string>();
if (!active_report_->SerializeToString(upload_data.get())) {
active_report_.reset();
return;
}
UploadReport(std::move(upload_data));
}
void ExtensionTelemetryService::CreateAndSendEnterpriseReport() {
if (!enterprise_enabled_) {
return;
}
std::unique_ptr<ExtensionTelemetryReportRequest> enterprise_report =
CreateReportForEnterprise();
if (enterprise_report) {
RecordEnterpriseReportSize(enterprise_report->ByteSizeLong());
#if BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
GetExtensionTelemetryEventRouter(profile_)->UploadTelemetryReport(
std::move(enterprise_report));
#endif // BUILDFLAG(ENTERPRISE_CLOUD_CONTENT_ANALYSIS)
} else {
DLOG(WARNING) << "Upload skipped due to empty enterprise report.";
}
}
void ExtensionTelemetryService::OnUploadComplete(
bool success,
const std::string& response_data) {
// TODO(crbug.com/40253384): Add `config_manager_` implementation
// to check server response and update config.
if (success) {
SetLastUploadTimeForExtensionTelemetry(*pref_service_, base::Time::Now());
ExtensionTelemetryReportResponse response;
if (response.ParseFromString(response_data)) {
ProcessOffstoreExtensionVerdicts(response);
}
}
if (esb_enabled_ && !persister_.is_null()) {
// Upload saved report(s) if there are any.
if (success) {
// Bind the callback to our current thread.
auto read_callback =
base::BindOnce(&ExtensionTelemetryService::UploadPersistedFile,
weak_factory_.GetWeakPtr());
persister_.AsyncCall(&ExtensionTelemetryPersister::ReadReport)
.Then(std::move(read_callback));
} else {
// Save report to disk on a failed upload.
std::string write_string;
active_report_->SerializeToString(&write_string);
persister_.AsyncCall(&ExtensionTelemetryPersister::WriteReport)
.WithArgs(std::move(write_string),
ExtensionTelemetryPersister::WriteReportTrigger::
kAtWriteInterval);
RecordWhenFileWasPersisted(/*persisted_at_write_interval=*/true);
}
}
active_report_.reset();
active_uploader_.reset();
}
void ExtensionTelemetryService::UploadPersistedFile(std::string report) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!report.empty()) {
auto upload_data = std::make_unique<std::string>(report);
// Ensure that `upload_data` is a properly formatted protobuf. If it
// isn't, skip the upload and attempt to upload the next persisted file.
active_report_ = std::make_unique<ExtensionTelemetryReportRequest>();
if (active_report_->ParseFromString(*upload_data.get())) {
UploadReport(std::move(upload_data));
} else {
active_report_.reset();
auto read_callback =
base::BindOnce(&ExtensionTelemetryService::UploadPersistedFile,
weak_factory_.GetWeakPtr());
persister_.AsyncCall(&ExtensionTelemetryPersister::ReadReport)
.Then(std::move(read_callback));
}
}
}
void ExtensionTelemetryService::UploadReport(
std::unique_ptr<std::string> report) {
auto callback = base::BindOnce(&ExtensionTelemetryService::OnUploadComplete,
weak_factory_.GetWeakPtr());
active_uploader_ = std::make_unique<ExtensionTelemetryUploader>(
std::move(callback), url_loader_factory_, std::move(report),
GetTokenFetcher());
active_uploader_->Start();
}
void ExtensionTelemetryService::StartUploadCheck() {
// This check is performed as a delayed task after enabling the service. The
// service may become disabled between the time this task is scheduled and it
// actually runs. So make sure service is enabled before performing the check.
if (!esb_enabled_) {
return;
}
if ((GetLastUploadTimeForExtensionTelemetry(*pref_service_) +
current_reporting_interval_) <= base::Time::Now()) {
CreateAndUploadReport();
}
}
void ExtensionTelemetryService::PersistOrUploadData() {
if (search_hijacking_detector_) {
search_hijacking_detector_->MaybeCheckForHeuristicMatch();
}
if (GetLastUploadTimeForExtensionTelemetry(*pref_service_) +
current_reporting_interval_ <=
base::Time::Now()) {
CreateAndUploadReport();
} else {
// Otherwise persist data gathered so far.
active_report_ = CreateReport();
if (!active_report_) {
return;
}
std::string write_string;
if (!active_report_->SerializeToString(&write_string)) {
active_report_.reset();
return;
}
persister_.AsyncCall(&ExtensionTelemetryPersister::WriteReport)
.WithArgs(
std::move(write_string),
ExtensionTelemetryPersister::WriteReportTrigger::kAtWriteInterval);
RecordWhenFileWasPersisted(/*persisted_at_write_interval=*/true);
}
}
std::unique_ptr<ExtensionTelemetryReportRequest>
ExtensionTelemetryService::CreateReport() {
if (!esb_enabled_) {
return nullptr;
}
// Don't create a telemetry report if there were no signals generated (i.e.,
// extension store is empty) AND there are no installed or command-line
// extensions present.
extensions::ExtensionSet extensions_to_report =
extension_registry_->GenerateInstalledExtensionsSet();
extensions_to_report.InsertAll(commandline_extensions_);
if (extension_store_.empty() && extensions_to_report.empty()) {
return nullptr;
}
std::unique_ptr<ExtensionTelemetryReportRequest> telemetry_report_pb =
CreateReportWithCommonFieldsPopulated();
RepeatedPtrField<ExtensionTelemetryReportRequest_Report>* reports_pb =
telemetry_report_pb->mutable_reports();
// Create per-extension reports for all the extensions in the extension store.
// These represent extensions that have signal information to report.
for (auto& extension_store_it : extension_store_) {
auto report_entry_pb =
std::make_unique<ExtensionTelemetryReportRequest_Report>();
// Populate all signal info for the extension by querying the signal
// processors.
RepeatedPtrField<ExtensionTelemetryReportRequest_SignalInfo>* signals_pb =
report_entry_pb->mutable_signals();
for (auto& processor_it : signal_processors_) {
std::unique_ptr<ExtensionTelemetryReportRequest_SignalInfo>
signal_info_pb = processor_it.second->GetSignalInfoForReport(
extension_store_it.first);
if (signal_info_pb) {
RecordSignalUploadSize(signal_info_pb->ByteSizeLong(),
processor_it.first);
signals_pb->AddAllocated(signal_info_pb.release());
}
}
report_entry_pb->set_allocated_extension(
extension_store_it.second.release());
reports_pb->AddAllocated(report_entry_pb.release());
}
// Create per-extension reports for the remaining extensions, i.e., exclude
// extension store extensions since reports have already been created for
// them. Note that these remaining extension reports will only contain
// extension information (and no signal data).
for (const auto& entry : extension_store_) {
extensions_to_report.Remove(/*extension_id=*/entry.first);
}
for (const scoped_refptr<const extensions::Extension>& installed_entry :
extensions_to_report) {
auto report_entry_pb =
std::make_unique<ExtensionTelemetryReportRequest_Report>();
report_entry_pb->set_allocated_extension(
GetExtensionInfoForReport(*installed_entry.get()).release());
reports_pb->AddAllocated(report_entry_pb.release());
}
DCHECK(!reports_pb->empty());
// Clear out the extension store data to ensure that:
// - extension info is refreshed for every telemetry report.
// - no stale extension entry is left over in the extension store.
extension_store_.clear();
return telemetry_report_pb;
}
std::unique_ptr<ExtensionTelemetryReportRequest>
ExtensionTelemetryService::CreateReportForEnterprise() {
// Don't create a telemetry report if there were no signals generated (i.e.,
// enterprise extension store is empty).
if (!enterprise_enabled_ || enterprise_extension_store_.empty()) {
return nullptr;
}
std::unique_ptr<ExtensionTelemetryReportRequest> telemetry_report_pb =
CreateReportWithCommonFieldsPopulated();
RepeatedPtrField<ExtensionTelemetryReportRequest_Report>* reports_pb =
telemetry_report_pb->mutable_reports();
// Create per-extension reports for all the extensions in the enterprise
// extension store. These represent extensions that have signal information to
// report.
for (auto& extension_store_it : enterprise_extension_store_) {
auto report_entry_pb =
std::make_unique<ExtensionTelemetryReportRequest_Report>();
// Populate all signal info for the extension by querying the signal
// processors.
RepeatedPtrField<ExtensionTelemetryReportRequest_SignalInfo>* signals_pb =
report_entry_pb->mutable_signals();
for (auto& processor_it : enterprise_signal_processors_) {
std::unique_ptr<ExtensionTelemetryReportRequest_SignalInfo>
signal_info_pb = processor_it.second->GetSignalInfoForReport(
extension_store_it.first);
if (signal_info_pb) {
signals_pb->AddAllocated(signal_info_pb.release());
}
}
report_entry_pb->set_allocated_extension(
extension_store_it.second.release());
reports_pb->AddAllocated(report_entry_pb.release());
}
DCHECK(!reports_pb->empty());
// Clear out the enterprise extension store data.
enterprise_extension_store_.clear();
return telemetry_report_pb;
}
std::unique_ptr<SafeBrowsingTokenFetcher>
ExtensionTelemetryService::GetTokenFetcher() {
DCHECK(!profile_->IsOffTheRecord() &&
IsEnhancedProtectionEnabled(*profile_->GetPrefs()));
signin::IdentityManager* identity_manager =
IdentityManagerFactory::GetForProfile(profile_);
if (identity_manager &&
safe_browsing::SyncUtils::IsPrimaryAccountSignedIn(identity_manager)) {
return std::make_unique<SafeBrowsingPrimaryAccountTokenFetcher>(
identity_manager);
}
return nullptr;
}
void ExtensionTelemetryService::DumpReportForTesting(
const ExtensionTelemetryReportRequest& report) {
base::Time creation_time = base::Time::FromMillisecondsSinceUnixEpoch(
report.creation_timestamp_msec());
std::stringstream ss;
ss << "Report creation time: "
<< base::UTF16ToUTF8(TimeFormatShortDateAndTimeWithTimeZone(creation_time))
<< "\n";
ss << "Developer mode enabled: " << report.developer_mode_enabled() << "\n";
ss << "Management authority: " << report.management_authority() << "\n";
const RepeatedPtrField<ExtensionTelemetryReportRequest_Report>& reports =
report.reports();
for (const auto& report_pb : reports) {
const auto& extension_pb = report_pb.extension();
base::Time install_time = base::Time::FromMillisecondsSinceUnixEpoch(
extension_pb.install_timestamp_msec());
ss << "\nExtensionId: " << extension_pb.id() << "\n"
<< " Name: " << extension_pb.name() << "\n"
<< " Version: " << extension_pb.version() << "\n"
<< " InstallTime: "
<< base::UTF16ToUTF8(
TimeFormatShortDateAndTimeWithTimeZone(install_time))
<< "\n"
<< " InstalledByDefault: "
<< (extension_pb.is_default_installed() ? "Y" : "N")
<< " InstalledByOEM: " << (extension_pb.is_oem_installed() ? "Y" : "N")
<< "\n"
<< " InstalledFromCWS: " << (extension_pb.is_from_store() ? "Y" : "N")
<< " UpdatesFromCWS: "
<< (extension_pb.updates_from_store() ? "Y" : "N") << "\n"
<< " ConvertedFromUserScript: "
<< (extension_pb.is_converted_from_user_script() ? "Y" : "N") << "\n"
<< " Type: " << extension_pb.type()
<< " InstallLocation: " << extension_pb.install_location()
<< " BlocklistState: " << extension_pb.blocklist_state() << "\n"
<< " DisableReasons: 0x" << std::hex << extension_pb.disable_reasons()
<< "\n"
<< " InstallationPolicy: " << extension_pb.installation_policy()
<< "\n";
if (extension_pb.has_manifest_json()) {
ss << " ManifestJSON: " << extension_pb.manifest_json() << "\n";
}
const RepeatedPtrField<
ExtensionTelemetryReportRequest_ExtensionInfo_FileInfo>& file_infos =
extension_pb.file_infos();
if (!file_infos.empty()) {
ss << " FileInfos: \n";
for (const auto& file_info : file_infos) {
ss << " File name: " << file_info.name()
<< " File hash: " << file_info.hash() << "\n";
}
}
const RepeatedPtrField<ExtensionTelemetryReportRequest_SignalInfo>&
signals = report_pb.signals();
for (const auto& signal_pb : signals) {
// Tabs Api
if (signal_pb.has_tabs_api_info()) {
const auto& tabs_api_info_pb = signal_pb.tabs_api_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_TabsApiInfo_CallDetails>&
call_details = tabs_api_info_pb.call_details();
if (!call_details.empty()) {
ss << " Signal: TabsApi\n";
for (const auto& entry : call_details) {
ss << " Call Details:\n"
<< " API method: "
<< base::NumberToString(static_cast<int>(entry.method())) << "\n"
<< " current URL: " << entry.current_url() << "\n"
<< " new URL: " << entry.new_url() << "\n"
<< " count: " << entry.count() << "\n";
const auto& js_callstacks = entry.js_callstacks();
int stack_idx = 0;
for (const auto& stack : js_callstacks) {
ss << " JS callstack " << stack_idx++ << " :";
ss << ExtensionJSCallStacks::SignalInfoJSCallStackAsString(stack);
}
}
}
continue;
}
// Tabs Execute Script
if (signal_pb.has_tabs_execute_script_info()) {
const auto& tabs_execute_script_info_pb =
signal_pb.tabs_execute_script_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_TabsExecuteScriptInfo_ScriptInfo>&
scripts = tabs_execute_script_info_pb.scripts();
if (!scripts.empty()) {
ss << " Signal: TabsExecuteScript\n";
for (const auto& script_pb : scripts) {
ss << " Script hash: " << base::HexEncode(script_pb.hash())
<< " count: " << script_pb.execution_count() << "\n";
}
}
continue;
}
// Remote Host Contacted
if (signal_pb.has_remote_host_contacted_info()) {
const auto& remote_host_contacted_info_pb =
signal_pb.remote_host_contacted_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_RemoteHostContactedInfo_RemoteHostInfo>&
remote_host_infos = remote_host_contacted_info_pb.remote_host();
if (!remote_host_infos.empty()) {
ss << " Signal: RemoteHostContacted\n";
for (const auto& remote_host_info_pb : remote_host_infos) {
ss << " RemoteHostInfo:\n"
<< " URL: " << remote_host_info_pb.url() << "\n"
<< " ConnectionProtocol: "
<< remote_host_info_pb.connection_protocol() << "\n"
<< " ContactedBy: " << remote_host_info_pb.contacted_by()
<< "\n"
<< " count: " << remote_host_info_pb.contact_count()
<< "\n";
}
}
continue;
}
// Cookies Get All
if (signal_pb.has_cookies_get_all_info()) {
const auto& cookies_get_all_info_pb = signal_pb.cookies_get_all_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_CookiesGetAllInfo_GetAllArgsInfo>&
get_all_args_infos = cookies_get_all_info_pb.get_all_args_info();
if (!get_all_args_infos.empty()) {
ss << " Signal: CookiesGetAll\n";
for (const auto& get_all_args_pb : get_all_args_infos) {
ss << " GetAllArgsInfo:\n"
<< " Domain: " << get_all_args_pb.domain() << "\n"
<< " Name: " << get_all_args_pb.name() << "\n"
<< " Path: " << get_all_args_pb.path() << "\n"
<< " Secure: " << (get_all_args_pb.secure() ? "Y" : "N")
<< "\n"
<< " StoreId: " << get_all_args_pb.store_id() << "\n"
<< " URL: " << get_all_args_pb.url() << "\n"
<< " IsSession: "
<< (get_all_args_pb.is_session() ? "Y" : "N") << "\n"
<< " count: " << get_all_args_pb.count() << "\n";
const auto& js_callstacks = get_all_args_pb.js_callstacks();
int stack_idx = 0;
for (const auto& stack : js_callstacks) {
ss << " JS callstack " << stack_idx++ << " :";
ss << ExtensionJSCallStacks::SignalInfoJSCallStackAsString(stack);
}
}
}
continue;
}
// Cookies Get
if (signal_pb.has_cookies_get_info()) {
const auto& cookies_get_info_pb = signal_pb.cookies_get_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_CookiesGetInfo_GetArgsInfo>&
get_args_infos = cookies_get_info_pb.get_args_info();
if (!get_args_infos.empty()) {
ss << " Signal: CookiesGet\n";
for (const auto& get_args_pb : get_args_infos) {
ss << " GetArgsInfo:\n"
<< " Name: " << get_args_pb.name() << "\n"
<< " URL: " << get_args_pb.url() << "\n"
<< " StoreId: " << get_args_pb.store_id() << "\n"
<< " count: " << get_args_pb.count() << "\n";
const auto& js_callstacks = get_args_pb.js_callstacks();
int stack_idx = 0;
for (const auto& stack : js_callstacks) {
ss << " JS callstack " << stack_idx++ << " :";
ss << ExtensionJSCallStacks::SignalInfoJSCallStackAsString(stack);
}
}
}
continue;
}
// Potential Password Theft
if (signal_pb.has_potential_password_theft_info()) {
const auto& potential_password_theft_info_pb =
signal_pb.potential_password_theft_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_PotentialPasswordTheftInfo_PasswordReuseInfo>&
reused_password_infos =
potential_password_theft_info_pb.reused_password_infos();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_PotentialPasswordTheftInfo_RemoteHostData>&
remote_hosts_data =
potential_password_theft_info_pb.remote_hosts_data();
if (!reused_password_infos.empty() && !remote_hosts_data.empty()) {
ss << " Signal: PotentialPasswordTheft\n";
for (const auto& remote_hosts_data_pb : remote_hosts_data) {
ss << " RemoteHostData:\n"
<< " URL: " << remote_hosts_data_pb.remote_host_url()
<< "\n"
<< " count: " << remote_hosts_data_pb.count() << "\n";
}
for (const auto& reused_password_infos_pb : reused_password_infos) {
ss << " PasswordReuseInfo:\n";
ss << " DomainsMatchingPassword:\n";
for (const std::string& matching_domain :
reused_password_infos_pb.domains_matching_password()) {
ss << " " << matching_domain << "\n";
}
ss << " IsChromeSigninPassword: "
<< (reused_password_infos_pb.is_chrome_signin_password() ? "Y"
: "N")
<< "\n";
ss << " ReusedPasswordAccountType:\n";
ss << " IsAccountSyncing: "
<< (reused_password_infos_pb.reused_password_account_type()
.is_account_syncing()
? "Y"
: "N")
<< "\n";
ss << " AccountType: "
<< reused_password_infos_pb.reused_password_account_type()
.account_type()
<< "\n";
ss << " ReuseCount: " << reused_password_infos_pb.count()
<< "\n";
}
}
}
// Declarative Net Request Api
if (signal_pb.has_declarative_net_request_info()) {
const auto& declarative_net_request_info_pb =
signal_pb.declarative_net_request_info();
const RepeatedPtrField<std::string>& rules =
declarative_net_request_info_pb.rules();
if (!rules.empty()) {
ss << " Signal: DeclarativeNetRequest\n";
for (const auto& rule : rules) {
ss << " Rule:" << rule << "\n";
}
ss << " MaxExceededRulesCount:"
<< declarative_net_request_info_pb.max_exceeded_rules_count()
<< "\n";
}
continue;
}
// Declarative Net Request Action
if (signal_pb.has_declarative_net_request_action_info()) {
const auto& dnr_action_info_pb =
signal_pb.declarative_net_request_action_info();
const RepeatedPtrField<
ExtensionTelemetryReportRequest_SignalInfo_DeclarativeNetRequestActionInfo_ActionDetails>&
action_details = dnr_action_info_pb.action_details();
if (!action_details.empty()) {
ss << " Signal: DeclarativeNetRequestAction\n";
for (const auto& entry : action_details) {
ss << " Action Details:\n"
<< " action type: "
<< base::NumberToString(static_cast<int>(entry.type())) << "\n"
<< " request URL: " << entry.request_url() << "\n"
<< " redirect URL: " << entry.redirect_url() << "\n"
<< " count: " << entry.count() << "\n";
}
}
continue;
}
}
}
DVLOG(1) << "Telemetry Report: " << ss.str();
}
ExtensionTelemetryService::OffstoreExtensionFileData::
OffstoreExtensionFileData() = default;
ExtensionTelemetryService::OffstoreExtensionFileData::
~OffstoreExtensionFileData() = default;
ExtensionTelemetryService::OffstoreExtensionFileData::
OffstoreExtensionFileData::OffstoreExtensionFileData(
const OffstoreExtensionFileData& src) = default;
std::optional<ExtensionTelemetryService::OffstoreExtensionFileData>
ExtensionTelemetryService::RetrieveOffstoreFileDataForReport(
const extensions::ExtensionId& extension_id) {
const auto& pref_dict = GetExtensionTelemetryFileData(*pref_service_);
const base::Value::Dict* extension_dict = pref_dict.FindDict(extension_id);
if (!extension_dict) {
return std::nullopt;
}
const base::Value::Dict* file_data_dict =
extension_dict->FindDict(kFileDataDictPref);
if (!file_data_dict || file_data_dict->empty()) {
return std::nullopt;
}
OffstoreExtensionFileData offstore_extension_file_data;
base::Value::Dict dict = file_data_dict->Clone();
std::optional<base::Value> manifest_value = dict.Extract(kManifestFile);
if (manifest_value.has_value()) {
offstore_extension_file_data.manifest =
std::move(manifest_value.value().GetString());
}
for (auto&& [file_name, file_hash] : dict) {
ExtensionTelemetryReportRequest_ExtensionInfo_FileInfo file_info;
file_info.set_name(std::move(file_name));
file_info.set_hash(std::move(file_hash.GetString()));
offstore_extension_file_data.file_infos.emplace_back(std::move(file_info));
}
return std::make_optional(offstore_extension_file_data);
}
std::unique_ptr<ExtensionInfo>
ExtensionTelemetryService::GetExtensionInfoForReport(
const extensions::Extension& extension) {
ExtensionManagement* extension_management =
extensions::ExtensionManagementFactory::GetForBrowserContext(profile_);
auto extension_info = std::make_unique<ExtensionInfo>();
extension_info->set_id(extension.id());
extension_info->set_name(extension.name());
extension_info->set_version(extension.version().GetString());
if (extension.location() == ManifestLocation::kCommandLine) {
// Set the install timestamp to 0 explicitly for a command-line extension
// to indicate that it is not actually installed. This is required
// because the extension may still have associated extension prefs
// from the last time it was installed (eg. when ESB was disabled).
extension_info->set_install_timestamp_msec(0);
} else {
extension_info->set_install_timestamp_msec(
GetLastUpdateTime(extension_prefs_, extension.id())
.InMillisecondsSinceUnixEpoch());
}
extension_info->set_is_default_installed(
extension.was_installed_by_default());
extension_info->set_is_oem_installed(extension.was_installed_by_oem());
extension_info->set_is_from_store(extension.from_webstore());
extension_info->set_updates_from_store(
extension_management->UpdatesFromWebstore(extension));
extension_info->set_is_converted_from_user_script(
extension.converted_from_user_script());
extension_info->set_type(GetType(extension.GetType()));
extension_info->set_install_location(
GetInstallLocation(extension.location()));
extension_info->set_blocklist_state(
GetBlocklistState(extension.id(), extension_prefs_));
extension_info->set_telemetry_blocklist_state(
GetExtensionTelemetryServiceBlocklistState(extension.id(),
extension_prefs_));
// Use the GetRawDisableReasons() getter here as we want all the disable
// reasons (known and unknown).
extensions::ExtensionPrefs::DisableReasonRawManipulationPasskey passkey;
base::flat_set<int> disable_reasons =
extension_prefs_->GetRawDisableReasons(passkey, extension.id());
for (int reason : disable_reasons) {
extension_info->add_disable_reasons_list(reason);
}
// TODO(crbug.com/372186532): Remove this code and deprecate the
// disable_reasons field in the proto after the Safe Browsing service is
// migrated to use disable_reasons_list.
const int disable_reasons_bitflag =
extensions::IntegerSetToBitflag(disable_reasons);
extension_info->set_disable_reasons(disable_reasons_bitflag);
if (base::FeatureList::IsEnabled(kExtensionTelemetryIncludePolicyData)) {
bool installation_managed =
extension_management->IsInstallationExplicitlyAllowed(extension.id()) ||
extension_management->IsInstallationExplicitlyBlocked(extension.id());
ExtensionInfo::InstallationPolicy installation_policy =
installation_managed
? ExtensionManagementInstallationModeToExtensionInfoInstallationPolicy(
extension_management->GetInstallationMode(&extension))
: ExtensionInfo::NO_POLICY;
extension_info->set_installation_policy(installation_policy);
}
std::optional<OffstoreExtensionFileData> offstore_file_data =
RetrieveOffstoreFileDataForReport(extension.id());
if (offstore_file_data.has_value()) {
extension_info->set_manifest_json(
std::move(offstore_file_data.value().manifest));
for (auto& file_info : offstore_file_data.value().file_infos) {
extension_info->mutable_file_infos()->Add(std::move(file_info));
}
}
return extension_info;
}
void ExtensionTelemetryService::SetUpSignalProcessorsAndSubscribersForESB() {
// Create signal processors.
// Map the processors to the signals they output reports for.
signal_processors_.emplace(ExtensionSignalType::kCookiesGet,
std::make_unique<CookiesGetSignalProcessor>());
signal_processors_.emplace(ExtensionSignalType::kCookiesGetAll,
std::make_unique<CookiesGetAllSignalProcessor>());
signal_processors_.emplace(
ExtensionSignalType::kDeclarativeNetRequestAction,
std::make_unique<DeclarativeNetRequestActionSignalProcessor>());
signal_processors_.emplace(
ExtensionSignalType::kDeclarativeNetRequest,
std::make_unique<DeclarativeNetRequestSignalProcessor>());
signal_processors_.emplace(
ExtensionSignalType::kPotentialPasswordTheft,
std::make_unique<PotentialPasswordTheftSignalProcessor>());
signal_processors_.emplace(
ExtensionSignalType::kRemoteHostContacted,
std::make_unique<RemoteHostContactedSignalProcessor>());
signal_processors_.emplace(ExtensionSignalType::kTabsApi,
std::make_unique<TabsApiSignalProcessor>());
signal_processors_.emplace(
ExtensionSignalType::kTabsExecuteScript,
std::make_unique<TabsExecuteScriptSignalProcessor>());
// Create subscriber lists for each telemetry signal type.
// Map the signal processors to the signals that they consume.
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_cookies_get = {
signal_processors_[ExtensionSignalType::kCookiesGet].get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_cookies_get_all = {
signal_processors_[ExtensionSignalType::kCookiesGetAll].get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_declarative_net_request_action = {
signal_processors_[ExtensionSignalType::kDeclarativeNetRequestAction]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_declarative_net_request = {
signal_processors_[ExtensionSignalType::kDeclarativeNetRequest]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_password_reuse = {
signal_processors_[ExtensionSignalType::kPotentialPasswordTheft]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_remote_host_contacted = {
signal_processors_[ExtensionSignalType::kRemoteHostContacted].get(),
signal_processors_[ExtensionSignalType::kPotentialPasswordTheft]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_tabs_api = {
signal_processors_[ExtensionSignalType::kTabsApi].get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
subscribers_for_tabs_execute_script = {
signal_processors_[ExtensionSignalType::kTabsExecuteScript].get()};
signal_subscribers_.emplace(ExtensionSignalType::kCookiesGet,
std::move(subscribers_for_cookies_get));
signal_subscribers_.emplace(ExtensionSignalType::kCookiesGetAll,
std::move(subscribers_for_cookies_get_all));
signal_subscribers_.emplace(
ExtensionSignalType::kDeclarativeNetRequestAction,
std::move(subscribers_for_declarative_net_request_action));
signal_subscribers_.emplace(
ExtensionSignalType::kDeclarativeNetRequest,
std::move(subscribers_for_declarative_net_request));
signal_subscribers_.emplace(ExtensionSignalType::kPasswordReuse,
std::move(subscribers_for_password_reuse));
signal_subscribers_.emplace(ExtensionSignalType::kRemoteHostContacted,
std::move(subscribers_for_remote_host_contacted));
signal_subscribers_.emplace(ExtensionSignalType::kTabsApi,
std::move(subscribers_for_tabs_api));
signal_subscribers_.emplace(ExtensionSignalType::kTabsExecuteScript,
std::move(subscribers_for_tabs_execute_script));
}
void ExtensionTelemetryService::
SetUpSignalProcessorsAndSubscribersForEnterprise() {
// Create signal processors.
// Map the processors to the signals they output reports for.
enterprise_signal_processors_.emplace(
ExtensionSignalType::kCookiesGet,
std::make_unique<CookiesGetSignalProcessor>());
enterprise_signal_processors_.emplace(
ExtensionSignalType::kCookiesGetAll,
std::make_unique<CookiesGetAllSignalProcessor>());
enterprise_signal_processors_.emplace(
ExtensionSignalType::kRemoteHostContacted,
std::make_unique<RemoteHostContactedSignalProcessor>());
enterprise_signal_processors_.emplace(
ExtensionSignalType::kTabsApi,
std::make_unique<TabsApiSignalProcessor>());
// Create subscriber lists for each telemetry signal type.
// Map the signal processors to the signals that they consume.
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
enterprise_subscribers_for_cookies_get = {
enterprise_signal_processors_[ExtensionSignalType::kCookiesGet]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
enterprise_subscribers_for_cookies_get_all = {
enterprise_signal_processors_[ExtensionSignalType::kCookiesGetAll]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
enterprise_subscribers_for_remote_host_contacted = {
enterprise_signal_processors_
[ExtensionSignalType::kRemoteHostContacted]
.get()};
std::vector<raw_ptr<ExtensionSignalProcessor, VectorExperimental>>
enterprise_subscribers_for_tabs_api = {
enterprise_signal_processors_[ExtensionSignalType::kTabsApi].get()};
enterprise_signal_subscribers_.emplace(
ExtensionSignalType::kCookiesGet,
std::move(enterprise_subscribers_for_cookies_get));
enterprise_signal_subscribers_.emplace(
ExtensionSignalType::kCookiesGetAll,
std::move(enterprise_subscribers_for_cookies_get_all));
enterprise_signal_subscribers_.emplace(
ExtensionSignalType::kRemoteHostContacted,
std::move(enterprise_subscribers_for_remote_host_contacted));
enterprise_signal_subscribers_.emplace(
ExtensionSignalType::kTabsApi,
std::move(enterprise_subscribers_for_tabs_api));
}
ExtensionTelemetryService::OffstoreExtensionFileDataContext::
OffstoreExtensionFileDataContext(
const extensions::ExtensionId& extension_id,
const base::FilePath& root_dir)
: extension_id(extension_id), root_dir(root_dir) {}
ExtensionTelemetryService::OffstoreExtensionFileDataContext::
OffstoreExtensionFileDataContext(
const extensions::ExtensionId& extension_id,
const base::FilePath& root_dir,
const base::Time& last_processed_time)
: extension_id(extension_id),
root_dir(root_dir),
last_processed_time(last_processed_time) {}
bool ExtensionTelemetryService::OffstoreExtensionFileDataContext::operator<(
const OffstoreExtensionFileDataContext& other) const {
// Use extension_id to break ties.
return std::tie(last_processed_time, extension_id) <
std::tie(other.last_processed_time, other.extension_id);
}
void ExtensionTelemetryService::SetUpOffstoreFileDataCollection() {
// If both is enabled, set up has already been done.
if (esb_enabled_ && enterprise_enabled_) {
return;
}
// File data collection.
file_processor_ = base::SequenceBound<ExtensionTelemetryFileProcessor>(
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN}));
offstore_file_data_collection_timer_.Start(
FROM_HERE, kOffstoreFileDataCollectionStartupDelaySeconds, this,
&ExtensionTelemetryService::StartOffstoreFileDataCollection);
}
void ExtensionTelemetryService::StartOffstoreFileDataCollection() {
if (!enabled()) {
return;
}
offstore_file_data_collection_start_time_ = base::TimeTicks::Now();
offstore_extension_dirs_.clear();
offstore_extension_file_data_contexts_.clear();
GetOffstoreExtensionDirs();
RemoveStaleExtensionsFileDataFromPref();
// Gather context to process offstore extensions.
const auto& pref_dict = GetExtensionTelemetryFileData(*pref_service_);
for (const auto& [extension_id, root_dir] : offstore_extension_dirs_) {
const base::Value::Dict* extension_dict = pref_dict.FindDict(extension_id);
if (!extension_dict) {
offstore_extension_file_data_contexts_.emplace(extension_id, root_dir);
continue;
}
const base::Value* timestamp_value =
extension_dict->Find(kFileDataProcessTimestampPref);
if (!timestamp_value) {
offstore_extension_file_data_contexts_.emplace(extension_id, root_dir);
continue;
}
std::optional<base::Time> timestamp = base::ValueToTime(timestamp_value);
if (!timestamp.has_value()) {
offstore_extension_file_data_contexts_.emplace(extension_id, root_dir);
} else if (base::Time::Now() - timestamp.value() > base::Days(1)) {
offstore_extension_file_data_contexts_.emplace(extension_id, root_dir,
timestamp.value());
}
}
CollectOffstoreFileData();
}
void ExtensionTelemetryService::OnCommandLineExtensionsInfoCollected(
extensions::ExtensionSet commandline_extensions) {
if (esb_enabled_) {
// Only store this information if the telemetry service is enabled.
commandline_extensions_ = std::move(commandline_extensions);
}
}
void ExtensionTelemetryService::GetOffstoreExtensionDirs() {
const extensions::ExtensionSet installed_extensions =
extension_registry_->GenerateInstalledExtensionsSet();
for (const auto& extension : installed_extensions) {
if (!extension->from_webstore() &&
!extensions::Manifest::IsComponentLocation(extension->location())) {
offstore_extension_dirs_[extension->id()] = extension->path();
}
}
// Also add any extensions that were part of the --load-extension commandline
// switch if applicable. The information about these extensions is collected
// (only one time) off-thread using `CollectCommandLineExtensionsInfo` when
// the extension telemetry service is enabled.
for (const auto& extension : commandline_extensions_) {
offstore_extension_dirs_[extension->id()] = extension->path();
}
RecordNumOffstoreExtensions(offstore_extension_dirs_.size());
}
void ExtensionTelemetryService::RemoveStaleExtensionsFileDataFromPref() {
ScopedDictPrefUpdate pref_update(pref_service_,
prefs::kExtensionTelemetryFileData);
base::Value::Dict& pref_dict = pref_update.Get();
std::vector<extensions::ExtensionId> stale_extensions;
for (auto&& offstore : pref_dict) {
if (offstore_extension_dirs_.find(offstore.first) ==
offstore_extension_dirs_.end()) {
stale_extensions.emplace_back(offstore.first);
}
}
for (const auto& extension : stale_extensions) {
pref_dict.Remove(extension);
}
}
void ExtensionTelemetryService::CollectOffstoreFileData() {
if (!enabled()) {
return;
}
// If data for all offstore extensions has been collected or
// |offstore_file_data_collection_duration_limit_| has been
// exceeded, start the timer again to schedule the next pass of data
// collection.
if (offstore_extension_file_data_contexts_.empty() ||
(base::TimeTicks::Now() - offstore_file_data_collection_start_time_) >=
offstore_file_data_collection_duration_limit_) {
offstore_file_data_collection_timer_.Start(
FROM_HERE, kOffstoreFileDataCollectionIntervalSeconds, this,
&ExtensionTelemetryService::StartOffstoreFileDataCollection);
// Record only if there are off-store extensions installed.
if (!offstore_extension_dirs_.empty()) {
RecordCollectionDuration(base::TimeTicks::Now() -
offstore_file_data_collection_start_time_);
}
return;
}
auto context = offstore_extension_file_data_contexts_.begin();
auto process_extension_callback =
base::BindOnce(&ExtensionTelemetryService::OnOffstoreFileDataCollected,
weak_factory_.GetWeakPtr(), context);
file_processor_.AsyncCall(&ExtensionTelemetryFileProcessor::ProcessExtension)
.WithArgs(context->root_dir)
.Then(std::move(process_extension_callback));
}
void ExtensionTelemetryService::OnOffstoreFileDataCollected(
base::flat_set<OffstoreExtensionFileDataContext>::iterator context,
base::Value::Dict file_data) {
// Save to Prefs
base::Value::Dict extension_dict;
extension_dict.Set(kFileDataProcessTimestampPref,
base::TimeToValue(base::Time::Now()));
extension_dict.Set(kFileDataDictPref, std::move(file_data));
ScopedDictPrefUpdate pref_update(pref_service_,
prefs::kExtensionTelemetryFileData);
pref_update->Set(context->extension_id, std::move(extension_dict));
// Remove context and repeat.
offstore_extension_file_data_contexts_.erase(context);
CollectOffstoreFileData();
}
void ExtensionTelemetryService::StopOffstoreFileDataCollection() {
if (file_processor_.is_null() || timer_.IsRunning() ||
enterprise_timer_.IsRunning()) {
return;
}
offstore_file_data_collection_timer_.Stop();
offstore_extension_dirs_.clear();
offstore_extension_file_data_contexts_.clear();
commandline_extensions_.Clear();
collected_commandline_extension_info_ = false;
}
void ExtensionTelemetryService::ProcessOffstoreExtensionVerdicts(
const ExtensionTelemetryReportResponse& response) {
if (response.offstore_extension_verdicts_size() == 0) {
return;
}
std::map<std::string, extensions::BlocklistState> blocklist_states;
for (const auto& verdict : response.offstore_extension_verdicts()) {
const auto& extension_id = verdict.extension_id();
const extensions::Extension* extension =
extension_registry_->GetInstalledExtension(extension_id);
// Ignore the verdict if the extension is not an off-store extension. This
// should not happen and is just a sanity check.
if (!extension || extension->from_webstore() ||
extensions::Manifest::IsComponentLocation(extension->location())) {
continue;
}
blocklist_states[extension_id] =
ConvertTelemetryResponseVerdictToBlocklistState(verdict.verdict_type());
}
auto* extension_system = extensions::ExtensionSystem::Get(profile_);
if (!extension_system) {
return;
}
extensions::ExtensionService* extension_service =
extension_system->extension_service();
if (!extension_service) {
return;
}
extension_service->PerformActionBasedOnExtensionTelemetryServiceVerdicts(
blocklist_states);
}
base::TimeDelta
ExtensionTelemetryService::GetOffstoreFileDataCollectionStartupDelaySeconds() {
return kOffstoreFileDataCollectionStartupDelaySeconds;
}
base::TimeDelta
ExtensionTelemetryService::GetOffstoreFileDataCollectionIntervalSeconds() {
return kOffstoreFileDataCollectionIntervalSeconds;
}
void ExtensionTelemetryService::OnDseSerpLoaded() {
if (search_hijacking_detector_) {
search_hijacking_detector_->OnDseSerpLoaded();
}
}
} // namespace safe_browsing