blob: 6c04dcaf05660e537a950d709bc07507982675ee [file] [log] [blame]
// Copyright (c) 2014 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/component_updater/sw_reporter_installer_win.h"
#include <stdint.h>
#include <map>
#include <string>
#include <vector>
#include "base/base_paths.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram.h"
#include "base/metrics/sparse_histogram.h"
#include "base/path_service.h"
#include "base/strings/string_tokenizer.h"
#include "base/thread_task_runner_handle.h"
#include "base/threading/worker_pool.h"
#include "base/time/time.h"
#include "base/win/registry.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/safe_browsing/srt_fetcher_win.h"
#include "chrome/browser/safe_browsing/srt_field_trial_win.h"
#include "components/component_updater/component_updater_paths.h"
#include "components/component_updater/component_updater_service.h"
#include "components/component_updater/default_component_installer.h"
#include "components/component_updater/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/update_client/update_client.h"
#include "components/update_client/utils.h"
#include "content/public/browser/browser_thread.h"
namespace component_updater {
namespace {
// These two sets of values are used to send UMA information and are replicated
// in the histograms.xml file, so the order MUST NOT CHANGE.
enum SRTCompleted {
SRT_COMPLETED_NOT_YET = 0,
SRT_COMPLETED_YES = 1,
SRT_COMPLETED_LATER = 2,
SRT_COMPLETED_MAX,
};
// CRX hash. The extension id is: gkmgaooipdjhmangpemjhigmamcehddo. The hash was
// generated in Python with something like this:
// hashlib.sha256().update(open("<file>.crx").read()[16:16+294]).digest().
const uint8_t kSha256Hash[] = {0x6a, 0xc6, 0x0e, 0xe8, 0xf3, 0x97, 0xc0, 0xd6,
0xf4, 0xc9, 0x78, 0x6c, 0x0c, 0x24, 0x73, 0x3e,
0x05, 0xa5, 0x62, 0x4b, 0x2e, 0xc7, 0xb7, 0x1c,
0x5f, 0xea, 0xf0, 0x88, 0xf6, 0x97, 0x9b, 0xc7};
const base::FilePath::CharType kSwReporterExeName[] =
FILE_PATH_LITERAL("software_reporter_tool.exe");
// SRT registry keys and value names.
const wchar_t kCleanerSuffixRegistryKey[] = L"Cleaner";
const wchar_t kExitCodeValueName[] = L"ExitCode";
const wchar_t kUploadResultsValueName[] = L"UploadResults";
const wchar_t kVersionValueName[] = L"Version";
void SRTHasCompleted(SRTCompleted value) {
UMA_HISTOGRAM_ENUMERATION("SoftwareReporter.Cleaner.HasCompleted", value,
SRT_COMPLETED_MAX);
}
void ReportVersionWithUma(const base::Version& version) {
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];
UMA_HISTOGRAM_SPARSE_SLOWLY("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];
}
UMA_HISTOGRAM_SPARSE_SLOWLY("SoftwareReporter.MajorVersion", major_version);
}
void ReportUploadsWithUma(const base::string16& upload_results) {
base::WStringTokenizer tokenizer(upload_results, L";");
int failure_count = 0;
int success_count = 0;
int longest_failure_run = 0;
int current_failure_run = 0;
bool last_result = false;
while (tokenizer.GetNext()) {
if (tokenizer.token() == L"0") {
++failure_count;
++current_failure_run;
last_result = false;
} else {
++success_count;
current_failure_run = 0;
last_result = true;
}
if (current_failure_run > longest_failure_run)
longest_failure_run = current_failure_run;
}
UMA_HISTOGRAM_COUNTS_100("SoftwareReporter.UploadFailureCount",
failure_count);
UMA_HISTOGRAM_COUNTS_100("SoftwareReporter.UploadSuccessCount",
success_count);
UMA_HISTOGRAM_COUNTS_100("SoftwareReporter.UploadLongestFailureRun",
longest_failure_run);
UMA_HISTOGRAM_BOOLEAN("SoftwareReporter.LastUploadResult", last_result);
}
class SwReporterInstallerTraits : public ComponentInstallerTraits {
public:
SwReporterInstallerTraits() {}
~SwReporterInstallerTraits() override {}
bool VerifyInstallation(const base::DictionaryValue& manifest,
const base::FilePath& dir) const override {
return base::PathExists(dir.Append(kSwReporterExeName));
}
bool CanAutoUpdate() const override { return true; }
bool OnCustomInstall(const base::DictionaryValue& manifest,
const base::FilePath& install_dir) override {
return true;
}
void ComponentReady(const base::Version& version,
const base::FilePath& install_dir,
scoped_ptr<base::DictionaryValue> manifest) override {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
ReportVersionWithUma(version);
safe_browsing::RunSwReporter(install_dir.Append(kSwReporterExeName),
version.GetString(),
base::ThreadTaskRunnerHandle::Get(),
base::WorkerPool::GetTaskRunner(true));
}
base::FilePath GetBaseDirectory() const override { return install_dir(); }
void GetHash(std::vector<uint8_t>* hash) const override { GetPkHash(hash); }
std::string GetName() const override { return "Software Reporter Tool"; }
static base::FilePath install_dir() {
// The base directory on windows looks like:
// <profile>\AppData\Local\Google\Chrome\User Data\SwReporter\.
base::FilePath result;
PathService::Get(DIR_SW_REPORTER, &result);
return result;
}
static std::string ID() {
update_client::CrxComponent component;
component.version = Version("0.0.0.0");
GetPkHash(&component.pk_hash);
return update_client::GetCrxComponentID(component);
}
private:
static void GetPkHash(std::vector<uint8_t>* hash) {
DCHECK(hash);
hash->assign(kSha256Hash, kSha256Hash + sizeof(kSha256Hash));
}
};
} // namespace
void RegisterSwReporterComponent(ComponentUpdateService* cus) {
if (!safe_browsing::IsSwReporterEnabled())
return;
// Check if we have information from Cleaner and record UMA statistics.
base::string16 cleaner_key_name(
safe_browsing::kSoftwareRemovalToolRegistryKey);
cleaner_key_name.append(1, L'\\').append(kCleanerSuffixRegistryKey);
base::win::RegKey cleaner_key(
HKEY_CURRENT_USER, cleaner_key_name.c_str(), KEY_ALL_ACCESS);
// Cleaner is assumed to have run if we have a start time.
if (cleaner_key.Valid()) {
if (cleaner_key.HasValue(safe_browsing::kStartTimeValueName)) {
// Get version number.
if (cleaner_key.HasValue(kVersionValueName)) {
DWORD version;
cleaner_key.ReadValueDW(kVersionValueName, &version);
UMA_HISTOGRAM_SPARSE_SLOWLY("SoftwareReporter.Cleaner.Version",
version);
cleaner_key.DeleteValue(kVersionValueName);
}
// Get start & end time. If we don't have an end time, we can assume the
// cleaner has not completed.
int64_t start_time_value;
cleaner_key.ReadInt64(safe_browsing::kStartTimeValueName,
&start_time_value);
bool completed = cleaner_key.HasValue(safe_browsing::kEndTimeValueName);
SRTHasCompleted(completed ? SRT_COMPLETED_YES : SRT_COMPLETED_NOT_YET);
if (completed) {
int64_t end_time_value;
cleaner_key.ReadInt64(safe_browsing::kEndTimeValueName,
&end_time_value);
cleaner_key.DeleteValue(safe_browsing::kEndTimeValueName);
base::TimeDelta run_time(
base::Time::FromInternalValue(end_time_value) -
base::Time::FromInternalValue(start_time_value));
UMA_HISTOGRAM_LONG_TIMES("SoftwareReporter.Cleaner.RunningTime",
run_time);
}
// Get exit code. Assume nothing was found if we can't read the exit code.
DWORD exit_code = safe_browsing::kSwReporterNothingFound;
if (cleaner_key.HasValue(kExitCodeValueName)) {
cleaner_key.ReadValueDW(kExitCodeValueName, &exit_code);
UMA_HISTOGRAM_SPARSE_SLOWLY("SoftwareReporter.Cleaner.ExitCode",
exit_code);
cleaner_key.DeleteValue(kExitCodeValueName);
}
cleaner_key.DeleteValue(safe_browsing::kStartTimeValueName);
if (exit_code == safe_browsing::kSwReporterPostRebootCleanupNeeded ||
exit_code ==
safe_browsing::kSwReporterDelayedPostRebootCleanupNeeded) {
// Check if we are running after the user has rebooted.
base::TimeDelta elapsed(
base::Time::Now() -
base::Time::FromInternalValue(start_time_value));
DCHECK_GT(elapsed.InMilliseconds(), 0);
UMA_HISTOGRAM_BOOLEAN(
"SoftwareReporter.Cleaner.HasRebooted",
static_cast<uint64_t>(elapsed.InMilliseconds()) > ::GetTickCount());
}
if (cleaner_key.HasValue(kUploadResultsValueName)) {
base::string16 upload_results;
cleaner_key.ReadValue(kUploadResultsValueName, &upload_results);
ReportUploadsWithUma(upload_results);
}
} else {
if (cleaner_key.HasValue(safe_browsing::kEndTimeValueName)) {
SRTHasCompleted(SRT_COMPLETED_LATER);
cleaner_key.DeleteValue(safe_browsing::kEndTimeValueName);
}
}
}
// Install the component.
scoped_ptr<ComponentInstallerTraits> traits(new SwReporterInstallerTraits());
// |cus| will take ownership of |installer| during installer->Register(cus).
DefaultComponentInstaller* installer =
new DefaultComponentInstaller(traits.Pass());
installer->Register(cus, base::Closure());
}
void RegisterPrefsForSwReporter(PrefRegistrySimple* registry) {
registry->RegisterInt64Pref(prefs::kSwReporterLastTimeTriggered, 0);
registry->RegisterIntegerPref(prefs::kSwReporterLastExitCode, -1);
registry->RegisterBooleanPref(prefs::kSwReporterPendingPrompt, false);
}
void RegisterProfilePrefsForSwReporter(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterStringPref(prefs::kSwReporterPromptVersion, "");
registry->RegisterStringPref(prefs::kSwReporterPromptSeed, "");
}
} // namespace component_updater