blob: a7bd91a84dc746eea91ecdf8068ac6a2962d1e62 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// 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/srt_fetcher_win.h"
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/debug/leak_annotations.h"
#include "base/files/file_path.h"
#include "base/macros.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram_macros.h"
#include "base/metrics/sparse_histogram.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task_runner_util.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/metrics/chrome_metrics_service_accessor.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/profiles/profile_io_data.h"
#include "chrome/browser/safe_browsing/srt_client_info_win.h"
#include "chrome/browser/safe_browsing/srt_global_error_win.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/browser_list_observer.h"
#include "chrome/browser/ui/global_error/global_error_service.h"
#include "chrome/browser/ui/global_error/global_error_service_factory.h"
#include "chrome/common/pref_names.h"
#include "components/component_updater/pref_names.h"
#include "components/data_use_measurement/core/data_use_user_data.h"
#include "components/prefs/pref_service.h"
#include "components/rappor/rappor_service.h"
#include "components/safe_browsing_db/safe_browsing_prefs.h"
#include "components/variations/net/variations_http_headers.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_thread.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_fetcher.h"
#include "net/url_request/url_fetcher_delegate.h"
#include "net/url_request/url_request_context_getter.h"
using content::BrowserThread;
namespace safe_browsing {
// TODO(b/647763) Change the registry key to properly handle cases when the user
// runs Google Chrome stable alongside Google Chrome SxS.
const wchar_t kSoftwareRemovalToolRegistryKey[] =
L"Software\\Google\\Software Removal Tool";
const wchar_t kCleanerSubKey[] = L"Cleaner";
const wchar_t kEndTimeValueName[] = L"EndTime";
const wchar_t kStartTimeValueName[] = L"StartTime";
const char kExtendedSafeBrowsingEnabledSwitch[] =
"extended-safebrowsing-enabled";
const base::Feature kSwReporterExtendedSafeBrowsingFeature{
"SwReporterExtendedSafeBrowsingFeature", base::FEATURE_DISABLED_BY_DEFAULT};
namespace {
// Used to send UMA information about missing start and end time registry
// values for the reporter. Replicated in the histograms.xml file, so the order
// MUST NOT CHANGE.
enum SwReporterRunningTimeRegistryError {
REPORTER_RUNNING_TIME_ERROR_NO_ERROR = 0,
REPORTER_RUNNING_TIME_ERROR_REGISTRY_KEY_INVALID = 1,
REPORTER_RUNNING_TIME_ERROR_MISSING_START_TIME = 2,
REPORTER_RUNNING_TIME_ERROR_MISSING_END_TIME = 3,
REPORTER_RUNNING_TIME_ERROR_MISSING_BOTH_TIMES = 4,
REPORTER_RUNNING_TIME_ERROR_MAX,
};
// Used to send UMA information about the progress of the SwReporter launch and
// prompt sequence. Replicated in the histograms.xml file, so the order MUST
// NOT CHANGE.
enum SwReporterUmaValue {
// Deprecated.
SW_REPORTER_EXPLICIT_REQUEST = 0,
// Deprecated.
SW_REPORTER_STARTUP_RETRY = 1,
// Deprecated.
SW_REPORTER_RETRIED_TOO_MANY_TIMES = 2,
SW_REPORTER_START_EXECUTION = 3,
SW_REPORTER_FAILED_TO_START = 4,
// Deprecated.
SW_REPORTER_REGISTRY_EXIT_CODE = 5,
// Deprecated.
SW_REPORTER_RESET_RETRIES = 6,
SW_REPORTER_DOWNLOAD_START = 7,
SW_REPORTER_NO_BROWSER = 8,
SW_REPORTER_NO_LOCAL_STATE = 9,
SW_REPORTER_NO_PROMPT_NEEDED = 10,
SW_REPORTER_NO_PROMPT_FIELD_TRIAL = 11,
SW_REPORTER_ALREADY_PROMPTED = 12,
SW_REPORTER_RAN_DAILY = 13,
SW_REPORTER_ADDED_TO_MENU = 14,
SW_REPORTER_MAX,
};
// Used to send UMA information showing whether uploading of Software Reporter
// logs is enabled, or the reason why not.
// Replicated in the histograms.xml file, so the order MUST NOT CHANGE.
enum SwReporterLogsUploadsEnabled {
REPORTER_LOGS_UPLOADS_ENABLED = 0,
REPORTER_LOGS_UPLOADS_SBER_DISABLED = 1,
REPORTER_LOGS_UPLOADS_RECENTLY_SENT_LOGS = 2,
REPORTER_LOGS_UPLOADS_MAX,
};
// Used to send UMA information about missing logs upload result in the registry
// for the reporter. Replicated in the histograms.xml file, so the order
// MUST NOT CHANGE.
enum SwReporterLogsUploadResultRegistryError {
REPORTER_LOGS_UPLOAD_RESULT_ERROR_NO_ERROR = 0,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_REGISTRY_KEY_INVALID = 1,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_VALUE_NOT_FOUND = 2,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_VALUE_OUT_OF_BOUNDS = 3,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_MAX,
};
const char kRunningTimeErrorMetricName[] =
"SoftwareReporter.RunningTimeRegistryError";
SwReporterTestingDelegate* g_testing_delegate_ = nullptr;
const wchar_t kScanTimesSubKey[] = L"ScanTimes";
const wchar_t kFoundUwsValueName[] = L"FoundUws";
const wchar_t kMemoryUsedValueName[] = L"MemoryUsed";
const wchar_t kLogsUploadResultValueName[] = L"LogsUploadResult";
const wchar_t kExitCodeValueName[] = L"ExitCode";
const wchar_t kEngineErrorCodeValueName[] = L"EngineErrorCode";
const char kFoundUwsMetricName[] = "SoftwareReporter.FoundUwS";
const char kFoundUwsReadErrorMetricName[] =
"SoftwareReporter.FoundUwSReadError";
const char kScanTimesMetricName[] = "SoftwareReporter.UwSScanTimes";
const char kMemoryUsedMetricName[] = "SoftwareReporter.MemoryUsed";
const char kStepMetricName[] = "SoftwareReporter.Step";
const char kLogsUploadEnabledMetricName[] =
"SoftwareReporter.LogsUploadEnabled";
const char kLogsUploadResultMetricName[] = "SoftwareReporter.LogsUploadResult";
const char kLogsUploadResultRegistryErrorMetricName[] =
"SoftwareReporter.LogsUploadResultRegistryError";
const char kExitCodeMetricName[] = "SoftwareReporter.ExitCodeFromRegistry";
const char kEngineErrorCodeMetricName[] = "SoftwareReporter.EngineErrorCode";
// The max value for histogram SoftwareReporter.LogsUploadResult, which is used
// to send UMA information about the result of Software Reporter's attempt to
// upload logs, when logs are enabled. This value must be consistent with the
// SoftwareReporterLogsUploadResult enum defined in the histograms.xml file.
const int kSwReporterLogsUploadResultMax = 30;
// Reports metrics about the software reporter via UMA (and sometimes Rappor).
class UMAHistogramReporter {
public:
UMAHistogramReporter() : UMAHistogramReporter(std::string()) {}
explicit UMAHistogramReporter(const std::string& suffix)
: suffix_(suffix),
registry_key_(suffix.empty() ? kSoftwareRemovalToolRegistryKey
: base::StringPrintf(
L"%ls\\%ls",
kSoftwareRemovalToolRegistryKey,
base::UTF8ToUTF16(suffix).c_str())) {
}
// Reports the software reporter tool's version via UMA.
void ReportVersion(const base::Version& version) const {
DCHECK(!version.components().empty());
// The minor version is the 2nd last component of the version,
// or just the first component if there is only 1.
uint32_t minor_version = 0;
if (version.components().size() > 1)
minor_version = version.components()[version.components().size() - 2];
else
minor_version = version.components()[0];
RecordSparseHistogram("SoftwareReporter.MinorVersion", minor_version);
// The major version for X.Y.Z is X*256^3+Y*256+Z. If there are additional
// components, only the first three count, and if there are less than 3, the
// missing values are just replaced by zero. So 1 is equivalent 1.0.0.
DCHECK_LT(version.components()[0], 0x100U);
uint32_t major_version = 0x1000000 * version.components()[0];
if (version.components().size() >= 2) {
DCHECK_LT(version.components()[1], 0x10000U);
major_version += 0x100 * version.components()[1];
}
if (version.components().size() >= 3) {
DCHECK_LT(version.components()[2], 0x100U);
major_version += version.components()[2];
}
RecordSparseHistogram("SoftwareReporter.MajorVersion", major_version);
}
void ReportExitCode(int exit_code) const {
RecordSparseHistogram("SoftwareReporter.ExitCode", exit_code);
// Also report the exit code that the reporter writes to the registry.
base::win::RegKey reporter_key;
DWORD exit_code_in_registry;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE) != ERROR_SUCCESS ||
reporter_key.ReadValueDW(kExitCodeValueName, &exit_code_in_registry) !=
ERROR_SUCCESS) {
return;
}
RecordSparseHistogram(kExitCodeMetricName, exit_code_in_registry);
reporter_key.DeleteValue(kExitCodeValueName);
}
void ReportEngineErrorCode() const {
base::win::RegKey reporter_key;
DWORD engine_error_code;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE) != ERROR_SUCCESS ||
reporter_key.ReadValueDW(kEngineErrorCodeValueName,
&engine_error_code) != ERROR_SUCCESS) {
return;
}
RecordSparseHistogram(kEngineErrorCodeMetricName, engine_error_code);
reporter_key.DeleteValue(kEngineErrorCodeValueName);
}
// Reports UwS found by the software reporter tool via UMA and RAPPOR.
void ReportFoundUwS(bool use_rappor) const {
base::win::RegKey reporter_key;
std::vector<base::string16> found_uws_strings;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE) != ERROR_SUCCESS ||
reporter_key.ReadValues(kFoundUwsValueName, &found_uws_strings) !=
ERROR_SUCCESS) {
return;
}
rappor::RapporService* rappor_service = nullptr;
if (use_rappor)
rappor_service = g_browser_process->rappor_service();
bool parse_error = false;
for (const base::string16& uws_string : found_uws_strings) {
// All UwS ids are expected to be integers.
uint32_t uws_id = 0;
if (base::StringToUint(uws_string, &uws_id)) {
RecordSparseHistogram(kFoundUwsMetricName, uws_id);
if (rappor_service) {
rappor_service->RecordSample(kFoundUwsMetricName,
rappor::COARSE_RAPPOR_TYPE,
base::UTF16ToUTF8(uws_string));
}
} else {
parse_error = true;
}
// Clean up the old value.
reporter_key.DeleteValue(kFoundUwsValueName);
RecordBooleanHistogram(kFoundUwsReadErrorMetricName, parse_error);
}
}
// Reports to UMA the memory usage of the software reporter tool as reported
// by the tool itself in the Windows registry.
void ReportMemoryUsage() const {
base::win::RegKey reporter_key;
DWORD memory_used = 0;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE) != ERROR_SUCCESS ||
reporter_key.ReadValueDW(kMemoryUsedValueName, &memory_used) !=
ERROR_SUCCESS) {
return;
}
RecordMemoryKBHistogram(kMemoryUsedMetricName, memory_used);
reporter_key.DeleteValue(kMemoryUsedValueName);
}
// Reports the SwReporter run time with UMA both as reported by the tool via
// the registry and as measured by |ReporterRunner|.
void ReportRuntime(const base::TimeDelta& reporter_running_time) const {
RecordLongTimesHistogram("SoftwareReporter.RunningTimeAccordingToChrome",
reporter_running_time);
// TODO(b/641081): This should only have KEY_QUERY_VALUE and KEY_SET_VALUE.
base::win::RegKey reporter_key;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_ALL_ACCESS) != ERROR_SUCCESS) {
RecordEnumerationHistogram(
kRunningTimeErrorMetricName,
REPORTER_RUNNING_TIME_ERROR_REGISTRY_KEY_INVALID,
REPORTER_RUNNING_TIME_ERROR_MAX);
return;
}
bool has_start_time = false;
int64_t start_time_value = 0;
if (reporter_key.HasValue(kStartTimeValueName) &&
reporter_key.ReadInt64(kStartTimeValueName, &start_time_value) ==
ERROR_SUCCESS) {
has_start_time = true;
reporter_key.DeleteValue(kStartTimeValueName);
}
bool has_end_time = false;
int64_t end_time_value = 0;
if (reporter_key.HasValue(kEndTimeValueName) &&
reporter_key.ReadInt64(kEndTimeValueName, &end_time_value) ==
ERROR_SUCCESS) {
has_end_time = true;
reporter_key.DeleteValue(kEndTimeValueName);
}
if (has_start_time && has_end_time) {
base::TimeDelta registry_run_time =
base::Time::FromInternalValue(end_time_value) -
base::Time::FromInternalValue(start_time_value);
RecordLongTimesHistogram("SoftwareReporter.RunningTime",
registry_run_time);
RecordEnumerationHistogram(kRunningTimeErrorMetricName,
REPORTER_RUNNING_TIME_ERROR_NO_ERROR,
REPORTER_RUNNING_TIME_ERROR_MAX);
} else if (!has_start_time && !has_end_time) {
RecordEnumerationHistogram(kRunningTimeErrorMetricName,
REPORTER_RUNNING_TIME_ERROR_MISSING_BOTH_TIMES,
REPORTER_RUNNING_TIME_ERROR_MAX);
} else if (!has_start_time) {
RecordEnumerationHistogram(kRunningTimeErrorMetricName,
REPORTER_RUNNING_TIME_ERROR_MISSING_START_TIME,
REPORTER_RUNNING_TIME_ERROR_MAX);
} else {
DCHECK(!has_end_time);
RecordEnumerationHistogram(kRunningTimeErrorMetricName,
REPORTER_RUNNING_TIME_ERROR_MISSING_END_TIME,
REPORTER_RUNNING_TIME_ERROR_MAX);
}
}
// Reports the UwS scan times of the software reporter tool via UMA.
void ReportScanTimes() const {
base::string16 scan_times_key_path = base::StringPrintf(
L"%ls\\%ls", registry_key_.c_str(), kScanTimesSubKey);
// TODO(b/641081): This should only have KEY_QUERY_VALUE and KEY_SET_VALUE.
base::win::RegKey scan_times_key;
if (scan_times_key.Open(HKEY_CURRENT_USER, scan_times_key_path.c_str(),
KEY_ALL_ACCESS) != ERROR_SUCCESS) {
return;
}
base::string16 value_name;
int uws_id = 0;
int64_t raw_scan_time = 0;
int num_scan_times = scan_times_key.GetValueCount();
for (int i = 0; i < num_scan_times; ++i) {
if (scan_times_key.GetValueNameAt(i, &value_name) == ERROR_SUCCESS &&
base::StringToInt(value_name, &uws_id) &&
scan_times_key.ReadInt64(value_name.c_str(), &raw_scan_time) ==
ERROR_SUCCESS) {
base::TimeDelta scan_time =
base::TimeDelta::FromInternalValue(raw_scan_time);
// We report the number of seconds plus one because it can take less
// than one second to scan some UwS and the count passed to |AddCount|
// must be at least one.
RecordSparseHistogramCount(kScanTimesMetricName, uws_id,
scan_time.InSeconds() + 1);
}
}
// Clean up by deleting the scan times key, which is a subkey of the main
// reporter key.
scan_times_key.Close();
base::win::RegKey reporter_key;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_ENUMERATE_SUB_KEYS) == ERROR_SUCCESS) {
reporter_key.DeleteKey(kScanTimesSubKey);
}
}
void RecordReporterStep(SwReporterUmaValue value) {
RecordEnumerationHistogram(kStepMetricName, value, SW_REPORTER_MAX);
}
void RecordLogsUploadEnabled(SwReporterLogsUploadsEnabled value) {
RecordEnumerationHistogram(kLogsUploadEnabledMetricName, value,
REPORTER_LOGS_UPLOADS_MAX);
}
void RecordLogsUploadResult() {
base::win::RegKey reporter_key;
DWORD logs_upload_result = 0;
if (reporter_key.Open(HKEY_CURRENT_USER, registry_key_.c_str(),
KEY_QUERY_VALUE | KEY_SET_VALUE) != ERROR_SUCCESS) {
RecordEnumerationHistogram(
kLogsUploadResultRegistryErrorMetricName,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_REGISTRY_KEY_INVALID,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_MAX);
return;
}
if (reporter_key.ReadValueDW(kLogsUploadResultValueName,
&logs_upload_result) != ERROR_SUCCESS) {
RecordEnumerationHistogram(
kLogsUploadResultRegistryErrorMetricName,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_VALUE_NOT_FOUND,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_MAX);
return;
}
if (logs_upload_result >= kSwReporterLogsUploadResultMax) {
RecordEnumerationHistogram(
kLogsUploadResultRegistryErrorMetricName,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_VALUE_OUT_OF_BOUNDS,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_MAX);
return;
}
RecordEnumerationHistogram(kLogsUploadResultMetricName,
static_cast<Sample>(logs_upload_result),
kSwReporterLogsUploadResultMax);
reporter_key.DeleteValue(kLogsUploadResultValueName);
RecordEnumerationHistogram(kLogsUploadResultRegistryErrorMetricName,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_NO_ERROR,
REPORTER_LOGS_UPLOAD_RESULT_ERROR_MAX);
}
private:
using Sample = base::HistogramBase::Sample;
static constexpr base::HistogramBase::Flags kUmaHistogramFlag =
base::HistogramBase::kUmaTargetedHistogramFlag;
// Helper functions to record histograms with an optional suffix added to the
// histogram name. The UMA_HISTOGRAM macros can't be used because they
// require a constant string.
std::string FullName(const std::string& name) const {
if (suffix_.empty())
return name;
return base::StringPrintf("%s_%s", name.c_str(), suffix_.c_str());
}
void RecordBooleanHistogram(const std::string& name, bool sample) const {
auto histogram =
base::BooleanHistogram::FactoryGet(FullName(name), kUmaHistogramFlag);
if (histogram)
histogram->AddBoolean(sample);
}
void RecordEnumerationHistogram(const std::string& name,
Sample sample,
Sample boundary) const {
// See HISTOGRAM_ENUMERATION_WITH_FLAG for the parameters to |FactoryGet|.
auto histogram = base::LinearHistogram::FactoryGet(
FullName(name), 1, boundary, boundary + 1, kUmaHistogramFlag);
if (histogram)
histogram->Add(sample);
}
void RecordLongTimesHistogram(const std::string& name,
const base::TimeDelta& sample) const {
// See UMA_HISTOGRAM_LONG_TIMES for the parameters to |FactoryTimeGet|.
auto histogram = base::Histogram::FactoryTimeGet(
FullName(name), base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromHours(1), 100, kUmaHistogramFlag);
if (histogram)
histogram->AddTime(sample);
}
void RecordMemoryKBHistogram(const std::string& name, Sample sample) const {
// See UMA_HISTOGRAM_MEMORY_KB for the parameters to |FactoryGet|.
auto histogram = base::Histogram::FactoryGet(FullName(name), 1000, 500000,
50, kUmaHistogramFlag);
if (histogram)
histogram->Add(sample);
}
void RecordSparseHistogram(const std::string& name, Sample sample) const {
auto histogram =
base::SparseHistogram::FactoryGet(FullName(name), kUmaHistogramFlag);
if (histogram)
histogram->Add(sample);
}
void RecordSparseHistogramCount(const std::string& name,
Sample sample,
int count) const {
auto histogram =
base::SparseHistogram::FactoryGet(FullName(name), kUmaHistogramFlag);
if (histogram)
histogram->AddCount(sample, count);
}
const std::string suffix_;
const std::wstring registry_key_;
};
// Records the reporter step without a suffix. (For steps that are never run by
// the experimental reporter.)
void RecordReporterStepHistogram(SwReporterUmaValue value) {
UMAHistogramReporter uma;
uma.RecordReporterStep(value);
}
void DisplaySRTPrompt(const base::FilePath& download_path) {
// Find the last active browser, which may be NULL, in which case we won't
// show the prompt this time and will wait until the next run of the
// reporter. We can't use other ways of finding a browser because we don't
// have a profile.
Browser* browser = chrome::FindLastActive();
if (!browser)
return;
Profile* profile = browser->profile();
DCHECK(profile);
// Make sure we have a tabbed browser since we need to anchor the bubble to
// the toolbar's wrench menu. Create one if none exist already.
if (browser->type() != Browser::TYPE_TABBED) {
browser = chrome::FindTabbedBrowser(profile, false);
if (!browser)
browser = new Browser(Browser::CreateParams(profile));
}
GlobalErrorService* global_error_service =
GlobalErrorServiceFactory::GetForProfile(profile);
SRTGlobalError* global_error =
new SRTGlobalError(global_error_service, download_path);
// Ownership of |global_error| is passed to the service. The error removes
// itself from the service and self-destructs when done.
global_error_service->AddGlobalError(global_error);
bool show_bubble = true;
PrefService* local_state = g_browser_process->local_state();
if (local_state && local_state->GetBoolean(prefs::kSwReporterPendingPrompt)) {
// Don't show the bubble if there's already a pending prompt to only be
// sown in the Chrome menu.
RecordReporterStepHistogram(SW_REPORTER_ADDED_TO_MENU);
show_bubble = false;
} else {
// Do not try to show bubble if another GlobalError is already showing
// one. The bubble will be shown once the others have been dismissed.
for (GlobalError* error : global_error_service->errors()) {
if (error->GetBubbleView()) {
show_bubble = false;
break;
}
}
}
if (show_bubble)
global_error->ShowBubbleView(browser);
}
bool SafeBrowsingExtendedEnabledForBrowser(const Browser* browser) {
const Profile* profile = browser->profile();
return profile && !profile->IsOffTheRecord() &&
IsExtendedReportingEnabled(*profile->GetPrefs());
}
// Returns true if there is a profile that is not in incognito mode and the user
// has opted into Safe Browsing extended reporting.
bool SafeBrowsingExtendedReportingEnabled() {
BrowserList* browser_list = BrowserList::GetInstance();
return std::any_of(browser_list->begin_last_active(),
browser_list->end_last_active(),
&SafeBrowsingExtendedEnabledForBrowser);
}
// This function is called from a worker thread to launch the SwReporter and
// wait for termination to collect its exit code. This task could be
// interrupted by a shutdown at any time, so it shouldn't depend on anything
// external that could be shut down beforehand.
int LaunchAndWaitForExit(const SwReporterInvocation& invocation) {
if (g_testing_delegate_)
return g_testing_delegate_->LaunchReporter(invocation);
base::Process reporter_process =
base::LaunchProcess(invocation.command_line, base::LaunchOptions());
// This exit code is used to identify that a reporter run didn't happen, so
// the result should be ignored and a rerun scheduled for the usual delay.
int exit_code = kReporterFailureExitCode;
UMAHistogramReporter uma(invocation.suffix);
if (reporter_process.IsValid()) {
uma.RecordReporterStep(SW_REPORTER_START_EXECUTION);
bool success = reporter_process.WaitForExit(&exit_code);
DCHECK(success);
} else {
uma.RecordReporterStep(SW_REPORTER_FAILED_TO_START);
}
return exit_code;
}
} // namespace
// Class that will attempt to download the SRT, showing the SRT notification
// bubble when the download operation is complete. Instances of SRTFetcher own
// themselves, they will self-delete on completion of the network request when
// OnURLFetchComplete is called.
class SRTFetcher : public net::URLFetcherDelegate {
public:
explicit SRTFetcher(Profile* profile)
: profile_(profile),
url_fetcher_(net::URLFetcher::Create(0,
GURL(GetSRTDownloadURL()),
net::URLFetcher::GET,
this)) {
data_use_measurement::DataUseUserData::AttachToFetcher(
url_fetcher_.get(),
data_use_measurement::DataUseUserData::SAFE_BROWSING);
url_fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE);
url_fetcher_->SetMaxRetriesOn5xx(3);
url_fetcher_->SaveResponseToTemporaryFile(
BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE));
url_fetcher_->SetRequestContext(
g_browser_process->system_request_context());
// Adds the UMA bit to the download request if the user is enrolled in UMA.
ProfileIOData* io_data = ProfileIOData::FromResourceContext(
profile_->GetResourceContext());
net::HttpRequestHeaders headers;
variations::AppendVariationHeaders(
url_fetcher_->GetOriginalURL(), io_data->IsOffTheRecord(),
ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled(),
&headers);
url_fetcher_->SetExtraRequestHeaders(headers.ToString());
url_fetcher_->Start();
}
// net::URLFetcherDelegate:
void OnURLFetchComplete(const net::URLFetcher* source) override {
// Take ownership of the fetcher in this scope (source == url_fetcher_).
DCHECK_EQ(url_fetcher_.get(), source);
base::FilePath download_path;
if (source->GetStatus().is_success() &&
source->GetResponseCode() == net::HTTP_OK) {
if (source->GetResponseAsFilePath(true, &download_path)) {
DCHECK(!download_path.empty());
}
}
// As long as the fetch didn't fail due to HTTP_NOT_FOUND, show a prompt
// (either offering the tool directly or pointing to the download page).
// If the fetch failed to find the file, don't prompt the user since the
// tool is not currently available.
// TODO(mad): Consider implementing another layer of retries / alternate
// fetching mechanisms. http://crbug.com/460293
// TODO(mad): In the event the browser is closed before the prompt displays,
// we will wait until the next scanner run to re-display it.
// Improve this. http://crbug.com/460295
if (source->GetResponseCode() != net::HTTP_NOT_FOUND)
DisplaySRTPrompt(download_path);
else
RecordSRTPromptHistogram(SRT_PROMPT_DOWNLOAD_UNAVAILABLE);
// Explicitly destroy the url_fetcher_ to avoid destruction races.
url_fetcher_.reset();
// At this point, the url_fetcher_ is gone and this SRTFetcher instance is
// no longer needed.
delete this;
}
private:
~SRTFetcher() override {}
// The user profile.
Profile* profile_;
// The underlying URL fetcher. The instance is alive from construction through
// OnURLFetchComplete.
std::unique_ptr<net::URLFetcher> url_fetcher_;
DISALLOW_COPY_AND_ASSIGN(SRTFetcher);
};
namespace {
// Try to fetch the SRT, and on success, show the prompt to run it.
void MaybeFetchSRT(Browser* browser, const base::Version& reporter_version) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (g_testing_delegate_) {
g_testing_delegate_->TriggerPrompt(browser, reporter_version.GetString());
return;
}
Profile* profile = browser->profile();
DCHECK(profile);
PrefService* prefs = profile->GetPrefs();
DCHECK(prefs);
// Don't show the prompt again if it's been shown before for this profile
// and for the current variations seed, unless there's a pending prompt to
// show in the Chrome menu.
std::string incoming_seed = GetIncomingSRTSeed();
std::string old_seed = prefs->GetString(prefs::kSwReporterPromptSeed);
PrefService* local_state = g_browser_process->local_state();
bool pending_prompt =
local_state && local_state->GetBoolean(prefs::kSwReporterPendingPrompt);
if (!incoming_seed.empty() && incoming_seed == old_seed && !pending_prompt) {
RecordReporterStepHistogram(SW_REPORTER_ALREADY_PROMPTED);
return;
}
if (!incoming_seed.empty() && incoming_seed != old_seed) {
prefs->SetString(prefs::kSwReporterPromptSeed, incoming_seed);
// Forget about pending prompts if prompt seed has changed.
if (local_state)
local_state->SetBoolean(prefs::kSwReporterPendingPrompt, false);
}
prefs->SetString(prefs::kSwReporterPromptVersion,
reporter_version.GetString());
// Download the SRT.
RecordReporterStepHistogram(SW_REPORTER_DOWNLOAD_START);
// All the work happens in the self-deleting class below.
new SRTFetcher(profile);
}
} // namespace
// This class tries to run a queue of reporters and react to their exit codes.
// It schedules subsequent runs of the queue as needed, or retries as soon as a
// browser is available when none is on first try.
class ReporterRunner : public chrome::BrowserListObserver {
public:
// Registers |invocations| to run next time |TryToRun| is scheduled. (And if
// it's not already scheduled, call it now.)
static void ScheduleInvocations(
const SwReporterQueue& invocations,
const base::Version& version,
scoped_refptr<base::TaskRunner> main_thread_task_runner,
scoped_refptr<base::TaskRunner> blocking_task_runner) {
if (!instance_) {
instance_ = new ReporterRunner;
ANNOTATE_LEAKING_OBJECT_PTR(instance_);
}
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// There's nothing to do if the invocation parameters and version of the
// reporter have not changed, we just keep running the tasks that are
// running now.
if (instance_->pending_invocations_ == invocations &&
instance_->version_.IsValid() && instance_->version_ == version)
return;
instance_->pending_invocations_ = invocations;
instance_->version_ = version;
instance_->main_thread_task_runner_ = std::move(main_thread_task_runner);
instance_->blocking_task_runner_ = std::move(blocking_task_runner);
if (instance_->first_run_) {
instance_->first_run_ = false;
instance_->TryToRun();
}
}
private:
ReporterRunner() {}
~ReporterRunner() override {}
// BrowserListObserver.
void OnBrowserSetLastActive(Browser* browser) override {}
void OnBrowserRemoved(Browser* browser) override {}
void OnBrowserAdded(Browser* browser) override {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(browser);
MaybeFetchSRT(browser, version_);
BrowserList::RemoveObserver(this);
}
// Launches the command line at the head of the queue.
void ScheduleNextInvocation() {
DCHECK(!current_invocations_.empty());
auto next_invocation = current_invocations_.front();
current_invocations_.pop();
if (g_testing_delegate_)
g_testing_delegate_->NotifyLaunchReady();
AppendInvocationSpecificSwitches(&next_invocation);
// It's OK to simply |PostTaskAndReplyWithResult| so that
// |LaunchAndWaitForExit| doesn't need to access |main_thread_task_runner_|
// since the callback is not delayed and the test task runner won't need to
// force it.
base::PostTaskAndReplyWithResult(
blocking_task_runner_.get(), FROM_HERE,
base::Bind(&LaunchAndWaitForExit, next_invocation),
base::Bind(&ReporterRunner::ReporterDone, base::Unretained(this),
base::Time::Now(), version_, next_invocation));
}
// This method is called on the UI thread when an invocation of the reporter
// has completed. This is run as a task posted from an interruptible worker
// thread so should be resilient to unexpected shutdown.
void ReporterDone(const base::Time& reporter_start_time,
const base::Version& version,
const SwReporterInvocation& finished_invocation,
int exit_code) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (g_testing_delegate_)
g_testing_delegate_->NotifyReporterDone();
base::TimeDelta reporter_running_time =
base::Time::Now() - reporter_start_time;
// Don't continue the current queue of reporters if one failed to launch.
if (exit_code == kReporterFailureExitCode)
current_invocations_ = SwReporterQueue();
// As soon as we're not running this queue, schedule the next overall queue
// run after the regular delay. (If there was a failure it's not worth
// retrying earlier, risking running too often if it always fails, since
// not many users fail here.)
if (current_invocations_.empty()) {
main_thread_task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&ReporterRunner::TryToRun, base::Unretained(this)),
base::TimeDelta::FromDays(days_between_reporter_runs_));
} else {
ScheduleNextInvocation();
}
// If the reporter failed to launch, do not process the results. (The exit
// code itself doesn't need to be logged in this case because
// SW_REPORTER_FAILED_TO_START is logged in |LaunchAndWaitForExit|.)
if (exit_code == kReporterFailureExitCode)
return;
UMAHistogramReporter uma(finished_invocation.suffix);
uma.ReportVersion(version);
uma.ReportExitCode(exit_code);
uma.ReportEngineErrorCode();
uma.ReportFoundUwS(finished_invocation.BehaviourIsSupported(
SwReporterInvocation::BEHAVIOUR_LOG_TO_RAPPOR));
PrefService* local_state = g_browser_process->local_state();
if (local_state) {
if (finished_invocation.BehaviourIsSupported(
SwReporterInvocation::BEHAVIOUR_LOG_EXIT_CODE_TO_PREFS)) {
local_state->SetInteger(prefs::kSwReporterLastExitCode, exit_code);
}
local_state->SetInt64(prefs::kSwReporterLastTimeTriggered,
base::Time::Now().ToInternalValue());
}
uma.ReportRuntime(reporter_running_time);
uma.ReportScanTimes();
uma.ReportMemoryUsage();
if (finished_invocation.logs_upload_enabled)
uma.RecordLogsUploadResult();
if (!finished_invocation.BehaviourIsSupported(
SwReporterInvocation::BEHAVIOUR_TRIGGER_PROMPT)) {
return;
}
if (!IsInSRTPromptFieldTrialGroups()) {
// Knowing about disabled field trial is more important than reporter not
// finding anything to remove, so check this case first.
RecordReporterStepHistogram(SW_REPORTER_NO_PROMPT_FIELD_TRIAL);
return;
}
if (exit_code != kSwReporterPostRebootCleanupNeeded &&
exit_code != kSwReporterCleanupNeeded) {
RecordReporterStepHistogram(SW_REPORTER_NO_PROMPT_NEEDED);
return;
}
// Find the last active browser, which may be NULL, in which case we need
// to wait for one to be available. We can't use other ways of finding a
// browser because we don't have a profile. And we need a browser to get to
// a profile, which we need, to tell whether we should prompt or not.
// TODO(mad): crbug.com/503269, investigate whether we should change how we
// decide when it's time to download the SRT and when to display the prompt.
Browser* browser = chrome::FindLastActive();
if (!browser) {
RecordReporterStepHistogram(SW_REPORTER_NO_BROWSER);
BrowserList::AddObserver(this);
} else {
MaybeFetchSRT(browser, version_);
}
}
void TryToRun() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PrefService* local_state = g_browser_process->local_state();
if (!version_.IsValid() || !local_state) {
// TODO(b/641081): This doesn't look right. Even on first run, |version_|
// should be valid (and this is already checked in RunSwReporters). We
// should abort if local_state is missing, but this has nothing to do
// with |first_run_|.
DCHECK(first_run_);
return;
}
// Run a queue of reporters if none have been triggered in the last
// |days_between_reporter_runs_| days, which depends if there is a pending
// prompt to be added to Chrome's menu.
if (local_state->GetBoolean(prefs::kSwReporterPendingPrompt)) {
days_between_reporter_runs_ = kDaysBetweenSwReporterRunsForPendingPrompt;
RecordReporterStepHistogram(SW_REPORTER_RAN_DAILY);
} else {
days_between_reporter_runs_ = kDaysBetweenSuccessfulSwReporterRuns;
}
const base::Time now = base::Time::Now();
const base::Time last_time_triggered = base::Time::FromInternalValue(
local_state->GetInt64(prefs::kSwReporterLastTimeTriggered));
const base::Time next_trigger(
last_time_triggered +
base::TimeDelta::FromDays(days_between_reporter_runs_));
if (!pending_invocations_.empty() &&
(next_trigger <= now ||
// Also make sure the kSwReporterLastTimeTriggered value is not set in
// the future.
last_time_triggered > now)) {
DCHECK(current_invocations_.empty());
current_invocations_ = pending_invocations_;
ScheduleNextInvocation();
} else {
main_thread_task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&ReporterRunner::TryToRun, base::Unretained(this)),
next_trigger - now);
}
}
// Returns true if the experiment to send reporter logs is enabled, the user
// opted into Safe Browsing extended reporting, and logs have been sent at
// least |kSwReporterLastTimeSentReport| days ago.
bool ShouldSendReporterLogs(const PrefService& local_state) {
if (!base::FeatureList::IsEnabled(kSwReporterExtendedSafeBrowsingFeature))
return false;
UMAHistogramReporter uma;
if (!SafeBrowsingExtendedReportingEnabled()) {
uma.RecordLogsUploadEnabled(REPORTER_LOGS_UPLOADS_SBER_DISABLED);
return false;
}
const base::Time now = base::Time::Now();
const base::Time last_time_sent_logs = base::Time::FromInternalValue(
local_state.GetInt64(prefs::kSwReporterLastTimeSentReport));
const base::Time next_time_send_logs =
last_time_sent_logs +
base::TimeDelta::FromDays(kDaysBetweenReporterLogsSent);
// Send the logs if the last send is the future or if the interval has
// passed. The former is intended as a measure for failure recovery, in
// case the time in local state is incorrectly set to the future.
if (last_time_sent_logs > now || next_time_send_logs <= now) {
uma.RecordLogsUploadEnabled(REPORTER_LOGS_UPLOADS_ENABLED);
return true;
}
uma.RecordLogsUploadEnabled(REPORTER_LOGS_UPLOADS_RECENTLY_SENT_LOGS);
return false;
}
// Appends switches to the next invocation that depend on the user current
// state with respect to opting into extended Safe Browsing reporting and
// metrics and crash reporting. The invocation object is changed locally right
// before the actual process is launched because user status can change
// between this and the next run for this ReporterRunner object. For example,
// the ReporterDone() callback schedules the next run for a few days later,
// and the user might have changed settings in the meantime.
void AppendInvocationSpecificSwitches(SwReporterInvocation* next_invocation) {
// Add switches for users who opted into extended Safe Browsing reporting.
PrefService* local_state = g_browser_process->local_state();
if (next_invocation->BehaviourIsSupported(
SwReporterInvocation::BEHAVIOUR_ALLOW_SEND_REPORTER_LOGS) &&
local_state && ShouldSendReporterLogs(*local_state)) {
next_invocation->logs_upload_enabled = true;
AddSwitchesForExtendedReportingUser(next_invocation);
// Set the local state value before the first attempt to run the
// reporter, because we only want to upload logs once in the window
// defined by |kDaysBetweenReporterLogsSent|. If we set with other local
// state values after the reporter runs, we could send logs again too
// quickly (for example, if Chrome stops before the reporter finishes).
local_state->SetInt64(prefs::kSwReporterLastTimeSentReport,
base::Time::Now().ToInternalValue());
}
if (ChromeMetricsServiceAccessor::IsMetricsAndCrashReportingEnabled())
next_invocation->command_line.AppendSwitch(kEnableCrashReporting);
}
// Adds switches to be sent to the Software Reporter when the user opted into
// extended Safe Browsing reporting and is not incognito.
void AddSwitchesForExtendedReportingUser(SwReporterInvocation* invocation) {
invocation->command_line.AppendSwitch(kExtendedSafeBrowsingEnabledSwitch);
invocation->command_line.AppendSwitchASCII(
kChromeVersionSwitch, version_info::GetVersionNumber());
invocation->command_line.AppendSwitchNative(
kChromeChannelSwitch, base::IntToString16(ChannelAsInt()));
}
bool first_run_ = true;
// The queue of invocations that are currently running.
SwReporterQueue current_invocations_;
// The invocations to run next time the SwReporter is run.
SwReporterQueue pending_invocations_;
base::Version version_;
scoped_refptr<base::TaskRunner> main_thread_task_runner_;
scoped_refptr<base::TaskRunner> blocking_task_runner_;
// This value is used to identify how long to wait before starting a new run
// of the reporter queue. It's initialized with the default value and may be
// changed to a different value when a prompt is pending and the reporter
// should be run before adding the global error to the Chrome menu.
int days_between_reporter_runs_ = kDaysBetweenSuccessfulSwReporterRuns;
// A single leaky instance.
static ReporterRunner* instance_;
DISALLOW_COPY_AND_ASSIGN(ReporterRunner);
};
ReporterRunner* ReporterRunner::instance_ = nullptr;
SwReporterInvocation::SwReporterInvocation()
: command_line(base::CommandLine::NO_PROGRAM) {}
SwReporterInvocation SwReporterInvocation::FromFilePath(
const base::FilePath& exe_path) {
SwReporterInvocation invocation;
invocation.command_line = base::CommandLine(exe_path);
return invocation;
}
SwReporterInvocation SwReporterInvocation::FromCommandLine(
const base::CommandLine& command_line) {
SwReporterInvocation invocation;
invocation.command_line = command_line;
return invocation;
}
bool SwReporterInvocation::operator==(const SwReporterInvocation& other) const {
return command_line.argv() == other.command_line.argv() &&
suffix == other.suffix &&
supported_behaviours == other.supported_behaviours &&
logs_upload_enabled == other.logs_upload_enabled;
}
bool SwReporterInvocation::BehaviourIsSupported(
SwReporterInvocation::Behaviours intended_behaviour) const {
return (supported_behaviours & intended_behaviour) != 0;
}
void RunSwReporters(const SwReporterQueue& invocations,
const base::Version& version,
scoped_refptr<base::TaskRunner> main_thread_task_runner,
scoped_refptr<base::TaskRunner> blocking_task_runner) {
DCHECK(!invocations.empty());
DCHECK(version.IsValid());
ReporterRunner::ScheduleInvocations(invocations, version,
std::move(main_thread_task_runner),
std::move(blocking_task_runner));
}
bool ReporterFoundUws() {
PrefService* local_state = g_browser_process->local_state();
if (!local_state)
return false;
int exit_code = local_state->GetInteger(prefs::kSwReporterLastExitCode);
return exit_code == kSwReporterCleanupNeeded;
}
bool UserHasRunCleaner() {
base::string16 cleaner_key_path(kSoftwareRemovalToolRegistryKey);
cleaner_key_path.append(L"\\").append(kCleanerSubKey);
base::win::RegKey srt_cleaner_key;
return srt_cleaner_key.Open(HKEY_CURRENT_USER, cleaner_key_path.c_str(),
KEY_QUERY_VALUE) == ERROR_SUCCESS &&
srt_cleaner_key.GetValueCount() > 0;
}
void SetSwReporterTestingDelegate(SwReporterTestingDelegate* delegate) {
g_testing_delegate_ = delegate;
}
} // namespace safe_browsing