1 // Copyright 2019 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/engines/target/cleaner_engine_requests_proxy.h"
6
7 #include <limits>
8 #include <memory>
9 #include <string>
10 #include <tuple>
11 #include <utility>
12 #include <vector>
13
14 #include "base/files/file_path.h"
15 #include "base/files/file_util.h"
16 #include "base/files/scoped_temp_dir.h"
17 #include "base/numerics/safe_conversions.h"
18 #include "base/path_service.h"
19 #include "base/process/kill.h"
20 #include "base/process/launch.h"
21 #include "base/process/process.h"
22 #include "base/process/process_handle.h"
23 #include "base/strings/string_number_conversions.h"
24 #include "base/test/task_environment.h"
25 #include "base/test/test_timeouts.h"
26 #include "chrome/chrome_cleaner/engines/common/registry_util.h"
27 #include "chrome/chrome_cleaner/engines/target/sandboxed_test_helpers.h"
28 #include "chrome/chrome_cleaner/ipc/ipc_test_util.h"
29 #include "chrome/chrome_cleaner/os/system_util_cleaner.h"
30 #include "chrome/chrome_cleaner/test/scoped_process_protector.h"
31 #include "chrome/chrome_cleaner/test/test_executables.h"
32 #include "chrome/chrome_cleaner/test/test_file_util.h"
33 #include "chrome/chrome_cleaner/test/test_native_reg_util.h"
34 #include "chrome/chrome_cleaner/test/test_scoped_service_handle.h"
35 #include "chrome/chrome_cleaner/test/test_strings.h"
36 #include "chrome/chrome_cleaner/test/test_task_scheduler.h"
37 #include "chrome/chrome_cleaner/test/test_util.h"
38 #include "components/chrome_cleaner/test/test_name_helper.h"
39 #include "testing/gtest/include/gtest/gtest.h"
40 #include "testing/multiprocess_func_list.h"
41
42 namespace chrome_cleaner {
43
44 namespace {
45
46 // Switches with information about resources to pass to the subprocess.
47 constexpr char kLongRunningProcessIdSwitch[] = "test-process-id";
48 constexpr char kServiceNameSwitch[] = "test-service-name";
49 constexpr char kTempDirectoryPathSwitch[] = "test-temp-dir";
50 constexpr char kTempRegistryKeyPath[] = "test-temp-key-path";
51
52 // Constants shared with the subprocess.
53 constexpr wchar_t kTempFileName[] = L"temp_file.exe";
54 constexpr wchar_t kMissingFileName[] = L"missing_file.exe";
55 constexpr wchar_t kRegistryKeyWithNulls[] = L"ab\0c";
56 constexpr size_t kRegistryKeyWithNullsLength = 5; // Including trailing null.
57 constexpr wchar_t kRegistryValueNameWithNulls[] = L"fo\0o";
58 constexpr size_t kRegistryValueNameWithNullsLength =
59 5; // Including trailing null.
60 constexpr wchar_t kRegistryValueWithNulls[] = L"b\0ar";
61 constexpr size_t kRegistryValueWithNullsLength = 5; // Including trailing null.
62
SetupChildProcess()63 scoped_refptr<SandboxChildProcess> SetupChildProcess() {
64 scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
65 auto child_process =
66 base::MakeRefCounted<SandboxChildProcess>(mojo_task_runner);
67 child_process->LowerToken();
68 return child_process;
69 }
70
GetTestProcessId(const base::CommandLine & command_line)71 base::ProcessId GetTestProcessId(const base::CommandLine& command_line) {
72 std::wstring pid_string =
73 command_line.GetSwitchValueNative(kLongRunningProcessIdSwitch);
74 uint64_t pid;
75 if (!base::StringToUint64(pid_string, &pid)) {
76 LOG(ERROR) << "Invalid process id switch: " << pid_string;
77 return 0;
78 }
79 return base::checked_cast<base::ProcessId>(pid);
80 }
81
GetTestFilePath(const base::CommandLine & command_line,const std::wstring & file_name)82 base::FilePath GetTestFilePath(const base::CommandLine& command_line,
83 const std::wstring& file_name) {
84 base::FilePath path =
85 command_line.GetSwitchValuePath(kTempDirectoryPathSwitch);
86 if (path.empty()) {
87 LOG(ERROR) << "Missing temp directory path switch";
88 return path;
89 }
90 return path.Append(file_name);
91 }
92
GetTestRegistryKeyPath(const base::CommandLine & command_line)93 WStringEmbeddedNulls GetTestRegistryKeyPath(
94 const base::CommandLine& command_line) {
95 std::wstring path = command_line.GetSwitchValueNative(kTempRegistryKeyPath);
96 if (path.empty()) {
97 LOG(ERROR) << "Missing temp registry key path switch";
98 return WStringEmbeddedNulls();
99 }
100 path += L"\\";
101 path.append(kRegistryKeyWithNulls, kRegistryKeyWithNullsLength);
102 return WStringEmbeddedNulls(path);
103 }
104
GetTestRegistryValueName()105 WStringEmbeddedNulls GetTestRegistryValueName() {
106 return WStringEmbeddedNulls(kRegistryValueNameWithNulls,
107 kRegistryValueNameWithNullsLength);
108 }
109
GetTestRegistryValue()110 WStringEmbeddedNulls GetTestRegistryValue() {
111 return WStringEmbeddedNulls(kRegistryValueWithNulls,
112 kRegistryValueWithNullsLength);
113 }
114
115 class CleanerEngineRequestsProxyTestBase : public ::testing::Test {
116 public:
117 using TestParentProcess = MaybeSandboxedParentProcess<SandboxedParentProcess>;
118
SetUp()119 void SetUp() override {
120 mojo_task_runner_ = MojoTaskRunner::Create();
121
122 parent_process_ = base::MakeRefCounted<TestParentProcess>(
123 mojo_task_runner_,
124 TestParentProcess::CallbacksToSetup::kCleanupRequests);
125 }
126
127 protected:
LaunchConnectedChildProcess(const std::string & child_main_function,int32_t expected_exit_code=0)128 ::testing::AssertionResult LaunchConnectedChildProcess(
129 const std::string& child_main_function,
130 int32_t expected_exit_code = 0) {
131 int32_t exit_code = -1;
132 if (!parent_process_->LaunchConnectedChildProcess(child_main_function,
133 &exit_code)) {
134 return ::testing::AssertionFailure()
135 << "Failed to launch child process for " << child_main_function;
136 }
137 if (exit_code != expected_exit_code) {
138 return ::testing::AssertionFailure()
139 << "Got exit code " << exit_code << ", expected "
140 << expected_exit_code;
141 }
142 return ::testing::AssertionSuccess();
143 }
144
145 scoped_refptr<MojoTaskRunner> mojo_task_runner_;
146 scoped_refptr<TestParentProcess> parent_process_;
147
148 private:
149 base::test::TaskEnvironment task_environment_;
150 };
151
152 // CleanerEngineRequestsProxyTest is parameterized with:
153 // - resource_status_: how the test is expected to affect its resources;
154 // - child_main_function_: the name of the MULTIPROCESS_TEST_MAIN function for
155 // the child process.
156 enum class ResourceStatus {
157 kUnspecified, // For tests that don't check the resource status.
158 kNoChange,
159 kDeleted,
160 };
161
operator <<(std::ostream & stream,ResourceStatus status)162 std::ostream& operator<<(std::ostream& stream, ResourceStatus status) {
163 return stream << static_cast<int>(status);
164 }
165
166 typedef std::tuple<ResourceStatus, std::string>
167 CleanerEngineRequestsProxyTestParams;
168
169 /*
170 * Tests Without Extra Setup
171 */
172
173 class CleanerEngineRequestsProxyTest
174 : public CleanerEngineRequestsProxyTestBase,
175 public ::testing::WithParamInterface<
176 CleanerEngineRequestsProxyTestParams> {
177 public:
SetUp()178 void SetUp() override {
179 std::tie(resource_status_, child_main_function_) = GetParam();
180 CleanerEngineRequestsProxyTestBase::SetUp();
181 }
182
183 protected:
184 ResourceStatus resource_status_ = ResourceStatus::kUnspecified;
185 std::string child_main_function_;
186 };
187
MULTIPROCESS_TEST_MAIN(DeleteTask)188 MULTIPROCESS_TEST_MAIN(DeleteTask) {
189 auto child_process = SetupChildProcess();
190 if (!child_process)
191 return 1;
192
193 scoped_refptr<CleanerEngineRequestsProxy> proxy(
194 child_process->GetCleanerEngineRequestsProxy());
195
196 TestTaskScheduler test_task_scheduler;
197
198 TaskScheduler::TaskInfo task_info;
199 if (!RegisterTestTask(&test_task_scheduler, &task_info)) {
200 LOG(ERROR) << "Failed to register a test task";
201 return 1;
202 }
203
204 EXPECT_TRUE(proxy->DeleteTask(task_info.name));
205
206 return ::testing::Test::HasNonfatalFailure();
207 }
208
MULTIPROCESS_TEST_MAIN(DeleteTaskNoHang)209 MULTIPROCESS_TEST_MAIN(DeleteTaskNoHang) {
210 auto child_process = SetupChildProcess();
211 if (!child_process)
212 return 1;
213
214 scoped_refptr<CleanerEngineRequestsProxy> proxy(
215 child_process->GetCleanerEngineRequestsProxy());
216 child_process->UnbindRequestsRemotes();
217
218 TestTaskScheduler test_task_scheduler;
219
220 TaskScheduler::TaskInfo task_info;
221 if (!RegisterTestTask(&test_task_scheduler, &task_info)) {
222 LOG(ERROR) << "Failed to register a test task";
223 return 1;
224 }
225
226 EXPECT_FALSE(proxy->DeleteTask(task_info.name));
227
228 return ::testing::Test::HasNonfatalFailure();
229 }
230
TEST_P(CleanerEngineRequestsProxyTest,TestRequest)231 TEST_P(CleanerEngineRequestsProxyTest, TestRequest) {
232 EXPECT_TRUE(LaunchConnectedChildProcess(child_main_function_));
233 }
234
235 INSTANTIATE_TEST_SUITE_P(
236 ProxyTests,
237 CleanerEngineRequestsProxyTest,
238 testing::Combine(testing::Values(ResourceStatus::kUnspecified),
239 testing::Values("DeleteTask", "DeleteTaskNoHang")),
240 GetParamNameForTest());
241
242 /*
243 * File Tests
244 */
245
246 class CleanerEngineRequestsProxyFileTest
247 : public CleanerEngineRequestsProxyTest {
248 public:
SetUp()249 void SetUp() override {
250 CleanerEngineRequestsProxyTest::SetUp();
251 ASSERT_TRUE(parent_process_);
252
253 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
254 parent_process_->AppendSwitchPath(kTempDirectoryPathSwitch,
255 temp_dir_.GetPath());
256
257 ASSERT_TRUE(CreateEmptyFile(temp_dir_.GetPath().Append(kTempFileName)));
258 ASSERT_TRUE(CreateEmptyFile(temp_dir_.GetPath().Append(kValidUtf8Name)));
259 ASSERT_TRUE(CreateEmptyFile(temp_dir_.GetPath().Append(kInvalidUtf8Name)));
260 }
261
262 protected:
263 base::ScopedTempDir temp_dir_;
264 };
265
MULTIPROCESS_TEST_MAIN(DeleteFileBasic)266 MULTIPROCESS_TEST_MAIN(DeleteFileBasic) {
267 auto child_process = SetupChildProcess();
268 if (!child_process)
269 return 1;
270
271 scoped_refptr<CleanerEngineRequestsProxy> proxy(
272 child_process->GetCleanerEngineRequestsProxy());
273
274 EXPECT_TRUE(proxy->DeleteFile(
275 GetTestFilePath(child_process->command_line(), kTempFileName)));
276
277 // Succeeds on absent files.
278 EXPECT_TRUE(proxy->DeleteFile(
279 GetTestFilePath(child_process->command_line(), kMissingFileName)));
280
281 EXPECT_TRUE(proxy->DeleteFile(
282 GetTestFilePath(child_process->command_line(), kValidUtf8Name)));
283
284 EXPECT_TRUE(proxy->DeleteFile(
285 GetTestFilePath(child_process->command_line(), kInvalidUtf8Name)));
286
287 return ::testing::Test::HasNonfatalFailure();
288 }
289
MULTIPROCESS_TEST_MAIN(DeleteFileNoHang)290 MULTIPROCESS_TEST_MAIN(DeleteFileNoHang) {
291 auto child_process = SetupChildProcess();
292 if (!child_process)
293 return 1;
294
295 scoped_refptr<CleanerEngineRequestsProxy> proxy(
296 child_process->GetCleanerEngineRequestsProxy());
297 child_process->UnbindRequestsRemotes();
298
299 EXPECT_FALSE(proxy->DeleteFile(
300 GetTestFilePath(child_process->command_line(), kTempFileName)));
301
302 return ::testing::Test::HasNonfatalFailure();
303 }
304
MULTIPROCESS_TEST_MAIN(DeleteFilePostReboot)305 MULTIPROCESS_TEST_MAIN(DeleteFilePostReboot) {
306 auto child_process = SetupChildProcess();
307 if (!child_process)
308 return 1;
309
310 scoped_refptr<CleanerEngineRequestsProxy> proxy(
311 child_process->GetCleanerEngineRequestsProxy());
312 EXPECT_TRUE(proxy->DeleteFilePostReboot(
313 GetTestFilePath(child_process->command_line(), kTempFileName)));
314
315 // Succeeds on absent files.
316 EXPECT_TRUE(proxy->DeleteFilePostReboot(
317 GetTestFilePath(child_process->command_line(), kMissingFileName)));
318
319 EXPECT_TRUE(proxy->DeleteFilePostReboot(
320 GetTestFilePath(child_process->command_line(), kValidUtf8Name)));
321
322 EXPECT_TRUE(proxy->DeleteFilePostReboot(
323 GetTestFilePath(child_process->command_line(), kInvalidUtf8Name)));
324
325 return ::testing::Test::HasNonfatalFailure();
326 }
327
MULTIPROCESS_TEST_MAIN(DeleteFilePostRebootNoHang)328 MULTIPROCESS_TEST_MAIN(DeleteFilePostRebootNoHang) {
329 auto child_process = SetupChildProcess();
330 if (!child_process)
331 return 1;
332
333 scoped_refptr<CleanerEngineRequestsProxy> proxy(
334 child_process->GetCleanerEngineRequestsProxy());
335 child_process->UnbindRequestsRemotes();
336
337 EXPECT_FALSE(proxy->DeleteFilePostReboot(
338 GetTestFilePath(child_process->command_line(), kTempFileName)));
339
340 return ::testing::Test::HasNonfatalFailure();
341 }
342
TEST_P(CleanerEngineRequestsProxyFileTest,TestRequest)343 TEST_P(CleanerEngineRequestsProxyFileTest, TestRequest) {
344 EXPECT_TRUE(LaunchConnectedChildProcess(child_main_function_));
345
346 bool files_exist = resource_status_ != ResourceStatus::kDeleted;
347 EXPECT_EQ(base::PathExists(temp_dir_.GetPath().Append(kTempFileName)),
348 files_exist);
349 EXPECT_EQ(base::PathExists(temp_dir_.GetPath().Append(kValidUtf8Name)),
350 files_exist);
351 EXPECT_EQ(base::PathExists(temp_dir_.GetPath().Append(kInvalidUtf8Name)),
352 files_exist);
353 EXPECT_FALSE(base::PathExists(temp_dir_.GetPath().Append(kMissingFileName)));
354 }
355
356 INSTANTIATE_TEST_SUITE_P(
357 FilesDeleted,
358 CleanerEngineRequestsProxyFileTest,
359 testing::Combine(testing::Values(ResourceStatus::kDeleted),
360 testing::Values("DeleteFileBasic")),
361 GetParamNameForTest());
362
363 // For post-reboot tests, files aren't actually deleted until after reboot, so
364 // they will still exist even if the test passes.
365 INSTANTIATE_TEST_SUITE_P(
366 FilesRemain,
367 CleanerEngineRequestsProxyFileTest,
368 testing::Combine(testing::Values(ResourceStatus::kNoChange),
369 testing::Values("DeleteFileNoHang",
370 "DeleteFilePostReboot",
371 "DeleteFilePostRebootNoHang")),
372 GetParamNameForTest());
373
374 /*
375 * Registry Tests
376 */
377
378 class CleanerEngineRequestsProxyRegistryTest
379 : public CleanerEngineRequestsProxyTest {
380 public:
SetUp()381 void SetUp() override {
382 CleanerEngineRequestsProxyTest::SetUp();
383 ASSERT_TRUE(parent_process_);
384
385 std::vector<wchar_t> key_name(
386 kRegistryKeyWithNulls,
387 kRegistryKeyWithNulls + kRegistryKeyWithNullsLength);
388
389 ULONG disposition = 0;
390 ASSERT_EQ(chrome_cleaner_sandbox::NativeCreateKey(
391 temp_key_.Get(), &key_name, &temp_key_handle_, &disposition),
392 STATUS_SUCCESS);
393 ASSERT_EQ(disposition, static_cast<ULONG>(REG_CREATED_NEW_KEY));
394 ASSERT_NE(temp_key_handle_, INVALID_HANDLE_VALUE);
395
396 parent_process_->AppendSwitchNative(kTempRegistryKeyPath,
397 temp_key_.FullyQualifiedPath());
398
399 WStringEmbeddedNulls value_name_buffer{L'f', L'o', L'o', L'\0'};
400 WStringEmbeddedNulls value_buffer{L'b', L'a', L'r', L'\0'};
401 ASSERT_EQ(chrome_cleaner_sandbox::NativeSetValueKey(
402 temp_key_handle_, GetTestRegistryValueName(), REG_SZ,
403 GetTestRegistryValue()),
404 STATUS_SUCCESS);
405 }
406
TearDown()407 void TearDown() override {
408 // Clean up the test key if it hasn't already been deleted.
409 ASSERT_NE(temp_key_handle_, INVALID_HANDLE_VALUE);
410 chrome_cleaner_sandbox::NativeDeleteKey(temp_key_handle_);
411 ::CloseHandle(temp_key_handle_);
412 }
413
414 protected:
415 chrome_cleaner_sandbox::ScopedTempRegistryKey temp_key_;
416 HANDLE temp_key_handle_ = INVALID_HANDLE_VALUE;
417 };
418
MULTIPROCESS_TEST_MAIN(NtDeleteRegistryKey)419 MULTIPROCESS_TEST_MAIN(NtDeleteRegistryKey) {
420 auto child_process = SetupChildProcess();
421 if (!child_process)
422 return 1;
423
424 scoped_refptr<CleanerEngineRequestsProxy> proxy(
425 child_process->GetCleanerEngineRequestsProxy());
426
427 EXPECT_TRUE(proxy->NtDeleteRegistryKey(
428 GetTestRegistryKeyPath(child_process->command_line())));
429
430 return ::testing::Test::HasNonfatalFailure();
431 }
432
MULTIPROCESS_TEST_MAIN(NtDeleteRegistryKeyNoHang)433 MULTIPROCESS_TEST_MAIN(NtDeleteRegistryKeyNoHang) {
434 auto child_process = SetupChildProcess();
435 if (!child_process)
436 return 1;
437
438 scoped_refptr<CleanerEngineRequestsProxy> proxy(
439 child_process->GetCleanerEngineRequestsProxy());
440
441 EXPECT_FALSE(proxy->NtDeleteRegistryKey(WStringEmbeddedNulls(nullptr)));
442
443 child_process->UnbindRequestsRemotes();
444
445 EXPECT_FALSE(proxy->NtDeleteRegistryKey(
446 GetTestRegistryKeyPath(child_process->command_line())));
447
448 return ::testing::Test::HasNonfatalFailure();
449 }
450
MULTIPROCESS_TEST_MAIN(NtDeleteRegistryValue)451 MULTIPROCESS_TEST_MAIN(NtDeleteRegistryValue) {
452 auto child_process = SetupChildProcess();
453 if (!child_process)
454 return 1;
455
456 scoped_refptr<CleanerEngineRequestsProxy> proxy(
457 child_process->GetCleanerEngineRequestsProxy());
458
459 EXPECT_TRUE(proxy->NtDeleteRegistryValue(
460 GetTestRegistryKeyPath(child_process->command_line()),
461 GetTestRegistryValueName()));
462
463 return ::testing::Test::HasNonfatalFailure();
464 }
465
MULTIPROCESS_TEST_MAIN(NtDeleteRegistryValueNoHang)466 MULTIPROCESS_TEST_MAIN(NtDeleteRegistryValueNoHang) {
467 auto child_process = SetupChildProcess();
468 if (!child_process)
469 return 1;
470
471 scoped_refptr<CleanerEngineRequestsProxy> proxy(
472 child_process->GetCleanerEngineRequestsProxy());
473
474 EXPECT_FALSE(proxy->NtDeleteRegistryValue(
475 GetTestRegistryKeyPath(child_process->command_line()),
476 WStringEmbeddedNulls(nullptr)));
477 EXPECT_FALSE(proxy->NtDeleteRegistryValue(WStringEmbeddedNulls(nullptr),
478 GetTestRegistryValueName()));
479
480 child_process->UnbindRequestsRemotes();
481
482 EXPECT_FALSE(proxy->NtDeleteRegistryValue(
483 GetTestRegistryKeyPath(child_process->command_line()),
484 GetTestRegistryValueName()));
485
486 return ::testing::Test::HasNonfatalFailure();
487 }
488
MULTIPROCESS_TEST_MAIN(NtChangeRegistryValue)489 MULTIPROCESS_TEST_MAIN(NtChangeRegistryValue) {
490 auto child_process = SetupChildProcess();
491 if (!child_process)
492 return 1;
493
494 scoped_refptr<CleanerEngineRequestsProxy> proxy(
495 child_process->GetCleanerEngineRequestsProxy());
496
497 const WStringEmbeddedNulls new_value(
498 GetTestRegistryValue().CastAsWStringPiece().substr(1));
499
500 EXPECT_TRUE(proxy->NtChangeRegistryValue(
501 GetTestRegistryKeyPath(child_process->command_line()),
502 GetTestRegistryValueName(), new_value));
503
504 // Remove the terminating null char from the new value and ensure it still
505 // works.
506 EXPECT_TRUE(proxy->NtChangeRegistryValue(
507 GetTestRegistryKeyPath(child_process->command_line()),
508 GetTestRegistryValueName(),
509 WStringEmbeddedNulls(new_value.CastAsWCharArray(), new_value.size())));
510
511 // Set the new value to an empty string.
512 EXPECT_TRUE(proxy->NtChangeRegistryValue(
513 GetTestRegistryKeyPath(child_process->command_line()),
514 GetTestRegistryValueName(), WStringEmbeddedNulls()));
515
516 return ::testing::Test::HasNonfatalFailure();
517 }
518
MULTIPROCESS_TEST_MAIN(NtChangeRegistryValueNoHang)519 MULTIPROCESS_TEST_MAIN(NtChangeRegistryValueNoHang) {
520 auto child_process = SetupChildProcess();
521 if (!child_process)
522 return 1;
523
524 scoped_refptr<CleanerEngineRequestsProxy> proxy(
525 child_process->GetCleanerEngineRequestsProxy());
526
527 const WStringEmbeddedNulls new_value(
528 GetTestRegistryValue().CastAsWStringPiece().substr(1));
529
530 EXPECT_FALSE(proxy->NtChangeRegistryValue(
531 WStringEmbeddedNulls(nullptr), GetTestRegistryValueName(), new_value));
532 EXPECT_FALSE(proxy->NtChangeRegistryValue(
533 GetTestRegistryKeyPath(child_process->command_line()),
534 WStringEmbeddedNulls(nullptr), new_value));
535
536 child_process->UnbindRequestsRemotes();
537
538 EXPECT_FALSE(proxy->NtChangeRegistryValue(
539 GetTestRegistryKeyPath(child_process->command_line()),
540 GetTestRegistryValueName(), new_value));
541
542 return ::testing::Test::HasNonfatalFailure();
543 }
544
TEST_P(CleanerEngineRequestsProxyRegistryTest,TestRequest)545 TEST_P(CleanerEngineRequestsProxyRegistryTest, TestRequest) {
546 EXPECT_TRUE(LaunchConnectedChildProcess(child_main_function_));
547 }
548
549 INSTANTIATE_TEST_SUITE_P(
550 ProxyTests,
551 CleanerEngineRequestsProxyRegistryTest,
552 testing::Combine(testing::Values(ResourceStatus::kUnspecified),
553 testing::Values("NtDeleteRegistryKey",
554 "NtDeleteRegistryKeyNoHang",
555 "NtDeleteRegistryValue",
556 "NtDeleteRegistryValueNoHang",
557 "NtChangeRegistryValue",
558 "NtChangeRegistryValueNoHang")),
559 GetParamNameForTest());
560
561 /*
562 * Service Tests
563 */
564
565 class CleanerEngineRequestsProxyServiceTest
566 : public CleanerEngineRequestsProxyTest {
567 public:
SetUp()568 void SetUp() override {
569 CleanerEngineRequestsProxyTest::SetUp();
570 ASSERT_TRUE(parent_process_);
571
572 ASSERT_TRUE(EnsureNoTestServicesRunning());
573
574 ASSERT_TRUE(service_handle_.InstallService());
575 service_handle_.Close();
576
577 parent_process_->AppendSwitchNative(kServiceNameSwitch,
578 service_handle_.service_name());
579 }
580
581 protected:
582 TestScopedServiceHandle service_handle_;
583 };
584
MULTIPROCESS_TEST_MAIN(DeleteService)585 MULTIPROCESS_TEST_MAIN(DeleteService) {
586 auto child_process = SetupChildProcess();
587 if (!child_process)
588 return 1;
589
590 scoped_refptr<CleanerEngineRequestsProxy> proxy(
591 child_process->GetCleanerEngineRequestsProxy());
592
593 std::wstring service_name =
594 base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
595 kServiceNameSwitch);
596 CHECK(!service_name.empty());
597
598 EXPECT_TRUE(proxy->DeleteService(service_name));
599
600 return ::testing::Test::HasNonfatalFailure();
601 }
602
MULTIPROCESS_TEST_MAIN(DeleteServiceNoHang)603 MULTIPROCESS_TEST_MAIN(DeleteServiceNoHang) {
604 auto child_process = SetupChildProcess();
605 if (!child_process)
606 return 1;
607
608 scoped_refptr<CleanerEngineRequestsProxy> proxy(
609 child_process->GetCleanerEngineRequestsProxy());
610 child_process->UnbindRequestsRemotes();
611
612 std::wstring service_name =
613 base::CommandLine::ForCurrentProcess()->GetSwitchValueNative(
614 kServiceNameSwitch);
615 CHECK(!service_name.empty());
616
617 EXPECT_FALSE(proxy->DeleteService(service_name));
618
619 return ::testing::Test::HasNonfatalFailure();
620 }
621
TEST_P(CleanerEngineRequestsProxyServiceTest,TestRequest)622 TEST_P(CleanerEngineRequestsProxyServiceTest, TestRequest) {
623 EXPECT_TRUE(LaunchConnectedChildProcess(child_main_function_));
624
625 bool service_exists = resource_status_ != ResourceStatus::kDeleted;
626 EXPECT_EQ(DoesServiceExist(service_handle_.service_name()), service_exists);
627 }
628
629 INSTANTIATE_TEST_SUITE_P(
630 ServiceDeleted,
631 CleanerEngineRequestsProxyServiceTest,
632 testing::Combine(testing::Values(ResourceStatus::kDeleted),
633 testing::Values("DeleteService")),
634 GetParamNameForTest());
635
636 INSTANTIATE_TEST_SUITE_P(
637 ServiceRemains,
638 CleanerEngineRequestsProxyServiceTest,
639 testing::Combine(testing::Values(ResourceStatus::kNoChange),
640 testing::Values("DeleteServiceNoHang")),
641 GetParamNameForTest());
642
643 /*
644 * Terminate Process Tests
645 */
646
647 class CleanerEngineRequestsProxyTerminateTest
648 : public CleanerEngineRequestsProxyTestBase {
649 public:
SetUp()650 void SetUp() override {
651 // Note that this test will fail under the debugger since the debugged test
652 // process will inherit the SeDebugPrivilege which allows the test to get
653 // an ALL_ACCESS handle. So if under the debugger, skip the test without
654 // failing.
655 if (::IsDebuggerPresent()) {
656 LOG(ERROR) << "TerminateProcessTest skipped when running in debugger.";
657 silently_skip_ = true;
658 return;
659 }
660
661 CleanerEngineRequestsProxyTestBase::SetUp();
662 ASSERT_TRUE(parent_process_);
663
664 test_process_ = LongRunningProcess(/*command_line=*/nullptr);
665 ASSERT_TRUE(test_process_.IsValid());
666
667 std::wstring switch_str = base::NumberToWString(test_process_.Pid());
668 parent_process_->AppendSwitchNative(kLongRunningProcessIdSwitch,
669 switch_str);
670 }
671
TearDown()672 void TearDown() override {
673 // Clean up if the test didn't kill the process.
674 if (test_process_.IsValid())
675 test_process_.Terminate(2, false);
676 }
677
678 protected:
WaitForChildProcessToDie()679 ::testing::AssertionResult WaitForChildProcessToDie() {
680 int test_process_exit_code = 0;
681 if (!test_process_.WaitForExitWithTimeout(TestTimeouts::action_timeout(),
682 &test_process_exit_code)) {
683 return ::testing::AssertionFailure() << "Child process did not exit";
684 }
685 if (test_process_exit_code != 1) {
686 return ::testing::AssertionFailure()
687 << "Child process exited with unexpected exit code "
688 << test_process_exit_code;
689 }
690 return ::testing::AssertionSuccess();
691 }
692
ChildProcessIsRunning()693 ::testing::AssertionResult ChildProcessIsRunning() {
694 DWORD test_process_exit_code = 420042;
695 if (!::GetExitCodeProcess(test_process_.Handle(),
696 &test_process_exit_code)) {
697 return ::testing::AssertionFailure()
698 << "Error polling for exit for of child process";
699 }
700 if (test_process_exit_code != STILL_ACTIVE) {
701 return ::testing::AssertionFailure()
702 << "Child process exited with unexpected exit code "
703 << test_process_exit_code << " (expected STILL_ACTIVE)";
704 }
705 return ::testing::AssertionSuccess();
706 }
707
708 base::Process test_process_;
709
710 bool silently_skip_ = false;
711 };
712
MULTIPROCESS_TEST_MAIN(TerminateProcess)713 MULTIPROCESS_TEST_MAIN(TerminateProcess) {
714 auto child_process = SetupChildProcess();
715 if (!child_process)
716 return 1;
717
718 scoped_refptr<CleanerEngineRequestsProxy> proxy(
719 child_process->GetCleanerEngineRequestsProxy());
720
721 base::ProcessId pid = GetTestProcessId(child_process->command_line());
722 if (!pid) {
723 LOG(ERROR) << "Couldn't find test process";
724 return 1;
725 }
726
727 EXPECT_TRUE(proxy->TerminateProcess(pid));
728
729 return ::testing::Test::HasNonfatalFailure();
730 }
731
MULTIPROCESS_TEST_MAIN(TerminateProcessProtected)732 MULTIPROCESS_TEST_MAIN(TerminateProcessProtected) {
733 auto child_process = SetupChildProcess();
734 if (!child_process)
735 return 1;
736
737 scoped_refptr<CleanerEngineRequestsProxy> proxy(
738 child_process->GetCleanerEngineRequestsProxy());
739
740 base::ProcessId pid = GetTestProcessId(child_process->command_line());
741 if (!pid) {
742 LOG(ERROR) << "Couldn't find test process";
743 return 1;
744 }
745
746 // We should not be able to kill the protected process.
747 EXPECT_FALSE(proxy->TerminateProcess(pid));
748
749 return ::testing::Test::HasNonfatalFailure();
750 }
751
MULTIPROCESS_TEST_MAIN(TerminateProcessNoHang)752 MULTIPROCESS_TEST_MAIN(TerminateProcessNoHang) {
753 auto child_process = SetupChildProcess();
754 if (!child_process)
755 return 1;
756
757 scoped_refptr<CleanerEngineRequestsProxy> proxy(
758 child_process->GetCleanerEngineRequestsProxy());
759 child_process->UnbindRequestsRemotes();
760
761 base::ProcessId pid = GetTestProcessId(child_process->command_line());
762 if (!pid) {
763 LOG(ERROR) << "Couldn't find test process";
764 return 1;
765 }
766
767 EXPECT_FALSE(proxy->TerminateProcess(pid));
768
769 return ::testing::Test::HasNonfatalFailure();
770 }
771
TEST_F(CleanerEngineRequestsProxyTerminateTest,TerminateProcess)772 TEST_F(CleanerEngineRequestsProxyTerminateTest, TerminateProcess) {
773 if (silently_skip_)
774 return;
775
776 EXPECT_TRUE(LaunchConnectedChildProcess("TerminateProcess"));
777 EXPECT_TRUE(WaitForChildProcessToDie());
778 }
779
TEST_F(CleanerEngineRequestsProxyTerminateTest,TerminateProcessProtected)780 TEST_F(CleanerEngineRequestsProxyTerminateTest, TerminateProcessProtected) {
781 if (silently_skip_)
782 return;
783
784 ScopedProcessProtector process_protector(test_process_.Pid());
785
786 EXPECT_TRUE(LaunchConnectedChildProcess("TerminateProcessProtected"));
787 EXPECT_TRUE(ChildProcessIsRunning());
788 }
789
TEST_F(CleanerEngineRequestsProxyTerminateTest,TerminateProcessNoHang)790 TEST_F(CleanerEngineRequestsProxyTerminateTest, TerminateProcessNoHang) {
791 if (silently_skip_)
792 return;
793
794 EXPECT_TRUE(LaunchConnectedChildProcess("TerminateProcessNoHang"));
795 EXPECT_TRUE(ChildProcessIsRunning());
796 }
797
798 } // namespace
799
800 } // namespace chrome_cleaner
801