| // Copyright (c) 2012 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 "sandbox/win/tests/common/controller.h" |
| |
| #include <string> |
| |
| #include "base/process/process.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/win/windows_version.h" |
| #include "sandbox/win/src/sandbox_factory.h" |
| |
| namespace { |
| |
| static const int kDefaultTimeout = 60000; |
| |
| // Constructs a full path to a file inside the system32 folder. |
| std::wstring MakePathToSys32(const wchar_t* name, bool is_obj_man_path) { |
| wchar_t windows_path[MAX_PATH] = {0}; |
| if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH)) |
| return std::wstring(); |
| |
| std::wstring full_path(windows_path); |
| if (full_path.empty()) |
| return full_path; |
| |
| if (is_obj_man_path) |
| full_path.insert(0, L"\\??\\"); |
| |
| full_path += L"\\system32\\"; |
| full_path += name; |
| return full_path; |
| } |
| |
| // Constructs a full path to a file inside the syswow64 folder. |
| std::wstring MakePathToSysWow64(const wchar_t* name, bool is_obj_man_path) { |
| wchar_t windows_path[MAX_PATH] = {0}; |
| if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH)) |
| return std::wstring(); |
| |
| std::wstring full_path(windows_path); |
| if (full_path.empty()) |
| return full_path; |
| |
| if (is_obj_man_path) |
| full_path.insert(0, L"\\??\\"); |
| |
| full_path += L"\\SysWOW64\\"; |
| full_path += name; |
| return full_path; |
| } |
| |
| bool IsProcessRunning(HANDLE process) { |
| DWORD exit_code = 0; |
| if (::GetExitCodeProcess(process, &exit_code)) |
| return exit_code == STILL_ACTIVE; |
| return false; |
| } |
| |
| } // namespace |
| |
| namespace sandbox { |
| |
| std::wstring MakePathToSys(const wchar_t* name, bool is_obj_man_path) { |
| return (base::win::OSInfo::GetInstance()->wow64_status() == |
| base::win::OSInfo::WOW64_ENABLED) ? |
| MakePathToSysWow64(name, is_obj_man_path) : |
| MakePathToSys32(name, is_obj_man_path); |
| } |
| |
| BrokerServices* GetBroker() { |
| static BrokerServices* broker = SandboxFactory::GetBrokerServices(); |
| static bool is_initialized = false; |
| |
| if (!broker) { |
| return NULL; |
| } |
| |
| if (!is_initialized) { |
| if (SBOX_ALL_OK != broker->Init()) |
| return NULL; |
| |
| is_initialized = true; |
| } |
| |
| return broker; |
| } |
| |
| TestRunner::TestRunner(JobLevel job_level, TokenLevel startup_token, |
| TokenLevel main_token) |
| : is_init_(false), is_async_(false), no_sandbox_(false), |
| target_process_id_(0) { |
| Init(job_level, startup_token, main_token); |
| } |
| |
| TestRunner::TestRunner() |
| : is_init_(false), is_async_(false), no_sandbox_(false), |
| target_process_id_(0) { |
| Init(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); |
| } |
| |
| void TestRunner::Init(JobLevel job_level, TokenLevel startup_token, |
| TokenLevel main_token) { |
| broker_ = NULL; |
| policy_ = NULL; |
| timeout_ = kDefaultTimeout; |
| state_ = AFTER_REVERT; |
| is_async_= false; |
| kill_on_destruction_ = true; |
| target_process_id_ = 0; |
| |
| broker_ = GetBroker(); |
| if (!broker_) |
| return; |
| |
| policy_ = broker_->CreatePolicy(); |
| if (!policy_) |
| return; |
| |
| policy_->SetJobLevel(job_level, 0); |
| policy_->SetTokenLevel(startup_token, main_token); |
| |
| is_init_ = true; |
| } |
| |
| TargetPolicy* TestRunner::GetPolicy() { |
| return policy_; |
| } |
| |
| TestRunner::~TestRunner() { |
| if (target_process_ && kill_on_destruction_) |
| ::TerminateProcess(target_process_, 0); |
| |
| if (policy_) |
| policy_->Release(); |
| } |
| |
| bool TestRunner::AddRule(TargetPolicy::SubSystem subsystem, |
| TargetPolicy::Semantics semantics, |
| const wchar_t* pattern) { |
| if (!is_init_) |
| return false; |
| |
| return (SBOX_ALL_OK == policy_->AddRule(subsystem, semantics, pattern)); |
| } |
| |
| bool TestRunner::AddRuleSys32(TargetPolicy::Semantics semantics, |
| const wchar_t* pattern) { |
| if (!is_init_) |
| return false; |
| |
| std::wstring win32_path = MakePathToSys32(pattern, false); |
| if (win32_path.empty()) |
| return false; |
| |
| if (!AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str())) |
| return false; |
| |
| if (base::win::OSInfo::GetInstance()->wow64_status() != |
| base::win::OSInfo::WOW64_ENABLED) |
| return true; |
| |
| win32_path = MakePathToSysWow64(pattern, false); |
| if (win32_path.empty()) |
| return false; |
| |
| return AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str()); |
| } |
| |
| bool TestRunner::AddFsRule(TargetPolicy::Semantics semantics, |
| const wchar_t* pattern) { |
| if (!is_init_) |
| return false; |
| |
| return AddRule(TargetPolicy::SUBSYS_FILES, semantics, pattern); |
| } |
| |
| int TestRunner::RunTest(const wchar_t* command) { |
| if (MAX_STATE > 10) |
| return SBOX_TEST_INVALID_PARAMETER; |
| |
| wchar_t state_number[2]; |
| state_number[0] = L'0' + state_; |
| state_number[1] = L'\0'; |
| std::wstring full_command(state_number); |
| full_command += L" "; |
| full_command += command; |
| |
| return InternalRunTest(full_command.c_str()); |
| } |
| |
| int TestRunner::InternalRunTest(const wchar_t* command) { |
| if (!is_init_) |
| return SBOX_TEST_FAILED_TO_RUN_TEST; |
| |
| // For simplicity TestRunner supports only one process per instance. |
| if (target_process_) { |
| if (IsProcessRunning(target_process_)) |
| return SBOX_TEST_FAILED_TO_RUN_TEST; |
| target_process_.Close(); |
| target_process_id_ = 0; |
| } |
| |
| // Get the path to the sandboxed process. |
| wchar_t prog_name[MAX_PATH]; |
| GetModuleFileNameW(NULL, prog_name, MAX_PATH); |
| |
| // Launch the sandboxed process. |
| ResultCode result = SBOX_ALL_OK; |
| PROCESS_INFORMATION target = {0}; |
| |
| std::wstring arguments(L"\""); |
| arguments += prog_name; |
| arguments += L"\" -child"; |
| arguments += no_sandbox_ ? L"-no-sandbox " : L" "; |
| arguments += command; |
| |
| if (no_sandbox_) { |
| STARTUPINFO startup_info = {sizeof(STARTUPINFO)}; |
| if (!::CreateProcessW(prog_name, &arguments[0], NULL, NULL, FALSE, 0, |
| NULL, NULL, &startup_info, &target)) { |
| return SBOX_ERROR_GENERIC; |
| } |
| broker_->AddTargetPeer(target.hProcess); |
| } else { |
| result = broker_->SpawnTarget(prog_name, arguments.c_str(), policy_, |
| &target); |
| } |
| |
| if (SBOX_ALL_OK != result) |
| return SBOX_TEST_FAILED_TO_RUN_TEST; |
| |
| ::ResumeThread(target.hThread); |
| |
| // For an asynchronous run we don't bother waiting. |
| if (is_async_) { |
| target_process_.Set(target.hProcess); |
| target_process_id_ = target.dwProcessId; |
| ::CloseHandle(target.hThread); |
| return SBOX_TEST_SUCCEEDED; |
| } |
| |
| if (::IsDebuggerPresent()) { |
| // Don't kill the target process on a time-out while we are debugging. |
| timeout_ = INFINITE; |
| } |
| |
| if (WAIT_TIMEOUT == ::WaitForSingleObject(target.hProcess, timeout_)) { |
| ::TerminateProcess(target.hProcess, SBOX_TEST_TIMED_OUT); |
| ::CloseHandle(target.hProcess); |
| ::CloseHandle(target.hThread); |
| return SBOX_TEST_TIMED_OUT; |
| } |
| |
| DWORD exit_code = SBOX_TEST_LAST_RESULT; |
| if (!::GetExitCodeProcess(target.hProcess, &exit_code)) { |
| ::CloseHandle(target.hProcess); |
| ::CloseHandle(target.hThread); |
| return SBOX_TEST_FAILED_TO_RUN_TEST; |
| } |
| |
| ::CloseHandle(target.hProcess); |
| ::CloseHandle(target.hThread); |
| |
| return exit_code; |
| } |
| |
| void TestRunner::SetTimeout(DWORD timeout_ms) { |
| timeout_ = timeout_ms; |
| } |
| |
| void TestRunner::SetTestState(SboxTestsState desired_state) { |
| state_ = desired_state; |
| } |
| |
| // This is the main procedure for the target (child) application. We'll find out |
| // the target test and call it. |
| // We expect the arguments to be: |
| // argv[1] = "-child" |
| // argv[2] = SboxTestsState when to run the command |
| // argv[3] = command to run |
| // argv[4...] = command arguments. |
| int DispatchCall(int argc, wchar_t **argv) { |
| if (argc < 4) |
| return SBOX_TEST_INVALID_PARAMETER; |
| |
| // We hard code two tests to avoid dispatch failures. |
| if (0 == _wcsicmp(argv[3], L"wait")) { |
| Sleep(INFINITE); |
| return SBOX_TEST_TIMED_OUT; |
| } |
| |
| if (0 == _wcsicmp(argv[3], L"ping")) |
| return SBOX_TEST_PING_OK; |
| |
| SboxTestsState state = static_cast<SboxTestsState>(_wtoi(argv[2])); |
| if ((state <= MIN_STATE) || (state >= MAX_STATE)) |
| return SBOX_TEST_INVALID_PARAMETER; |
| |
| HMODULE module; |
| if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | |
| GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, |
| reinterpret_cast<wchar_t*>(&DispatchCall), &module)) |
| return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; |
| |
| std::string command_name = base::SysWideToMultiByte(argv[3], CP_UTF8); |
| CommandFunction command = reinterpret_cast<CommandFunction>( |
| ::GetProcAddress(module, command_name.c_str())); |
| if (!command) |
| return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; |
| |
| if (BEFORE_INIT == state) |
| return command(argc - 4, argv + 4); |
| else if (EVERY_STATE == state) |
| command(argc - 4, argv + 4); |
| |
| TargetServices* target = SandboxFactory::GetTargetServices(); |
| if (target) { |
| if (SBOX_ALL_OK != target->Init()) |
| return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; |
| |
| if (BEFORE_REVERT == state) |
| return command(argc - 4, argv + 4); |
| else if (EVERY_STATE == state) |
| command(argc - 4, argv + 4); |
| |
| target->LowerToken(); |
| } else if (0 != _wcsicmp(argv[1], L"-child-no-sandbox")) { |
| return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; |
| } |
| |
| return command(argc - 4, argv + 4); |
| } |
| |
| } // namespace sandbox |