blob: b02032e10e284d3078531a0515f4a9d2c77f6950 [file] [log] [blame]
// Copyright 2018 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/chrome_cleaner/logging/cleaner_logging_service.h"
#include <memory>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/cpu.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/sys_info.h"
#include "base/win/i18n.h"
#include "base/win/windows_version.h"
#include "chrome/chrome_cleaner/chrome_utils/chrome_util.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/constants/chrome_cleanup_tool_branding.h"
#include "chrome/chrome_cleaner/constants/version.h"
#include "chrome/chrome_cleaner/logging/api_keys.h"
#include "chrome/chrome_cleaner/logging/pending_logs_service.h"
#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
#include "chrome/chrome_cleaner/logging/registry_logger.h"
#include "chrome/chrome_cleaner/logging/utils.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
#include "chrome/chrome_cleaner/os/file_removal_status_updater.h"
#include "chrome/chrome_cleaner/os/rebooter.h"
#include "chrome/chrome_cleaner/os/registry_util.h"
#include "chrome/chrome_cleaner/os/resource_util.h"
#include "chrome/chrome_cleaner/os/system_util.h"
#include "chrome/chrome_cleaner/settings/settings.h"
#include "chrome/chrome_cleaner/strings/string_util.h"
#include "components/chrome_cleaner/public/constants/constants.h"
namespace chrome_cleaner {
namespace {
// TODO(joenotcharles): Refer to the report definition in the "data" section.
constexpr net::NetworkTrafficAnnotationTag kCleanerReportTrafficAnnotation =
net::DefineNetworkTrafficAnnotation("chrome_cleanup_report", R"(
semantics {
sender: "Chrome Cleanup"
description:
"Chrome on Windows is able to detect and remove software that "
"violates Google's Unwanted Software Policy "
"(https://www.google.com/about/unwanted-software-policy.html). "
"When potentially unwanted software is detected in the "
"background, Chrome offers to remove it. If the user accepts the "
"cleanup and chooses to \"Report details to Google\", Chrome "
"will upload details of the unwanted software and its removal, "
"as well as some details about the system, to help Google track "
"the spread of unwanted software. "
"The user can also use the settings page to ask Chrome to search "
"for unwanted software and remove it. In this case if the user "
"chooses \"Report details to Google\", the system details will "
"be uploaded to Google whether or not unwanted software is found."
trigger:
"The user either accepted a prompt to remove unwanted software, "
"or went to \"Clean up computer\" in the settings page and chose "
"to \"Find and remove harmful software\", and enabled \"Report "
"details to Google\"."
data:
"The user's Chrome version, Windows version, and locale, file "
"metadata related to the unwanted software that was detected, "
"automatically installed Chrome extensions, and system settings "
"commonly used by malicious software as described at "
"https://www.google.com/chrome/privacy/whitepaper.html#unwantedsoftware. "
"Contents of files are never reported. No user identifiers are "
"reported, and common user identifiers found in metadata are "
"replaced with generic strings, but it is possible some metadata "
"may contain personally identifiable information."
destination: GOOGLE_OWNED_SERVICE
}
policy {
cookies_allowed: NO
setting:
"Chrome Cleanup can be explicitly requested by the user in "
"\"Clean up computer\" in the \"Reset and cleanup\" section of "
"settings, under Advanced. To disable this report, turn off "
"\"Report details to Google\" before choosing \"Find and remove "
"harmful software\"."
chrome_policy {
ChromeCleanupReportingEnabled {
ChromeCleanupReportingEnabled: false
}
ChromeCleanupEnabled {
ChromeCleanupEnabled: false
}
}
}
comments:
"If ChromeCleanupEnabled is set to \"false\", Chrome Cleanup will "
"never run, so no reports will be uploaded to Google. Otherwise, "
"ChromeCleanupReportingEnabled can be used to override the "
"\"Report details to Google\" control: if it is set to \"true\", "
"reports will always be sent, and if it is set to \"false\", "
"reports will never be sent."
)");
// Convert a FileInformation proto object to its corresponding
// internal::FileInformation struct. This function is thread lock safe.
void ProtoObjectToFileInformation(const FileInformation& proto_file_information,
internal::FileInformation* file_information) {
DCHECK(file_information);
file_information->path = base::UTF8ToUTF16(proto_file_information.path());
file_information->creation_date = proto_file_information.creation_date();
file_information->last_modified_date =
proto_file_information.last_modified_date();
file_information->sha256 = proto_file_information.sha256();
file_information->size = proto_file_information.size();
file_information->company_name =
base::UTF8ToUTF16(proto_file_information.company_name());
file_information->company_short_name =
base::UTF8ToUTF16(proto_file_information.company_short_name());
file_information->product_name =
base::UTF8ToUTF16(proto_file_information.product_name());
file_information->product_short_name =
base::UTF8ToUTF16(proto_file_information.product_short_name());
file_information->internal_name =
base::UTF8ToUTF16(proto_file_information.internal_name());
file_information->original_filename =
base::UTF8ToUTF16(proto_file_information.original_filename());
file_information->file_description =
base::UTF8ToUTF16(proto_file_information.file_description());
file_information->file_version =
base::UTF8ToUTF16(proto_file_information.file_version());
file_information->active_file = proto_file_information.active_file();
}
} // namespace
void AppendFileInformation(const FileInformation& file,
MessageBuilder* builder) {
internal::FileInformation file_information;
ProtoObjectToFileInformation(file, &file_information);
builder->Add(FileInformationToString(file_information));
}
void AppendFolderInformation(const FolderInformation& folder,
MessageBuilder* builder) {
if (folder.path().empty())
return;
builder->Add(L"path = '", folder.path(), L"'");
if (!folder.creation_date().empty())
builder->Add(L", folder_creation_date = '", folder.creation_date(), L"'");
if (!folder.last_modified_date().empty()) {
builder->Add(L", folder_last_modified_date = '",
folder.last_modified_date(), L"'");
}
}
void AppendMatchedFile(const MatchedFile& file, MessageBuilder* builder) {
AppendFileInformation(file.file_information(), builder);
builder->Add(L", removal_status = ", file.removal_status());
}
void AppendMatchedRegistryEntry(const MatchedRegistryEntry& registry,
MessageBuilder* builder) {
builder->Add(registry.key_path(), L"\\", registry.value_name(), L" ",
registry.value_substring());
}
void AppendScheduledTask(const ScheduledTask& scheduled_task,
MessageBuilder* builder) {
MessageBuilder::ScopedIndent scoped_indent(builder);
builder->AddLine(scheduled_task.description(), L" (", scheduled_task.name(),
"):");
MessageBuilder::ScopedIndent scoped_indent_2(builder);
builder->AddHeaderLine(L"Actions");
for (auto action : scheduled_task.actions()) {
MessageBuilder::ScopedIndent scoped_indent(builder);
builder->Add(L"File information: ");
AppendFileInformation(action.file_information(), builder);
builder->NewLine();
builder->AddFieldValueLine(L"Working directory: ", action.working_dir());
builder->AddFieldValueLine(L"Arguments: ", action.arguments());
}
}
ChromeCleanerReport::CleanerStartup GetCleanerStartupFromCommandLine(
const base::CommandLine* command_line) {
if (!command_line->HasSwitch(kChromePromptSwitch))
return ChromeCleanerReport::CLEANER_STARTUP_NOT_PROMPTED;
std::string chrome_prompt_string =
command_line->GetSwitchValueASCII(kChromePromptSwitch);
int chrome_prompt_value = 0;
if (base::StringToInt(chrome_prompt_string, &chrome_prompt_value) &&
ChromeCleanerReport_CleanerStartup_IsValid(chrome_prompt_value) &&
(chrome_prompt_value == ChromeCleanerReport::CLEANER_STARTUP_PROMPTED ||
chrome_prompt_value ==
ChromeCleanerReport::CLEANER_STARTUP_SHOWN_FROM_MENU ||
chrome_prompt_value ==
ChromeCleanerReport::CLEANER_STARTUP_USER_INITIATED)) {
return static_cast<ChromeCleanerReport::CleanerStartup>(
chrome_prompt_value);
}
LOG(ERROR) << "Invalid value passed to --" << kChromePromptSwitch << ": '"
<< chrome_prompt_string << "'.";
return ChromeCleanerReport::CLEANER_STARTUP_UNKNOWN;
}
CleanerLoggingService* CleanerLoggingService::GetInstance() {
return base::Singleton<CleanerLoggingService>::get();
}
// Please avoid setting any state here, all setup should be done in
// SetupInitialState to allow Terminate to reset this class to its default
// state.
CleanerLoggingService::CleanerLoggingService()
: uploads_enabled_(false),
initialized_(false),
sampler_(DetailedInfoSampler::kDefaultMaxFiles) {}
CleanerLoggingService::~CleanerLoggingService() {
// If initialize is called, the function |Terminate| must be called by the
// user of this class before deleting this object.
DCHECK(!initialized_)
<< "'Terminate' must be called before deleting CleanerLoggingService.";
}
void CleanerLoggingService::Initialize(RegistryLogger* registry_logger) {
DCHECK(!initialized_) << "CleanerLoggingService already initialized.";
EnableUploads(false, registry_logger);
FileRemovalStatusUpdater::GetInstance()->Clear();
logging::SetLogMessageHandler(
CleanerLoggingService::LogMessageHandlerFunction);
Settings* settings = Settings::GetInstance();
const bool metrics_enabled = settings->metrics_enabled();
const bool sber_enabled = settings->sber_enabled();
const std::string cleanup_id = settings->cleanup_id();
const Engine::Name engine = settings->engine();
const std::string engine_version = settings->engine_version();
const base::CommandLine* command_line =
base::CommandLine::ForCurrentProcess();
const ChromeCleanerReport::CleanerStartup cleaner_startup =
GetCleanerStartupFromCommandLine(command_line);
const bool chrome_prompt =
cleaner_startup == ChromeCleanerReport::CLEANER_STARTUP_PROMPTED;
int channel = 0;
bool has_chrome_channel = GetChromeChannelFromCommandLine(&channel);
const bool post_reboot = Rebooter::IsPostReboot();
std::vector<base::string16> languages;
base::win::i18n::GetUserPreferredUILanguageList(&languages);
base::string16 chrome_version_string;
bool chrome_version_string_succeeded =
RetrieveChromeVersionAndInstalledDomain(&chrome_version_string, nullptr);
base::CPU cpu_info;
std::string cpu_architecture = base::SysInfo::OperatingSystemArchitecture();
{
base::AutoLock lock(raw_log_lines_buffer_lock_);
raw_log_lines_buffer_.clear();
}
{
base::AutoLock lock(lock_);
// Ensure that logging report starts in a cleared state.
chrome_cleaner_report_.Clear();
matched_files_.clear();
matched_folders_.clear();
// Initialize with invalid exit code to identify whether it was set or not.
chrome_cleaner_report_.set_exit_code(RESULT_CODE_PENDING);
chrome_cleaner_report_.set_intermediate_log(true);
chrome_cleaner_report_.set_uma_user(metrics_enabled);
chrome_cleaner_report_.set_sber_enabled(sber_enabled);
chrome_cleaner_report_.set_post_reboot(post_reboot);
// TODO(veranika): this field is deprecated. Stop reporting it.
chrome_cleaner_report_.set_chrome_prompt(chrome_prompt);
if (cleaner_startup != ChromeCleanerReport::CLEANER_STARTUP_UNSPECIFIED)
chrome_cleaner_report_.set_cleaner_startup(cleaner_startup);
chrome_cleaner_report_.set_cleanup_id(cleanup_id);
// Set invariant environment / machine data.
ChromeCleanerReport_EnvironmentData* env_data =
chrome_cleaner_report_.mutable_environment();
env_data->set_windows_version(base::win::GetVersion());
env_data->set_cleaner_version(CHROME_VERSION_UTF8_STRING);
if (languages.size() > 0)
env_data->set_default_locale(base::WideToUTF8(languages[0]));
env_data->set_detailed_system_report(false);
env_data->set_bitness(IsX64Process() ? 64 : 32);
if (chrome_version_string_succeeded)
env_data->set_chrome_version(base::WideToUTF8(chrome_version_string));
if (has_chrome_channel)
env_data->set_chrome_channel(channel);
ChromeCleanerReport_EnvironmentData_Machine* machine =
env_data->mutable_machine();
machine->set_cpu_architecture(cpu_architecture);
machine->set_cpu_vendor(cpu_info.vendor_name());
machine->set_cpuid(cpu_info.signature());
env_data->mutable_engine()->set_name(engine);
if (!engine_version.empty())
env_data->mutable_engine()->set_version(engine_version);
initialized_ = true;
}
}
void CleanerLoggingService::Terminate() {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(initialized_) << "Logging service is not initialized.";
size_t num_log_lines = 0;
{
// Get all values from the logging report we are interested in before
// clearing it.
base::AutoLock lock(raw_log_lines_buffer_lock_);
num_log_lines = raw_log_lines_buffer_.size();
}
if (num_log_lines > 0) {
LOG(WARNING) << "At least the last " << num_log_lines
<< " log lines have not been uploaded to Safe Browsing.";
}
{
base::AutoLock lock(raw_log_lines_buffer_lock_);
raw_log_lines_buffer_.clear();
initialized_ = false;
}
}
void CleanerLoggingService::SendLogsToSafeBrowsing(
const UploadResultCallback& done_callback,
RegistryLogger* registry_logger) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(initialized_);
// If reporting is not enabled or required, call |done_callback|
if (!uploads_enabled() || !IsReportingNeeded())
return done_callback.Run(false); // false since no logs were uploaded.
ChromeCleanerReport chrome_cleaner_report;
GetCurrentChromeCleanerReport(&chrome_cleaner_report);
{
base::AutoLock lock(lock_);
// No need to repeat the log lines in subsequent uploads.
chrome_cleaner_report_.clear_raw_log_line();
chrome_cleaner_report_.mutable_environment()->set_detailed_system_report(
false);
}
// TODO(csharp): Move this to the main controller.
// Register a task to try again if we ever fail half way through uploading
// |chrome_cleaner_report|. Will be cleared upon success in
// |OnReportUploadResult|.
ClearTempLogFile(registry_logger);
PendingLogsService::ScheduleLogsUploadTask(PRODUCT_SHORTNAME_STRING,
chrome_cleaner_report,
&temp_log_file_, registry_logger);
std::string serialized_report;
if (!chrome_cleaner_report.SerializeToString(&serialized_report)) {
LOG(WARNING) << "Failed to serialize report";
return done_callback.Run(false); // false since no logs were uploaded.
}
SafeBrowsingReporter::UploadReport(
base::BindRepeating(&CleanerLoggingService::OnReportUploadResult,
base::Unretained(this), done_callback,
registry_logger),
kSafeBrowsingCleanerUrl, serialized_report,
kCleanerReportTrafficAnnotation);
}
void CleanerLoggingService::CancelWaitForShutdown() {
SafeBrowsingReporter::CancelWaitForShutdown();
}
void CleanerLoggingService::EnableUploads(bool enable,
RegistryLogger* registry_logger) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
if (enable == uploads_enabled_)
return;
uploads_enabled_ = enable;
// Make sure not to keep any scheduled logs upload if the user opts-out of
// logs upload. TODO(csharp): maybe we should also clear all other pending
// logs, not just ours.
if (!enable)
ClearTempLogFile(registry_logger);
}
bool CleanerLoggingService::uploads_enabled() const {
return uploads_enabled_;
}
void CleanerLoggingService::SetDetailedSystemReport(
bool detailed_system_report) {
base::AutoLock lock(lock_);
chrome_cleaner_report_.mutable_environment()->set_detailed_system_report(
detailed_system_report);
}
bool CleanerLoggingService::detailed_system_report_enabled() const {
return chrome_cleaner_report_.environment().detailed_system_report();
}
void CleanerLoggingService::AddFoundUwS(const std::string& found_uws_name) {
base::AutoLock lock(lock_);
chrome_cleaner_report_.add_found_uws(found_uws_name);
}
void CleanerLoggingService::AddDetectedUwS(const PUPData::PUP* found_uws,
UwSDetectedFlags flags) {
DCHECK(found_uws);
UwS detected_uws =
PUPToUwS(found_uws, flags, /*cleaning_files=*/true, &sampler_);
AddDetectedUwS(detected_uws);
}
void CleanerLoggingService::AddDetectedUwS(const UwS& uws) {
base::AutoLock lock(lock_);
UwS* added_uws = chrome_cleaner_report_.add_detected_uws();
*added_uws = uws;
UpdateMatchedFilesAndFoldersMaps(added_uws);
}
void CleanerLoggingService::SetExitCode(ResultCode exit_code) {
ResultCode previous_exit_code = RESULT_CODE_INVALID;
{
base::AutoLock lock(lock_);
previous_exit_code =
static_cast<ResultCode>(chrome_cleaner_report_.exit_code());
chrome_cleaner_report_.set_exit_code(exit_code);
// Once an exit code has been provided, this is not an intermediate log
// anymore.
chrome_cleaner_report_.set_intermediate_log(false);
}
// The DCHECK can't be under |lock_|. The only valid reason to overwrite a non
// pending exit code, is when we failed to read pending upload log files.
DCHECK(previous_exit_code == RESULT_CODE_PENDING ||
exit_code == RESULT_CODE_FAILED_TO_READ_UPLOAD_LOGS_FILE);
}
void CleanerLoggingService::AddLoadedModule(
const base::string16& name,
ModuleHost module_host,
const internal::FileInformation& file_information) {
FileInformation reported_file_information;
FileInformationToProtoObject(file_information, &reported_file_information);
base::AutoLock lock(lock_);
ChromeCleanerReport::SystemReport::LoadedModule* loaded_module =
chrome_cleaner_report_.mutable_system_report()->add_loaded_modules();
loaded_module->set_name(base::UTF16ToUTF8(name));
loaded_module->set_host(module_host);
*loaded_module->mutable_file_information() = reported_file_information;
}
void CleanerLoggingService::AddService(
const base::string16& display_name,
const base::string16& service_name,
const internal::FileInformation& file_information) {
FileInformation reported_file_information;
FileInformationToProtoObject(file_information, &reported_file_information);
base::AutoLock lock(lock_);
ChromeCleanerReport::SystemReport::Service* service =
chrome_cleaner_report_.mutable_system_report()->add_services();
service->set_display_name(base::UTF16ToUTF8(display_name));
service->set_service_name(base::UTF16ToUTF8(service_name));
*service->mutable_file_information() = reported_file_information;
}
void CleanerLoggingService::AddInstalledProgram(
const base::FilePath& folder_path) {
FolderInformation folder_information;
if (!RetrieveFolderInformation(folder_path, &folder_information))
return;
base::AutoLock lock(lock_);
ChromeCleanerReport::SystemReport::InstalledProgram* installed_program =
chrome_cleaner_report_.mutable_system_report()->add_installed_programs();
*installed_program->mutable_folder_information() = folder_information;
}
void CleanerLoggingService::AddProcess(
const base::string16& name,
const internal::FileInformation& file_information) {
FileInformation reported_file_information;
FileInformationToProtoObject(file_information, &reported_file_information);
base::AutoLock lock(lock_);
ChromeCleanerReport::SystemReport::Process* process =
chrome_cleaner_report_.mutable_system_report()->add_processes();
process->set_name(base::UTF16ToUTF8(name));
*process->mutable_file_information() = reported_file_information;
}
void CleanerLoggingService::AddRegistryValue(
const internal::RegistryValue& registry_value,
const std::vector<internal::FileInformation>& file_informations) {
RegistryValue new_registry_value;
new_registry_value.set_key_path(base::UTF16ToUTF8(registry_value.key_path));
new_registry_value.set_value_name(
base::UTF16ToUTF8(registry_value.value_name));
new_registry_value.set_data(base::UTF16ToUTF8(registry_value.data));
for (const auto& file_information : file_informations) {
FileInformation* reported_file_information =
new_registry_value.add_file_informations();
FileInformationToProtoObject(file_information, reported_file_information);
}
base::AutoLock lock(lock_);
*chrome_cleaner_report_.mutable_system_report()->add_registry_values() =
new_registry_value;
}
void CleanerLoggingService::AddLayeredServiceProvider(
const std::vector<base::string16>& guids,
const internal::FileInformation& file_information) {
ChromeCleanerReport_SystemReport_LayeredServiceProvider
layered_service_provider;
FileInformation* reported_file_information =
layered_service_provider.mutable_file_information();
FileInformationToProtoObject(file_information, reported_file_information);
for (const auto& guid : guids)
layered_service_provider.add_guids(base::UTF16ToUTF8(guid));
base::AutoLock lock(lock_);
*chrome_cleaner_report_.mutable_system_report()
->add_layered_service_providers() = layered_service_provider;
}
void CleanerLoggingService::SetWinInetProxySettings(
const base::string16& config,
const base::string16& bypass,
const base::string16& auto_config_url,
bool autodetect) {
base::AutoLock lock(lock_);
ChromeCleanerReport_SystemReport_SystemProxySettings*
win_inet_proxy_settings = chrome_cleaner_report_.mutable_system_report()
->mutable_win_inet_proxy_settings();
win_inet_proxy_settings->set_config(base::UTF16ToUTF8(config));
win_inet_proxy_settings->set_bypass(base::UTF16ToUTF8(bypass));
win_inet_proxy_settings->set_auto_config_url(
base::UTF16ToUTF8(auto_config_url));
win_inet_proxy_settings->set_autodetect(autodetect);
}
void CleanerLoggingService::SetWinHttpProxySettings(
const base::string16& config,
const base::string16& bypass) {
base::AutoLock lock(lock_);
ChromeCleanerReport_SystemReport_SystemProxySettings*
win_http_proxy_settings = chrome_cleaner_report_.mutable_system_report()
->mutable_win_http_proxy_settings();
win_http_proxy_settings->set_config(base::UTF16ToUTF8(config));
win_http_proxy_settings->set_bypass(base::UTF16ToUTF8(bypass));
}
void CleanerLoggingService::AddInstalledExtension(
const base::string16& extension_id,
ExtensionInstallMethod install_method) {
base::AutoLock lock(lock_);
ChromeCleanerReport_SystemReport_InstalledExtension* installed_extension =
chrome_cleaner_report_.mutable_system_report()
->add_installed_extensions();
installed_extension->set_extension_id(base::UTF16ToUTF8(extension_id));
installed_extension->set_install_method(install_method);
}
void CleanerLoggingService::AddScheduledTask(
const base::string16& name,
const base::string16& description,
const std::vector<internal::FileInformation>& actions) {
ScheduledTask scheduled_task;
scheduled_task.set_name(base::UTF16ToUTF8(name));
scheduled_task.set_description(base::UTF16ToUTF8(description));
for (const auto& action : actions) {
FileInformation* reported_action =
scheduled_task.add_actions()->mutable_file_information();
FileInformationToProtoObject(action, reported_action);
}
base::AutoLock lock(lock_);
*chrome_cleaner_report_.mutable_system_report()->add_scheduled_tasks() =
scheduled_task;
}
void CleanerLoggingService::LogProcessInformation(
SandboxType process_type,
const SystemResourceUsage& usage) {
ProcessInformation info =
GetProcessInformationProtoObject(process_type, usage);
base::AutoLock lock(lock_);
chrome_cleaner_report_.add_process_information()->Swap(&info);
}
bool CleanerLoggingService::AllExpectedRemovalsConfirmed() const {
FileRemovalStatusUpdater* status_updater =
FileRemovalStatusUpdater::GetInstance();
base::AutoLock lock(lock_);
for (const UwS& uws : chrome_cleaner_report_.detected_uws()) {
if (uws.state() != UwS::REMOVABLE)
continue;
for (const MatchedFile& file : uws.files()) {
base::string16 sanitized_path =
base::UTF8ToUTF16(file.file_information().path());
RemovalStatus removal_status =
status_updater->GetRemovalStatusOfSanitizedPath(sanitized_path);
// If the removal status was never set in FileRemovalStatusUpdater, fall
// back to the status that was originally set when the MatchedFile object
// was created.
if (removal_status == REMOVAL_STATUS_UNSPECIFIED)
removal_status = file.removal_status();
// Non-active files are collected in the report for later auditing, but
// not expected to be removed.
if (removal_status == REMOVAL_STATUS_MATCHED_ONLY &&
!file.file_information().active_file()) {
continue;
}
if (removal_status != REMOVAL_STATUS_REMOVED &&
removal_status != REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL &&
removal_status != REMOVAL_STATUS_NOT_FOUND &&
removal_status != REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL_FALLBACK &&
removal_status != REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION) {
return false;
}
}
}
return true;
}
std::string CleanerLoggingService::RawReportContent() {
ChromeCleanerReport chrome_cleaner_report;
GetCurrentChromeCleanerReport(&chrome_cleaner_report);
std::string chrome_cleaner_report_string;
chrome_cleaner_report.SerializeToString(&chrome_cleaner_report_string);
return chrome_cleaner_report_string;
}
bool CleanerLoggingService::ReadContentFromFile(
const base::FilePath& log_file) {
std::string proto_string;
if (!base::ReadFileToString(log_file, &proto_string)) {
LOG(ERROR) << "Can't read content of '" << SanitizePath(log_file) << "'.";
return false;
} else if (proto_string.empty()) {
LOG(ERROR) << "Empty log file: " << SanitizePath(log_file);
return false;
}
bool succeeded = false;
{
base::AutoLock lock(lock_);
succeeded = chrome_cleaner_report_.ParseFromString(proto_string);
if (succeeded) {
matched_files_.clear();
matched_folders_.clear();
for (UwS& detected_uws : *chrome_cleaner_report_.mutable_detected_uws())
UpdateMatchedFilesAndFoldersMaps(&detected_uws);
}
}
if (!succeeded) {
LOG(ERROR) << "Read invalid protobuf from '" << SanitizePath(log_file)
<< "'.";
return false;
}
return true;
}
void CleanerLoggingService::ScheduleFallbackLogsUpload(
RegistryLogger* registry_logger,
ResultCode result_code) {
DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
DCHECK(initialized_);
// Even if we don't upload logs, we can still let Chrome UMA know we got to
// this stage.
DCHECK(registry_logger);
registry_logger->WriteExitCode(result_code);
if (!uploads_enabled())
return;
ChromeCleanerReport chrome_cleaner_report;
GetCurrentChromeCleanerReport(&chrome_cleaner_report);
chrome_cleaner_report.set_exit_code(result_code);
ClearTempLogFile(registry_logger);
PendingLogsService::ScheduleLogsUploadTask(PRODUCT_SHORTNAME_STRING,
chrome_cleaner_report,
&temp_log_file_, registry_logger);
}
void CleanerLoggingService::OnReportUploadResult(
const UploadResultCallback& done_callback,
RegistryLogger* registry_logger,
SafeBrowsingReporter::Result result,
const std::string& serialized_report,
std::unique_ptr<ChromeFoilResponse> response) {
DCHECK(registry_logger);
if (result == SafeBrowsingReporter::Result::UPLOAD_SUCCESS)
ClearTempLogFile(registry_logger);
done_callback.Run(result == SafeBrowsingReporter::Result::UPLOAD_SUCCESS);
}
bool CleanerLoggingService::IsReportingNeeded() const {
Settings* settings = Settings::GetInstance();
if (settings->execution_mode() != ExecutionMode::kScanning &&
settings->execution_mode() != ExecutionMode::kCleanup) {
NOTREACHED();
return false;
}
// Raw log lines are collected in vector raw_log_lines_buffer_ and moved to
// proto field raw_log_line whenever the proto is generated. Logs should be
// uploaded if either is non-empty.
{
base::AutoLock lock(raw_log_lines_buffer_lock_);
if (!raw_log_lines_buffer_.empty())
return true;
}
{
base::AutoLock lock(lock_);
return chrome_cleaner_report_.exit_code() != RESULT_CODE_PENDING ||
chrome_cleaner_report_.raw_log_line_size() > 0 ||
chrome_cleaner_report_.found_uws_size() > 0;
}
}
void CleanerLoggingService::ClearTempLogFile(RegistryLogger* registry_logger) {
if (!temp_log_file_.empty()) {
PendingLogsService::ClearPendingLogFile(PRODUCT_SHORTNAME_STRING,
temp_log_file_, registry_logger);
temp_log_file_.clear();
}
}
// static.
bool CleanerLoggingService::LogMessageHandlerFunction(int severity,
const char* file,
int line,
size_t message_start,
const std::string& str) {
CleanerLoggingService* logging_service = CleanerLoggingService::GetInstance();
std::string utf8_str;
if (base::IsStringUTF8(str))
utf8_str = str;
else
utf8_str = RemoveInvalidUTF8Chars(str);
base::AutoLock lock(logging_service->raw_log_lines_buffer_lock_);
logging_service->raw_log_lines_buffer_.push_back(utf8_str);
// Returning false, pretending the event wasn't handled here, let the other
// handlers receive it.
return false;
}
void CleanerLoggingService::GetCurrentChromeCleanerReport(
ChromeCleanerReport* chrome_cleaner_report) {
DCHECK(chrome_cleaner_report);
UpdateFileRemovalStatuses();
std::vector<std::string> raw_log_lines_to_send;
{
base::AutoLock lock(raw_log_lines_buffer_lock_);
raw_log_lines_to_send.reserve(raw_log_lines_buffer_.size());
raw_log_lines_to_send.insert(raw_log_lines_to_send.begin(),
raw_log_lines_buffer_.begin(),
raw_log_lines_buffer_.end());
raw_log_lines_buffer_.clear();
}
{
base::AutoLock lock(lock_);
for (const std::string& line : raw_log_lines_to_send)
chrome_cleaner_report_.add_raw_log_line(line);
chrome_cleaner_report->CopyFrom(chrome_cleaner_report_);
}
}
void CleanerLoggingService::UpdateMatchedFilesAndFoldersMaps(UwS* added_uws) {
for (MatchedFile& file : *added_uws->mutable_files())
matched_files_[file.file_information().path()].push_back(&file);
for (MatchedFolder& folder : *added_uws->mutable_folders())
matched_folders_[folder.folder_information().path()].push_back(&folder);
}
void CleanerLoggingService::UpdateFileRemovalStatuses() {
for (const auto& path_and_status :
FileRemovalStatusUpdater::GetInstance()->GetAllRemovalStatuses()) {
std::string sanitized_path = base::UTF16ToUTF8(path_and_status.first);
FileRemovalStatusUpdater::FileRemovalStatus status = path_and_status.second;
DCHECK(status.removal_status != REMOVAL_STATUS_UNSPECIFIED);
bool known_matched_file = true;
{
base::AutoLock lock(lock_);
auto file_it = matched_files_.find(sanitized_path);
auto folder_it = matched_folders_.find(sanitized_path);
if (file_it != matched_files_.end()) {
for (MatchedFile* matched_file : file_it->second)
matched_file->set_removal_status(status.removal_status);
} else if (folder_it != matched_folders_.end()) {
for (MatchedFolder* matched_folder : folder_it->second)
matched_folder->set_removal_status(status.removal_status);
} else {
known_matched_file = false;
}
}
// Files that were deleted should be matched with an UwS by calling
// AddDetectedUwS before logging. But there are some circumstances where
// AddDetectedUwS is not called, such as when there are no existing files
// for that UwS at the moment it is logged, so it should not be marked
// removable. These should match up to circumstances where no files are
// deleted, but just in case, log any file deletions that aren't matched to
// an UwS for investigation.
if (!known_matched_file) {
FileInformation file_information;
bool got_file_information = GetFileInformationProtoObject(
status.path, /*detailed_information=*/true, &file_information);
base::AutoLock lock(lock_);
// Since the item might have been deleted at this point, just assume it
// was a file.
MatchedFile* file = nullptr;
auto* unknown_uws = chrome_cleaner_report_.mutable_unknown_uws();
for (int i = 0; i < unknown_uws->files_size(); i++) {
if (unknown_uws->files(i).file_information().path() == sanitized_path) {
file = unknown_uws->mutable_files(i);
break;
}
}
if (file == nullptr)
file = chrome_cleaner_report_.mutable_unknown_uws()->add_files();
if (got_file_information) {
*file->mutable_file_information() = file_information;
} else {
// If we can't get the detailed file information, still record at least
// the path.
file->mutable_file_information()->set_path(sanitized_path);
}
file->set_removal_status(status.removal_status);
}
}
}
} // namespace chrome_cleaner