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