blob: 48475967016bbd09e3de59b0c4b2c4ef86a29e56 [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/registry_logger.h"
#include <stdint.h>
#include <set>
#include "base/files/file_path.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "chrome/chrome_cleaner/constants/version.h"
#include "components/chrome_cleaner/public/constants/constants.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chrome_cleaner {
const wchar_t kLogFilePath1[] = L"AFilePath";
const wchar_t kLogFilePath2[] = L"AnotherOne";
const wchar_t kLogFilePath3[] = L"ThirdsTheCharm";
const char kCorruptFilePath[] = "x";
const wchar_t kWideCorruptFilePath[] = L"x";
const wchar_t kRawFilePath[] = L"AFilePath\0\0";
const char kTestSuffix[] = "TestSuffix";
const char kInvalidUTF8[] = "\xed\xa0\x80\xed\xbf\xbf";
class TestRegistryLogger : public RegistryLogger {
public:
explicit TestRegistryLogger(RegistryLogger::Mode mode)
: RegistryLogger(mode) {}
TestRegistryLogger(RegistryLogger::Mode mode, std::string suffix)
: RegistryLogger(mode, suffix) {}
using RegistryLogger::GetLoggingKeyPath;
using RegistryLogger::GetScanTimesKeyPath;
using RegistryLogger::GetKeySuffix;
using RegistryLogger::ReadValues;
using RegistryLogger::kMaxUploadResultLength;
using RegistryLogger::kPendingLogFilesValue;
};
bool ReadFoundPUPs(std::set<UwSId>* stored_pups) {
DCHECK(stored_pups);
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(mode);
base::win::RegKey logging_key;
logging_key.Open(HKEY_CURRENT_USER, logger.GetLoggingKeyPath(mode).c_str(),
KEY_QUERY_VALUE);
if (!logging_key.Valid())
return false;
std::vector<base::string16> existing_pups;
bool success = true;
if (logger.ReadValues(logging_key, kFoundUwsValueName, &existing_pups,
nullptr)) {
for (base::string16 pup_id_string : existing_pups) {
UwSId pup_id = 0;
if (base::StringToUint(pup_id_string, &pup_id))
stored_pups->insert(pup_id);
else
success = false;
}
}
return success;
}
// Open |reg_key| using GetLoggingKeyPath(). Returns true if the key is
// created properly, false otherwise.
bool OpenLoggingRegKey(base::win::RegKey* reg_key,
const TestRegistryLogger& logger,
const REGSAM access,
const RegistryLogger::Mode mode) {
LONG result = reg_key->Open(HKEY_CURRENT_USER,
logger.GetLoggingKeyPath(mode).c_str(), access);
return reg_key->Valid() && (result == ERROR_SUCCESS);
}
// Open |reg_key| using GetScanTimesKeyPath(). Returns true if the key is
// created properly, false otherwise.
bool OpenScanTimeRegKey(base::win::RegKey* reg_key,
const TestRegistryLogger& logger,
const REGSAM access,
const RegistryLogger::Mode mode) {
LONG result = reg_key->Open(HKEY_CURRENT_USER,
logger.GetScanTimesKeyPath(mode).c_str(), access);
return reg_key->Valid() && (result == ERROR_SUCCESS);
}
class RegistryLoggerTest : public testing::Test {
public:
void SetUp() override {
registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER);
}
protected:
registry_util::RegistryOverrideManager registry_override_manager_;
};
TEST_F(RegistryLoggerTest, AppendLogUploadResultBasic) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(mode);
logger.AppendLogUploadResult(true);
logger.AppendLogUploadResult(false);
logger.AppendLogUploadResult(false);
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
base::string16 upload_results;
LONG result = logging_key.ReadValue(kUploadResultsValueName, &upload_results);
EXPECT_EQ(ERROR_SUCCESS, result);
EXPECT_STREQ(L"1;0;0;", upload_results.c_str());
}
TEST_F(RegistryLoggerTest, WriteReporterLogsUploadResult) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
TestRegistryLogger logger(mode);
logger.WriteReporterLogsUploadResult(
SafeBrowsingReporter::Result::UPLOAD_INTERNAL_ERROR);
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
DWORD upload_result;
EXPECT_EQ(ERROR_SUCCESS, logging_key.ReadValueDW(kLogsUploadResultValueName,
&upload_result));
EXPECT_EQ(
static_cast<DWORD>(SafeBrowsingReporter::Result::UPLOAD_INTERNAL_ERROR),
upload_result);
}
TEST_F(RegistryLoggerTest, WriteAndClearScanTimes) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
TestRegistryLogger logger(mode);
base::TimeDelta scan_time = base::TimeDelta::FromSeconds(3);
logger.WriteScanTime(42, scan_time);
base::win::RegKey scan_times_key;
ASSERT_TRUE(
OpenScanTimeRegKey(&scan_times_key, logger, KEY_QUERY_VALUE, mode));
int64_t us_scan_time = 0;
LONG result = scan_times_key.ReadInt64(L"42", &us_scan_time);
EXPECT_EQ(ERROR_SUCCESS, result);
base::TimeDelta read_scan_time =
base::TimeDelta::FromMicroseconds(us_scan_time);
EXPECT_EQ(read_scan_time, scan_time);
logger.ClearScanTimes();
ASSERT_TRUE(
OpenScanTimeRegKey(&scan_times_key, logger, KEY_QUERY_VALUE, mode));
EXPECT_EQ(0U, scan_times_key.GetValueCount());
}
TEST_F(RegistryLoggerTest, AppendLogUploadResultMaxLength) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
static const size_t kMaxNumUploadResults =
TestRegistryLogger::kMaxUploadResultLength / 2;
for (size_t i = 0; i < kMaxNumUploadResults; ++i)
logger.AppendLogUploadResult(true);
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
base::string16 upload_results;
LONG result = logging_key.ReadValue(kUploadResultsValueName, &upload_results);
EXPECT_EQ(ERROR_SUCCESS, result);
base::string16 expected_string;
for (size_t i = 0; i < kMaxNumUploadResults; ++i)
expected_string += L"1;";
EXPECT_EQ(expected_string, upload_results);
logger.AppendLogUploadResult(false);
result = logging_key.ReadValue(kUploadResultsValueName, &upload_results);
EXPECT_EQ(ERROR_SUCCESS, result);
expected_string[expected_string.length() - 2] = '0';
EXPECT_EQ(expected_string, upload_results);
}
TEST_F(RegistryLoggerTest, LogFilePath) {
TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
base::FilePath log_file;
base::FilePath log_file_path1(kLogFilePath1);
EXPECT_TRUE(logger.AppendLogFilePath(log_file_path1));
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(log_file_path1, log_file);
base::FilePath log_file_path2(kLogFilePath2);
EXPECT_TRUE(logger.AppendLogFilePath(log_file_path2));
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(log_file_path1, log_file);
// If we remove the second file, there is still the first file.
EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path2));
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(log_file_path1, log_file);
// Also test removing the first file in the list.
EXPECT_TRUE(logger.AppendLogFilePath(log_file_path2));
EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path1));
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(log_file_path2, log_file);
// And now middle file removal.
base::FilePath log_file_path3(kLogFilePath3);
EXPECT_TRUE(logger.AppendLogFilePath(log_file_path1));
EXPECT_TRUE(logger.AppendLogFilePath(log_file_path3));
EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path1));
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(log_file_path2, log_file);
EXPECT_TRUE(logger.RemoveLogFilePath(log_file_path2));
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(log_file_path3, log_file);
EXPECT_FALSE(logger.RemoveLogFilePath(log_file_path3));
logger.GetNextLogFilePath(&log_file);
EXPECT_TRUE(log_file.empty());
}
TEST_F(RegistryLoggerTest, LogFilePathFailures) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(mode);
// Make sure we get an empty file path when there's no value.
base::FilePath log_file;
logger.GetNextLogFilePath(&log_file);
EXPECT_TRUE(log_file.empty());
// Create an empty value, and make sure we also get an empty file path.
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_SET_VALUE, mode));
logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue, L"");
logger.GetNextLogFilePath(&log_file);
EXPECT_TRUE(log_file.empty());
// And the value should have been wiped.
EXPECT_FALSE(logging_key.HasValue(TestRegistryLogger::kPendingLogFilesValue));
// Now try with an invalid value type.
logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue, 42);
logger.GetNextLogFilePath(&log_file);
EXPECT_TRUE(log_file.empty());
// And again, the value should have been wiped.
EXPECT_FALSE(logging_key.HasValue(TestRegistryLogger::kPendingLogFilesValue));
}
TEST_F(RegistryLoggerTest, LogFilePathRegistryFailure) {
TestRegistryLogger logger(RegistryLogger::Mode::NOOP_FOR_TESTING);
base::FilePath log_file_path1(kLogFilePath1);
EXPECT_FALSE(logger.AppendLogFilePath(log_file_path1));
EXPECT_FALSE(logger.RemoveLogFilePath(log_file_path1));
base::FilePath log_file;
logger.GetNextLogFilePath(&log_file);
EXPECT_TRUE(log_file.empty());
}
TEST_F(RegistryLoggerTest, LogFilePathWithCorruptKey) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(mode);
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_SET_VALUE, mode));
// Write a corrupt registry key (one byte, no ending null character).
logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue,
kCorruptFilePath, 1, REG_MULTI_SZ);
base::FilePath log_file;
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(kWideCorruptFilePath, log_file.value());
}
TEST_F(RegistryLoggerTest, LogFilePathRawContent) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(mode);
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_SET_VALUE, mode));
// Try every possibility of ending trailing null characters.
size_t length = wcslen(kRawFilePath) * sizeof(wchar_t);
for (; length < sizeof(kRawFilePath); ++length) {
logging_key.WriteValue(TestRegistryLogger::kPendingLogFilesValue,
kRawFilePath, length, REG_MULTI_SZ);
base::FilePath log_file;
logger.GetNextLogFilePath(&log_file);
EXPECT_EQ(kRawFilePath, log_file.value());
}
}
TEST_F(RegistryLoggerTest, RecordFoundPUPs) {
TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
std::set<UwSId> stored_pups;
ReadFoundPUPs(&stored_pups);
EXPECT_TRUE(stored_pups.empty());
std::vector<UwSId> pup_list{1, 2, 3, 4, 5};
EXPECT_TRUE(logger.RecordFoundPUPs(pup_list));
EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
std::set<UwSId> expected_pups{1, 2, 3, 4, 5};
for (UwSId pup_id : stored_pups)
EXPECT_EQ(1UL, expected_pups.count(pup_id));
EXPECT_EQ(expected_pups.size(), stored_pups.size());
std::vector<UwSId> pup_list2{6};
EXPECT_TRUE(logger.RecordFoundPUPs(pup_list2));
EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
std::set<UwSId> expected_pups2{1, 2, 3, 4, 5, 6};
for (UwSId pup_id : stored_pups)
EXPECT_EQ(1UL, expected_pups2.count(pup_id));
EXPECT_EQ(expected_pups2.size(), stored_pups.size());
std::vector<UwSId> pup_list3{5, 6, 7, 8};
EXPECT_TRUE(logger.RecordFoundPUPs(pup_list3));
EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
std::set<UwSId> expected_pups3{1, 2, 3, 4, 5, 6, 7, 8};
for (UwSId pup_id : stored_pups)
EXPECT_EQ(1UL, expected_pups3.count(pup_id));
EXPECT_EQ(expected_pups3.size(), stored_pups.size());
}
TEST_F(RegistryLoggerTest, RecordFoundPUPsDuplicates) {
TestRegistryLogger logger(RegistryLogger::Mode::REMOVER);
std::set<UwSId> stored_pups;
ReadFoundPUPs(&stored_pups);
EXPECT_TRUE(stored_pups.empty());
std::vector<UwSId> pup_list{1, 1, 2, 2, 3};
EXPECT_TRUE(logger.RecordFoundPUPs(pup_list));
EXPECT_TRUE(ReadFoundPUPs(&stored_pups));
std::set<UwSId> expected_pups{1, 2, 3};
for (UwSId pup_id : stored_pups)
EXPECT_EQ(1UL, expected_pups.count(pup_id));
}
// TODO(joenotcharles):
// Have components/chrome_cleaner/public/constants/constant.cc use branding
// instead of hardcoded strings so that we expect the correct company name here
// on all builds
TEST_F(RegistryLoggerTest, DISABLED_LoggingKeyPathContainsCompanyName) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
TestRegistryLogger logger(mode);
const base::string16 expected_name = base::StrCat(
{L"Software\\", COMPANY_SHORTNAME_STRING, L"\\Software Removal Tool"});
EXPECT_EQ(expected_name, logger.GetLoggingKeyPath(mode));
}
// TODO(joenotcharles):
// Have components/chrome_cleaner/public/constants/constant.cc use branding
// instead of hardcoded strings so that we expect the correct company name here
// on all builds
TEST_F(RegistryLoggerTest, DISABLED_UseSuffixRegistryKey) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
TestRegistryLogger logger(mode, kTestSuffix);
TestRegistryLogger no_suffix_logger(mode);
base::string16 key_name = no_suffix_logger.GetLoggingKeyPath(mode);
EXPECT_EQ(base::string16::npos,
key_name.find(base::UTF8ToUTF16(kTestSuffix).c_str()));
key_name = logger.GetLoggingKeyPath(mode);
base::string16 expected_name = base::StrCat(
{L"Software\\", COMPANY_SHORTNAME_STRING, L"\\Software Removal Tool\\",
base::UTF8ToUTF16(kTestSuffix)});
EXPECT_EQ(expected_name, key_name);
base::win::RegKey no_suffix_key;
EXPECT_TRUE(
OpenLoggingRegKey(&no_suffix_key, no_suffix_logger, KEY_SET_VALUE, mode));
base::win::RegKey logger_key;
EXPECT_TRUE(OpenLoggingRegKey(&logger_key, logger, KEY_SET_VALUE, mode));
}
TEST_F(RegistryLoggerTest, InvalidRegistrySuffixes) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
// Invalid characters in suffix.
TestRegistryLogger logger_invalid_utf8(mode, kInvalidUTF8);
base::win::RegKey invalid_utf8_key;
EXPECT_FALSE(OpenLoggingRegKey(&invalid_utf8_key, logger_invalid_utf8,
KEY_SET_VALUE, mode));
// Suffix too long.
std::string suffix_too_long(0x4000, 'A');
TestRegistryLogger logger_too_long(RegistryLogger::Mode::REPORTER,
suffix_too_long);
base::win::RegKey too_long_key;
EXPECT_FALSE(
OpenLoggingRegKey(&too_long_key, logger_too_long, KEY_SET_VALUE, mode));
}
TEST_F(RegistryLoggerTest, WriteExperimentalEngineResultCode) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REPORTER;
TestRegistryLogger logger(mode);
logger.WriteExperimentalEngineResultCode(42);
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
DWORD engine_error_code;
EXPECT_EQ(ERROR_SUCCESS, logging_key.ReadValueDW(kEngineErrorCodeValueName,
&engine_error_code));
EXPECT_EQ(static_cast<DWORD>(42), engine_error_code);
}
TEST_F(RegistryLoggerTest, RecordAndResetCompletedCleanup) {
const RegistryLogger::Mode mode = RegistryLogger::Mode::REMOVER;
TestRegistryLogger logger(mode);
logger.RecordCompletedCleanup();
{
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
DWORD recorded_value;
EXPECT_EQ(ERROR_SUCCESS, logging_key.ReadValueDW(kCleanupCompletedValueName,
&recorded_value));
EXPECT_EQ(static_cast<DWORD>(1), recorded_value);
}
logger.ResetCompletedCleanup();
{
base::win::RegKey logging_key;
ASSERT_TRUE(OpenLoggingRegKey(&logging_key, logger, KEY_QUERY_VALUE, mode));
EXPECT_FALSE(logging_key.HasValue(kCleanupCompletedValueName));
}
}
} // namespace chrome_cleaner