Commit 5fbf3bbb authored by Joe Mason's avatar Joe Mason Committed by Commit Bot

Enable quarantine feature in chrome_cleaner

R=csharp@chromium.org

Bug: 830892
Change-Id: I6c3a17c2761545f58e18d9058bf2dedc31acf39e
Reviewed-on: https://chromium-review.googlesource.com/c/1338566Reviewed-by: default avatarChris Sharp <csharp@chromium.org>
Commit-Queue: Joe Mason <joenotcharles@google.com>
Cr-Commit-Position: refs/heads/master@{#608862}
parent 1b94510d
...@@ -163,6 +163,7 @@ void AppendFolderInformation(const FolderInformation& folder, ...@@ -163,6 +163,7 @@ void AppendFolderInformation(const FolderInformation& folder,
void AppendMatchedFile(const MatchedFile& file, MessageBuilder* builder) { void AppendMatchedFile(const MatchedFile& file, MessageBuilder* builder) {
AppendFileInformation(file.file_information(), builder); AppendFileInformation(file.file_information(), builder);
builder->Add(L", removal_status = ", file.removal_status()); builder->Add(L", removal_status = ", file.removal_status());
builder->Add(L", quarantine_status = ", file.quarantine_status());
} }
void AppendMatchedRegistryEntry(const MatchedRegistryEntry& registry, void AppendMatchedRegistryEntry(const MatchedRegistryEntry& registry,
...@@ -834,11 +835,17 @@ void CleanerLoggingService::UpdateFileRemovalStatuses() { ...@@ -834,11 +835,17 @@ void CleanerLoggingService::UpdateFileRemovalStatuses() {
auto folder_it = matched_folders_.find(sanitized_path); auto folder_it = matched_folders_.find(sanitized_path);
if (file_it != matched_files_.end()) { if (file_it != matched_files_.end()) {
for (MatchedFile* matched_file : file_it->second) for (MatchedFile* matched_file : file_it->second) {
matched_file->set_removal_status(status.removal_status); matched_file->set_removal_status(status.removal_status);
matched_file->set_quarantine_status(status.quarantine_status);
}
} else if (folder_it != matched_folders_.end()) { } else if (folder_it != matched_folders_.end()) {
for (MatchedFolder* matched_folder : folder_it->second) for (MatchedFolder* matched_folder : folder_it->second) {
matched_folder->set_removal_status(status.removal_status); matched_folder->set_removal_status(status.removal_status);
// We don't quarantine folders. So the quarantine status should be
// |QUARANTINE_STATUS_UNSPECIFIED| and we don't need to record it.
DCHECK(status.quarantine_status == QUARANTINE_STATUS_UNSPECIFIED);
}
} else { } else {
known_matched_file = false; known_matched_file = false;
} }
...@@ -879,6 +886,7 @@ void CleanerLoggingService::UpdateFileRemovalStatuses() { ...@@ -879,6 +886,7 @@ void CleanerLoggingService::UpdateFileRemovalStatuses() {
file->mutable_file_information()->set_path(sanitized_path); file->mutable_file_information()->set_path(sanitized_path);
} }
file->set_removal_status(status.removal_status); file->set_removal_status(status.removal_status);
file->set_quarantine_status(status.quarantine_status);
} }
} }
} }
......
...@@ -88,7 +88,7 @@ const wchar_t kMatchedFileToStringExpectedString[] = ...@@ -88,7 +88,7 @@ const wchar_t kMatchedFileToStringExpectedString[] =
L"product_short_name = 'PNShort', internal_name = 'Internal Name', " L"product_short_name = 'PNShort', internal_name = 'Internal Name', "
L"original_filename = 'Something_Original.tmp', file_description = 'Very " L"original_filename = 'Something_Original.tmp', file_description = 'Very "
L"descriptive', file_version = '42.1.2', active_file = '0', removal_status " L"descriptive', file_version = '42.1.2', active_file = '0', removal_status "
L"= 0"; L"= 0, quarantine_status = 0";
const wchar_t kMatchedRegistryEntryKey[] = L"123"; const wchar_t kMatchedRegistryEntryKey[] = L"123";
const wchar_t kMatchedRegistryEntryValueName[] = L"Value Name"; const wchar_t kMatchedRegistryEntryValueName[] = L"Value Name";
...@@ -1189,6 +1189,19 @@ void ExpectRemovalStatus(const ChromeCleanerReport& report, ...@@ -1189,6 +1189,19 @@ void ExpectRemovalStatus(const ChromeCleanerReport& report,
} }
} }
// Checks that all files for all UwS entries in |report| have quarantine status
// |expected_status|.
void ExpectQuarantineStatus(const ChromeCleanerReport& report,
QuarantineStatus expected_status) {
for (const UwS& uws : report.detected_uws()) {
for (const MatchedFile& file : uws.files())
EXPECT_EQ(expected_status, file.quarantine_status());
// We should not quarantine any folder.
EXPECT_TRUE(uws.folders().empty());
}
}
// Checks that the given path appears in the unknown UwS field with the expected // Checks that the given path appears in the unknown UwS field with the expected
// removal status. // removal status.
void ExpectUnknownRemovalStatus(const ChromeCleanerReport& report, void ExpectUnknownRemovalStatus(const ChromeCleanerReport& report,
...@@ -1293,6 +1306,52 @@ TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus) { ...@@ -1293,6 +1306,52 @@ TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus) {
} }
} }
TEST_P(CleanerLoggingServiceTest, UpdateQuarantineStatus) {
const base::FilePath kFile1(L"C:\\Program Files\\My Dear UwS\\File1.exe");
// Creates a vector of all QuarantineStatus enum values to improve readability
// of loops in this test and ensure that all QuarantineStatus enumerators are
// checked.
std::vector<QuarantineStatus> all_quarantine_status;
for (int i = QuarantineStatus_MIN; i <= QuarantineStatus_MAX; ++i) {
// Status cannot be set to QUARANTINE_STATUS_UNSPECIFIED - this is guarded
// by an assert.
QuarantineStatus status = static_cast<QuarantineStatus>(i);
if (QuarantineStatus_IsValid(status) &&
status != QUARANTINE_STATUS_UNSPECIFIED)
all_quarantine_status.push_back(status);
}
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
for (QuarantineStatus status : all_quarantine_status) {
logging_service_->EnableUploads(true, registry_logger_.get());
UwS uws;
uws.set_id(1);
AddFileToUwS(kFile1, &uws);
logging_service_->AddDetectedUwS(uws);
ChromeCleanerReport report;
// The default quarantine status should be |QUARANTINE_STATUS_UNSPECIFIED|.
EXPECT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
ExpectQuarantineStatus(report, QUARANTINE_STATUS_UNSPECIFIED);
// Removal status has to be updated with a valid status.
removal_status_updater->UpdateRemovalStatus(kFile1,
REMOVAL_STATUS_MATCHED_ONLY);
removal_status_updater->UpdateQuarantineStatus(kFile1, status);
// It should always succeed to override |QUARANTINE_STATUS_UNSPECIFIED|.
EXPECT_TRUE(report.ParseFromString(logging_service_->RawReportContent()));
ExpectQuarantineStatus(report, status);
// Reset the logging service, so one the current test doesn't interfere with
// the next one.
logging_service_->Terminate();
logging_service_->Initialize(registry_logger_.get());
}
}
TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus_UwSAdded) { TEST_P(CleanerLoggingServiceTest, UpdateRemovalStatus_UwSAdded) {
constexpr wchar_t kFile1[] = L"C:\\Program Files\\My Dear UwS\\File1.exe"; constexpr wchar_t kFile1[] = L"C:\\Program Files\\My Dear UwS\\File1.exe";
......
...@@ -103,14 +103,19 @@ source_set("cleaner_os") { ...@@ -103,14 +103,19 @@ source_set("cleaner_os") {
":common_os", ":common_os",
"//base:base", "//base:base",
"//chrome/chrome_cleaner/constants:common_strings", "//chrome/chrome_cleaner/constants:common_strings",
"//chrome/chrome_cleaner/constants:quarantine_constants",
"//chrome/chrome_cleaner/constants:version_header", "//chrome/chrome_cleaner/constants:version_header",
"//chrome/chrome_cleaner/interfaces:zip_archiver_interface",
"//chrome/chrome_cleaner/logging/proto:removal_status_proto", "//chrome/chrome_cleaner/logging/proto:removal_status_proto",
"//chrome/chrome_cleaner/proto:shared_pup_enums_proto", "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
"//chrome/chrome_cleaner/zip_archiver:common",
"//components/chrome_cleaner/public/constants:constants", "//components/chrome_cleaner/public/constants:constants",
"//sandbox/win:sandbox",
] ]
public_deps = [ public_deps = [
":file_remover_api", ":file_remover_api",
"//chrome/chrome_cleaner/logging/proto:removal_status_proto",
] ]
} }
...@@ -146,7 +151,9 @@ source_set("unittest_sources") { ...@@ -146,7 +151,9 @@ source_set("unittest_sources") {
"//base/test:test_config", "//base/test:test_config",
"//base/test:test_support", "//base/test:test_support",
"//chrome/chrome_cleaner/constants:common_strings", "//chrome/chrome_cleaner/constants:common_strings",
"//chrome/chrome_cleaner/constants:quarantine_constants",
"//chrome/chrome_cleaner/constants:version_header", "//chrome/chrome_cleaner/constants:version_header",
"//chrome/chrome_cleaner/ipc:mojo_task_runner",
"//chrome/chrome_cleaner/logging/proto:removal_status_proto", "//chrome/chrome_cleaner/logging/proto:removal_status_proto",
"//chrome/chrome_cleaner/proto:shared_pup_enums_proto", "//chrome/chrome_cleaner/proto:shared_pup_enums_proto",
"//chrome/chrome_cleaner/strings", "//chrome/chrome_cleaner/strings",
...@@ -156,7 +163,11 @@ source_set("unittest_sources") { ...@@ -156,7 +163,11 @@ source_set("unittest_sources") {
"//chrome/chrome_cleaner/test:test_strings", "//chrome/chrome_cleaner/test:test_strings",
"//chrome/chrome_cleaner/test:test_util", "//chrome/chrome_cleaner/test:test_util",
"//chrome/chrome_cleaner/test/resources:test_resources", "//chrome/chrome_cleaner/test/resources:test_resources",
"//chrome/chrome_cleaner/zip_archiver:common",
"//chrome/chrome_cleaner/zip_archiver/broker:common",
"//chrome/chrome_cleaner/zip_archiver/target:common",
"//components/chrome_cleaner/public/constants:constants", "//components/chrome_cleaner/public/constants:constants",
"//sandbox/win:sandbox",
"//testing/gmock", "//testing/gmock",
"//testing/gtest", "//testing/gtest",
] ]
......
...@@ -215,10 +215,14 @@ void FileRemovalStatusUpdater::Clear() { ...@@ -215,10 +215,14 @@ void FileRemovalStatusUpdater::Clear() {
void FileRemovalStatusUpdater::UpdateRemovalStatus(const base::FilePath& path, void FileRemovalStatusUpdater::UpdateRemovalStatus(const base::FilePath& path,
RemovalStatus status) { RemovalStatus status) {
// Force update of RemovalStatusCanBeOverriddenBy() if RemovalStatus enum // Compare against the highest known removal status, not RemovalStatus_MAX.
// changes. REMOVAL_STATUS_UNSPECIFIED should never be set. // That way if the RemovalStatus enum changes, a unit test that iterates up
// to RemovalStatus_MAX will fail on this DCHECK. This is a reminder to add
// the new RemovalStatus to RemovalStatusCanBeOverriddenBy().
DCHECK(status > REMOVAL_STATUS_UNSPECIFIED && DCHECK(status > REMOVAL_STATUS_UNSPECIFIED &&
status <= REMOVAL_STATUS_ERROR_IN_ARCHIVER); status <= REMOVAL_STATUS_ERROR_IN_ARCHIVER)
<< "Unknown RemovalStatus: need to update "
"RemovalStatusCanBeOverriddenBy()?";
const base::string16 sanitized_path = SanitizePath(path); const base::string16 sanitized_path = SanitizePath(path);
...@@ -229,6 +233,7 @@ void FileRemovalStatusUpdater::UpdateRemovalStatus(const base::FilePath& path, ...@@ -229,6 +233,7 @@ void FileRemovalStatusUpdater::UpdateRemovalStatus(const base::FilePath& path,
FileRemovalStatus new_status; FileRemovalStatus new_status;
new_status.path = path; new_status.path = path;
new_status.removal_status = status; new_status.removal_status = status;
new_status.quarantine_status = QUARANTINE_STATUS_UNSPECIFIED;
removal_statuses_.emplace(sanitized_path, new_status); removal_statuses_.emplace(sanitized_path, new_status);
} else { } else {
// Only update the entry if the new status is allowed to override the // Only update the entry if the new status is allowed to override the
...@@ -254,6 +259,44 @@ RemovalStatus FileRemovalStatusUpdater::GetRemovalStatusOfSanitizedPath( ...@@ -254,6 +259,44 @@ RemovalStatus FileRemovalStatusUpdater::GetRemovalStatusOfSanitizedPath(
: it->second.removal_status; : it->second.removal_status;
} }
void FileRemovalStatusUpdater::UpdateQuarantineStatus(
const base::FilePath& path,
QuarantineStatus status) {
// QUARANTINE_STATUS_UNSPECIFIED should never be set.
DCHECK(status > QUARANTINE_STATUS_UNSPECIFIED &&
status <= QuarantineStatus_MAX);
const base::string16 sanitized_path = SanitizePath(path);
base::AutoLock lock(removal_status_lock_);
auto it = removal_statuses_.find(sanitized_path);
// If the |sanitized_path| is not found, it will initialize the removal status
// with |REMOVAL_STATUS_UNSPECIFIED|, which should be updated with other valid
// statuses later.
if (it == removal_statuses_.end()) {
FileRemovalStatus new_status;
new_status.path = path;
new_status.removal_status = REMOVAL_STATUS_UNSPECIFIED;
new_status.quarantine_status = status;
removal_statuses_.emplace(sanitized_path, new_status);
} else {
it->second.path = path;
it->second.quarantine_status = status;
}
}
QuarantineStatus FileRemovalStatusUpdater::GetQuarantineStatus(
const base::FilePath& path) const {
const base::string16 sanitized_path = SanitizePath(path);
base::AutoLock lock(removal_status_lock_);
const auto it = removal_statuses_.find(sanitized_path);
return it == removal_statuses_.end() ? QUARANTINE_STATUS_UNSPECIFIED
: it->second.quarantine_status;
}
FileRemovalStatusUpdater::SanitizedPathToRemovalStatusMap FileRemovalStatusUpdater::SanitizedPathToRemovalStatusMap
FileRemovalStatusUpdater::GetAllRemovalStatuses() const { FileRemovalStatusUpdater::GetAllRemovalStatuses() const {
base::AutoLock lock(removal_status_lock_); base::AutoLock lock(removal_status_lock_);
......
...@@ -50,20 +50,23 @@ GetRemovalStatusOverridePermissionMap(); ...@@ -50,20 +50,23 @@ GetRemovalStatusOverridePermissionMap();
} // namespace internal } // namespace internal
// This class manages a map of RemovalStatus values for all files and folders // This class manages a map of remove statuses for all files and folders
// encountered during cleaning, keyed by path. It does not distinguish whether // encountered during cleaning, keyed by path. It does not distinguish whether
// the path refers to a file or a folder. // the path refers to a file or a folder.
class FileRemovalStatusUpdater { class FileRemovalStatusUpdater {
public: public:
struct FileRemovalStatus { struct FileRemovalStatus {
// The full path that was passed to UpdateRemovalStatus. This is needed // The full path that was passed to UpdateRemovalStatus or
// because when a file removal status is logged, // UpdateQuarantineStatus. This is needed because when a file removal status
// GetFileInformationProtoObject can be called, which needs a full path // is logged, GetFileInformationProtoObject can be called, which needs a
// that can be resolved. // full path that can be resolved.
base::FilePath path; base::FilePath path;
// The status of the last attempted file removal at the above path. // The removal status of the last attempted update at the above path.
RemovalStatus removal_status = REMOVAL_STATUS_UNSPECIFIED; RemovalStatus removal_status = REMOVAL_STATUS_UNSPECIFIED;
// The quarantine status of the last attempted update at the above path.
QuarantineStatus quarantine_status = QUARANTINE_STATUS_UNSPECIFIED;
}; };
typedef std::unordered_map<base::string16, FileRemovalStatus> typedef std::unordered_map<base::string16, FileRemovalStatus>
...@@ -82,19 +85,30 @@ class FileRemovalStatusUpdater { ...@@ -82,19 +85,30 @@ class FileRemovalStatusUpdater {
void UpdateRemovalStatus(const base::FilePath& path, RemovalStatus status); void UpdateRemovalStatus(const base::FilePath& path, RemovalStatus status);
// Returns the removal status of |path|, or REMOVAL_STATUS_UNSPECIFIED if // Returns the removal status of |path|, or REMOVAL_STATUS_UNSPECIFIED if
// UpdateRemovalStatus has never been called for that path. // the removal status have never been updated for that path.
RemovalStatus GetRemovalStatus(const base::FilePath& path) const; RemovalStatus GetRemovalStatus(const base::FilePath& path) const;
// Returns the removal status of |sanitized_path|, or // Returns the removal status of |sanitized_path|, or
// REMOVAL_STATUS_UNSPECIFIED if UpdateRemovalStatus has never been called // REMOVAL_STATUS_UNSPECIFIED if the removal status have never
// for an unsanitized form of that path. // been updated for an unsanitized form of that path.
RemovalStatus GetRemovalStatusOfSanitizedPath( RemovalStatus GetRemovalStatusOfSanitizedPath(
const base::string16& sanitized_path) const; const base::string16& sanitized_path) const;
// Updates quarantine status for a file given by |path|.
// Note: UpdateRemovalStatus should be called for |path| at some point as
// well, because it is invalid to quarantine a file that doesn't have some
// removal status.
void UpdateQuarantineStatus(const base::FilePath& path,
QuarantineStatus status);
// Returns the quarantine status of |path|, or QUARANTINE_STATUS_UNSPECIFIED
// if the quarantine status have never been updated for that path.
QuarantineStatus GetQuarantineStatus(const base::FilePath& path) const;
// Returns all saved removal statuses, keyed by sanitized path. Each // Returns all saved removal statuses, keyed by sanitized path. Each
// sanitized path is mapped to a single FileRemovalStatus which holds the // sanitized path is mapped to a single FileRemovalStatus which holds the
// path and status values from the most recent call to UpdateRemovalStatus // path and status values from the most recent call to UpdateRemovalStatus or
// that had an effect. // UpdateQuarantineStatus that had an effect.
SanitizedPathToRemovalStatusMap GetAllRemovalStatuses() const; SanitizedPathToRemovalStatusMap GetAllRemovalStatuses() const;
private: private:
......
...@@ -4,11 +4,12 @@ ...@@ -4,11 +4,12 @@
#include "chrome/chrome_cleaner/os/file_removal_status_updater.h" #include "chrome/chrome_cleaner/os/file_removal_status_updater.h"
#include <vector>
#include "base/base_paths.h" #include "base/base_paths.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "chrome/chrome_cleaner/os/file_path_sanitization.h" #include "chrome/chrome_cleaner/os/file_path_sanitization.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
namespace chrome_cleaner { namespace chrome_cleaner {
...@@ -129,6 +130,60 @@ TEST_F(FileRemovalStatusUpdaterTest, UpdateRemovalStatus) { ...@@ -129,6 +130,60 @@ TEST_F(FileRemovalStatusUpdaterTest, UpdateRemovalStatus) {
} }
} }
TEST_F(FileRemovalStatusUpdaterTest, UpdateQuarantineStatus) {
// Creates a vector of all QuarantineStatus enum values to improve readability
// of loops in this test and ensure that all QuarantineStatus enumerators are
// checked.
std::vector<QuarantineStatus> all_quarantine_status;
for (int i = QuarantineStatus_MIN; i <= QuarantineStatus_MAX; ++i) {
// Status cannot be set to QUARANTINE_STATUS_UNSPECIFIED - this is guarded
// by an assert.
QuarantineStatus status = static_cast<QuarantineStatus>(i);
if (QuarantineStatus_IsValid(status) &&
status != QUARANTINE_STATUS_UNSPECIFIED)
all_quarantine_status.push_back(status);
}
for (QuarantineStatus status : all_quarantine_status) {
for (QuarantineStatus new_status : all_quarantine_status) {
// Empty the map for the next test.
instance_->Clear();
// Quarantine status should be |QUARANTINE_STATUS_UNSPECIFIED| if the file
// is not in the map.
EXPECT_EQ(QUARANTINE_STATUS_UNSPECIFIED,
instance_->GetQuarantineStatus(file_1_));
instance_->UpdateQuarantineStatus(file_1_, status);
// It should always succeed to override |QUARANTINE_STATUS_UNSPECIFIED|.
EXPECT_EQ(status, instance_->GetQuarantineStatus(file_1_));
instance_->UpdateQuarantineStatus(file_1_, new_status);
// It should always succeed to override the old quarantine status.
EXPECT_EQ(new_status, instance_->GetQuarantineStatus(file_1_));
}
}
}
TEST_F(FileRemovalStatusUpdaterTest, MixedRemovalQuarantineUpdate) {
instance_->UpdateRemovalStatus(file_1_, REMOVAL_STATUS_REMOVED);
// The initial quarantine status should be |QUARANTINE_STATUS_UNSPECIFIED|.
EXPECT_EQ(QUARANTINE_STATUS_UNSPECIFIED,
instance_->GetQuarantineStatus(file_1_));
instance_->UpdateQuarantineStatus(file_1_, QUARANTINE_STATUS_QUARANTINED);
// |UpdateQuarantineStatus| should not change removal status.
EXPECT_EQ(REMOVAL_STATUS_REMOVED, instance_->GetRemovalStatus(file_1_));
instance_->UpdateQuarantineStatus(file_2_, QUARANTINE_STATUS_SKIPPED);
// The initial removal status should be |REMOVAL_STATUS_UNSPECIFIED|.
EXPECT_EQ(REMOVAL_STATUS_UNSPECIFIED, instance_->GetRemovalStatus(file_2_));
instance_->UpdateRemovalStatus(file_2_, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
// |UpdateRemovalStatus| should not change quarantine status.
EXPECT_EQ(QUARANTINE_STATUS_SKIPPED, instance_->GetQuarantineStatus(file_2_));
}
TEST_F(FileRemovalStatusUpdaterTest, PathSanitization) { TEST_F(FileRemovalStatusUpdaterTest, PathSanitization) {
base::FilePath home_dir; base::FilePath home_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_HOME, &home_dir)); ASSERT_TRUE(base::PathService::Get(base::DIR_HOME, &home_dir));
......
...@@ -9,9 +9,12 @@ ...@@ -9,9 +9,12 @@
#include <unordered_set> #include <unordered_set>
#include <utility> #include <utility>
#include "base/bind.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/path_service.h" #include "base/path_service.h"
#include "base/synchronization/waitable_event.h"
#include "chrome/chrome_cleaner/interfaces/zip_archiver.mojom.h"
#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h" #include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
#include "chrome/chrome_cleaner/os/disk_util.h" #include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/os/file_path_sanitization.h" #include "chrome/chrome_cleaner/os/file_path_sanitization.h"
...@@ -77,13 +80,33 @@ bool IsSafeNameForDeletion(const base::FilePath& path) { ...@@ -77,13 +80,33 @@ bool IsSafeNameForDeletion(const base::FilePath& path) {
return false; return false;
} }
void OnArchiveDone(FileRemover::QuarantineResultCallback archival_done_callback,
mojom::ZipArchiverResultCode result_code) {
switch (result_code) {
// If the archive exists, |path| has already been quarantined.
case mojom::ZipArchiverResultCode::kSuccess:
case mojom::ZipArchiverResultCode::kZipFileExists:
std::move(archival_done_callback).Run(QUARANTINE_STATUS_QUARANTINED);
return;
case mojom::ZipArchiverResultCode::kIgnoredSourceFile:
std::move(archival_done_callback).Run(QUARANTINE_STATUS_SKIPPED);
return;
default:
LOG(ERROR) << "ZipArchiver returned an error code: " << result_code;
break;
}
std::move(archival_done_callback).Run(QUARANTINE_STATUS_ERROR);
}
} // namespace } // namespace
FileRemover::FileRemover(std::shared_ptr<DigestVerifier> digest_verifier, FileRemover::FileRemover(std::shared_ptr<DigestVerifier> digest_verifier,
std::unique_ptr<SandboxedZipArchiver> archiver,
const LayeredServiceProviderAPI& lsp, const LayeredServiceProviderAPI& lsp,
const FilePathSet& deletion_allowed_paths, const FilePathSet& deletion_allowed_paths,
base::RepeatingClosure reboot_needed_callback) base::RepeatingClosure reboot_needed_callback)
: digest_verifier_(digest_verifier), : digest_verifier_(digest_verifier),
archiver_(std::move(archiver)),
deletion_allowed_paths_(deletion_allowed_paths), deletion_allowed_paths_(deletion_allowed_paths),
reboot_needed_callback_(reboot_needed_callback) { reboot_needed_callback_(reboot_needed_callback) {
LSPPathToGUIDs providers; LSPPathToGUIDs providers;
...@@ -96,18 +119,21 @@ FileRemover::FileRemover(std::shared_ptr<DigestVerifier> digest_verifier, ...@@ -96,18 +119,21 @@ FileRemover::FileRemover(std::shared_ptr<DigestVerifier> digest_verifier,
FileRemover::~FileRemover() = default; FileRemover::~FileRemover() = default;
bool FileRemover::RemoveNow(const base::FilePath& path) const { void FileRemover::RemoveNow(const base::FilePath& path,
DoneCallback callback) const {
FileRemovalStatusUpdater* removal_status_updater = FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance(); FileRemovalStatusUpdater::GetInstance();
switch (CanRemove(path)) { switch (CanRemove(path)) {
case DeletionValidationStatus::FORBIDDEN: case DeletionValidationStatus::FORBIDDEN:
removal_status_updater->UpdateRemovalStatus( removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL); path, REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
return false; std::move(callback).Run(false);
return;
case DeletionValidationStatus::INACTIVE: case DeletionValidationStatus::INACTIVE:
removal_status_updater->UpdateRemovalStatus( removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION); path, REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION);
return true; std::move(callback).Run(true);
return;
case DeletionValidationStatus::ALLOWED: case DeletionValidationStatus::ALLOWED:
// No-op. Proceed to removal. // No-op. Proceed to removal.
break; break;
...@@ -116,45 +142,30 @@ bool FileRemover::RemoveNow(const base::FilePath& path) const { ...@@ -116,45 +142,30 @@ bool FileRemover::RemoveNow(const base::FilePath& path) const {
chrome_cleaner::ScopedDisableWow64Redirection disable_wow64_redirection; chrome_cleaner::ScopedDisableWow64Redirection disable_wow64_redirection;
if (!base::PathExists(path)) { if (!base::PathExists(path)) {
removal_status_updater->UpdateRemovalStatus(path, REMOVAL_STATUS_NOT_FOUND); removal_status_updater->UpdateRemovalStatus(path, REMOVAL_STATUS_NOT_FOUND);
return true; std::move(callback).Run(true);
} return;
if (!base::DeleteFile(path, /*recursive=*/false)) {
// If the attempt to delete the file fails, propagate the failure as
// normal so that the engine knows about it and can try a backup action,
// but also register the file for post-reboot removal in case the engine
// doesn't have any effective backup action.
//
// A potential downside to this implementation is that if the file is
// later successfully deleted, we might ask users to reboot when no
// reboot is needed.
if (RegisterFileForPostRebootRemoval(path)) {
reboot_needed_callback_.Run();
removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL_FALLBACK);
} else {
removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_FAILED_TO_REMOVE);
}
return false;
} }
removal_status_updater->UpdateRemovalStatus(path, REMOVAL_STATUS_REMOVED);
DeleteEmptyDirectories(path.DirName()); TryToQuarantine(
return true; path, base::BindOnce(&FileRemover::RemoveFile, base::Unretained(this),
path, base::Passed(&callback)));
} }
bool FileRemover::RegisterPostRebootRemoval( void FileRemover::RegisterPostRebootRemoval(const base::FilePath& file_path,
const base::FilePath& file_path) const { DoneCallback callback) const {
FileRemovalStatusUpdater* removal_status_updater = FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance(); FileRemovalStatusUpdater::GetInstance();
switch (CanRemove(file_path)) { switch (CanRemove(file_path)) {
case DeletionValidationStatus::FORBIDDEN: case DeletionValidationStatus::FORBIDDEN:
removal_status_updater->UpdateRemovalStatus( removal_status_updater->UpdateRemovalStatus(
file_path, REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL); file_path, REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
return false; std::move(callback).Run(false);
return;
case DeletionValidationStatus::INACTIVE: case DeletionValidationStatus::INACTIVE:
removal_status_updater->UpdateRemovalStatus( removal_status_updater->UpdateRemovalStatus(
file_path, REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION); file_path, REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION);
return true; std::move(callback).Run(true);
return;
case DeletionValidationStatus::ALLOWED: case DeletionValidationStatus::ALLOWED:
// No-op. Proceed to removal. // No-op. Proceed to removal.
break; break;
...@@ -164,19 +175,13 @@ bool FileRemover::RegisterPostRebootRemoval( ...@@ -164,19 +175,13 @@ bool FileRemover::RegisterPostRebootRemoval(
if (!base::PathExists(file_path)) { if (!base::PathExists(file_path)) {
removal_status_updater->UpdateRemovalStatus(file_path, removal_status_updater->UpdateRemovalStatus(file_path,
REMOVAL_STATUS_NOT_FOUND); REMOVAL_STATUS_NOT_FOUND);
return true; std::move(callback).Run(true);
} return;
if (!RegisterFileForPostRebootRemoval(file_path)) {
PLOG(ERROR) << "Failed to schedule delete file: "
<< chrome_cleaner::SanitizePath(file_path);
removal_status_updater->UpdateRemovalStatus(
file_path, REMOVAL_STATUS_FAILED_TO_SCHEDULE_FOR_REMOVAL);
return false;
} }
reboot_needed_callback_.Run();
removal_status_updater->UpdateRemovalStatus( TryToQuarantine(file_path, base::BindOnce(&FileRemover::ScheduleRemoval,
file_path, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL); base::Unretained(this), file_path,
return true; base::Passed(&callback)));
} }
FileRemoverAPI::DeletionValidationStatus FileRemover::CanRemove( FileRemoverAPI::DeletionValidationStatus FileRemover::CanRemove(
...@@ -233,4 +238,81 @@ FileRemoverAPI::DeletionValidationStatus FileRemover::IsFileRemovalAllowed( ...@@ -233,4 +238,81 @@ FileRemoverAPI::DeletionValidationStatus FileRemover::IsFileRemovalAllowed(
return DeletionValidationStatus::INACTIVE; return DeletionValidationStatus::INACTIVE;
} }
void FileRemover::TryToQuarantine(const base::FilePath& path,
QuarantineResultCallback callback) const {
// The quarantine feature is disabled.
if (archiver_ == nullptr) {
std::move(callback).Run(QUARANTINE_STATUS_DISABLED);
return;
}
archiver_->Archive(path, base::BindOnce(&OnArchiveDone, std::move(callback)));
}
void FileRemover::RemoveFile(const base::FilePath& path,
DoneCallback removal_done_callback,
QuarantineStatus quarantine_status) const {
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
removal_status_updater->UpdateQuarantineStatus(path, quarantine_status);
if (quarantine_status == QUARANTINE_STATUS_ERROR) {
removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_ERROR_IN_ARCHIVER);
std::move(removal_done_callback).Run(false);
return;
}
if (!base::DeleteFile(path, /*recursive=*/false)) {
// If the attempt to delete the file fails, propagate the failure as
// normal so that the engine knows about it and can try a backup action,
// but also register the file for post-reboot removal in case the engine
// doesn't have any effective backup action.
//
// A potential downside to this implementation is that if the file is
// later successfully deleted, we might ask users to reboot when no
// reboot is needed. See b/66944160 for more details.
if (RegisterFileForPostRebootRemoval(path)) {
reboot_needed_callback_.Run();
removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL_FALLBACK);
} else {
removal_status_updater->UpdateRemovalStatus(
path, REMOVAL_STATUS_FAILED_TO_REMOVE);
}
std::move(removal_done_callback).Run(false);
return;
}
removal_status_updater->UpdateRemovalStatus(path, REMOVAL_STATUS_REMOVED);
DeleteEmptyDirectories(path.DirName());
std::move(removal_done_callback).Run(true);
}
void FileRemover::ScheduleRemoval(
const base::FilePath& file_path,
FileRemover::DoneCallback removal_done_callback,
QuarantineStatus quarantine_status) const {
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
removal_status_updater->UpdateQuarantineStatus(file_path, quarantine_status);
if (quarantine_status == QUARANTINE_STATUS_ERROR) {
removal_status_updater->UpdateRemovalStatus(
file_path, REMOVAL_STATUS_ERROR_IN_ARCHIVER);
std::move(removal_done_callback).Run(false);
return;
}
if (!RegisterFileForPostRebootRemoval(file_path)) {
PLOG(ERROR) << "Failed to schedule delete file: "
<< chrome_cleaner::SanitizePath(file_path);
removal_status_updater->UpdateRemovalStatus(
file_path, REMOVAL_STATUS_FAILED_TO_SCHEDULE_FOR_REMOVAL);
std::move(removal_done_callback).Run(false);
return;
}
reboot_needed_callback_.Run();
removal_status_updater->UpdateRemovalStatus(
file_path, REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
std::move(removal_done_callback).Run(true);
}
} // namespace chrome_cleaner } // namespace chrome_cleaner
...@@ -12,16 +12,20 @@ ...@@ -12,16 +12,20 @@
#include "base/callback.h" #include "base/callback.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
#include "chrome/chrome_cleaner/os/digest_verifier.h" #include "chrome/chrome_cleaner/os/digest_verifier.h"
#include "chrome/chrome_cleaner/os/file_path_set.h" #include "chrome/chrome_cleaner/os/file_path_set.h"
#include "chrome/chrome_cleaner/os/file_remover_api.h" #include "chrome/chrome_cleaner/os/file_remover_api.h"
#include "chrome/chrome_cleaner/os/layered_service_provider_api.h" #include "chrome/chrome_cleaner/os/layered_service_provider_api.h"
#include "chrome/chrome_cleaner/zip_archiver/sandboxed_zip_archiver.h"
namespace chrome_cleaner { namespace chrome_cleaner {
// This class implements the |FileRemoverAPI| for production code. // This class implements the |FileRemoverAPI| for production code.
class FileRemover : public FileRemoverAPI { class FileRemover : public FileRemoverAPI {
public: public:
typedef base::OnceCallback<void(QuarantineStatus)> QuarantineResultCallback;
// Checks whether deletion of the file at |path| is allowed. Files at paths in // Checks whether deletion of the file at |path| is allowed. Files at paths in
// |fordbid_deletion| are never allowed to be deleted. Files at paths in // |fordbid_deletion| are never allowed to be deleted. Files at paths in
// |allow_deletion| are allowed to be deleted even if they do not appear to be // |allow_deletion| are allowed to be deleted even if they do not appear to be
...@@ -35,6 +39,7 @@ class FileRemover : public FileRemoverAPI { ...@@ -35,6 +39,7 @@ class FileRemover : public FileRemoverAPI {
// If it is an instance of DigestVerifier, any files known to the // If it is an instance of DigestVerifier, any files known to the
// DigestVerifier will not be removed. // DigestVerifier will not be removed.
FileRemover(std::shared_ptr<DigestVerifier> digest_verifier, FileRemover(std::shared_ptr<DigestVerifier> digest_verifier,
std::unique_ptr<SandboxedZipArchiver> archiver,
const LayeredServiceProviderAPI& lsp, const LayeredServiceProviderAPI& lsp,
const FilePathSet& deletion_allowed_paths, const FilePathSet& deletion_allowed_paths,
base::RepeatingClosure reboot_needed_callback); base::RepeatingClosure reboot_needed_callback);
...@@ -42,15 +47,27 @@ class FileRemover : public FileRemoverAPI { ...@@ -42,15 +47,27 @@ class FileRemover : public FileRemoverAPI {
~FileRemover() override; ~FileRemover() override;
// FileRemoverAPI implementation. // FileRemoverAPI implementation.
bool RemoveNow(const base::FilePath& path) const override; void RemoveNow(const base::FilePath& path,
bool RegisterPostRebootRemoval( DoneCallback callback) const override;
const base::FilePath& file_path) const override; void RegisterPostRebootRemoval(const base::FilePath& file_path,
DoneCallback callback) const override;
// Checks if the file is active and can be deleted. // Checks if the file is active and can be deleted.
DeletionValidationStatus CanRemove(const base::FilePath& file) const override; DeletionValidationStatus CanRemove(const base::FilePath& file) const override;
private: private:
void TryToQuarantine(const base::FilePath& path,
QuarantineResultCallback callback) const;
void RemoveFile(const base::FilePath& path,
DoneCallback removal_done_callback,
QuarantineStatus result) const;
void ScheduleRemoval(const base::FilePath& file_path,
DoneCallback removal_done_callback,
QuarantineStatus quarantine_status) const;
std::shared_ptr<DigestVerifier> digest_verifier_; std::shared_ptr<DigestVerifier> digest_verifier_;
std::unique_ptr<SandboxedZipArchiver> archiver_;
FilePathSet deletion_forbidden_paths_; FilePathSet deletion_forbidden_paths_;
FilePathSet deletion_allowed_paths_; FilePathSet deletion_allowed_paths_;
base::RepeatingClosure reboot_needed_callback_; base::RepeatingClosure reboot_needed_callback_;
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include <vector> #include <vector>
#include "base/callback.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
#include "chrome/chrome_cleaner/os/file_path_set.h" #include "chrome/chrome_cleaner/os/file_path_set.h"
...@@ -21,16 +22,20 @@ class FileRemoverAPI { ...@@ -21,16 +22,20 @@ class FileRemoverAPI {
FORBIDDEN, FORBIDDEN,
INACTIVE, INACTIVE,
}; };
// Callback used for the asynchronous versions of RemoveNow
// and RegisterPostRebootRemoval.
typedef base::OnceCallback<void(bool)> DoneCallback;
virtual ~FileRemoverAPI() {} virtual ~FileRemoverAPI() {}
// Remove file at |path| from the user's disk. Return false on failure. // Remove file at |path| from the user's disk. Return false on failure.
virtual bool RemoveNow(const base::FilePath& path) const = 0; virtual void RemoveNow(const base::FilePath& path,
DoneCallback callback) const = 0;
// Register the file in |file_path| to be deleted after a machine reboot. // Register the file in |file_path| to be deleted after a machine reboot.
// Return false on failure. // Return false on failure.
virtual bool RegisterPostRebootRemoval( virtual void RegisterPostRebootRemoval(const base::FilePath& file_path,
const base::FilePath& file_path) const = 0; DoneCallback callback) const = 0;
// Check if file at |path| is not whitelisted and can be deleted. // Check if file at |path| is not whitelisted and can be deleted.
virtual DeletionValidationStatus CanRemove( virtual DeletionValidationStatus CanRemove(
......
...@@ -29,6 +29,7 @@ const char* kSwitchesToPropagate[]{ ...@@ -29,6 +29,7 @@ const char* kSwitchesToPropagate[]{
kChromeVersionSwitch, kDumpRawLogsSwitch, kEnableCrashReportingSwitch, kChromeVersionSwitch, kDumpRawLogsSwitch, kEnableCrashReportingSwitch,
kEngineSwitch, kExecutionModeSwitch, kLogUploadRetryIntervalSwitch, kEngineSwitch, kExecutionModeSwitch, kLogUploadRetryIntervalSwitch,
kNoSelfDeleteSwitch, kTestingSwitch, kUmaUserSwitch, kNoSelfDeleteSwitch, kTestingSwitch, kUmaUserSwitch,
kQuarantineSwitch,
}; };
// The name of the task to run post reboot. // The name of the task to run post reboot.
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <windows.h> #include <windows.h>
#include <accctrl.h>
#include <aclapi.h> #include <aclapi.h>
#include <lmcons.h> #include <lmcons.h>
#include <shellapi.h> #include <shellapi.h>
...@@ -14,6 +15,8 @@ ...@@ -14,6 +15,8 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/process/process_info.h" #include "base/process/process_info.h"
#include "base/process/process_iterator.h" #include "base/process/process_iterator.h"
...@@ -24,8 +27,13 @@ ...@@ -24,8 +27,13 @@
#include "base/win/scoped_com_initializer.h" #include "base/win/scoped_com_initializer.h"
#include "base/win/scoped_handle.h" #include "base/win/scoped_handle.h"
#include "base/win/windows_version.h" #include "base/win/windows_version.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/constants/quarantine_constants.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/os/scoped_service_handle.h" #include "chrome/chrome_cleaner/os/scoped_service_handle.h"
#include "chrome/chrome_cleaner/os/system_util.h" #include "chrome/chrome_cleaner/os/system_util.h"
#include "sandbox/win/src/acl.h"
#include "sandbox/win/src/sid.h"
namespace chrome_cleaner { namespace chrome_cleaner {
...@@ -231,6 +239,23 @@ bool SendStopToService(const wchar_t* service_name) { ...@@ -231,6 +239,23 @@ bool SendStopToService(const wchar_t* service_name) {
return true; return true;
} }
bool GetQuarantineFolderPath(base::FilePath* output_path) {
DCHECK(output_path);
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(kQuarantineDirSwitch)) {
*output_path = command_line->GetSwitchValuePath(kQuarantineDirSwitch);
} else {
base::FilePath product_path;
if (!GetAppDataProductDirectory(&product_path)) {
LOG(ERROR) << "Failed to get AppData product directory.";
return false;
}
*output_path = product_path.Append(kQuarantineFolder);
}
return true;
}
} // namespace } // namespace
bool AcquireDebugRightsPrivileges() { bool AcquireDebugRightsPrivileges() {
...@@ -474,4 +499,49 @@ base::Process LaunchElevatedProcessWithAssociatedWindow( ...@@ -474,4 +499,49 @@ base::Process LaunchElevatedProcessWithAssociatedWindow(
return runner.GetProcess(); return runner.GetProcess();
} }
// Only allow administrator group to access the quarantine folder.
bool InitializeQuarantineFolder(base::FilePath* output_quarantine_path) {
DCHECK(output_quarantine_path);
base::FilePath quarantine_path;
if (!GetQuarantineFolderPath(&quarantine_path)) {
LOG(ERROR) << "Failed to get quarantine folder path.";
return false;
}
if (!base::DirectoryExists(quarantine_path) &&
!base::CreateDirectory(quarantine_path)) {
LOG(ERROR) << "Failed to create quarantine folder.";
return false;
}
sandbox::Sid admin_sid(WinBuiltinAdministratorsSid);
if (!admin_sid.IsValid()) {
LOG(ERROR) << "Failed to get administrator sid.";
return false;
}
PACL dacl;
if (!sandbox::AddSidToDacl(admin_sid, /*old_dacl=*/nullptr, SET_ACCESS,
GENERIC_ALL, &dacl)) {
LOG(ERROR) << "Failed to create ACLs.";
return false;
}
DWORD result_code = ERROR_SUCCESS;
// |PROTECTED_DACL_SECURITY_INFORMATION| will remove inherited ACLs.
result_code = ::SetNamedSecurityInfo(
const_cast<wchar_t*>(quarantine_path.value().c_str()), SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION |
PROTECTED_DACL_SECURITY_INFORMATION,
admin_sid.GetPSID(), /*psidGroup=*/nullptr, dacl, /*pSacl=*/nullptr);
::LocalFree(dacl);
if (result_code != ERROR_SUCCESS) {
LOG(ERROR) << "Failed to set ACLs to quarantine folder.";
return false;
}
*output_quarantine_path = quarantine_path;
return true;
}
} // namespace chrome_cleaner } // namespace chrome_cleaner
...@@ -75,6 +75,8 @@ base::Process LaunchElevatedProcessWithAssociatedWindow( ...@@ -75,6 +75,8 @@ base::Process LaunchElevatedProcessWithAssociatedWindow(
const base::CommandLine& command_line, const base::CommandLine& command_line,
HWND hwnd); HWND hwnd);
bool InitializeQuarantineFolder(base::FilePath* output_quarantine_path);
} // namespace chrome_cleaner } // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_OS_SYSTEM_UTIL_CLEANER_H_ #endif // CHROME_CHROME_CLEANER_OS_SYSTEM_UTIL_CLEANER_H_
...@@ -6,6 +6,8 @@ ...@@ -6,6 +6,8 @@
#include <windows.h> #include <windows.h>
#include <aclapi.h>
#include <shlobj.h>
#include <shlwapi.h> #include <shlwapi.h>
#include <stdint.h> #include <stdint.h>
#include <wincrypt.h> #include <wincrypt.h>
...@@ -14,6 +16,7 @@ ...@@ -14,6 +16,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "base/base_paths_win.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file.h" #include "base/files/file.h"
#include "base/files/file_util.h" #include "base/files/file_util.h"
...@@ -22,10 +25,15 @@ ...@@ -22,10 +25,15 @@
#include "base/process/launch.h" #include "base/process/launch.h"
#include "base/strings/string16.h" #include "base/strings/string16.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/test/scoped_command_line.h"
#include "base/test/scoped_path_override.h" #include "base/test/scoped_path_override.h"
#include "base/test/test_shortcut_win.h" #include "base/test/test_shortcut_win.h"
#include "base/test/test_timeouts.h" #include "base/test/test_timeouts.h"
#include "base/win/shortcut.h" #include "base/win/shortcut.h"
#include "chrome/chrome_cleaner/constants/chrome_cleaner_switches.h"
#include "chrome/chrome_cleaner/constants/quarantine_constants.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/os/file_path_sanitization.h"
#include "chrome/chrome_cleaner/os/layered_service_provider_api.h" #include "chrome/chrome_cleaner/os/layered_service_provider_api.h"
#include "chrome/chrome_cleaner/os/layered_service_provider_wrapper.h" #include "chrome/chrome_cleaner/os/layered_service_provider_wrapper.h"
#include "chrome/chrome_cleaner/strings/string_util.h" #include "chrome/chrome_cleaner/strings/string_util.h"
...@@ -33,6 +41,7 @@ ...@@ -33,6 +41,7 @@
#include "chrome/chrome_cleaner/test/test_scoped_service_handle.h" #include "chrome/chrome_cleaner/test/test_scoped_service_handle.h"
#include "chrome/chrome_cleaner/test/test_strings.h" #include "chrome/chrome_cleaner/test/test_strings.h"
#include "chrome/chrome_cleaner/test/test_util.h" #include "chrome/chrome_cleaner/test/test_util.h"
#include "sandbox/win/src/sid.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -127,4 +136,81 @@ TEST_F(ServiceUtilCleanerTest, DISABLED_DeleteRunningService) { ...@@ -127,4 +136,81 @@ TEST_F(ServiceUtilCleanerTest, DISABLED_DeleteRunningService) {
EXPECT_FALSE(IsProcessRunning(kTestServiceExecutableName)); EXPECT_FALSE(IsProcessRunning(kTestServiceExecutableName));
} }
TEST_F(ServiceUtilCleanerTest, QuarantineFolderPermission) {
base::ScopedPathOverride local_app_data_override(
CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA));
base::FilePath quarantine_path;
EXPECT_TRUE(InitializeQuarantineFolder(&quarantine_path));
PSID owner_sid;
PACL dacl;
PSECURITY_DESCRIPTOR security_descriptor;
// Get the ownership and ACL of the quarantine folder and check the values.
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
::GetNamedSecurityInfo(
quarantine_path.AsUTF16Unsafe().c_str(), SE_FILE_OBJECT,
OWNER_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION,
&owner_sid, /*psidGroup=*/nullptr, &dacl,
/*pSacl=*/nullptr, &security_descriptor));
sandbox::Sid admin_sid(WinBuiltinAdministratorsSid);
ASSERT_TRUE(admin_sid.IsValid());
// Check that the administrator group is the owner.
EXPECT_TRUE(::EqualSid(owner_sid, admin_sid.GetPSID()));
EXPLICIT_ACCESS* explicit_access;
ULONG entry_count;
ASSERT_EQ(static_cast<DWORD>(ERROR_SUCCESS),
::GetExplicitEntriesFromAcl(dacl, &entry_count, &explicit_access));
// ACL should only contains one rule.
ASSERT_EQ(1UL, entry_count);
// Administrator group should have full access.
EXPECT_EQ(static_cast<DWORD>(FILE_ALL_ACCESS),
FILE_ALL_ACCESS & explicit_access[0].grfAccessPermissions);
EXPECT_EQ(static_cast<DWORD>(NO_INHERITANCE),
explicit_access[0].grfInheritance);
EXPECT_EQ(TRUSTEE_IS_SID, explicit_access[0].Trustee.TrusteeForm);
// The trustee of the rule should be administrator group.
EXPECT_TRUE(
::EqualSid(explicit_access[0].Trustee.ptstrName, admin_sid.GetPSID()));
::LocalFree(explicit_access);
::LocalFree(security_descriptor);
}
TEST_F(ServiceUtilCleanerTest, DefaultQuarantineFolderPath) {
base::ScopedPathOverride local_app_data_override(
CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA));
base::FilePath quarantine_path;
EXPECT_TRUE(InitializeQuarantineFolder(&quarantine_path));
base::FilePath product_path;
ASSERT_TRUE(GetAppDataProductDirectory(&product_path));
const base::FilePath default_path = product_path.Append(kQuarantineFolder);
EXPECT_EQ(quarantine_path, default_path);
}
TEST_F(ServiceUtilCleanerTest, SpecifiedQuarantineFolderPath) {
// Override the default path of local appdata, so if we fail to redirect the
// quarantine folder, the test won't drop any file in the real directory.
base::ScopedPathOverride local_app_data_override(
CsidlToPathServiceKey(CSIDL_LOCAL_APPDATA));
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::test::ScopedCommandLine scoped_command_line;
scoped_command_line.GetProcessCommandLine()->AppendSwitchPath(
kQuarantineDirSwitch, temp_dir.GetPath());
base::FilePath quarantine_path;
EXPECT_TRUE(InitializeQuarantineFolder(&quarantine_path));
EXPECT_EQ(quarantine_path, temp_dir.GetPath());
}
} // namespace chrome_cleaner } // namespace chrome_cleaner
...@@ -134,6 +134,8 @@ source_set("test_util") { ...@@ -134,6 +134,8 @@ source_set("test_util") {
testonly = true testonly = true
sources = [ sources = [
"file_remover_test_util.cc",
"file_remover_test_util.h",
"reboot_deletion_helper.cc", "reboot_deletion_helper.cc",
"reboot_deletion_helper.h", "reboot_deletion_helper.h",
"test_file_util.cc", "test_file_util.cc",
......
// 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/test/file_remover_test_util.h"
#include <utility>
#include "base/bind.h"
#include "base/run_loop.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chrome_cleaner {
namespace {
void SaveBoolValueCallback(base::OnceClosure run_loop_closure,
bool* result_storage,
bool result) {
*result_storage = result;
std::move(run_loop_closure).Run();
}
void VerifyRemoveNow(const base::FilePath& path,
FileRemoverAPI* remover,
bool expected_result) {
bool returned_result = !expected_result;
base::RunLoop run_loop;
remover->RemoveNow(
path, base::BindOnce(&SaveBoolValueCallback, run_loop.QuitClosure(),
&returned_result));
run_loop.Run();
EXPECT_EQ(expected_result, returned_result);
}
void VerifyRegisterPostRebootRemoval(const base::FilePath& path,
FileRemoverAPI* remover,
bool expected_result) {
bool returned_result = !expected_result;
base::RunLoop run_loop;
remover->RegisterPostRebootRemoval(
path, base::BindOnce(&SaveBoolValueCallback, run_loop.QuitClosure(),
&returned_result));
run_loop.Run();
EXPECT_EQ(expected_result, returned_result);
}
} // namespace
void VerifyRemoveNowSuccess(const base::FilePath& path,
FileRemoverAPI* remover) {
VerifyRemoveNow(path, remover, /*expected_result=*/true);
}
void VerifyRemoveNowFailure(const base::FilePath& path,
FileRemoverAPI* remover) {
VerifyRemoveNow(path, remover, /*expected_result=*/false);
}
void VerifyRegisterPostRebootRemovalSuccess(const base::FilePath& path,
FileRemoverAPI* remover) {
VerifyRegisterPostRebootRemoval(path, remover, /*expected_result=*/true);
}
void VerifyRegisterPostRebootRemovalFailure(const base::FilePath& path,
FileRemoverAPI* remover) {
VerifyRegisterPostRebootRemoval(path, remover, /*expected_result=*/false);
}
} // namespace chrome_cleaner
// 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.
#ifndef CHROME_CHROME_CLEANER_TEST_FILE_REMOVER_TEST_UTIL_H_
#define CHROME_CHROME_CLEANER_TEST_FILE_REMOVER_TEST_UTIL_H_
#include "base/files/file_path.h"
#include "chrome/chrome_cleaner/os/file_remover_api.h"
namespace chrome_cleaner {
void VerifyRemoveNowSuccess(const base::FilePath& path,
FileRemoverAPI* remover);
void VerifyRemoveNowFailure(const base::FilePath& path,
FileRemoverAPI* remover);
void VerifyRegisterPostRebootRemovalSuccess(const base::FilePath& path,
FileRemoverAPI* remover);
void VerifyRegisterPostRebootRemovalFailure(const base::FilePath& path,
FileRemoverAPI* remover);
} // namespace chrome_cleaner
#endif // CHROME_CHROME_CLEANER_TEST_FILE_REMOVER_TEST_UTIL_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment