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