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/engine_requests_proxy.h"
6 
7 #include <limits>
8 #include <memory>
9 #include <sstream>
10 #include <string>
11 #include <tuple>
12 #include <utility>
13 #include <vector>
14 
15 #include "base/callback_helpers.h"
16 #include "base/command_line.h"
17 #include "base/strings/string_util.h"
18 #include "base/test/task_environment.h"
19 #include "base/win/registry.h"
20 #include "base/win/scoped_com_initializer.h"
21 #include "chrome/chrome_cleaner/engines/common/registry_util.h"
22 #include "chrome/chrome_cleaner/engines/target/sandboxed_test_helpers.h"
23 #include "chrome/chrome_cleaner/os/pre_fetched_paths.h"
24 #include "chrome/chrome_cleaner/os/task_scheduler.h"
25 #include "chrome/chrome_cleaner/strings/string_test_helpers.h"
26 #include "chrome/chrome_cleaner/strings/wstring_embedded_nulls.h"
27 #include "chrome/chrome_cleaner/test/test_native_reg_util.h"
28 #include "chrome/chrome_cleaner/test/test_util.h"
29 #include "components/chrome_cleaner/test/test_name_helper.h"
30 #include "sandbox/win/src/sid.h"
31 #include "testing/gtest/include/gtest/gtest.h"
32 #include "testing/multiprocess_func_list.h"
33 
34 namespace chrome_cleaner {
35 
36 namespace {
37 
38 // Temp keys to create under HKLM.
39 constexpr char kTempKeyPathSwitch[] = "temp-key-path";
40 constexpr char kTempKeyFullPathSwitch[] = "temp-key-full-path";
41 constexpr wchar_t kKeyWithNulls[] = L"fake0key0with0nulls";
42 
43 // Switch with a path to the windows directory.
44 constexpr char kWindowsDirectorySwitch[] = "windows-directory";
45 
46 class TestChildProcess : public SandboxChildProcess {
47  public:
TestChildProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner)48   explicit TestChildProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner)
49       : SandboxChildProcess(std::move(mojo_task_runner)) {}
50 
Initialize()51   bool Initialize() {
52     LowerToken();
53 
54     windows_directory_ =
55         command_line().GetSwitchValuePath(kWindowsDirectorySwitch);
56     if (windows_directory_.empty()) {
57       LOG(ERROR) << "Initialize failed: Missing " << kWindowsDirectorySwitch
58                  << " switch";
59       return false;
60     }
61 
62     temp_key_path_ = command_line().GetSwitchValueNative(kTempKeyPathSwitch);
63     if (temp_key_path_.empty()) {
64       LOG(ERROR) << "Initialize failed: Missing " << kTempKeyPathSwitch
65                  << " switch";
66       return false;
67     }
68 
69     temp_key_full_path_ =
70         command_line().GetSwitchValueNative(kTempKeyFullPathSwitch);
71     if (temp_key_full_path_.empty()) {
72       LOG(ERROR) << "Initialize failed: Missing " << kTempKeyFullPathSwitch
73                  << " switch";
74       return false;
75     }
76 
77     return true;
78   }
79 
windows_directory() const80   base::FilePath windows_directory() const { return windows_directory_; }
81 
temp_key_path() const82   std::wstring temp_key_path() const { return temp_key_path_; }
83 
temp_key_full_path() const84   std::wstring temp_key_full_path() const { return temp_key_full_path_; }
85 
86  private:
87   ~TestChildProcess() override = default;
88 
89   base::FilePath windows_directory_;
90   std::wstring temp_key_path_;
91   std::wstring temp_key_full_path_;
92 };
93 
SetupSandboxedChildProcess()94 scoped_refptr<TestChildProcess> SetupSandboxedChildProcess() {
95   scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create();
96   auto child_process = base::MakeRefCounted<TestChildProcess>(mojo_task_runner);
97   if (!child_process->Initialize())
98     return base::MakeRefCounted<TestChildProcess>(nullptr);
99   return child_process;
100 }
101 
MULTIPROCESS_TEST_MAIN(GetFileAttributesTest)102 MULTIPROCESS_TEST_MAIN(GetFileAttributesTest) {
103   auto child_process = SetupSandboxedChildProcess();
104   if (!child_process)
105     return 1;
106 
107   scoped_refptr<EngineRequestsProxy> proxy(
108       child_process->GetEngineRequestsProxy());
109 
110   uint32_t attributes;
111   EXPECT_EQ(INVALID_FILE_PATH,
112             proxy->GetFileAttributes(base::FilePath(), &attributes));
113 
114   EXPECT_EQ(NULL_DATA_HANDLE, proxy->GetFileAttributes(
115                                   child_process->windows_directory(), nullptr));
116 
117   EXPECT_EQ(uint32_t{ERROR_SUCCESS},
118             proxy->GetFileAttributes(child_process->windows_directory(),
119                                      &attributes));
120 
121   return ::testing::Test::HasNonfatalFailure();
122 }
123 
MULTIPROCESS_TEST_MAIN(GetFileAttributesNoHangs)124 MULTIPROCESS_TEST_MAIN(GetFileAttributesNoHangs) {
125   auto child_process = SetupSandboxedChildProcess();
126   if (!child_process)
127     return 1;
128 
129   child_process->UnbindRequestsRemotes();
130 
131   scoped_refptr<EngineRequestsProxy> proxy(
132       child_process->GetEngineRequestsProxy());
133 
134   uint32_t attributes;
135   EXPECT_EQ(INTERNAL_ERROR,
136             proxy->GetFileAttributes(child_process->windows_directory(),
137                                      &attributes));
138 
139   return ::testing::Test::HasNonfatalFailure();
140 }
141 
MULTIPROCESS_TEST_MAIN(GetKnownFolderPath)142 MULTIPROCESS_TEST_MAIN(GetKnownFolderPath) {
143   auto child_process = SetupSandboxedChildProcess();
144   if (!child_process)
145     return 1;
146 
147   scoped_refptr<EngineRequestsProxy> proxy(
148       child_process->GetEngineRequestsProxy());
149 
150   EXPECT_FALSE(
151       proxy->GetKnownFolderPath(mojom::KnownFolder::kWindows, nullptr));
152 
153   base::FilePath folder_path;
154   if (!proxy->GetKnownFolderPath(mojom::KnownFolder::kWindows, &folder_path)) {
155     LOG(ERROR) << "Failed to call GetKnownFolderPathCallback";
156     return 1;
157   }
158 
159   if (!base::EqualsCaseInsensitiveASCII(
160           child_process->windows_directory().value(), folder_path.value())) {
161     LOG(ERROR) << "Retrieved known folder path was " << folder_path
162                << " expected " << child_process->windows_directory();
163   }
164 
165   return ::testing::Test::HasNonfatalFailure();
166 }
167 
MULTIPROCESS_TEST_MAIN(GetKnownFolderPathInvalidParam)168 MULTIPROCESS_TEST_MAIN(GetKnownFolderPathInvalidParam) {
169   auto child_process = SetupSandboxedChildProcess();
170   if (!child_process)
171     return 1;
172 
173   scoped_refptr<EngineRequestsProxy> proxy(
174       child_process->GetEngineRequestsProxy());
175 
176   base::FilePath folder_path;
177   // This call should trigger mojo deserialization error, the broker will close
178   // the pipe, which will cause the current process to die.
179   proxy->GetKnownFolderPath(static_cast<mojom::KnownFolder>(-1), &folder_path);
180 
181   LOG(ERROR) << "Child process still alive after sending invalid enum value";
182   return 1;
183 }
184 
MULTIPROCESS_TEST_MAIN(GetKnownFolderPathNoHangs)185 MULTIPROCESS_TEST_MAIN(GetKnownFolderPathNoHangs) {
186   auto child_process = SetupSandboxedChildProcess();
187   if (!child_process)
188     return 1;
189 
190   child_process->UnbindRequestsRemotes();
191 
192   scoped_refptr<EngineRequestsProxy> proxy(
193       child_process->GetEngineRequestsProxy());
194 
195   base::FilePath folder_path;
196   EXPECT_FALSE(
197       proxy->GetKnownFolderPath(mojom::KnownFolder::kWindows, &folder_path));
198 
199   return ::testing::Test::HasNonfatalFailure();
200 }
201 
MULTIPROCESS_TEST_MAIN(GetProcesses)202 MULTIPROCESS_TEST_MAIN(GetProcesses) {
203   auto child_process = SetupSandboxedChildProcess();
204   if (!child_process)
205     return 1;
206 
207   scoped_refptr<EngineRequestsProxy> proxy(
208       child_process->GetEngineRequestsProxy());
209 
210   EXPECT_FALSE(proxy->GetProcesses(nullptr));
211 
212   std::vector<base::ProcessId> processes;
213   if (!proxy->GetProcesses(&processes)) {
214     LOG(ERROR) << "Failed to call GetProcesses";
215     return 1;
216   }
217 
218   base::ProcessId current_pid = ::GetCurrentProcessId();
219   if (std::count(processes.begin(), processes.end(), current_pid) != 1) {
220     LOG(ERROR)
221         << "Failed to find current process in list of returned processes";
222     return 1;
223   }
224 
225   return ::testing::Test::HasNonfatalFailure();
226 }
227 
MULTIPROCESS_TEST_MAIN(GetProcessesNoHangs)228 MULTIPROCESS_TEST_MAIN(GetProcessesNoHangs) {
229   auto child_process = SetupSandboxedChildProcess();
230   if (!child_process)
231     return 1;
232   child_process->UnbindRequestsRemotes();
233 
234   scoped_refptr<EngineRequestsProxy> proxy(
235       child_process->GetEngineRequestsProxy());
236 
237   EXPECT_FALSE(proxy->GetProcesses(nullptr));
238 
239   return ::testing::Test::HasNonfatalFailure();
240 }
241 
MULTIPROCESS_TEST_MAIN(GetTasks)242 MULTIPROCESS_TEST_MAIN(GetTasks) {
243   // Enable COM and the TaskScheduler. In the broker process this is done in
244   // test_main.cc, but we don't want to enable COM in the sandbox process
245   // except in tests where it's actually used.
246   base::win::ScopedCOMInitializer scoped_com_initializer(
247       base::win::ScopedCOMInitializer::kMTA);
248   if (!TaskScheduler::Initialize()) {
249     LOG(ERROR) << "TaskScheduler::Initialize() failed.";
250     return 1;
251   }
252 
253   // Create a test task.
254   TaskScheduler* task_scheduler = TaskScheduler::CreateInstance();
255   TaskScheduler::TaskInfo created_task;
256   if (!RegisterTestTask(task_scheduler, &created_task)) {
257     LOG(ERROR) << "Failed to create a test task";
258     return 1;
259   }
260 
261   // Ensure the test task is deleted when the test is finished.
262   base::ScopedClosureRunner scoped_exit(base::BindOnce(
263       base::IgnoreResult(&TaskScheduler::DeleteTask),
264       base::Unretained(task_scheduler), created_task.name.c_str()));
265 
266   auto child_process = SetupSandboxedChildProcess();
267   if (!child_process)
268     return 1;
269 
270   scoped_refptr<EngineRequestsProxy> proxy(
271       child_process->GetEngineRequestsProxy());
272 
273   EXPECT_FALSE(proxy->GetTasks(nullptr));
274 
275   std::vector<TaskScheduler::TaskInfo> tasks;
276   if (!proxy->GetTasks(&tasks)) {
277     LOG(ERROR) << "Failed to call GetTasks";
278     return 1;
279   }
280 
281   int matching_tasks = 0;
282   for (const auto& task : tasks) {
283     if (task.name == created_task.name &&
284         task.description == created_task.description &&
285         task.exec_actions.size() == created_task.exec_actions.size()) {
286       bool all_actions_match = true;
287       for (size_t i = 0; i < task.exec_actions.size(); ++i) {
288         if (task.exec_actions[i].application_path !=
289                 created_task.exec_actions[i].application_path ||
290             task.exec_actions[i].working_dir !=
291                 created_task.exec_actions[i].working_dir ||
292             task.exec_actions[i].arguments !=
293                 created_task.exec_actions[i].arguments) {
294           all_actions_match = false;
295           break;
296         }
297       }
298       if (all_actions_match)
299         ++matching_tasks;
300     }
301   }
302 
303   if (matching_tasks != 1) {
304     LOG(ERROR) << "Didn't get the expected number of matching tasks. Expected "
305                   "1 and got "
306                << matching_tasks;
307     return 1;
308   }
309 
310   return ::testing::Test::HasNonfatalFailure();
311 }
312 
MULTIPROCESS_TEST_MAIN(GetTasksNoHangs)313 MULTIPROCESS_TEST_MAIN(GetTasksNoHangs) {
314   auto child_process = SetupSandboxedChildProcess();
315   if (!child_process)
316     return 1;
317   child_process->UnbindRequestsRemotes();
318 
319   scoped_refptr<EngineRequestsProxy> proxy(
320       child_process->GetEngineRequestsProxy());
321 
322   EXPECT_FALSE(proxy->GetTasks(nullptr));
323 
324   return ::testing::Test::HasNonfatalFailure();
325 }
326 
MULTIPROCESS_TEST_MAIN(GetProcessImagePath)327 MULTIPROCESS_TEST_MAIN(GetProcessImagePath) {
328   auto child_process = SetupSandboxedChildProcess();
329   if (!child_process)
330     return 1;
331 
332   scoped_refptr<EngineRequestsProxy> proxy(
333       child_process->GetEngineRequestsProxy());
334 
335   EXPECT_FALSE(proxy->GetProcessImagePath(::GetCurrentProcessId(), nullptr));
336 
337   base::FilePath image_path;
338   if (!proxy->GetProcessImagePath(::GetCurrentProcessId(), &image_path)) {
339     LOG(ERROR) << "Failed to get current image path";
340     return 1;
341   }
342 
343   const base::FilePath exe_path =
344       PreFetchedPaths::GetInstance()->GetExecutablePath();
345 
346   if (!base::EqualsCaseInsensitiveASCII(exe_path.value(), image_path.value())) {
347     LOG(ERROR) << "Retrieved image path was " << image_path << " expected "
348                << exe_path;
349     return 1;
350   }
351 
352   return ::testing::Test::HasNonfatalFailure();
353 }
354 
MULTIPROCESS_TEST_MAIN(GetProcessImagePathNoHangs)355 MULTIPROCESS_TEST_MAIN(GetProcessImagePathNoHangs) {
356   auto child_process = SetupSandboxedChildProcess();
357   if (!child_process)
358     return 1;
359   child_process->UnbindRequestsRemotes();
360 
361   scoped_refptr<EngineRequestsProxy> proxy(
362       child_process->GetEngineRequestsProxy());
363 
364   EXPECT_FALSE(proxy->GetProcessImagePath(0, nullptr));
365 
366   return ::testing::Test::HasNonfatalFailure();
367 }
368 
MULTIPROCESS_TEST_MAIN(GetLoadedModules)369 MULTIPROCESS_TEST_MAIN(GetLoadedModules) {
370   auto child_process = SetupSandboxedChildProcess();
371   if (!child_process)
372     return 1;
373 
374   scoped_refptr<EngineRequestsProxy> proxy(
375       child_process->GetEngineRequestsProxy());
376 
377   EXPECT_FALSE(proxy->GetLoadedModules(::GetCurrentProcessId(), nullptr));
378 
379   std::vector<std::wstring> module_names;
380   if (!proxy->GetLoadedModules(::GetCurrentProcessId(), &module_names)) {
381     LOG(ERROR) << "Failed to get loaded modules for current process";
382     return 1;
383   }
384 
385   // Every process contains its executable as a module.
386   const base::FilePath exe_path =
387       PreFetchedPaths::GetInstance()->GetExecutablePath();
388   if (std::count(module_names.begin(), module_names.end(), exe_path.value()) !=
389       1) {
390     LOG(ERROR) << "Failed to find executable in own process";
391     return 1;
392   }
393 
394   return ::testing::Test::HasNonfatalFailure();
395 }
396 
MULTIPROCESS_TEST_MAIN(GetLoadedModulesNoHangs)397 MULTIPROCESS_TEST_MAIN(GetLoadedModulesNoHangs) {
398   auto child_process = SetupSandboxedChildProcess();
399   if (!child_process)
400     return 1;
401   child_process->UnbindRequestsRemotes();
402 
403   scoped_refptr<EngineRequestsProxy> proxy(
404       child_process->GetEngineRequestsProxy());
405 
406   EXPECT_FALSE(proxy->GetLoadedModules(0, nullptr));
407 
408   return ::testing::Test::HasNonfatalFailure();
409 }
410 
MULTIPROCESS_TEST_MAIN(GetProcessCommandLine)411 MULTIPROCESS_TEST_MAIN(GetProcessCommandLine) {
412   auto child_process = SetupSandboxedChildProcess();
413   if (!child_process)
414     return 1;
415 
416   scoped_refptr<EngineRequestsProxy> proxy(
417       child_process->GetEngineRequestsProxy());
418 
419   EXPECT_FALSE(proxy->GetProcessCommandLine(::GetCurrentProcessId(), nullptr));
420 
421   std::wstring retrieved_cmd;
422   if (!proxy->GetProcessCommandLine(::GetCurrentProcessId(), &retrieved_cmd)) {
423     LOG(ERROR) << "Failed to get command line for the current process";
424     return 1;
425   }
426 
427   const base::CommandLine* current_cmd = base::CommandLine::ForCurrentProcess();
428   base::CommandLine cmd = base::CommandLine::FromString(retrieved_cmd);
429   EXPECT_EQ(current_cmd->GetProgram(), cmd.GetProgram());
430   EXPECT_EQ(current_cmd->GetSwitches(), cmd.GetSwitches());
431   EXPECT_EQ(current_cmd->GetArgs(), cmd.GetArgs());
432 
433   return ::testing::Test::HasNonfatalFailure();
434 }
435 
MULTIPROCESS_TEST_MAIN(GetProcessCommandLineNoHangs)436 MULTIPROCESS_TEST_MAIN(GetProcessCommandLineNoHangs) {
437   auto child_process = SetupSandboxedChildProcess();
438   if (!child_process)
439     return 1;
440   child_process->UnbindRequestsRemotes();
441 
442   scoped_refptr<EngineRequestsProxy> proxy(
443       child_process->GetEngineRequestsProxy());
444   std::wstring cmd;
445   EXPECT_FALSE(proxy->GetProcessCommandLine(::GetCurrentProcessId(), &cmd));
446 
447   return ::testing::Test::HasNonfatalFailure();
448 }
449 
MULTIPROCESS_TEST_MAIN(GetUserInfoFromSID)450 MULTIPROCESS_TEST_MAIN(GetUserInfoFromSID) {
451   auto child_process = SetupSandboxedChildProcess();
452   if (!child_process)
453     return 1;
454 
455   scoped_refptr<EngineRequestsProxy> proxy(
456       child_process->GetEngineRequestsProxy());
457 
458   EXPECT_FALSE(proxy->GetUserInfoFromSID(nullptr, nullptr));
459 
460   mojom::UserInformation user_info;
461   EXPECT_FALSE(proxy->GetUserInfoFromSID(nullptr, &user_info));
462 
463   sandbox::Sid sid(WinSelfSid);
464   EXPECT_FALSE(
465       proxy->GetUserInfoFromSID(static_cast<SID*>(sid.GetPSID()), nullptr));
466   if (!proxy->GetUserInfoFromSID(static_cast<SID*>(sid.GetPSID()),
467                                  &user_info)) {
468     LOG(ERROR) << "Failed to get user infomation";
469     return 1;
470   }
471 
472   EXPECT_EQ(L"SELF", user_info.name);
473   EXPECT_EQ(L"NT AUTHORITY", user_info.domain);
474   EXPECT_EQ(SidTypeWellKnownGroup,
475             static_cast<_SID_NAME_USE>(user_info.account_type));
476 
477   return ::testing::Test::HasNonfatalFailure();
478 }
479 
MULTIPROCESS_TEST_MAIN(GetUserInfoFromSIDNoHangs)480 MULTIPROCESS_TEST_MAIN(GetUserInfoFromSIDNoHangs) {
481   auto child_process = SetupSandboxedChildProcess();
482   if (!child_process)
483     return 1;
484   child_process->UnbindRequestsRemotes();
485 
486   scoped_refptr<EngineRequestsProxy> proxy(
487       child_process->GetEngineRequestsProxy());
488 
489   sandbox::Sid sid(WinLocalSid);
490   mojom::UserInformation user_info;
491   EXPECT_FALSE(
492       proxy->GetUserInfoFromSID(static_cast<SID*>(sid.GetPSID()), &user_info));
493   return ::testing::Test::HasNonfatalFailure();
494 }
495 
MULTIPROCESS_TEST_MAIN(OpenReadOnlyRegistry)496 MULTIPROCESS_TEST_MAIN(OpenReadOnlyRegistry) {
497   auto child_process = SetupSandboxedChildProcess();
498   if (!child_process)
499     return 1;
500 
501   scoped_refptr<EngineRequestsProxy> proxy(
502       child_process->GetEngineRequestsProxy());
503 
504   // TODO(joenotcharles): Test with all predefined keys and combinations of
505   // WOW64 flags.
506   const std::wstring fake_key_name = L"fake/key/I/just/made";
507   HANDLE reg_handle;
508   uint32_t result = proxy->OpenReadOnlyRegistry(
509       HKEY_LOCAL_MACHINE, fake_key_name, KEY_READ, &reg_handle);
510   if (reg_handle != INVALID_HANDLE_VALUE) {
511     LOG(ERROR) << "Got a valid handle when trying to open a fake key";
512     return 1;
513   }
514   if (result != ERROR_FILE_NOT_FOUND) {
515     LOG(ERROR) << std::hex
516                << "Got unexpected return code when opening a fake key. "
517                   "Expected ERROR_FILE_NOT_FOUND(0x"
518                << ERROR_FILE_NOT_FOUND << ") and got 0x" << result;
519     return 1;
520   }
521 
522   result = proxy->OpenReadOnlyRegistry(HKEY_LOCAL_MACHINE, std::wstring(),
523                                        KEY_READ, &reg_handle);
524   if (reg_handle == INVALID_HANDLE_VALUE) {
525     LOG(ERROR) << std::hex
526                << "Failed to get a valid registry handle for "
527                   "HKEY_LOCAL_MACHINE. Error code: 0x"
528                << result;
529     return 1;
530   }
531 
532   result = proxy->OpenReadOnlyRegistry(HKEY_LOCAL_MACHINE,
533                                        child_process->temp_key_path(), KEY_READ,
534                                        &reg_handle);
535   if (reg_handle == INVALID_HANDLE_VALUE) {
536     LOG(ERROR)
537         << std::hex
538         << "Failed to get a valid registry handle for HKEY_CURRENT_USER\\"
539         << child_process->temp_key_path() << ". Error code: 0x" << result;
540     return 1;
541   }
542 
543   return ::testing::Test::HasNonfatalFailure();
544 }
545 
MULTIPROCESS_TEST_MAIN(OpenReadOnlyRegistryNoHangs)546 MULTIPROCESS_TEST_MAIN(OpenReadOnlyRegistryNoHangs) {
547   auto child_process = SetupSandboxedChildProcess();
548   if (!child_process)
549     return 1;
550 
551   scoped_refptr<EngineRequestsProxy> proxy(
552       child_process->GetEngineRequestsProxy());
553 
554   HANDLE reg_handle;
555   EXPECT_EQ(
556       SandboxErrorCode::NULL_ROOT_KEY,
557       proxy->OpenReadOnlyRegistry(nullptr, std::wstring(), 0, &reg_handle));
558 
559   child_process->UnbindRequestsRemotes();
560 
561   EXPECT_EQ(
562       SandboxErrorCode::INTERNAL_ERROR,
563       proxy->OpenReadOnlyRegistry(nullptr, std::wstring(), 0, &reg_handle));
564 
565   return ::testing::Test::HasNonfatalFailure();
566 }
567 
MULTIPROCESS_TEST_MAIN(NtOpenReadOnlyRegistry)568 MULTIPROCESS_TEST_MAIN(NtOpenReadOnlyRegistry) {
569   auto child_process = SetupSandboxedChildProcess();
570   if (!child_process)
571     return 1;
572 
573   scoped_refptr<EngineRequestsProxy> proxy(
574       child_process->GetEngineRequestsProxy());
575 
576   // Get an existing root key.
577   HANDLE root_handle;
578   uint32_t result = proxy->OpenReadOnlyRegistry(HKEY_LOCAL_MACHINE,
579                                                 child_process->temp_key_path(),
580                                                 KEY_READ, &root_handle);
581   if (root_handle == INVALID_HANDLE_VALUE) {
582     LOG(ERROR)
583         << std::hex
584         << "Failed to get a valid registry handle for HKEY_CURRENT_USER\\"
585         << child_process->temp_key_path() << ". Error code: 0x" << result;
586     return 1;
587   }
588 
589   // Test with nonexistent key.
590   std::vector<wchar_t> nonexistent_key_with_nulls =
591       CreateVectorWithNulls(L"nonexistent0key0with0nulls");
592 
593   HANDLE reg_handle;
594   result = proxy->NtOpenReadOnlyRegistry(
595       root_handle,
596       WStringEmbeddedNulls(nonexistent_key_with_nulls.data(),
597                            nonexistent_key_with_nulls.size()),
598       KEY_READ, &reg_handle);
599   if (reg_handle != INVALID_HANDLE_VALUE) {
600     LOG(ERROR) << "Got a valid handle when trying to open a fake key.";
601     return 1;
602   }
603   if (static_cast<NTSTATUS>(result) != STATUS_OBJECT_NAME_NOT_FOUND) {
604     LOG(ERROR) << std::hex
605                << "Got unexpected return code when opening a fake key. "
606                   "Expected STATUS_OBJECT_NAME_NOT_FOUND(0x"
607                << STATUS_OBJECT_NAME_NOT_FOUND << ") and got 0x" << result;
608     return 1;
609   }
610 
611   // Test with embedded nulls and null terminator.
612   std::vector<wchar_t> key_with_nulls = CreateVectorWithNulls(kKeyWithNulls);
613   result = proxy->NtOpenReadOnlyRegistry(
614       root_handle,
615       WStringEmbeddedNulls(key_with_nulls.data(), key_with_nulls.size()),
616       KEY_READ, &reg_handle);
617   if (reg_handle == INVALID_HANDLE_VALUE) {
618     LOG(ERROR) << std::hex << "Failed to get a valid registry handle for "
619                << FormatVectorWithNulls(key_with_nulls) << ". Error code: 0x"
620                << result;
621     return 1;
622   }
623 
624   // Test with missing null terminator.
625   if (key_with_nulls.back() != L'\0') {
626     LOG(ERROR) << "CreateVectorWithNulls skipped the null terminator in "
627                << FormatVectorWithNulls(key_with_nulls);
628     return 1;
629   }
630   std::vector<wchar_t> truncated_key_with_nulls(key_with_nulls.begin(),
631                                                 key_with_nulls.end() - 1);
632   result = proxy->NtOpenReadOnlyRegistry(
633       root_handle,
634       WStringEmbeddedNulls(truncated_key_with_nulls.data(),
635                            truncated_key_with_nulls.size()),
636       KEY_READ, &reg_handle);
637   if (reg_handle != INVALID_HANDLE_VALUE) {
638     LOG(ERROR) << "Got a valid registry handle for "
639                << FormatVectorWithNulls(truncated_key_with_nulls)
640                << " that has no null-terminator.";
641     return 1;
642   }
643   if (result != SandboxErrorCode::INVALID_SUBKEY_STRING) {
644     LOG(ERROR) << std::hex << "Got unexpected return code for "
645                << FormatVectorWithNulls(truncated_key_with_nulls)
646                << " that has no null-terminator. Expected "
647                << "SandboxErrorCode::INVALID_SUBKEY_STRING (0x"
648                << SandboxErrorCode::INVALID_SUBKEY_STRING << ") and got 0x"
649                << result;
650     return 1;
651   }
652 
653   // Test with absolute path.
654   std::wstring temp_key_full_path = child_process->temp_key_full_path();
655   std::vector<wchar_t> full_path(temp_key_full_path.begin(),
656                                  temp_key_full_path.end());
657   full_path.push_back(L'\\');
658   full_path.insert(full_path.end(), key_with_nulls.begin(),
659                    key_with_nulls.end());
660   result = proxy->NtOpenReadOnlyRegistry(
661       nullptr, WStringEmbeddedNulls(full_path.data(), full_path.size()),
662       KEY_READ, &reg_handle);
663   if (reg_handle == INVALID_HANDLE_VALUE) {
664     LOG(ERROR) << std::hex << "Failed to get a valid registry handle for "
665                << FormatVectorWithNulls(full_path) << ". Error code: 0x"
666                << result;
667     return 1;
668   }
669 
670   return ::testing::Test::HasNonfatalFailure();
671 }
672 
MULTIPROCESS_TEST_MAIN(NtOpenReadOnlyRegistryNoHangs)673 MULTIPROCESS_TEST_MAIN(NtOpenReadOnlyRegistryNoHangs) {
674   auto child_process = SetupSandboxedChildProcess();
675   if (!child_process)
676     return 1;
677 
678   scoped_refptr<EngineRequestsProxy> proxy(
679       child_process->GetEngineRequestsProxy());
680 
681   std::wstring too_long(std::numeric_limits<int16_t>::max() + 1, '0');
682   HANDLE reg_handle;
683   EXPECT_EQ(SandboxErrorCode::INVALID_SUBKEY_STRING,
684             proxy->NtOpenReadOnlyRegistry(
685                 nullptr, WStringEmbeddedNulls(too_long), 0, &reg_handle));
686 
687   child_process->UnbindRequestsRemotes();
688 
689   EXPECT_EQ(SandboxErrorCode::INTERNAL_ERROR,
690             proxy->NtOpenReadOnlyRegistry(nullptr, WStringEmbeddedNulls(), 0,
691                                           &reg_handle));
692 
693   return ::testing::Test::HasNonfatalFailure();
694 }
695 
696 using TestParentProcess = MaybeSandboxedParentProcess<SandboxedParentProcess>;
697 
698 // EngineRequestsProxyTest is parametrized with:
699 //  - expected_exit_code_: expected exit code of the child process;
700 //  - child_main_function_: the name of the MULTIPROCESS_TEST_MAIN function for
701 //    the child process.
702 typedef std::tuple<int, std::string> EngineRequestsProxyTestParams;
703 
704 class EngineRequestsProxyTest
705     : public ::testing::TestWithParam<EngineRequestsProxyTestParams> {
706  public:
SetUp()707   void SetUp() override {
708     expected_exit_code_ = std::get<0>(GetParam());
709     child_main_function_ = std::get<1>(GetParam());
710 
711     mojo_task_runner_ = MojoTaskRunner::Create();
712 
713     parent_process_ = base::MakeRefCounted<TestParentProcess>(
714         mojo_task_runner_,
715         TestParentProcess::CallbacksToSetup::kScanAndCleanupRequests);
716   }
717 
718  protected:
719   int expected_exit_code_;
720   std::string child_main_function_;
721 
722   scoped_refptr<MojoTaskRunner> mojo_task_runner_;
723   scoped_refptr<TestParentProcess> parent_process_;
724 };
725 
TEST_P(EngineRequestsProxyTest,TestRequest)726 TEST_P(EngineRequestsProxyTest, TestRequest) {
727   base::test::TaskEnvironment task_environment;
728 
729   // Create resources that tests running in the sandbox will not have access to
730   // create for themselves, even before calling LowerToken.
731   chrome_cleaner_sandbox::ScopedTempRegistryKey temp_key;
732   parent_process_->AppendSwitchNative(kTempKeyPathSwitch, temp_key.Path());
733   parent_process_->AppendSwitchNative(kTempKeyFullPathSwitch,
734                                       temp_key.FullyQualifiedPath());
735 
736   std::vector<wchar_t> key_with_nulls = CreateVectorWithNulls(kKeyWithNulls);
737   ULONG disposition = 0;
738   HANDLE temp_handle = INVALID_HANDLE_VALUE;
739   chrome_cleaner_sandbox::NativeCreateKey(temp_key.Get(), &key_with_nulls,
740                                           &temp_handle, &disposition);
741 
742   // ScopedTempRegistryKey's destructor will try to delete this key using
743   // win::RegKey, which does not handle embedded nulls. So it must be deleted
744   // manually.
745   // TODO(joenotcharles): ScopedTempRegistryKey should do this automatically.
746   base::ScopedClosureRunner delete_temp_key(base::BindOnce(
747       [](HANDLE handle) {
748         chrome_cleaner_sandbox::NativeDeleteKey(handle);
749         ::CloseHandle(handle);
750       },
751       temp_handle));
752 
753   const base::FilePath windows_dir =
754       PreFetchedPaths::GetInstance()->GetWindowsFolder();
755   parent_process_->AppendSwitchPath(kWindowsDirectorySwitch, windows_dir);
756 
757   int32_t exit_code = -1;
758   EXPECT_TRUE(parent_process_->LaunchConnectedChildProcess(child_main_function_,
759                                                            &exit_code));
760   EXPECT_EQ(expected_exit_code_, exit_code);
761 }
762 
763 INSTANTIATE_TEST_SUITE_P(
764     Success,
765     EngineRequestsProxyTest,
766     testing::Combine(testing::Values(0),
767                      testing::Values("GetFileAttributesTest",
768                                      "GetFileAttributesNoHangs",
769                                      "GetKnownFolderPath",
770                                      "GetKnownFolderPathNoHangs",
771                                      "GetProcesses",
772                                      "GetProcessesNoHangs",
773                                      "GetTasks",
774                                      "GetTasksNoHangs",
775                                      "GetProcessImagePath",
776                                      "GetProcessImagePathNoHangs",
777                                      "GetLoadedModules",
778                                      "GetLoadedModulesNoHangs",
779                                      "GetProcessCommandLine",
780                                      "GetProcessCommandLineNoHangs",
781                                      "GetUserInfoFromSID",
782                                      "GetUserInfoFromSIDNoHangs",
783                                      "OpenReadOnlyRegistry",
784                                      "OpenReadOnlyRegistryNoHangs",
785                                      "NtOpenReadOnlyRegistry",
786                                      "NtOpenReadOnlyRegistryNoHangs")),
787     GetParamNameForTest());
788 
789 INSTANTIATE_TEST_SUITE_P(
790     ConnectionError,
791     EngineRequestsProxyTest,
792     testing::Combine(
793         testing::Values(TestChildProcess::kConnectionErrorExitCode),
794         testing::Values("GetKnownFolderPathInvalidParam")),
795     GetParamNameForTest());
796 
797 }  // namespace
798 
799 }  // namespace chrome_cleaner
800