Add integration tests from the internal chrome_cleaner repo.
Also adds SanitizePathVsRawPupDataCsidlTest which was really a pup_data
unit test, not an integration test.
(cherry picked from commit 1cfbc09c8488384fffaec9f2b0bef5b60147a8b2)
Change-Id: I03489b1759ce2640369c9f2aa502269d065eb0ec
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1744327
Commit-Queue: Joe Mason <[email protected]>
Reviewed-by: proberge <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#685671}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1752070
Reviewed-by: Joe Mason <[email protected]>
Cr-Commit-Position: refs/branch-heads/3865@{#354}
Cr-Branched-From: 0cdcc6158160790658d1f033d3db873603250124-refs/heads/master@{#681094}
diff --git a/chrome/chrome_cleaner/pup_data/pup_data_unittest.cc b/chrome/chrome_cleaner/pup_data/pup_data_unittest.cc
index 99672fb5a..2739ab0 100644
--- a/chrome/chrome_cleaner/pup_data/pup_data_unittest.cc
+++ b/chrome/chrome_cleaner/pup_data/pup_data_unittest.cc
@@ -8,6 +8,7 @@
#include <algorithm>
#include <deque>
+#include <map>
#include <string>
#include "base/files/file_path.h"
@@ -16,8 +17,11 @@
#include "base/path_service.h"
#include "base/stl_util.h"
#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
#include "base/test/test_reg_util_win.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
#include "chrome/chrome_cleaner/proto/shared_pup_enums.pb.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
#include "chrome/chrome_cleaner/test/test_file_util.h"
#include "chrome/chrome_cleaner/test/test_pup_data.h"
#include "chrome/chrome_cleaner/test/test_registry_util.h"
@@ -668,4 +672,47 @@
TestUwSCatalog::GetInstance().GetUwSIds().size());
}
+// Verify that SanitizePath is written to handle all the CSIDL values used in
+// the PuP data.
+TEST(SanitizePathVsRawPupDataCsidlTest, TestAllCsidlValues) {
+ using chrome_cleaner::PUPData;
+ using chrome_cleaner::sanitization_internal::PATH_CSIDL_END;
+ using chrome_cleaner::sanitization_internal::PATH_CSIDL_START;
+
+ // Get set containing the distinct CSIDL values used in rewrite_rules[].
+ std::set<int> csidl_list;
+ for (const auto& entry : chrome_cleaner::PathKeyToSanitizeString()) {
+ int id = entry.first;
+ // Exclude non-CSIDL replacements.
+ if (id < PATH_CSIDL_START || id > PATH_CSIDL_END) {
+ continue;
+ }
+
+ // id represents a key used by PathService to lookup a FilePath. A
+ // PathService Provider was registered to handle the CSIDL values with an
+ // offset of PATH_CSIDL_START to avoid collisions with other PathService
+ // Providers.
+ int csidl = id - PATH_CSIDL_START;
+ csidl_list.insert(csidl);
+ }
+
+ // Report any unchecked CSIDLs as unsanitized.
+ for (const auto& pup_id : *PUPData::GetUwSIds()) {
+ const PUPData::UwSSignature& signature =
+ PUPData::GetPUP(pup_id)->signature();
+ for (const PUPData::StaticDiskFootprint* disk_footprint =
+ signature.disk_footprints;
+ disk_footprint->path != nullptr;
+ ++disk_footprint) {
+ int csidl = disk_footprint->csidl;
+ if (csidl != PUPData::kInvalidCsidl &&
+ csidl_list.find(csidl) == csidl_list.end()) {
+ ADD_FAILURE() << "CSIDL " << csidl << " is not sanitized in "
+ << signature.name << " with footprint "
+ << disk_footprint->path;
+ }
+ }
+ }
+}
+
} // namespace chrome_cleaner
diff --git a/chrome/chrome_cleaner/test/BUILD.gn b/chrome/chrome_cleaner/test/BUILD.gn
index 66d5bfce..35654386 100644
--- a/chrome/chrome_cleaner/test/BUILD.gn
+++ b/chrome/chrome_cleaner/test/BUILD.gn
@@ -208,11 +208,32 @@
source_set("integration_test_sources") {
testonly = true
- # TODO(crbug.com/949669): Move integration test sources here from internal
- # repo.
+ sources = [
+ "cleaner_test.cc",
+ "secure_dll_loading_test.cc",
+ ]
+
+ deps = [
+ ":test_util",
+ "//base",
+ "//base/test:test_support",
+ "//chrome/chrome_cleaner:buildflags",
+ "//chrome/chrome_cleaner/constants:common_strings",
+ "//chrome/chrome_cleaner/logging/proto:chrome_cleaner_report_proto",
+ "//chrome/chrome_cleaner/logging/proto:reporter_logs_proto",
+ "//chrome/chrome_cleaner/logging/proto:shared_data_proto",
+ "//chrome/chrome_cleaner/os:common_os",
+ "//chrome/chrome_cleaner/pup_data:pup_data_base",
+ "//chrome/chrome_cleaner/pup_data:test_uws",
+ "//chrome/chrome_cleaner/zip_archiver:common",
+ "//components/chrome_cleaner/public/constants",
+ "//components/chrome_cleaner/test:test_name_helper",
+ "//testing/gtest",
+ ]
data_deps = [
# Binaries that will be loaded by the tests.
+ ":empty_dll",
"//chrome/chrome_cleaner/executables:chrome_cleanup_tool",
"//chrome/chrome_cleaner/executables:software_reporter_tool",
]
diff --git a/chrome/chrome_cleaner/test/cleaner_test.cc b/chrome/chrome_cleaner/test/cleaner_test.cc
new file mode 100644
index 0000000..d6df17e3
--- /dev/null
+++ b/chrome/chrome_cleaner/test/cleaner_test.cc
@@ -0,0 +1,514 @@
+// Copyright 2019 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 <string>
+#include <tuple>
+
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/test/test_timeouts.h"
+#include "base/win/windows_version.h"
+#include "chrome/chrome_cleaner/buildflags.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/logging/proto/chrome_cleaner_report.pb.h"
+#include "chrome/chrome_cleaner/logging/proto/reporter_logs.pb.h"
+#include "chrome/chrome_cleaner/logging/proto/shared_data.pb.h"
+#include "chrome/chrome_cleaner/os/disk_util.h"
+#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
+#include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
+#include "chrome/chrome_cleaner/pup_data/pup_data.h"
+#include "chrome/chrome_cleaner/pup_data/test_uws.h"
+#include "chrome/chrome_cleaner/test/test_util.h"
+#include "chrome/chrome_cleaner/zip_archiver/sandboxed_zip_archiver.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "components/chrome_cleaner/public/constants/result_codes.h"
+#include "components/chrome_cleaner/test/test_name_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+using chrome_cleaner::Engine;
+using chrome_cleaner::PUPData;
+using ::testing::Combine;
+using ::testing::Values;
+using ::testing::ValuesIn;
+
+const wchar_t kCleanerExecutable[] = L"chrome_cleanup_tool.exe";
+const wchar_t kScannerExecutable[] = L"software_reporter_tool.exe";
+
+const wchar_t kProtoExtension[] = L"pb";
+
+// Strings which identify log entries which should be allowed because they fail
+// the SanitizePath check, but are covered by one of the cases below:
+//
+// Case 1: When a prefix is added to a valid path, it shows up as unsanitized
+// even though SanitizePath() may have been used.
+//
+// Case 2: A registry value contains paths which would ideally be sanitized,
+// but the exact value should be reported to prevent ambiguity. Currently this
+// generally means we just ignore the registry fields in the proto.
+const std::vector<base::string16> kAllowedLogStringsForSanitizationCheck = {
+ // TryToExpandPath() in disk_util.cc fits Case 1.
+ L"] unable to retrieve file information on non-existing file: \'",
+
+ // IsFilePresentLocally in file_path_sanitization.cc fits Case 1.
+ L"isfilepresentlocally failed to get attributes: ",
+};
+
+// Parse to |report| the serialized report dumped by |executable_name|. Return
+// true on success.
+bool ParseSerializedReport(const wchar_t* const executable_name,
+ chrome_cleaner::ChromeCleanerReport* report) {
+ DCHECK(executable_name);
+ DCHECK(report);
+ base::FilePath cleaner_executable(executable_name);
+ base::FilePath exe_file_path =
+ chrome_cleaner::PreFetchedPaths::GetInstance()->GetExecutablePath();
+ base::FilePath proto_dump_file_path(exe_file_path.DirName().Append(
+ cleaner_executable.ReplaceExtension(kProtoExtension)));
+ std::string dumped_proto_string;
+ return base::ReadFileToString(proto_dump_file_path, &dumped_proto_string) &&
+ report->ParseFromString(dumped_proto_string);
+}
+
+base::string16 StringToWLower(const base::string16& source) {
+ // TODO(joenotcharles): Investigate moving this into string_util.cc and using
+ // it instead of ToLowerASCII() through the whole code base. Case insensitive
+ // compares will also need to be changed.
+ base::string16 copy = source;
+ for (wchar_t& character : copy) {
+ character = towlower(character);
+ }
+ return copy;
+}
+
+std::vector<base::string16> GetUnsanitizedPaths() {
+ std::vector<base::string16> unsanitized_path_strings;
+ bool success = true;
+ for (const auto& entry : chrome_cleaner::PathKeyToSanitizeString()) {
+ int id = entry.first;
+ base::FilePath unsanitized_path;
+ if (!base::PathService::Get(id, &unsanitized_path)) {
+ LOG(ERROR) << "Failed to convert id (" << id << ") in PathService";
+ success = false;
+ continue;
+ }
+ base::string16 unsanitized_path_string =
+ StringToWLower(unsanitized_path.value());
+ unsanitized_path_strings.push_back(unsanitized_path_string);
+ }
+ CHECK(success);
+ return unsanitized_path_strings;
+}
+
+bool ContainsAnyOf(const base::string16& main_string,
+ const std::vector<base::string16>& substrings) {
+ return std::any_of(substrings.begin(), substrings.end(),
+ [&main_string](const base::string16& path) -> bool {
+ return main_string.find(path) != base::string16::npos;
+ });
+}
+
+template <typename RepeatedTypeWithFileInformation>
+void CheckFieldForUnsanitizedPaths(
+ const RepeatedTypeWithFileInformation& field,
+ const std::string& field_name,
+ const std::vector<base::string16>& unsanitized_path_strings) {
+ for (const auto& sub_field : field) {
+ std::string utf8_path = sub_field.file_information().path();
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_path)),
+ unsanitized_path_strings))
+ << "Found unsanitized " << field_name << ":\n>>> " << utf8_path;
+ }
+}
+
+bool CheckCleanerReportForUnsanitizedPaths(
+ const chrome_cleaner::ChromeCleanerReport& report) {
+ EXPECT_GT(report.raw_log_line_size(), 0);
+ std::vector<base::string16> unsanitized_path_strings = GetUnsanitizedPaths();
+ size_t line_number = 0;
+ for (const auto& utf8_line : report.raw_log_line()) {
+ ++line_number;
+ if (!ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_line)),
+ kAllowedLogStringsForSanitizationCheck)) {
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_line)),
+ unsanitized_path_strings))
+ << "Found unsanitized logs line (" << line_number << "):\n>>> "
+ << utf8_line;
+ }
+ }
+
+ for (const auto& unknown_uws_file : report.unknown_uws().files()) {
+ std::string utf8_path = unknown_uws_file.file_information().path();
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_path)),
+ unsanitized_path_strings))
+ << "Found unsanitized unknown uws file:\n>>> " << utf8_path;
+ }
+
+ for (const auto& detected_uws : report.detected_uws()) {
+ CheckFieldForUnsanitizedPaths(detected_uws.files(), "uws files",
+ unsanitized_path_strings);
+ for (const auto& folder : detected_uws.folders()) {
+ std::string utf8_path = folder.folder_information().path();
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_path)),
+ unsanitized_path_strings))
+ << "Found unsanitized detected uws folder:\n>>> " << utf8_path;
+ }
+
+ // Intentionally skipping registry values since we don't sanitize them on
+ // purpose so we are sure what their value is.
+
+ for (const auto& scheduled_task : detected_uws.scheduled_tasks()) {
+ std::string utf8_path = scheduled_task.scheduled_task().name();
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_path)),
+ unsanitized_path_strings))
+ << "Found unsanitized detected uws scheduled task:\n>>> "
+ << utf8_path;
+ }
+ }
+
+ // TODO(joenotcharles): Switch this over to use report.DebugString() instead
+ // of checking individual fields.
+ CheckFieldForUnsanitizedPaths(report.system_report().loaded_modules(),
+ "loaded module", unsanitized_path_strings);
+ CheckFieldForUnsanitizedPaths(report.system_report().processes(), "process",
+ unsanitized_path_strings);
+ CheckFieldForUnsanitizedPaths(report.system_report().services(), "service",
+ unsanitized_path_strings);
+ CheckFieldForUnsanitizedPaths(
+ report.system_report().layered_service_providers(),
+ "layered service provider", unsanitized_path_strings);
+
+ for (const auto& installed_extensions :
+ report.system_report().installed_extensions()) {
+ for (const auto& extension_file : installed_extensions.extension_files()) {
+ std::string utf8_path = extension_file.path();
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_path)),
+ unsanitized_path_strings))
+ << "Found unsanitized installed extension file:\n>>> " << utf8_path;
+ }
+ }
+
+ for (const auto& installed_program :
+ report.system_report().installed_programs()) {
+ std::string utf8_path = installed_program.folder_information().path();
+ EXPECT_FALSE(ContainsAnyOf(StringToWLower(base::UTF8ToWide(utf8_path)),
+ unsanitized_path_strings))
+ << "Found unsanitized layered service provider:\n>>> " << utf8_path;
+ }
+
+ // Intentionally skipping |system_report().registry_values()| since we don't
+ // sanitize them on purpose so we are sure what their value is.
+
+ for (const auto& scheduled_task : report.system_report().scheduled_tasks()) {
+ CheckFieldForUnsanitizedPaths(
+ scheduled_task.actions(),
+ "scheduled task action for " + scheduled_task.name(),
+ unsanitized_path_strings);
+ }
+
+ return !::testing::Test::HasFailure();
+}
+
+enum class TestFeatures {
+ kNone,
+ kWithoutSandbox,
+};
+
+// We can't use testing::Range with an enum so create an array to use with
+// testing::ValuesIn.
+// clang-format off
+constexpr TestFeatures kAllTestFeatures[] = {
+ TestFeatures::kNone,
+ TestFeatures::kWithoutSandbox,
+};
+// clang-format on
+
+std::ostream& operator<<(std::ostream& stream, TestFeatures features) {
+ switch (features) {
+ case TestFeatures::kNone:
+ stream << "None";
+ break;
+ case TestFeatures::kWithoutSandbox:
+ stream << "WithoutSandbox";
+ break;
+ default:
+ stream << "Unknown" << static_cast<int>(features);
+ break;
+ }
+ return stream;
+}
+
+class CleanerTest
+ : public ::testing::TestWithParam<std::tuple<TestFeatures, Engine::Name>> {
+ public:
+ void SetUp() override {
+ std::tie(test_features_, engine_) = GetParam();
+
+ // Make sure the test UwS has the flags we expect.
+ ASSERT_FALSE(PUPData::IsConfirmedUwS(chrome_cleaner::kGoogleTestAUwSID));
+ ASSERT_FALSE(PUPData::IsRemovable(chrome_cleaner::kGoogleTestAUwSID));
+ ASSERT_TRUE(PUPData::IsConfirmedUwS(chrome_cleaner::kGoogleTestBUwSID));
+ ASSERT_TRUE(PUPData::IsRemovable(chrome_cleaner::kGoogleTestBUwSID));
+
+ base::FilePath start_menu_folder;
+ CHECK(base::PathService::Get(base::DIR_START_MENU, &start_menu_folder));
+ base::FilePath startup_dir = start_menu_folder.Append(L"Startup");
+
+ scan_only_test_uws_ = startup_dir.Append(chrome_cleaner::kTestUwsAFilename);
+ removable_test_uws_ = startup_dir.Append(chrome_cleaner::kTestUwsBFilename);
+
+ // Always create scan-only UwS. Only some tests will have removable UwS.
+ ASSERT_NE(-1, base::WriteFile(scan_only_test_uws_,
+ chrome_cleaner::kTestUwsAFileContents,
+ chrome_cleaner::kTestUwsAFileContentsSize));
+
+ ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
+ InitializeRemovableUwSArchivePath();
+ }
+
+ void TearDown() override {
+ if (locked_file_.IsValid())
+ locked_file_.Close();
+ // Remove any leftover UwS.
+ base::DeleteFile(scan_only_test_uws_, /*recursive=*/false);
+ base::DeleteFile(removable_test_uws_, /*recursive=*/false);
+ }
+
+ void InitializeRemovableUwSArchivePath() {
+ const std::string uws_content(chrome_cleaner::kTestUwsBFileContents,
+ chrome_cleaner::kTestUwsBFileContentsSize);
+ std::string uws_hash;
+ ASSERT_TRUE(
+ chrome_cleaner::ComputeSHA256DigestOfString(uws_content, &uws_hash));
+
+ const base::string16 zip_filename =
+ chrome_cleaner::internal::ConstructZipArchiveFileName(
+ chrome_cleaner::kTestUwsBFilename, uws_hash,
+ /*max_filename_length=*/255);
+ expected_uws_archive_ = temp_dir_.GetPath().Append(zip_filename);
+ }
+
+ void CreateRemovableUwS() {
+ ASSERT_NE(-1, base::WriteFile(removable_test_uws_,
+ chrome_cleaner::kTestUwsBFileContents,
+ chrome_cleaner::kTestUwsBFileContentsSize));
+ }
+
+ void LockRemovableUwS() {
+ // Opening a handle to the file will allow other processes read access, but
+ // not deletion.
+ locked_file_.Initialize(removable_test_uws_, base::File::FLAG_OPEN |
+ base::File::FLAG_READ |
+ base::File::FLAG_WRITE);
+ }
+
+ void ExpectExitCode(const base::CommandLine& command_line,
+ int expected_exit_code) {
+ base::Process process(
+ base::LaunchProcess(command_line, base::LaunchOptions()));
+ ASSERT_TRUE(process.IsValid());
+
+ int exit_code = -1;
+ bool exited_within_timeout = process.WaitForExitWithTimeout(
+ base::TimeDelta::FromMinutes(10), &exit_code);
+ EXPECT_TRUE(exited_within_timeout);
+ EXPECT_EQ(expected_exit_code, exit_code);
+
+ if (!exited_within_timeout)
+ process.Terminate(/*exit_code=*/-1, /*wait=*/false);
+ }
+
+ base::CommandLine BuildCommandLine(
+ const base::FilePath& executable_path,
+ chrome_cleaner::ExecutionMode execution_mode =
+ chrome_cleaner::ExecutionMode::kNone) {
+ base::CommandLine command_line(executable_path);
+ chrome_cleaner::AppendTestSwitches(&command_line);
+ command_line.AppendSwitchASCII(
+ chrome_cleaner::kEngineSwitch,
+ base::NumberToString(static_cast<int>(engine_)));
+ if (execution_mode != chrome_cleaner::ExecutionMode::kNone) {
+ command_line.AppendSwitchASCII(
+ chrome_cleaner::kExecutionModeSwitch,
+ base::NumberToString(static_cast<int>(execution_mode)));
+ }
+ command_line.AppendSwitchPath(chrome_cleaner::kQuarantineDirSwitch,
+ temp_dir_.GetPath());
+#if !BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
+ if (test_features_ == TestFeatures::kWithoutSandbox) {
+ command_line.AppendSwitch(
+ chrome_cleaner::kRunWithoutSandboxForTestingSwitch);
+ }
+#endif // BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
+
+ return command_line;
+ }
+
+ protected:
+ TestFeatures test_features_;
+ Engine::Name engine_;
+ base::FilePath scan_only_test_uws_;
+ base::FilePath removable_test_uws_;
+ base::FilePath expected_uws_archive_;
+ base::File locked_file_;
+
+ private:
+ base::ScopedTempDir temp_dir_;
+};
+
+TEST_P(CleanerTest, Scanner_ScanOnly) {
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kScannerExecutable));
+ ExpectExitCode(command_line,
+ chrome_cleaner::RESULT_CODE_REPORT_ONLY_PUPS_FOUND);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+}
+
+TEST_P(CleanerTest, Scanner_Removable) {
+ CreateRemovableUwS();
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kScannerExecutable));
+
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_SUCCESS);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+ EXPECT_TRUE(base::PathExists(removable_test_uws_));
+ EXPECT_FALSE(base::PathExists(expected_uws_archive_));
+}
+
+TEST_P(CleanerTest, Cleaner_ScanOnly) {
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable),
+ chrome_cleaner::ExecutionMode::kCleanup);
+ ExpectExitCode(command_line,
+ chrome_cleaner::RESULT_CODE_REPORT_ONLY_PUPS_FOUND);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+}
+
+TEST_P(CleanerTest, Cleaner_Removable) {
+ CreateRemovableUwS();
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable),
+ chrome_cleaner::ExecutionMode::kCleanup);
+
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_SUCCESS);
+ EXPECT_FALSE(base::PathExists(removable_test_uws_));
+ EXPECT_TRUE(base::PathExists(expected_uws_archive_));
+}
+
+TEST_P(CleanerTest, Cleaner_LockedFiles) {
+ CreateRemovableUwS();
+ LockRemovableUwS();
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable),
+ chrome_cleaner::ExecutionMode::kCleanup);
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_PENDING_REBOOT);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+ EXPECT_TRUE(base::PathExists(removable_test_uws_));
+ EXPECT_TRUE(base::PathExists(expected_uws_archive_));
+}
+
+TEST_P(CleanerTest, PostReboot_ScanOnly) {
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable),
+ chrome_cleaner::ExecutionMode::kCleanup);
+ command_line.AppendSwitch(chrome_cleaner::kPostRebootSwitch);
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_POST_REBOOT_SUCCESS);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+}
+
+TEST_P(CleanerTest, PostReboot_Removable) {
+ CreateRemovableUwS();
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable));
+ command_line.AppendSwitch(chrome_cleaner::kPostRebootSwitch);
+ command_line.AppendSwitchASCII(chrome_cleaner::kExecutionModeSwitch,
+ base::NumberToString(static_cast<int>(
+ chrome_cleaner::ExecutionMode::kCleanup)));
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_POST_REBOOT_SUCCESS);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+
+ // The engine should have removed the file.
+ EXPECT_FALSE(base::PathExists(removable_test_uws_));
+ EXPECT_TRUE(base::PathExists(expected_uws_archive_));
+}
+
+TEST_P(CleanerTest, PostReboot_LockedFiles) {
+ CreateRemovableUwS();
+ LockRemovableUwS();
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable));
+ command_line.AppendSwitch(chrome_cleaner::kPostRebootSwitch);
+ command_line.AppendSwitchASCII(chrome_cleaner::kExecutionModeSwitch,
+ base::NumberToString(static_cast<int>(
+ chrome_cleaner::ExecutionMode::kCleanup)));
+
+ ExpectExitCode(command_line,
+ chrome_cleaner::RESULT_CODE_POST_CLEANUP_VALIDATION_FAILED);
+ EXPECT_TRUE(base::PathExists(scan_only_test_uws_));
+ EXPECT_TRUE(base::PathExists(removable_test_uws_));
+ EXPECT_TRUE(base::PathExists(expected_uws_archive_));
+}
+
+TEST_P(CleanerTest, NoPotentialFalsePositivesOnCleanMachine) {
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable));
+ command_line.AppendSwitchASCII(chrome_cleaner::kExecutionModeSwitch,
+ base::NumberToString(static_cast<int>(
+ chrome_cleaner::ExecutionMode::kCleanup)));
+
+ // Delete the scan only uws to make the machine clean.
+ base::DeleteFile(scan_only_test_uws_, /*recursive=*/false);
+
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_NO_PUPS_FOUND);
+}
+
+TEST_P(CleanerTest, NoUnsanitizedPaths) {
+ CreateRemovableUwS();
+
+ base::CommandLine command_line =
+ BuildCommandLine(base::FilePath(kCleanerExecutable));
+ LOG(ERROR) << command_line.GetCommandLineString();
+ command_line.AppendSwitch(chrome_cleaner::kDumpRawLogsSwitch);
+ command_line.AppendSwitchASCII(chrome_cleaner::kExecutionModeSwitch,
+ base::NumberToString(static_cast<int>(
+ chrome_cleaner::ExecutionMode::kCleanup)));
+ ExpectExitCode(command_line, chrome_cleaner::RESULT_CODE_SUCCESS);
+
+ chrome_cleaner::ChromeCleanerReport chrome_cleaner_report;
+ EXPECT_TRUE(
+ ParseSerializedReport(kCleanerExecutable, &chrome_cleaner_report));
+
+ // Ensure the report doesn't have any unsanitized paths.
+ EXPECT_TRUE(CheckCleanerReportForUnsanitizedPaths(chrome_cleaner_report));
+}
+
+// Test all features with the TestOnly engine, which is quick.
+INSTANTIATE_TEST_CASE_P(AllFeatures,
+ CleanerTest,
+ Combine(ValuesIn(kAllTestFeatures),
+ Values(Engine::TEST_ONLY)),
+ chrome_cleaner::GetParamNameForTest());
+
+#if BUILDFLAG(IS_INTERNAL_CHROME_CLEANER_BUILD)
+// The full scan with the ESET engine takes too long to test more than once so
+// don't enable any test features. In fact, don't test it in debug builds since
+// they are slower.
+#ifdef NDEBUG
+INSTANTIATE_TEST_CASE_P(EsetFeatures,
+ CleanerTest,
+ Combine(Values(TestFeatures::kNone),
+ Values(Engine::ESET)),
+ chrome_cleaner::GetParamNameForTest());
+#endif // NDEBUG
+#endif // BUILDFLAG(IS_INTERNAL_CHROME_CLEANER_BUILD)
+
+} // namespace
diff --git a/chrome/chrome_cleaner/test/secure_dll_loading_test.cc b/chrome/chrome_cleaner/test/secure_dll_loading_test.cc
new file mode 100644
index 0000000..fe10c44
--- /dev/null
+++ b/chrome/chrome_cleaner/test/secure_dll_loading_test.cc
@@ -0,0 +1,195 @@
+// Copyright 2019 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/os/secure_dll_loading.h"
+
+#include <windows.h>
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/base_paths.h"
+#include "base/command_line.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/path_service.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/test/test_timeouts.h"
+#include "base/win/win_util.h"
+#include "chrome/chrome_cleaner/buildflags.h"
+#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
+#include "chrome/chrome_cleaner/os/inheritable_event.h"
+#include "chrome/chrome_cleaner/os/process.h"
+#include "chrome/chrome_cleaner/test/test_util.h"
+#include "components/chrome_cleaner/public/constants/constants.h"
+#include "components/chrome_cleaner/test/test_name_helper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void PrintChildProcessLogs(const base::FilePath& log_dir,
+ base::StringPiece16 file_name) {
+ base::string16 base_name;
+ if (file_name == L"software_reporter_tool") {
+ base_name = L"software_reporter_tool";
+ } else if (file_name == L"chrome_cleanup_tool") {
+ base_name = L"chrome_cleanup";
+ } else {
+ LOG(ERROR) << "Unknown file name " << file_name.data();
+ return;
+ }
+
+ base::FilePath log_path = log_dir.Append(base_name).AddExtension(L"log");
+
+ if (!base::PathExists(log_path)) {
+ LOG(ERROR) << "Child process log file doesn't exist";
+ return;
+ }
+
+ // Collect the child process log file, and dump the contents, to help
+ // debugging failures.
+ std::string log_file_contents;
+ if (!base::ReadFileToString(log_path, &log_file_contents)) {
+ LOG(ERROR) << "Failed to read child process log file";
+ return;
+ }
+
+ std::vector<base::StringPiece> lines =
+ base::SplitStringPiece(log_file_contents, "\n", base::TRIM_WHITESPACE,
+ base::SPLIT_WANT_NONEMPTY);
+ LOG(ERROR) << "Dumping child process logs";
+ for (const auto& line : lines) {
+ LOG(ERROR) << "Child process: " << line;
+ }
+}
+
+} // namespace
+
+class SecureDLLLoadingTest : public testing::TestWithParam<base::string16> {
+ protected:
+ void SetUp() override {
+ base::FilePath out_dir;
+ ASSERT_TRUE(base::PathService::Get(base::DIR_EXE, &out_dir));
+ exe_path_ = out_dir.Append(GetParam() + L".exe");
+ empty_dll_path_ = out_dir.Append(chrome_cleaner::kEmptyDll);
+ }
+
+ base::Process LaunchProcess(bool disable_secure_dll_loading) {
+ base::ScopedTempDir log_dir;
+ if (!log_dir.CreateUniqueTempDir()) {
+ ADD_FAILURE() << "Precondition failed: could not create log dir";
+ return base::Process();
+ }
+
+ std::unique_ptr<base::WaitableEvent> init_done_notifier =
+ chrome_cleaner::CreateInheritableEvent(
+ base::WaitableEvent::ResetPolicy::AUTOMATIC,
+ base::WaitableEvent::InitialState::NOT_SIGNALED);
+
+ base::CommandLine command_line(exe_path_);
+ command_line.AppendSwitchNative(
+ chrome_cleaner::kInitDoneNotifierSwitch,
+ base::NumberToString16(
+ base::win::HandleToUint32(init_done_notifier->handle())));
+ command_line.AppendSwitch(chrome_cleaner::kLoadEmptyDLLSwitch);
+ command_line.AppendSwitchPath(chrome_cleaner::kTestLoggingPathSwitch,
+ log_dir.GetPath());
+
+#if !BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
+ if (disable_secure_dll_loading)
+ command_line.AppendSwitch(chrome_cleaner::kAllowUnsecureDLLsSwitch);
+#endif // BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
+
+ // The default execution mode (ExecutionMode::kNone) is no longer supported
+ // and displays an error dialog instead of trying to load the DLLs.
+ command_line.AppendSwitchASCII(
+ chrome_cleaner::kExecutionModeSwitch,
+ base::NumberToString(
+ static_cast<int>(chrome_cleaner::ExecutionMode::kCleanup)));
+
+ base::LaunchOptions options;
+ options.handles_to_inherit.push_back(init_done_notifier->handle());
+ base::Process process = base::LaunchProcess(command_line, options);
+
+ // Make sure the process has finished its initialization (including loading
+ // DLLs). Also check the process handle in case it exits with an error.
+ std::vector<HANDLE> wait_handles{init_done_notifier->handle(),
+ process.Handle()};
+ DWORD wait_result = ::WaitForMultipleObjects(
+ wait_handles.size(), wait_handles.data(), /*bWaitAll=*/false,
+ TestTimeouts::action_max_timeout().InMilliseconds());
+ // WAIT_OBJECT_0 is the first handle in the vector.
+ if (wait_result == WAIT_OBJECT_0 + 1) {
+ DWORD exit_code = 0;
+ PLOG_IF(ERROR, !::GetExitCodeProcess(process.Handle(), &exit_code));
+ ADD_FAILURE() << "Process exited with " << exit_code
+ << " before signalling init_done_notifier";
+ PrintChildProcessLogs(log_dir.GetPath(), GetParam());
+ } else {
+ EXPECT_EQ(wait_result, WAIT_OBJECT_0);
+ }
+
+ return process;
+ }
+
+ bool EmptyDLLLoaded(const base::Process& process) {
+ std::set<base::string16> module_paths;
+ chrome_cleaner::GetLoadedModuleFileNames(process.Handle(), &module_paths);
+
+ for (const auto& module_path : module_paths) {
+ if (base::EqualsCaseInsensitiveASCII(empty_dll_path_.value(),
+ module_path))
+ return true;
+ }
+ return false;
+ }
+
+ private:
+ base::FilePath exe_path_;
+ base::FilePath empty_dll_path_;
+};
+
+INSTANTIATE_TEST_CASE_P(SecureDLLLoading,
+ SecureDLLLoadingTest,
+ // The value names cannot include ".exe" because "."
+ // is not a valid character in a test case name.
+ ::testing::Values(L"software_reporter_tool",
+ L"chrome_cleanup_tool"),
+ chrome_cleaner::GetParamNameForTest());
+
+#if !BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
+TEST_P(SecureDLLLoadingTest, Disabled) {
+ base::Process process = LaunchProcess(/*disable_secure_dll_loading=*/true);
+ EXPECT_TRUE(EmptyDLLLoaded(process));
+
+ // There is no need to finish running the process.
+ EXPECT_TRUE(process.Terminate(0U, /*wait=*/true));
+}
+#endif // BUILDFLAG(IS_OFFICIAL_CHROME_CLEANER_BUILD)
+
+TEST_P(SecureDLLLoadingTest, Default) {
+ if (!::GetProcAddress(::GetModuleHandleW(L"kernel32.dll"),
+ "SetDefaultDllDirectories")) {
+ // Skip this test if the SetDefaultDllDirectories function is unavailable
+ // (this is normal on Windows 7 without update KB2533623.)
+ return;
+ }
+
+ base::Process process = LaunchProcess(/*disable_secure_dll_loading=*/false);
+ EXPECT_FALSE(EmptyDLLLoaded(process));
+
+ // There is no need to finish running the process.
+ EXPECT_TRUE(process.Terminate(0U, /*wait=*/true));
+}