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, ®_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, ®_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 ®_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, ®_handle));
558
559 child_process->UnbindRequestsRemotes();
560
561 EXPECT_EQ(
562 SandboxErrorCode::INTERNAL_ERROR,
563 proxy->OpenReadOnlyRegistry(nullptr, std::wstring(), 0, ®_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, ®_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, ®_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, ®_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, ®_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, ®_handle));
686
687 child_process->UnbindRequestsRemotes();
688
689 EXPECT_EQ(SandboxErrorCode::INTERNAL_ERROR,
690 proxy->NtOpenReadOnlyRegistry(nullptr, WStringEmbeddedNulls(), 0,
691 ®_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