| // Copyright 2019 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/chrome_cleaner/engines/target/engine_requests_proxy.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <sstream> |
| #include <string> |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/callback_helpers.h" |
| #include "base/command_line.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/scoped_task_environment.h" |
| #include "base/win/registry.h" |
| #include "base/win/scoped_com_initializer.h" |
| #include "chrome/chrome_cleaner/engines/common/registry_util.h" |
| #include "chrome/chrome_cleaner/engines/target/sandboxed_test_helpers.h" |
| #include "chrome/chrome_cleaner/os/pre_fetched_paths.h" |
| #include "chrome/chrome_cleaner/os/task_scheduler.h" |
| #include "chrome/chrome_cleaner/strings/string16_embedded_nulls.h" |
| #include "chrome/chrome_cleaner/strings/string_test_helpers.h" |
| #include "chrome/chrome_cleaner/test/test_native_reg_util.h" |
| #include "chrome/chrome_cleaner/test/test_util.h" |
| #include "components/chrome_cleaner/test/test_name_helper.h" |
| #include "sandbox/win/src/sid.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "testing/multiprocess_func_list.h" |
| |
| namespace chrome_cleaner { |
| |
| namespace { |
| |
| // Temp keys to create under HKLM. |
| constexpr char kTempKeyPathSwitch[] = "temp-key-path"; |
| constexpr char kTempKeyFullPathSwitch[] = "temp-key-full-path"; |
| constexpr wchar_t kKeyWithNulls[] = L"fake0key0with0nulls"; |
| |
| // Switch with a path to the windows directory. |
| constexpr char kWindowsDirectorySwitch[] = "windows-directory"; |
| |
| class TestChildProcess : public SandboxChildProcess { |
| public: |
| explicit TestChildProcess(scoped_refptr<MojoTaskRunner> mojo_task_runner) |
| : SandboxChildProcess(std::move(mojo_task_runner)) {} |
| |
| bool Initialize() { |
| LowerToken(); |
| |
| windows_directory_ = |
| command_line().GetSwitchValuePath(kWindowsDirectorySwitch); |
| if (windows_directory_.empty()) { |
| LOG(ERROR) << "Initialize failed: Missing " << kWindowsDirectorySwitch |
| << " switch"; |
| return false; |
| } |
| |
| temp_key_path_ = command_line().GetSwitchValueNative(kTempKeyPathSwitch); |
| if (temp_key_path_.empty()) { |
| LOG(ERROR) << "Initialize failed: Missing " << kTempKeyPathSwitch |
| << " switch"; |
| return false; |
| } |
| |
| temp_key_full_path_ = |
| command_line().GetSwitchValueNative(kTempKeyFullPathSwitch); |
| if (temp_key_full_path_.empty()) { |
| LOG(ERROR) << "Initialize failed: Missing " << kTempKeyFullPathSwitch |
| << " switch"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| base::FilePath windows_directory() const { return windows_directory_; } |
| |
| base::string16 temp_key_path() const { return temp_key_path_; } |
| |
| base::string16 temp_key_full_path() const { return temp_key_full_path_; } |
| |
| private: |
| ~TestChildProcess() override = default; |
| |
| base::FilePath windows_directory_; |
| base::string16 temp_key_path_; |
| base::string16 temp_key_full_path_; |
| }; |
| |
| scoped_refptr<TestChildProcess> SetupSandboxedChildProcess() { |
| scoped_refptr<MojoTaskRunner> mojo_task_runner = MojoTaskRunner::Create(); |
| auto child_process = base::MakeRefCounted<TestChildProcess>(mojo_task_runner); |
| if (!child_process->Initialize()) |
| return base::MakeRefCounted<TestChildProcess>(nullptr); |
| return child_process; |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetFileAttributesTest) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| uint32_t attributes; |
| EXPECT_EQ(INVALID_FILE_PATH, |
| proxy->GetFileAttributes(base::FilePath(), &attributes)); |
| |
| EXPECT_EQ(NULL_DATA_HANDLE, proxy->GetFileAttributes( |
| child_process->windows_directory(), nullptr)); |
| |
| EXPECT_EQ(uint32_t{ERROR_SUCCESS}, |
| proxy->GetFileAttributes(child_process->windows_directory(), |
| &attributes)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetFileAttributesNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| uint32_t attributes; |
| EXPECT_EQ(INTERNAL_ERROR, |
| proxy->GetFileAttributes(child_process->windows_directory(), |
| &attributes)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetKnownFolderPath) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE( |
| proxy->GetKnownFolderPath(mojom::KnownFolder::kWindows, nullptr)); |
| |
| base::FilePath folder_path; |
| if (!proxy->GetKnownFolderPath(mojom::KnownFolder::kWindows, &folder_path)) { |
| LOG(ERROR) << "Failed to call GetKnownFolderPathCallback"; |
| return 1; |
| } |
| |
| if (!base::EqualsCaseInsensitiveASCII( |
| child_process->windows_directory().value(), folder_path.value())) { |
| LOG(ERROR) << "Retrieved known folder path was " << folder_path |
| << " expected " << child_process->windows_directory(); |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetKnownFolderPathInvalidParam) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| base::FilePath folder_path; |
| // This call should trigger mojo deserialization error, the broker will close |
| // the pipe, which will cause the current process to die. |
| proxy->GetKnownFolderPath(static_cast<mojom::KnownFolder>(-1), &folder_path); |
| |
| LOG(ERROR) << "Child process still alive after sending invalid enum value"; |
| return 1; |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetKnownFolderPathNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| base::FilePath folder_path; |
| EXPECT_FALSE( |
| proxy->GetKnownFolderPath(mojom::KnownFolder::kWindows, &folder_path)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetProcesses) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetProcesses(nullptr)); |
| |
| std::vector<base::ProcessId> processes; |
| if (!proxy->GetProcesses(&processes)) { |
| LOG(ERROR) << "Failed to call GetProcesses"; |
| return 1; |
| } |
| |
| base::ProcessId current_pid = ::GetCurrentProcessId(); |
| if (std::count(processes.begin(), processes.end(), current_pid) != 1) { |
| LOG(ERROR) |
| << "Failed to find current process in list of returned processes"; |
| return 1; |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetProcessesNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetProcesses(nullptr)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetTasks) { |
| // Enable COM and the TaskScheduler. In the broker process this is done in |
| // test_main.cc, but we don't want to enable COM in the sandbox process |
| // except in tests where it's actually used. |
| base::win::ScopedCOMInitializer scoped_com_initializer( |
| base::win::ScopedCOMInitializer::kMTA); |
| if (!TaskScheduler::Initialize()) { |
| LOG(ERROR) << "TaskScheduler::Initialize() failed."; |
| return 1; |
| } |
| |
| // Create a test task. |
| TaskScheduler* task_scheduler = TaskScheduler::CreateInstance(); |
| TaskScheduler::TaskInfo created_task; |
| if (!RegisterTestTask(task_scheduler, &created_task)) { |
| LOG(ERROR) << "Failed to create a test task"; |
| return 1; |
| } |
| |
| // Ensure the test task is deleted when the test is finished. |
| base::ScopedClosureRunner scoped_exit(base::BindOnce( |
| base::IgnoreResult(&TaskScheduler::DeleteTask), |
| base::Unretained(task_scheduler), created_task.name.c_str())); |
| |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetTasks(nullptr)); |
| |
| std::vector<TaskScheduler::TaskInfo> tasks; |
| if (!proxy->GetTasks(&tasks)) { |
| LOG(ERROR) << "Failed to call GetTasks"; |
| return 1; |
| } |
| |
| int matching_tasks = 0; |
| for (const auto& task : tasks) { |
| if (task.name == created_task.name && |
| task.description == created_task.description && |
| task.exec_actions.size() == created_task.exec_actions.size()) { |
| bool all_actions_match = true; |
| for (size_t i = 0; i < task.exec_actions.size(); ++i) { |
| if (task.exec_actions[i].application_path != |
| created_task.exec_actions[i].application_path || |
| task.exec_actions[i].working_dir != |
| created_task.exec_actions[i].working_dir || |
| task.exec_actions[i].arguments != |
| created_task.exec_actions[i].arguments) { |
| all_actions_match = false; |
| break; |
| } |
| } |
| if (all_actions_match) |
| ++matching_tasks; |
| } |
| } |
| |
| if (matching_tasks != 1) { |
| LOG(ERROR) << "Didn't get the expected number of matching tasks. Expected " |
| "1 and got " |
| << matching_tasks; |
| return 1; |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetTasksNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetTasks(nullptr)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetProcessImagePath) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetProcessImagePath(::GetCurrentProcessId(), nullptr)); |
| |
| base::FilePath image_path; |
| if (!proxy->GetProcessImagePath(::GetCurrentProcessId(), &image_path)) { |
| LOG(ERROR) << "Failed to get current image path"; |
| return 1; |
| } |
| |
| const base::FilePath exe_path = |
| PreFetchedPaths::GetInstance()->GetExecutablePath(); |
| |
| if (!base::EqualsCaseInsensitiveASCII(exe_path.value(), image_path.value())) { |
| LOG(ERROR) << "Retrieved image path was " << image_path << " expected " |
| << exe_path; |
| return 1; |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetProcessImagePathNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetProcessImagePath(0, nullptr)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetLoadedModules) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetLoadedModules(::GetCurrentProcessId(), nullptr)); |
| |
| std::vector<base::string16> module_names; |
| if (!proxy->GetLoadedModules(::GetCurrentProcessId(), &module_names)) { |
| LOG(ERROR) << "Failed to get loaded modules for current process"; |
| return 1; |
| } |
| |
| // Every process contains its executable as a module. |
| const base::FilePath exe_path = |
| PreFetchedPaths::GetInstance()->GetExecutablePath(); |
| if (std::count(module_names.begin(), module_names.end(), exe_path.value()) != |
| 1) { |
| LOG(ERROR) << "Failed to find executable in own process"; |
| return 1; |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetLoadedModulesNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetLoadedModules(0, nullptr)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetProcessCommandLine) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetProcessCommandLine(::GetCurrentProcessId(), nullptr)); |
| |
| base::string16 retrieved_cmd; |
| if (!proxy->GetProcessCommandLine(::GetCurrentProcessId(), &retrieved_cmd)) { |
| LOG(ERROR) << "Failed to get command line for the current process"; |
| return 1; |
| } |
| |
| const base::CommandLine* current_cmd = base::CommandLine::ForCurrentProcess(); |
| base::CommandLine cmd = base::CommandLine::FromString(retrieved_cmd); |
| EXPECT_EQ(current_cmd->GetProgram(), cmd.GetProgram()); |
| EXPECT_EQ(current_cmd->GetSwitches(), cmd.GetSwitches()); |
| EXPECT_EQ(current_cmd->GetArgs(), cmd.GetArgs()); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetProcessCommandLineNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| base::string16 cmd; |
| EXPECT_FALSE(proxy->GetProcessCommandLine(::GetCurrentProcessId(), &cmd)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetUserInfoFromSID) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| EXPECT_FALSE(proxy->GetUserInfoFromSID(nullptr, nullptr)); |
| |
| mojom::UserInformation user_info; |
| EXPECT_FALSE(proxy->GetUserInfoFromSID(nullptr, &user_info)); |
| |
| sandbox::Sid sid(WinSelfSid); |
| EXPECT_FALSE( |
| proxy->GetUserInfoFromSID(static_cast<SID*>(sid.GetPSID()), nullptr)); |
| if (!proxy->GetUserInfoFromSID(static_cast<SID*>(sid.GetPSID()), |
| &user_info)) { |
| LOG(ERROR) << "Failed to get user infomation"; |
| return 1; |
| } |
| |
| EXPECT_EQ(L"SELF", user_info.name); |
| EXPECT_EQ(L"NT AUTHORITY", user_info.domain); |
| EXPECT_EQ(SidTypeWellKnownGroup, |
| static_cast<_SID_NAME_USE>(user_info.account_type)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(GetUserInfoFromSIDNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| child_process->UnbindRequestsPtrs(); |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| sandbox::Sid sid(WinLocalSid); |
| mojom::UserInformation user_info; |
| EXPECT_FALSE( |
| proxy->GetUserInfoFromSID(static_cast<SID*>(sid.GetPSID()), &user_info)); |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(OpenReadOnlyRegistry) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| // TODO(joenotcharles): Test with all predefined keys and combinations of |
| // WOW64 flags. |
| const base::string16 fake_key_name = L"fake/key/I/just/made"; |
| HANDLE reg_handle; |
| uint32_t result = proxy->OpenReadOnlyRegistry( |
| HKEY_LOCAL_MACHINE, fake_key_name, KEY_READ, ®_handle); |
| if (reg_handle != INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << "Got a valid handle when trying to open a fake key"; |
| return 1; |
| } |
| if (result != ERROR_FILE_NOT_FOUND) { |
| LOG(ERROR) << std::hex |
| << "Got unexpected return code when opening a fake key. " |
| "Expected ERROR_FILE_NOT_FOUND(0x" |
| << ERROR_FILE_NOT_FOUND << ") and got 0x" << result; |
| return 1; |
| } |
| |
| result = proxy->OpenReadOnlyRegistry(HKEY_LOCAL_MACHINE, base::string16(), |
| KEY_READ, ®_handle); |
| if (reg_handle == INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << std::hex |
| << "Failed to get a valid registry handle for " |
| "HKEY_LOCAL_MACHINE. Error code: 0x" |
| << result; |
| return 1; |
| } |
| |
| result = proxy->OpenReadOnlyRegistry(HKEY_LOCAL_MACHINE, |
| child_process->temp_key_path(), KEY_READ, |
| ®_handle); |
| if (reg_handle == INVALID_HANDLE_VALUE) { |
| LOG(ERROR) |
| << std::hex |
| << "Failed to get a valid registry handle for HKEY_CURRENT_USER\\" |
| << child_process->temp_key_path() << ". Error code: 0x" << result; |
| return 1; |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(OpenReadOnlyRegistryNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| HANDLE reg_handle; |
| EXPECT_EQ( |
| SandboxErrorCode::NULL_ROOT_KEY, |
| proxy->OpenReadOnlyRegistry(nullptr, base::string16(), 0, ®_handle)); |
| |
| child_process->UnbindRequestsPtrs(); |
| |
| EXPECT_EQ( |
| SandboxErrorCode::INTERNAL_ERROR, |
| proxy->OpenReadOnlyRegistry(nullptr, base::string16(), 0, ®_handle)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(NtOpenReadOnlyRegistry) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| // Get an existing root key. |
| HANDLE root_handle; |
| uint32_t result = proxy->OpenReadOnlyRegistry(HKEY_LOCAL_MACHINE, |
| child_process->temp_key_path(), |
| KEY_READ, &root_handle); |
| if (root_handle == INVALID_HANDLE_VALUE) { |
| LOG(ERROR) |
| << std::hex |
| << "Failed to get a valid registry handle for HKEY_CURRENT_USER\\" |
| << child_process->temp_key_path() << ". Error code: 0x" << result; |
| return 1; |
| } |
| |
| // Test with nonexistent key. |
| std::vector<wchar_t> nonexistent_key_with_nulls = |
| CreateVectorWithNulls(L"nonexistent0key0with0nulls"); |
| |
| HANDLE reg_handle; |
| result = proxy->NtOpenReadOnlyRegistry( |
| root_handle, |
| String16EmbeddedNulls(nonexistent_key_with_nulls.data(), |
| nonexistent_key_with_nulls.size()), |
| KEY_READ, ®_handle); |
| if (reg_handle != INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << "Got a valid handle when trying to open a fake key."; |
| return 1; |
| } |
| if (static_cast<NTSTATUS>(result) != STATUS_OBJECT_NAME_NOT_FOUND) { |
| LOG(ERROR) << std::hex |
| << "Got unexpected return code when opening a fake key. " |
| "Expected STATUS_OBJECT_NAME_NOT_FOUND(0x" |
| << STATUS_OBJECT_NAME_NOT_FOUND << ") and got 0x" << result; |
| return 1; |
| } |
| |
| // Test with embedded nulls and null terminator. |
| std::vector<wchar_t> key_with_nulls = CreateVectorWithNulls(kKeyWithNulls); |
| result = proxy->NtOpenReadOnlyRegistry( |
| root_handle, |
| String16EmbeddedNulls(key_with_nulls.data(), key_with_nulls.size()), |
| KEY_READ, ®_handle); |
| if (reg_handle == INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << std::hex << "Failed to get a valid registry handle for " |
| << FormatVectorWithNulls(key_with_nulls) << ". Error code: 0x" |
| << result; |
| return 1; |
| } |
| |
| // Test with missing null terminator. |
| if (key_with_nulls.back() != L'\0') { |
| LOG(ERROR) << "CreateVectorWithNulls skipped the null terminator in " |
| << FormatVectorWithNulls(key_with_nulls); |
| return 1; |
| } |
| std::vector<wchar_t> truncated_key_with_nulls(key_with_nulls.begin(), |
| key_with_nulls.end() - 1); |
| result = proxy->NtOpenReadOnlyRegistry( |
| root_handle, |
| String16EmbeddedNulls(truncated_key_with_nulls.data(), |
| truncated_key_with_nulls.size()), |
| KEY_READ, ®_handle); |
| if (reg_handle != INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << "Got a valid registry handle for " |
| << FormatVectorWithNulls(truncated_key_with_nulls) |
| << " that has no null-terminator."; |
| return 1; |
| } |
| if (result != SandboxErrorCode::INVALID_SUBKEY_STRING) { |
| LOG(ERROR) << std::hex << "Got unexpected return code for " |
| << FormatVectorWithNulls(truncated_key_with_nulls) |
| << " that has no null-terminator. Expected " |
| << "SandboxErrorCode::INVALID_SUBKEY_STRING (0x" |
| << SandboxErrorCode::INVALID_SUBKEY_STRING << ") and got 0x" |
| << result; |
| return 1; |
| } |
| |
| // Test with absolute path. |
| base::string16 temp_key_full_path = child_process->temp_key_full_path(); |
| std::vector<wchar_t> full_path(temp_key_full_path.begin(), |
| temp_key_full_path.end()); |
| full_path.push_back(L'\\'); |
| full_path.insert(full_path.end(), key_with_nulls.begin(), |
| key_with_nulls.end()); |
| result = proxy->NtOpenReadOnlyRegistry( |
| nullptr, String16EmbeddedNulls(full_path.data(), full_path.size()), |
| KEY_READ, ®_handle); |
| if (reg_handle == INVALID_HANDLE_VALUE) { |
| LOG(ERROR) << std::hex << "Failed to get a valid registry handle for " |
| << FormatVectorWithNulls(full_path) << ". Error code: 0x" |
| << result; |
| return 1; |
| } |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| MULTIPROCESS_TEST_MAIN(NtOpenReadOnlyRegistryNoHangs) { |
| auto child_process = SetupSandboxedChildProcess(); |
| if (!child_process) |
| return 1; |
| |
| scoped_refptr<EngineRequestsProxy> proxy( |
| child_process->GetEngineRequestsProxy()); |
| |
| base::string16 too_long(std::numeric_limits<int16_t>::max() + 1, '0'); |
| HANDLE reg_handle; |
| EXPECT_EQ(SandboxErrorCode::INVALID_SUBKEY_STRING, |
| proxy->NtOpenReadOnlyRegistry( |
| nullptr, String16EmbeddedNulls(too_long), 0, ®_handle)); |
| |
| child_process->UnbindRequestsPtrs(); |
| |
| EXPECT_EQ(SandboxErrorCode::INTERNAL_ERROR, |
| proxy->NtOpenReadOnlyRegistry(nullptr, String16EmbeddedNulls(), 0, |
| ®_handle)); |
| |
| return ::testing::Test::HasNonfatalFailure(); |
| } |
| |
| using TestParentProcess = MaybeSandboxedParentProcess<SandboxedParentProcess>; |
| |
| // EngineRequestsProxyTest is parametrized with: |
| // - expected_exit_code_: expected exit code of the child process; |
| // - child_main_function_: the name of the MULTIPROCESS_TEST_MAIN function for |
| // the child process. |
| typedef std::tuple<int, std::string> EngineRequestsProxyTestParams; |
| |
| class EngineRequestsProxyTest |
| : public ::testing::TestWithParam<EngineRequestsProxyTestParams> { |
| public: |
| void SetUp() override { |
| expected_exit_code_ = std::get<0>(GetParam()); |
| child_main_function_ = std::get<1>(GetParam()); |
| |
| mojo_task_runner_ = MojoTaskRunner::Create(); |
| |
| parent_process_ = base::MakeRefCounted<TestParentProcess>( |
| mojo_task_runner_, |
| TestParentProcess::CallbacksToSetup::kScanAndCleanupRequests); |
| } |
| |
| protected: |
| int expected_exit_code_; |
| std::string child_main_function_; |
| |
| scoped_refptr<MojoTaskRunner> mojo_task_runner_; |
| scoped_refptr<TestParentProcess> parent_process_; |
| }; |
| |
| TEST_P(EngineRequestsProxyTest, TestRequest) { |
| base::test::ScopedTaskEnvironment scoped_task_environment; |
| |
| // Create resources that tests running in the sandbox will not have access to |
| // create for themselves, even before calling LowerToken. |
| chrome_cleaner_sandbox::ScopedTempRegistryKey temp_key; |
| parent_process_->AppendSwitchNative(kTempKeyPathSwitch, temp_key.Path()); |
| parent_process_->AppendSwitchNative(kTempKeyFullPathSwitch, |
| temp_key.FullyQualifiedPath()); |
| |
| std::vector<wchar_t> key_with_nulls = CreateVectorWithNulls(kKeyWithNulls); |
| ULONG disposition = 0; |
| HANDLE temp_handle = INVALID_HANDLE_VALUE; |
| chrome_cleaner_sandbox::NativeCreateKey(temp_key.Get(), &key_with_nulls, |
| &temp_handle, &disposition); |
| |
| // ScopedTempRegistryKey's destructor will try to delete this key using |
| // win::RegKey, which does not handle embedded nulls. So it must be deleted |
| // manually. |
| // TODO(joenotcharles): ScopedTempRegistryKey should do this automatically. |
| base::ScopedClosureRunner delete_temp_key(base::BindOnce( |
| [](HANDLE handle) { |
| chrome_cleaner_sandbox::NativeDeleteKey(handle); |
| ::CloseHandle(handle); |
| }, |
| temp_handle)); |
| |
| const base::FilePath windows_dir = |
| PreFetchedPaths::GetInstance()->GetWindowsFolder(); |
| parent_process_->AppendSwitchPath(kWindowsDirectorySwitch, windows_dir); |
| |
| int32_t exit_code = -1; |
| EXPECT_TRUE(parent_process_->LaunchConnectedChildProcess(child_main_function_, |
| &exit_code)); |
| EXPECT_EQ(expected_exit_code_, exit_code); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P( |
| Success, |
| EngineRequestsProxyTest, |
| testing::Combine(testing::Values(0), |
| testing::Values("GetFileAttributesTest", |
| "GetFileAttributesNoHangs", |
| "GetKnownFolderPath", |
| "GetKnownFolderPathNoHangs", |
| "GetProcesses", |
| "GetProcessesNoHangs", |
| "GetTasks", |
| "GetTasksNoHangs", |
| "GetProcessImagePath", |
| "GetProcessImagePathNoHangs", |
| "GetLoadedModules", |
| "GetLoadedModulesNoHangs", |
| "GetProcessCommandLine", |
| "GetProcessCommandLineNoHangs", |
| "GetUserInfoFromSID", |
| "GetUserInfoFromSIDNoHangs", |
| "OpenReadOnlyRegistry", |
| "OpenReadOnlyRegistryNoHangs", |
| "NtOpenReadOnlyRegistry", |
| "NtOpenReadOnlyRegistryNoHangs")), |
| GetParamNameForTest()); |
| |
| INSTANTIATE_TEST_SUITE_P( |
| ConnectionError, |
| EngineRequestsProxyTest, |
| testing::Combine( |
| testing::Values(TestChildProcess::kConnectionErrorExitCode), |
| testing::Values("GetKnownFolderPathInvalidParam")), |
| GetParamNameForTest()); |
| |
| } // namespace |
| |
| } // namespace chrome_cleaner |