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));
+}