1 // Copyright 2018 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "chrome/chrome_cleaner/os/file_remover.h"
6 
7 #include <stdint.h>
8 #include <memory>
9 #include <utility>
10 
11 #include "base/base_paths.h"
12 #include "base/bind.h"
13 #include "base/callback_helpers.h"
14 #include "base/check.h"
15 #include "base/command_line.h"
16 #include "base/files/file_path.h"
17 #include "base/files/file_util.h"
18 #include "base/memory/scoped_refptr.h"
19 #include "base/path_service.h"
20 #include "base/strings/strcat.h"
21 #include "base/strings/utf_string_conversions.h"
22 #include "base/test/multiprocess_test.h"
23 #include "base/test/scoped_path_override.h"
24 #include "base/test/task_environment.h"
25 #include "chrome/chrome_cleaner/ipc/mojo_task_runner.h"
26 #include "chrome/chrome_cleaner/logging/proto/removal_status.pb.h"
27 #include "chrome/chrome_cleaner/os/disk_util.h"
28 #include "chrome/chrome_cleaner/os/file_removal_status_updater.h"
29 #include "chrome/chrome_cleaner/os/layered_service_provider_wrapper.h"
30 #include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
31 #include "chrome/chrome_cleaner/os/system_util.h"
32 #include "chrome/chrome_cleaner/os/whitelisted_directory.h"
33 #include "chrome/chrome_cleaner/test/child_process_logger.h"
34 #include "chrome/chrome_cleaner/test/file_remover_test_util.h"
35 #include "chrome/chrome_cleaner/test/reboot_deletion_helper.h"
36 #include "chrome/chrome_cleaner/test/resources/grit/test_resources.h"
37 #include "chrome/chrome_cleaner/test/test_file_util.h"
38 #include "chrome/chrome_cleaner/test/test_layered_service_provider.h"
39 #include "chrome/chrome_cleaner/test/test_strings.h"
40 #include "chrome/chrome_cleaner/test/test_util.h"
41 #include "chrome/chrome_cleaner/zip_archiver/broker/sandbox_setup.h"
42 #include "chrome/chrome_cleaner/zip_archiver/sandboxed_zip_archiver.h"
43 #include "chrome/chrome_cleaner/zip_archiver/target/sandbox_setup.h"
44 #include "components/chrome_cleaner/test/test_name_helper.h"
45 #include "sandbox/win/src/sandbox.h"
46 #include "sandbox/win/src/sandbox_factory.h"
47 #include "testing/gtest/include/gtest/gtest.h"
48 #include "testing/multiprocess_func_list.h"
49 
50 namespace chrome_cleaner {
51 
52 namespace {
53 
54 using testing::_;
55 using testing::Eq;
56 using testing::Return;
57 using ValidationStatus = FileRemoverAPI::DeletionValidationStatus;
58 
59 const wchar_t kRemoveFile1[] = L"remove_one.exe";
60 const wchar_t kRemoveFile2[] = L"remove_two.exe";
61 const wchar_t kRemoveFolder[] = L"remove";
62 
63 class FileRemoverTest : public ::testing::Test {
64  protected:
FileRemoverTest()65   FileRemoverTest()
66       : default_file_remover_(
67             /*digest_verifier=*/nullptr,
68             /*archiver=*/nullptr,
69             LayeredServiceProviderWrapper(),
70             base::BindRepeating(&FileRemoverTest::RebootRequired,
71                                 base::Unretained(this))) {
72     FileRemovalStatusUpdater::GetInstance()->Clear();
73   }
74 
RebootRequired()75   void RebootRequired() { reboot_required_ = true; }
76 
TestBlacklistedRemoval(FileRemover * remover,const base::FilePath & path)77   void TestBlacklistedRemoval(FileRemover* remover,
78                               const base::FilePath& path) {
79     DCHECK(remover);
80 
81     EXPECT_EQ(ValidationStatus::FORBIDDEN, remover->CanRemove(path));
82 
83     FileRemovalStatusUpdater* removal_status_updater =
84         FileRemovalStatusUpdater::GetInstance();
85 
86     VerifyRemoveNowFailure(path, remover);
87     EXPECT_EQ(removal_status_updater->GetRemovalStatus(path),
88               REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
89 
90     removal_status_updater->Clear();
91     VerifyRegisterPostRebootRemovalFailure(path, remover);
92     EXPECT_EQ(removal_status_updater->GetRemovalStatus(path),
93               REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
94 
95     EXPECT_TRUE(base::PathExists(path));
96     EXPECT_FALSE(IsFileRegisteredForPostRebootRemoval(path));
97   }
98 
99   FileRemover default_file_remover_;
100   base::test::SingleThreadTaskEnvironment task_environment_;
101   bool reboot_required_ = false;
102 };
103 
CreateNetworkedFile(const base::FilePath & path)104 bool CreateNetworkedFile(const base::FilePath& path) {
105   chrome_cleaner::CreateEmptyFile(path);
106 
107   // Fake an attribute that would be present on a networked file.
108   // FILE_ATTRIBUTE_OFFLINE was chosen as FILE_ATTRIBUTE_RECALL_ON_DATA_ACCESS
109   // and FILE_ATTRIBUTE_RECALL_ON_OPEN do not seem to be user-settable.
110   const DWORD attr = ::GetFileAttributes(path.value().c_str());
111   return ::SetFileAttributesW(path.value().c_str(),
112                               attr | FILE_ATTRIBUTE_OFFLINE);
113 }
114 
115 }  // namespace
116 
TEST_F(FileRemoverTest,RemoveNowValidFile)117 TEST_F(FileRemoverTest, RemoveNowValidFile) {
118   // Create a temporary empty file.
119   base::ScopedTempDir temp;
120   ASSERT_TRUE(temp.CreateUniqueTempDir());
121 
122   const base::FilePath file_path = temp.GetPath().Append(kRemoveFile1);
123   EXPECT_TRUE(CreateEmptyFile(file_path));
124 
125   // Removing it must succeed.
126   VerifyRemoveNowSuccess(file_path, &default_file_remover_);
127   EXPECT_FALSE(base::PathExists(file_path));
128   EXPECT_EQ(
129       FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(file_path),
130       REMOVAL_STATUS_REMOVED);
131 }
132 
TEST_F(FileRemoverTest,RemoveNowAbsentFile)133 TEST_F(FileRemoverTest, RemoveNowAbsentFile) {
134   // Create a non-existing file name.
135   base::ScopedTempDir temp;
136   ASSERT_TRUE(temp.CreateUniqueTempDir());
137 
138   FileRemovalStatusUpdater* removal_status_updater =
139       FileRemovalStatusUpdater::GetInstance();
140 
141   const base::FilePath file_path = temp.GetPath().Append(kRemoveFile1);
142   EXPECT_FALSE(base::PathExists(file_path));
143 
144   // Removing it must not generate an error.
145   VerifyRemoveNowSuccess(file_path, &default_file_remover_);
146   EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path),
147             REMOVAL_STATUS_NOT_FOUND);
148 
149   // Ensure the non-existant files with non-existant parents don't generate an
150   // error.
151   base::FilePath file_path_deeper = file_path.Append(kRemoveFile2);
152   VerifyRemoveNowSuccess(file_path_deeper, &default_file_remover_);
153   EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path_deeper),
154             REMOVAL_STATUS_NOT_FOUND);
155 }
156 
TEST_F(FileRemoverTest,NoKnownFileRemoval)157 TEST_F(FileRemoverTest, NoKnownFileRemoval) {
158   base::ScopedTempDir temp_dir;
159   ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
160 
161   FileRemover remover(
162       DigestVerifier::CreateFromResource(IDS_TEST_SAMPLE_DLL_DIGEST),
163       /*archiver=*/nullptr, LayeredServiceProviderWrapper(),
164       base::DoNothing::Repeatedly());
165 
166   // Copy the sample DLL to the temp folder.
167   base::FilePath dll_path = GetSampleDLLPath();
168   ASSERT_TRUE(base::PathExists(dll_path)) << dll_path.value();
169 
170   base::FilePath target_dll_path(
171       temp_dir.GetPath().Append(dll_path.BaseName()));
172   ASSERT_TRUE(base::CopyFile(dll_path, target_dll_path));
173 
174   TestBlacklistedRemoval(&remover, target_dll_path);
175 }
176 
TEST_F(FileRemoverTest,NoSelfRemoval)177 TEST_F(FileRemoverTest, NoSelfRemoval) {
178   base::FilePath exe_path = PreFetchedPaths::GetInstance()->GetExecutablePath();
179   TestBlacklistedRemoval(&default_file_remover_, exe_path);
180 }
181 
TEST_F(FileRemoverTest,NoWhitelistedFileRemoval)182 TEST_F(FileRemoverTest, NoWhitelistedFileRemoval) {
183   base::FilePath program_files_dir =
184       PreFetchedPaths::GetInstance()->GetProgramFilesFolder();
185   TestBlacklistedRemoval(&default_file_remover_, program_files_dir);
186 }
187 
TEST_F(FileRemoverTest,NoWhitelistFileTempRemoval)188 TEST_F(FileRemoverTest, NoWhitelistFileTempRemoval) {
189   base::FilePath temp_dir;
190   ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &temp_dir));
191   TestBlacklistedRemoval(&default_file_remover_, temp_dir);
192 }
193 
TEST_F(FileRemoverTest,NoLSPRemoval)194 TEST_F(FileRemoverTest, NoLSPRemoval) {
195   base::ScopedTempDir temp;
196   ASSERT_TRUE(temp.CreateUniqueTempDir());
197   base::FilePath provider_path = temp.GetPath().Append(kRemoveFile1);
198   ASSERT_TRUE(CreateEmptyFile(provider_path));
199 
200   TestLayeredServiceProvider lsp;
201   lsp.AddProvider(kGUID1, provider_path);
202 
203   FileRemover remover(/*digest_verifier=*/nullptr, /*archiver=*/nullptr, lsp,
204                       base::DoNothing::Repeatedly());
205 
206   TestBlacklistedRemoval(&remover, provider_path);
207 }
208 
TEST_F(FileRemoverTest,CanRemoveAbsolutePath)209 TEST_F(FileRemoverTest, CanRemoveAbsolutePath) {
210   EXPECT_EQ(ValidationStatus::ALLOWED,
211             default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\bar")));
212 }
213 
TEST_F(FileRemoverTest,NoRelativePathRemoval)214 TEST_F(FileRemoverTest, NoRelativePathRemoval) {
215   EXPECT_EQ(ValidationStatus::UNSAFE,
216             default_file_remover_.CanRemove(base::FilePath(L"bar.txt")));
217 }
218 
TEST_F(FileRemoverTest,NoDriveRemoval)219 TEST_F(FileRemoverTest, NoDriveRemoval) {
220   EXPECT_EQ(ValidationStatus::UNSAFE,
221             default_file_remover_.CanRemove(base::FilePath(L"C:")));
222   EXPECT_EQ(ValidationStatus::FORBIDDEN,
223             default_file_remover_.CanRemove(base::FilePath(L"C:\\")));
224 }
225 
TEST_F(FileRemoverTest,NoNetworkedRemoval)226 TEST_F(FileRemoverTest, NoNetworkedRemoval) {
227   base::ScopedTempDir temp;
228   ASSERT_TRUE(temp.CreateUniqueTempDir());
229 
230   base::FilePath path = temp.GetPath().Append(kRemoveFile1);
231   CreateNetworkedFile(path);
232 
233   EXPECT_EQ(ValidationStatus::FORBIDDEN, default_file_remover_.CanRemove(path));
234 }
235 
TEST_F(FileRemoverTest,NoPathTraversal)236 TEST_F(FileRemoverTest, NoPathTraversal) {
237   EXPECT_EQ(
238       ValidationStatus::FORBIDDEN,
239       default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\..\\bar")));
240   EXPECT_EQ(ValidationStatus::UNSAFE, default_file_remover_.CanRemove(
241                                           base::FilePath(L"..\\foo\\bar.dll")));
242 }
243 
TEST_F(FileRemoverTest,CorrectPathTraversalDetection)244 TEST_F(FileRemoverTest, CorrectPathTraversalDetection) {
245   EXPECT_EQ(
246       ValidationStatus::ALLOWED,
247       default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\..bar.dll")));
248   EXPECT_EQ(
249       ValidationStatus::ALLOWED,
250       default_file_remover_.CanRemove(base::FilePath(L"C:\\foo\\bar..dll")));
251   EXPECT_EQ(
252       ValidationStatus::ALLOWED,
253       default_file_remover_.CanRemove(base::FilePath(L"C:\\foo..\\bar.dll")));
254 }
255 
TEST_F(FileRemoverTest,RemoveNowDoesNotDeleteFolders)256 TEST_F(FileRemoverTest, RemoveNowDoesNotDeleteFolders) {
257   base::ScopedTempDir temp;
258   ASSERT_TRUE(temp.CreateUniqueTempDir());
259 
260   // Create the folder and the files.
261   base::FilePath subfolder_path = temp.GetPath().Append(kRemoveFolder);
262   base::CreateDirectory(subfolder_path);
263   base::FilePath file_path1 = subfolder_path.Append(kRemoveFile1);
264   ASSERT_TRUE(CreateEmptyFile(file_path1));
265 
266   // The folder should not be removed.
267   VerifyRemoveNowFailure(subfolder_path, &default_file_remover_);
268   EXPECT_EQ(
269       FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(subfolder_path),
270       REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
271   EXPECT_TRUE(base::PathExists(subfolder_path));
272   EXPECT_TRUE(base::PathExists(file_path1));
273 }
274 
TEST_F(FileRemoverTest,RemoveNowDeletesEmptyFolders)275 TEST_F(FileRemoverTest, RemoveNowDeletesEmptyFolders) {
276   base::ScopedTempDir temp;
277   ASSERT_TRUE(temp.CreateUniqueTempDir());
278 
279   // Create the folder and the files.
280   base::FilePath subfolder_path = temp.GetPath().Append(kRemoveFolder);
281   base::CreateDirectory(subfolder_path);
282   base::FilePath file_path1 = subfolder_path.Append(kRemoveFile1);
283   ASSERT_TRUE(CreateEmptyFile(file_path1));
284   base::FilePath subsubfolder_path = subfolder_path.Append(kRemoveFolder);
285   base::CreateDirectory(subsubfolder_path);
286   base::FilePath file_path2 = subsubfolder_path.Append(kRemoveFile2);
287   ASSERT_TRUE(CreateEmptyFile(file_path2));
288 
289   FileRemovalStatusUpdater* removal_status_updater =
290       FileRemovalStatusUpdater::GetInstance();
291 
292   // Delete a file in a folder with other stuff, so the folder isn't deleted.
293   VerifyRemoveNowSuccess(file_path1, &default_file_remover_);
294   EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path1),
295             REMOVAL_STATUS_REMOVED);
296   EXPECT_TRUE(base::PathExists(subfolder_path));
297   EXPECT_FALSE(base::PathExists(file_path1));
298   EXPECT_TRUE(base::PathExists(subsubfolder_path));
299   EXPECT_TRUE(base::PathExists(file_path2));
300 
301   // Delete the file and ensure the two parent folders are deleted since they
302   // are now empty.
303   VerifyRemoveNowSuccess(file_path2, &default_file_remover_);
304   EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path2),
305             REMOVAL_STATUS_REMOVED);
306   EXPECT_FALSE(base::PathExists(subfolder_path));
307   EXPECT_FALSE(base::PathExists(subsubfolder_path));
308   EXPECT_FALSE(base::PathExists(file_path2));
309 }
310 
TEST_F(FileRemoverTest,RemoveNowDeletesEmptyFoldersNotTemp)311 TEST_F(FileRemoverTest, RemoveNowDeletesEmptyFoldersNotTemp) {
312   base::ScopedPathOverride temp_override(base::DIR_TEMP);
313 
314   base::FilePath scoped_temp_dir;
315   ASSERT_TRUE(base::PathService::Get(base::DIR_TEMP, &scoped_temp_dir));
316 
317   // Create a file in temp.
318   base::FilePath file_path = scoped_temp_dir.Append(kRemoveFile1);
319   ASSERT_TRUE(CreateEmptyFile(file_path));
320 
321   // Delete the file and ensure Temp isn't deleted since it is whitelisted.
322   VerifyRemoveNowSuccess(file_path, &default_file_remover_);
323   EXPECT_EQ(
324       FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(file_path),
325       REMOVAL_STATUS_REMOVED);
326   EXPECT_FALSE(base::PathExists(file_path));
327   base::FilePath temp_dir;
328   ASSERT_TRUE(base::GetTempDir(&temp_dir));
329   EXPECT_TRUE(base::PathExists(temp_dir));
330 }
331 
TEST_F(FileRemoverTest,RegisterPostRebootRemoval)332 TEST_F(FileRemoverTest, RegisterPostRebootRemoval) {
333   FileRemovalStatusUpdater* removal_status_updater =
334       FileRemovalStatusUpdater::GetInstance();
335 
336   // When trying to delete a whitelisted file, we should fail to register the
337   // file for removal, and no reboot should be required.
338   base::FilePath exe_path = PreFetchedPaths::GetInstance()->GetExecutablePath();
339   VerifyRegisterPostRebootRemovalFailure(exe_path, &default_file_remover_);
340   EXPECT_EQ(removal_status_updater->GetRemovalStatus(exe_path),
341             REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
342   EXPECT_FALSE(reboot_required_);
343 
344   base::ScopedTempDir temp;
345   ASSERT_TRUE(temp.CreateUniqueTempDir());
346   base::FilePath file_path = temp.GetPath().Append(kRemoveFile2);
347 
348   // When trying to delete an non-existant file, we should return success, but
349   // not require a reboot.
350   VerifyRegisterPostRebootRemovalSuccess(file_path, &default_file_remover_);
351   EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path),
352             REMOVAL_STATUS_NOT_FOUND);
353   EXPECT_FALSE(reboot_required_);
354 
355   // When trying to delete a real file, we should return success and require a
356   // reboot.
357   ASSERT_TRUE(CreateEmptyFile(file_path));
358   VerifyRegisterPostRebootRemovalSuccess(file_path, &default_file_remover_);
359   EXPECT_EQ(removal_status_updater->GetRemovalStatus(file_path),
360             REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL);
361   EXPECT_TRUE(reboot_required_);
362   EXPECT_TRUE(IsFileRegisteredForPostRebootRemoval(file_path));
363 }
364 
TEST_F(FileRemoverTest,RegisterPostRebootRemoval_Directories)365 TEST_F(FileRemoverTest, RegisterPostRebootRemoval_Directories) {
366   base::ScopedTempDir temp;
367   ASSERT_TRUE(temp.CreateUniqueTempDir());
368 
369   // Create an empty directory.
370   base::FilePath subfolder_path = temp.GetPath().Append(kRemoveFolder);
371   ASSERT_TRUE(base::CreateDirectory(subfolder_path));
372 
373   FileRemovalStatusUpdater* removal_status_updater =
374       FileRemovalStatusUpdater::GetInstance();
375 
376   // Directories shouldn't be registered for deletion.
377   VerifyRegisterPostRebootRemovalFailure(subfolder_path,
378                                          &default_file_remover_);
379   EXPECT_EQ(removal_status_updater->GetRemovalStatus(subfolder_path),
380             REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
381 
382   // Put a file into the directory and ensure the non-empty directory still
383   // isn't registered for removal.
384   removal_status_updater->Clear();
385   base::FilePath file_path1 = subfolder_path.Append(kRemoveFile1);
386   ASSERT_TRUE(CreateEmptyFile(file_path1));
387   VerifyRegisterPostRebootRemovalFailure(subfolder_path,
388                                          &default_file_remover_);
389   EXPECT_EQ(removal_status_updater->GetRemovalStatus(subfolder_path),
390             REMOVAL_STATUS_BLACKLISTED_FOR_REMOVAL);
391 }
392 
393 namespace {
394 
395 constexpr char kTestPassword[] = "1234";
396 constexpr char kTestContent[] = "Hello World";
397 constexpr wchar_t kTestFileName[] = L"temp_file.exe";
398 constexpr wchar_t kTestExpectArchiveName[] =
399     L"temp_file.exe_"
400     L"A591A6D40BF420404A011733CFB7B190D62C65BF0BCDA32B57B277D9AD9F146E.zip";
401 
402 class LoggedZipArchiverSandboxSetupHooks : public ZipArchiverSandboxSetupHooks {
403  public:
LoggedZipArchiverSandboxSetupHooks(scoped_refptr<MojoTaskRunner> mojo_task_runner,base::OnceClosure connection_error_handler,chrome_cleaner::ChildProcessLogger * child_process_logger)404   explicit LoggedZipArchiverSandboxSetupHooks(
405       scoped_refptr<MojoTaskRunner> mojo_task_runner,
406       base::OnceClosure connection_error_handler,
407       chrome_cleaner::ChildProcessLogger* child_process_logger)
408       : ZipArchiverSandboxSetupHooks(std::move(mojo_task_runner),
409                                      std::move(connection_error_handler)),
410         child_process_logger_(child_process_logger) {}
411 
UpdateSandboxPolicy(sandbox::TargetPolicy * policy,base::CommandLine * command_line)412   ResultCode UpdateSandboxPolicy(sandbox::TargetPolicy* policy,
413                                  base::CommandLine* command_line) override {
414     child_process_logger_->UpdateSandboxPolicy(policy);
415     return ZipArchiverSandboxSetupHooks::UpdateSandboxPolicy(policy,
416                                                              command_line);
417   }
418 
419  private:
420   chrome_cleaner::ChildProcessLogger* child_process_logger_;
421 };
422 
423 class FileRemoverQuarantineTest : public base::MultiProcessTest,
424                                   public ::testing::WithParamInterface<bool> {
425  public:
SetUp()426   void SetUp() override {
427     use_reboot_removal_ = GetParam();
428 
429     ASSERT_TRUE(child_process_logger_.Initialize());
430 
431     scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
432     LoggedZipArchiverSandboxSetupHooks setup_hooks(
433         mojo_task_runner.get(), base::BindOnce([] {
434           FAIL() << "ZipArchiver sandbox connection error";
435         }),
436         &child_process_logger_);
437     ResultCode result_code =
438         StartSandboxTarget(MakeCmdLine("FileRemoverQuarantineTargetMain"),
439                            &setup_hooks, SandboxType::kTest);
440     if (result_code != RESULT_CODE_SUCCESS)
441       child_process_logger_.DumpLogs();
442     ASSERT_EQ(RESULT_CODE_SUCCESS, result_code);
443 
444     ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
445 
446     auto zip_archiver = std::make_unique<SandboxedZipArchiver>(
447         mojo_task_runner, setup_hooks.TakeZipArchiverRemote(),
448         temp_dir_.GetPath(), kTestPassword);
449     file_remover_ = std::make_unique<FileRemover>(
450         /*digest_verifier=*/nullptr, std::move(zip_archiver),
451         LayeredServiceProviderWrapper(), base::DoNothing::Repeatedly());
452   }
453 
454  protected:
455   // Do removal corresponding to |use_reboot_removal_|. Also do corresponding
456   // check for file existence and removal status.
DoAndExpectCorrespondingRemoval(const base::FilePath & path)457   void DoAndExpectCorrespondingRemoval(const base::FilePath& path) {
458     if (use_reboot_removal_) {
459       VerifyRegisterPostRebootRemovalSuccess(path, file_remover_.get());
460       EXPECT_EQ(
461           REMOVAL_STATUS_SCHEDULED_FOR_REMOVAL,
462           FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
463       EXPECT_TRUE(base::PathExists(path));
464     } else {
465       VerifyRemoveNowSuccess(path, file_remover_.get());
466       EXPECT_EQ(
467           REMOVAL_STATUS_REMOVED,
468           FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
469       EXPECT_FALSE(base::PathExists(path));
470     }
471     return;
472   }
473 
474   bool use_reboot_removal_ = false;
475   base::test::SingleThreadTaskEnvironment task_environment_;
476   base::ScopedTempDir temp_dir_;
477   std::unique_ptr<FileRemover> file_remover_;
478   chrome_cleaner::ChildProcessLogger child_process_logger_;
479 };
480 
481 }  // namespace
482 
MULTIPROCESS_TEST_MAIN(FileRemoverQuarantineTargetMain)483 MULTIPROCESS_TEST_MAIN(FileRemoverQuarantineTargetMain) {
484   sandbox::TargetServices* sandbox_target_services =
485       sandbox::SandboxFactory::GetTargetServices();
486   DCHECK(sandbox_target_services);
487 
488   // |RunZipArchiverSandboxTarget| won't return. The mojo error handler will
489   // abort this process when the connection is broken.
490   RunZipArchiverSandboxTarget(*base::CommandLine::ForCurrentProcess(),
491                               sandbox_target_services);
492 
493   return 0;
494 }
495 
TEST_P(FileRemoverQuarantineTest,QuarantineFile)496 TEST_P(FileRemoverQuarantineTest, QuarantineFile) {
497   const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
498   ASSERT_NO_FATAL_FAILURE(
499       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
500 
501   DoAndExpectCorrespondingRemoval(path);
502   EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
503             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
504 
505   const base::FilePath expected_archive_path =
506       temp_dir_.GetPath().Append(kTestExpectArchiveName);
507   EXPECT_TRUE(base::PathExists(expected_archive_path));
508 }
509 
TEST_P(FileRemoverQuarantineTest,QuarantinesNotActiveFiles)510 TEST_P(FileRemoverQuarantineTest, QuarantinesNotActiveFiles) {
511   base::FilePath path = temp_dir_.GetPath().Append(L"temp_file.txt");
512   ASSERT_NO_FATAL_FAILURE(
513       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
514 
515   EXPECT_EQ(ValidationStatus::ALLOWED, file_remover_->CanRemove(path));
516 
517   DoAndExpectCorrespondingRemoval(path);
518   EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
519             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
520 }
521 
TEST_P(FileRemoverQuarantineTest,FailToQuarantine)522 TEST_P(FileRemoverQuarantineTest, FailToQuarantine) {
523   const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
524   // Acquire exclusive read access to the file, so the archiver can't open the
525   // file.
526   base::File file(path, base::File::FLAG_CREATE | base::File::FLAG_READ |
527                             base::File::FLAG_EXCLUSIVE_READ);
528   ASSERT_TRUE(file.IsValid());
529 
530   (use_reboot_removal_)
531       ? VerifyRegisterPostRebootRemovalFailure(path, file_remover_.get())
532       : VerifyRemoveNowFailure(path, file_remover_.get());
533   EXPECT_EQ(REMOVAL_STATUS_ERROR_IN_ARCHIVER,
534             FileRemovalStatusUpdater::GetInstance()->GetRemovalStatus(path));
535   EXPECT_EQ(QUARANTINE_STATUS_ERROR,
536             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
537   EXPECT_TRUE(base::PathExists(path));
538 }
539 
TEST_P(FileRemoverQuarantineTest,DuplicatedFile)540 TEST_P(FileRemoverQuarantineTest, DuplicatedFile) {
541   const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
542   const base::FilePath expected_archive_path =
543       temp_dir_.GetPath().Append(kTestExpectArchiveName);
544 
545   ASSERT_NO_FATAL_FAILURE(
546       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
547   DoAndExpectCorrespondingRemoval(path);
548   EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
549             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
550   EXPECT_TRUE(base::PathExists(expected_archive_path));
551 
552   // The file should not be archived twice if there is already one with the same
553   // name and content in the quarantine. So the modified time of the archive
554   // should not be updated.
555   base::File::Info old_info;
556   ASSERT_TRUE(base::GetFileInfo(expected_archive_path, &old_info));
557 
558   // Recreate the source file and remove it again.
559   ASSERT_NO_FATAL_FAILURE(
560       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
561   DoAndExpectCorrespondingRemoval(path);
562   // Although the file won't be archived again, it still has a backup in the
563   // quarantine. So the status should be |QUARANTINE_STATUS_QUARANTINED|.
564   EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
565             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
566   EXPECT_TRUE(base::PathExists(expected_archive_path));
567 
568   base::File::Info new_info;
569   ASSERT_TRUE(base::GetFileInfo(expected_archive_path, &new_info));
570   EXPECT_EQ(old_info.last_modified, new_info.last_modified);
571 }
572 
TEST_P(FileRemoverQuarantineTest,DoNotQuarantineSymbolicLink)573 TEST_P(FileRemoverQuarantineTest, DoNotQuarantineSymbolicLink) {
574   const base::FilePath path = temp_dir_.GetPath().Append(L"source_temp_file");
575   ASSERT_NO_FATAL_FAILURE(
576       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
577 
578   const base::FilePath sym_path = temp_dir_.GetPath().Append(kTestFileName);
579   ASSERT_NE(0, ::CreateSymbolicLink(sym_path.value().c_str(),
580                                     path.value().c_str(), 0));
581 
582   DoAndExpectCorrespondingRemoval(sym_path);
583   EXPECT_EQ(
584       QUARANTINE_STATUS_SKIPPED,
585       FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(sym_path));
586 
587   const base::FilePath expected_archive_path =
588       temp_dir_.GetPath().Append(kTestExpectArchiveName);
589   EXPECT_FALSE(base::PathExists(expected_archive_path));
590   // The original file should exist.
591   EXPECT_TRUE(base::PathExists(path));
592 }
593 
TEST_P(FileRemoverQuarantineTest,QuarantineDefaultFileStream)594 TEST_P(FileRemoverQuarantineTest, QuarantineDefaultFileStream) {
595   const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
596   ASSERT_NO_FATAL_FAILURE(
597       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
598 
599   base::FilePath stream_path(base::StrCat({path.value(), L"::$data"}));
600   ASSERT_NO_FATAL_FAILURE(
601       CreateFileWithContent(stream_path, kTestContent, strlen(kTestContent)));
602 
603   DoAndExpectCorrespondingRemoval(stream_path);
604   EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
605             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(
606                 stream_path));
607 
608   const base::FilePath expected_archive_path =
609       temp_dir_.GetPath().Append(kTestExpectArchiveName);
610   EXPECT_TRUE(base::PathExists(expected_archive_path));
611 }
612 
TEST_P(FileRemoverQuarantineTest,DoNotQuarantineNonDefaultFileStream)613 TEST_P(FileRemoverQuarantineTest, DoNotQuarantineNonDefaultFileStream) {
614   const base::FilePath path = temp_dir_.GetPath().Append(kTestFileName);
615   ASSERT_NO_FATAL_FAILURE(
616       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
617 
618   base::FilePath stream_path(base::StrCat({path.value(), L":stream:$data"}));
619   ASSERT_NO_FATAL_FAILURE(
620       CreateFileWithContent(stream_path, kTestContent, strlen(kTestContent)));
621 
622   DoAndExpectCorrespondingRemoval(stream_path);
623   EXPECT_EQ(QUARANTINE_STATUS_SKIPPED,
624             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(
625                 stream_path));
626 }
627 
TEST_P(FileRemoverQuarantineTest,LongFileName)628 TEST_P(FileRemoverQuarantineTest, LongFileName) {
629   // Craft a filename that is precisely MAX_PATH.
630   static constexpr base::FilePath::StringPieceType kExtension(
631       FILE_PATH_LITERAL(".exe"));
632   size_t long_filename_length =
633       MAX_PATH - temp_dir_.GetPath().value().size() - 1 - kExtension.size() - 1;
634   base::FilePath::StringType long_filename(long_filename_length, 'a');
635   long_filename.append(kExtension.data(), kExtension.size());
636 
637   const base::FilePath path = temp_dir_.GetPath().Append(long_filename);
638   ASSERT_NO_FATAL_FAILURE(
639       CreateFileWithContent(path, kTestContent, strlen(kTestContent)));
640 
641   DoAndExpectCorrespondingRemoval(path);
642   EXPECT_EQ(QUARANTINE_STATUS_QUARANTINED,
643             FileRemovalStatusUpdater::GetInstance()->GetQuarantineStatus(path));
644 }
645 
646 INSTANTIATE_TEST_SUITE_P(All,
647                          FileRemoverQuarantineTest,
648                          testing::Bool(),
649                          GetParamNameForTest());
650 
651 }  // namespace chrome_cleaner
652