blob: c1a9ad627c41a459b3f1b283b09f08d6401629bb [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/chrome_cleaner/os/file_remover.h"
#include <stdint.h>
#include <memory>
#include <utility>
#include "base/base_paths.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/message_loop/message_loop.h"
#include "base/path_service.h"
#include "base/strings/strcat.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/scoped_path_override.h"
#include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
#include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
#include "chrome/chrome_cleaner/os/disk_util.h"
#include "chrome/chrome_cleaner/os/file_removal_status_updater.h"
#include "chrome/chrome_cleaner/os/layered_service_provider_wrapper.h"
#include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
#include "chrome/chrome_cleaner/os/system_util.h"
#include "chrome/chrome_cleaner/os/whitelisted_directory.h"
#include "chrome/chrome_cleaner/test/file_remover_test_util.h"
#include "chrome/chrome_cleaner/test/reboot_deletion_helper.h"
#include "chrome/chrome_cleaner/test/resources/grit/test_resources.h"
#include "chrome/chrome_cleaner/test/test_file_util.h"
#include "chrome/chrome_cleaner/test/test_layered_service_provider.h"
#include "chrome/chrome_cleaner/test/test_name_helper.h"
#include "chrome/chrome_cleaner/test/test_strings.h"
#include "chrome/chrome_cleaner/test/test_util.h"
#include "chrome/chrome_cleaner/zip_archiver/broker/sandbox_setup.h"
#include "chrome/chrome_cleaner/zip_archiver/sandboxed_zip_archiver.h"
#include "chrome/chrome_cleaner/zip_archiver/target/sandbox_setup.h"
#include "sandbox/win/src/sandbox.h"
#include "sandbox/win/src/sandbox_factory.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
namespace chrome_cleaner {
namespace {
using testing::_;
using testing::Eq;
using testing::Return;
using ValidationStatus = FileRemoverAPI::DeletionValidationStatus;
const wchar_t kRemoveFile1[] = L"remove_one.exe";
const wchar_t kRemoveFile2[] = L"remove_two.exe";
const wchar_t kRemoveFolder[] = L"remove";
class FileRemoverTest : public ::testing::Test {
protected:
FileRemoverTest()
: default_file_remover_(
/*digest_verifier=*/nullptr,
/*archiver=*/nullptr,
LayeredServiceProviderWrapper(),
/*deletion_allowed_paths=*/{},
base::BindRepeating(&FileRemoverTest::RebootRequired,
base::Unretained(this))) {
FileRemovalStatusUpdater::GetInstance()->Clear();
}
void RebootRequired() { reboot_required_ = true; }
void TestBlacklistedRemoval(FileRemover* remover,
const base::FilePath& path) {
DCHECK(remover);
EXPECT_EQ(ValidationStatus::FORBIDDEN, remover->CanRemove(path));
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
VerifyRemoveNowFailure(path, remover);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(path),
REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
removal_status_updater->Clear();
VerifyRegisterPostRebootRemovalFailure(path, remover);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(path),
REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
EXPECT_TRUE(base::PathExists(path));
EXPECT_FALSE(IsFileRegisteredForPostRebootRemoval(path));
}
FileRemover default_file_remover_;
base::MessageLoop message_loop_;
bool reboot_required_ = false;
};
} // namespace
TEST_F(FileRemoverTest, RemoveNowValidFile) {
// Create a temporary empty file.
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
const base::FilePath file_path = temp.GetPath().Append(kRemoveFile1);
EXPECT_TRUE(CreateEmptyFile(file_path));
// Removing it must succeed.
VerifyRemoveNowSuccess(file_path, &default_file_remover_);
EXPECT_FALSE(base::PathExists(file_path));
EXPECT_EQ(
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(file_path),
REMOVAL_STATUS_REMOVED);
}
TEST_F(FileRemoverTest, RemoveNowAbsentFile) {
// Create a non-existing file name.
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
const base::FilePath file_path = temp.GetPath().Append(kRemoveFile1);
EXPECT_FALSE(base::PathExists(file_path));
// Removing it must not generate an error.
VerifyRemoveNowSuccess(file_path, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path),
REMOVAL_STATUS_NOT_FOUND);
// Ensure the non-existant files with non-existant parents don't generate an
// error.
base::FilePath file_path_deeper = file_path.Append(kRemoveFile2);
VerifyRemoveNowSuccess(file_path_deeper, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path_deeper),
REMOVAL_STATUS_NOT_FOUND);
}
TEST_F(FileRemoverTest, NoKnownFileRemoval) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
FileRemover remover(
DigestVerifier::CreateFromResource(IDS_TEST_SAMPLE_DLL_DIGEST),
/*archiver=*/nullptr, LayeredServiceProviderWrapper(),
/*deletion_allowed_paths=*/{}, base::DoNothing::Repeatedly());
// Copy the sample DLL to the temp folder.
base::FilePath dll_path = GetSampleDLLPath();
ASSERT_TRUE(base::PathExists(dll_path)) << dll_path.value();
base::FilePath target_dll_path(
temp_dir.GetPath().Append(dll_path.BaseName()));
ASSERT_TRUE(base::CopyFile(dll_path, target_dll_path));
TestBlacklistedRemoval(&remover, target_dll_path);
}
TEST_F(FileRemoverTest, NoSelfRemoval) {
base::FilePath exe_path = PreFetchedPaths::GetInstance()->GetExecutablePath();
TestBlacklistedRemoval(&default_file_remover_, exe_path);
}
TEST_F(FileRemoverTest, NoWhitelistedFileRemoval) {
base::FilePath program_files_dir =
PreFetchedPaths::GetInstance()->GetProgramFilesFolder();
TestBlacklistedRemoval(&default_file_remover_, program_files_dir);
}
TEST_F(FileRemoverTest, NoWhitelistFileTempRemoval) {
base::FilePath temp_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &temp_dir));
TestBlacklistedRemoval(&default_file_remover_, temp_dir);
}
TEST_F(FileRemoverTest, NoLSPRemoval) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
base::FilePath provider_path = temp.GetPath().Append(kRemoveFile1);
ASSERT_TRUE(CreateEmptyFile(provider_path));
TestLayeredServiceProvider lsp;
lsp.AddProvider(kGUID1, provider_path);
FileRemover remover(/*digest_verifier=*/nullptr, /*archiver=*/nullptr, lsp,
/*deletion_allowed_paths=*/{},
base::DoNothing::Repeatedly());
TestBlacklistedRemoval(&remover, provider_path);
}
TEST_F(FileRemoverTest, CanRemoveAbsolutePath) {
EXPECT_EQ(ValidationStatus::ALLOWED,
default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\bar")));
}
TEST_F(FileRemoverTest, NoRelativePathRemoval) {
EXPECT_EQ(ValidationStatus::FORBIDDEN,
default_file_remover_.CanRemove(base::FilePath(L"bar.txt")));
}
TEST_F(FileRemoverTest, NoDriveRemoval) {
EXPECT_EQ(ValidationStatus::FORBIDDEN,
default_file_remover_.CanRemove(base::FilePath(L"C:")));
EXPECT_EQ(ValidationStatus::FORBIDDEN,
default_file_remover_.CanRemove(base::FilePath(L"C:\\")));
}
TEST_F(FileRemoverTest, NoPathTraversal) {
EXPECT_EQ(
ValidationStatus::FORBIDDEN,
default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\..\\bar")));
EXPECT_EQ(
ValidationStatus::FORBIDDEN,
default_file_remover_.CanRemove(base::FilePath(L"..\\foo\\bar.dll")));
}
TEST_F(FileRemoverTest, CorrectPathTraversalDetection) {
EXPECT_EQ(
ValidationStatus::ALLOWED,
default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\..bar.dll")));
EXPECT_EQ(
ValidationStatus::ALLOWED,
default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\bar..dll")));
EXPECT_EQ(
ValidationStatus::ALLOWED,
default_file_remover_.CanRemove(base::FilePath(L"C:\\foo..\\bar.dll")));
}
TEST_F(FileRemoverTest, RemoveNowDoesNotDeleteFolders) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
// Create the folder and the files.
base::FilePath subfolder_path = temp.GetPath().Append(kRemoveFolder);
base::CreateDirectory(subfolder_path);
base::FilePath file_path1 = subfolder_path.Append(kRemoveFile1);
ASSERT_TRUE(CreateEmptyFile(file_path1));
// The folder should not be removed.
VerifyRemoveNowFailure(subfolder_path, &default_file_remover_);
EXPECT_EQ(
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(subfolder_path),
REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
EXPECT_TRUE(base::PathExists(subfolder_path));
EXPECT_TRUE(base::PathExists(file_path1));
}
TEST_F(FileRemoverTest, RemoveNowDeletesEmptyFolders) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
// Create the folder and the files.
base::FilePath subfolder_path = temp.GetPath().Append(kRemoveFolder);
base::CreateDirectory(subfolder_path);
base::FilePath file_path1 = subfolder_path.Append(kRemoveFile1);
ASSERT_TRUE(CreateEmptyFile(file_path1));
base::FilePath subsubfolder_path = subfolder_path.Append(kRemoveFolder);
base::CreateDirectory(subsubfolder_path);
base::FilePath file_path2 = subsubfolder_path.Append(kRemoveFile2);
ASSERT_TRUE(CreateEmptyFile(file_path2));
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
// Delete a file in a folder with other stuff, so the folder isn't deleted.
VerifyRemoveNowSuccess(file_path1, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path1),
REMOVAL_STATUS_REMOVED);
EXPECT_TRUE(base::PathExists(subfolder_path));
EXPECT_FALSE(base::PathExists(file_path1));
EXPECT_TRUE(base::PathExists(subsubfolder_path));
EXPECT_TRUE(base::PathExists(file_path2));
// Delete the file and ensure the two parent folders are deleted since they
// are now empty.
VerifyRemoveNowSuccess(file_path2, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path2),
REMOVAL_STATUS_REMOVED);
EXPECT_FALSE(base::PathExists(subfolder_path));
EXPECT_FALSE(base::PathExists(subsubfolder_path));
EXPECT_FALSE(base::PathExists(file_path2));
}
TEST_F(FileRemoverTest, RemoveNowDeletesEmptyFoldersNotTemp) {
base::ScopedPathOverride temp_override(base::DIR_TEMP);
base::FilePath scoped_temp_dir;
ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &scoped_temp_dir));
// Create a file in temp.
base::FilePath file_path = scoped_temp_dir.Append(kRemoveFile1);
ASSERT_TRUE(CreateEmptyFile(file_path));
// Delete the file and ensure Temp isn't deleted since it is whitelisted.
VerifyRemoveNowSuccess(file_path, &default_file_remover_);
EXPECT_EQ(
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(file_path),
REMOVAL_STATUS_REMOVED);
EXPECT_FALSE(base::PathExists(file_path));
base::FilePath temp_dir;
ASSERT_TRUE(base::GetTempDir(&temp_dir));
EXPECT_TRUE(base::PathExists(temp_dir));
}
TEST_F(FileRemoverTest, RegisterPostRebootRemoval) {
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
// When trying to delete a whitelisted file, we should fail to register the
// file for removal, and no reboot should be required.
base::FilePath exe_path = PreFetchedPaths::GetInstance()->GetExecutablePath();
VerifyRegisterPostRebootRemovalFailure(exe_path, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(exe_path),
REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
EXPECT_FALSE(reboot_required_);
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
base::FilePath file_path = temp.GetPath().Append(kRemoveFile2);
// When trying to delete an non-existant file, we should return success, but
// not require a reboot.
VerifyRegisterPostRebootRemovalSuccess(file_path, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path),
REMOVAL_STATUS_NOT_FOUND);
EXPECT_FALSE(reboot_required_);
// When trying to delete a real file, we should return success and require a
// reboot.
ASSERT_TRUE(CreateEmptyFile(file_path));
VerifyRegisterPostRebootRemovalSuccess(file_path, &default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path),
REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
EXPECT_TRUE(reboot_required_);
EXPECT_TRUE(IsFileRegisteredForPostRebootRemoval(file_path));
}
TEST_F(FileRemoverTest, RegisterPostRebootRemoval_Directories) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
// Create an empty directory.
base::FilePath subfolder_path = temp.GetPath().Append(kRemoveFolder);
ASSERT_TRUE(base::CreateDirectory(subfolder_path));
FileRemovalStatusUpdater* removal_status_updater =
FileRemovalStatusUpdater::GetInstance();
// Directories shouldn't be registered for deletion.
VerifyRegisterPostRebootRemovalFailure(subfolder_path,
&default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(subfolder_path),
REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
// Put a file into the directory and ensure the non-empty directory still
// isn't registered for removal.
removal_status_updater->Clear();
base::FilePath file_path1 = subfolder_path.Append(kRemoveFile1);
ASSERT_TRUE(CreateEmptyFile(file_path1));
VerifyRegisterPostRebootRemovalFailure(subfolder_path,
&default_file_remover_);
EXPECT_EQ(removal_status_updater->GetRemovalStatus(subfolder_path),
REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
}
TEST_F(FileRemoverTest, NotActiveFileType) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
base::FilePath path = temp.GetPath().Append(L"temp_file.txt");
ASSERT_TRUE(
CreateFileInFolder(path.DirName(), path.BaseName().value().c_str()));
EXPECT_EQ(ValidationStatus::INACTIVE,
FileRemover::IsFileRemovalAllowed(path, {}, {}));
VerifyRemoveNowSuccess(path, &default_file_remover_);
EXPECT_EQ(REMOVAL_STATUS_NOT_REMOVED_INACTIVE_EXTENSION,
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
EXPECT_TRUE(base::PathExists(path));
}
TEST_F(FileRemoverTest, NotActiveFileAllowed) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
base::FilePath path = temp.GetPath().Append(L"temp_file.txt");
ASSERT_TRUE(
CreateFileInFolder(path.DirName(), path.BaseName().value().c_str()));
FileRemover remover(/*digest_verifier=*/nullptr, /*archiver=*/nullptr,
LayeredServiceProviderWrapper(), {path.value().c_str()},
base::DoNothing::Repeatedly());
EXPECT_EQ(ValidationStatus::ALLOWED, FileRemover::IsFileRemovalAllowed(
path, {path.value().c_str()}, {}));
VerifyRemoveNowSuccess(path, &remover);
EXPECT_EQ(REMOVAL_STATUS_REMOVED,
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
EXPECT_FALSE(base::PathExists(path));
}
TEST_F(FileRemoverTest, DosMzExecutable) {
base::ScopedTempDir temp;
ASSERT_TRUE(temp.CreateUniqueTempDir());
base::FilePath path = temp.GetPath().Append(L"temp_file.txt");
constexpr char kMzExecutable[] = "MZ executable";
CreateFileWithContent(path, kMzExecutable, sizeof(kMzExecutable));
ASSERT_TRUE(base::PathExists(path));
FileRemover remover(/*digest_verifier=*/nullptr, /*archiver=*/nullptr,
LayeredServiceProviderWrapper(), {path.value().c_str()},
base::DoNothing::Repeatedly());
EXPECT_EQ(ValidationStatus::ALLOWED,
FileRemover::IsFileRemovalAllowed(path, {}, {}));
VerifyRemoveNowSuccess(path, &remover);
EXPECT_EQ(REMOVAL_STATUS_REMOVED,
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
EXPECT_FALSE(base::PathExists(path));
}
namespace {
constexpr char kTestPassword[] = "1234";
constexpr char kTestContent[] = "Hello World";
constexpr wchar_t kTestFileName[] = L"temp_file.exe";
constexpr wchar_t kTestExpectArchiveName[] =
L"temp_file.exe_"
L"A591A6D40BF420404A011733CFB7B190D62C65BF0BCDA32B57B277D9AD9F146E.zip";
class FileRemoverQuarantineTest : public base::MultiProcessTest,
public ::testing::WithParamInterface<bool> {
public:
void SetUp() override {
use_reboot_removal_ = GetParam();
scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
ZipArchiverSandboxSetupHooks setup_hooks(
mojo_task_runner.get(), base::BindOnce([] {
FAIL() << "ZipArchiver sandbox connection error";
}));
ASSERT_EQ(RESULT_CODE_SUCCESS,
StartSandboxTarget(MakeCmdLine("FileRemoverQuarantineTargetMain"),
&setup_hooks, SandboxType::kTest));
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
auto zip_archiver = std::make_unique<SandboxedZipArchiver>(
mojo_task_runner, setup_hooks.TakeZipArchiverPtr(), temp_dir_.GetPath(),
kTestPassword);
file_remover_ = std::make_unique<FileRemover>(
/*digest_verifier=*/nullptr, std::move(zip_archiver),
LayeredServiceProviderWrapper(), FilePathSet(),
base::DoNothing::Repeatedly());
}
protected:
// Do removal corresponding to |use_reboot_removal_|. Also do corresponding
// check for file existence and removal status.
void DoAndExpectCorrespondingRemoval(const base::FilePath& path) {
if (use_reboot_removal_) {
VerifyRegisterPostRebootRemovalSuccess(path, file_remover_.get());
EXPECT_EQ(
REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL,
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
EXPECT_TRUE(base::PathExists(path));
} else {
VerifyRemoveNowSuccess(path, file_remover_.get());
EXPECT_EQ(
REMOVAL_STATUS_REMOVED,
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
EXPECT_FALSE(base::PathExists(path));
}
return;
}
bool use_reboot_removal_ = false;
base::MessageLoop message_loop_;
base::ScopedTempDir temp_dir_;
std::unique_ptr<FileRemover> file_remover_;
};
} // namespace
MULTIPROCESS_TEST_MAIN(FileRemoverQuarantineTargetMain) {
sandbox::TargetServices* sandbox_target_services =
sandbox::SandboxFactory::GetTargetServices();
DCHECK(sandbox_target_services);
// |RunZipArchiverSandboxTarget| won't return. The mojo error handler will
// abort this process when the connection is broken.
RunZipArchiverSandboxTarget(*base::CommandLine::ForCurrentProcess(),
sandbox_target_services);
return 0;
}
TEST_P(FileRemoverQuarantineTest, QuarantineFile) {
const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
CreateFileWithContent(path, kTestContent, strlen(kTestContent));
DoAndExpectCorrespondingRemoval(path);
EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
const base::FilePath expected_archive_path =
temp_dir_.GetPath().Append(kTestExpectArchiveName);
EXPECT_TRUE(base::PathExists(expected_archive_path));
}
TEST_P(FileRemoverQuarantineTest, FailToQuarantine) {
const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
// Acquire exclusive read access to the file, so the archiver can't open the
// file.
base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ |
base::File::FLAG_EXCLUSIVE_READ);
ASSERT_TRUE(file.IsValid());
(use_reboot_removal_)
? VerifyRegisterPostRebootRemovalFailure(path, file_remover_.get())
: VerifyRemoveNowFailure(path, file_remover_.get());
EXPECT_EQ(REMOVAL_STATUS_ERROR_IN_ARCHIVER,
FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
EXPECT_EQ(QUARANTINE_STATUS_ERROR,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
EXPECT_TRUE(base::PathExists(path));
}
TEST_P(FileRemoverQuarantineTest, DuplicatedFile) {
const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
const base::FilePath expected_archive_path =
temp_dir_.GetPath().Append(kTestExpectArchiveName);
CreateFileWithContent(path, kTestContent, strlen(kTestContent));
DoAndExpectCorrespondingRemoval(path);
EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
EXPECT_TRUE(base::PathExists(expected_archive_path));
// The file should not be archived twice if there is already one with the same
// name and content in the quarantine. So the modified time of the archive
// should not be updated.
base::File::Info old_info;
ASSERT_TRUE(base::GetFileInfo(expected_archive_path, &old_info));
// Recreate the source file and remove it again.
CreateFileWithContent(path, kTestContent, strlen(kTestContent));
DoAndExpectCorrespondingRemoval(path);
// Although the file won't be archived again, it still has a backup in the
// quarantine. So the status should be |QUARANTINE_STATUS_QUARANTINED|.
EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
EXPECT_TRUE(base::PathExists(expected_archive_path));
base::File::Info new_info;
ASSERT_TRUE(base::GetFileInfo(expected_archive_path, &new_info));
EXPECT_EQ(old_info.last_modified, new_info.last_modified);
}
TEST_P(FileRemoverQuarantineTest, DoNotQuarantineSymbolicLink) {
const base::FilePath path = temp_dir_.GetPath().Append(L"source_temp_file");
CreateFileWithContent(path, kTestContent, strlen(kTestContent));
const base::FilePath sym_path = temp_dir_.GetPath().Append(kTestFileName);
ASSERT_NE(0, ::CreateSymbolicLink(sym_path.AsUTF16Unsafe().c_str(),
path.AsUTF16Unsafe().c_str(), 0));
DoAndExpectCorrespondingRemoval(sym_path);
EXPECT_EQ(
QUARANTINE_STATUS_SKIPPED,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(sym_path));
const base::FilePath expected_archive_path =
temp_dir_.GetPath().Append(kTestExpectArchiveName);
EXPECT_FALSE(base::PathExists(expected_archive_path));
// The original file should exist.
EXPECT_TRUE(base::PathExists(path));
}
TEST_P(FileRemoverQuarantineTest, QuarantineDefaultFileStream) {
const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
CreateFileWithContent(path, kTestContent, strlen(kTestContent));
base::FilePath stream_path(base::StrCat({path.AsUTF16Unsafe(), L"::$data"}));
CreateFileWithContent(stream_path, kTestContent, strlen(kTestContent));
DoAndExpectCorrespondingRemoval(stream_path);
EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(
stream_path));
const base::FilePath expected_archive_path =
temp_dir_.GetPath().Append(kTestExpectArchiveName);
EXPECT_TRUE(base::PathExists(expected_archive_path));
}
TEST_P(FileRemoverQuarantineTest, DoNotQuarantineNonDefaultFileStream) {
const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
CreateFileWithContent(path, kTestContent, strlen(kTestContent));
base::FilePath stream_path(
base::StrCat({path.AsUTF16Unsafe(), L":stream:$data"}));
CreateFileWithContent(stream_path, kTestContent, strlen(kTestContent));
DoAndExpectCorrespondingRemoval(stream_path);
EXPECT_EQ(QUARANTINE_STATUS_SKIPPED,
FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(
stream_path));
}
INSTANTIATE_TEST_CASE_P(All,
FileRemoverQuarantineTest,
testing::Bool(),
GetParamNameForTest());
} // namespace chrome_cleaner