| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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 "sandbox/win/tests/common/controller.h" | |
| 6 | |
| 7 #include <string> | |
| 8 | |
| 9 #include "base/memory/shared_memory.h" | |
| 10 #include "base/process/process.h" | |
| 11 #include "base/process/process_handle.h" | |
| 12 #include "base/strings/string_number_conversions.h" | |
| 13 #include "base/strings/sys_string_conversions.h" | |
| 14 #include "base/win/windows_version.h" | |
| 15 #include "sandbox/win/src/sandbox_factory.h" | |
| 16 | |
| 17 namespace { | |
| 18 | |
| 19 static const int kDefaultTimeout = 60000; | |
| 20 | |
| 21 bool IsProcessRunning(HANDLE process) { | |
| 22 DWORD exit_code = 0; | |
| 23 if (::GetExitCodeProcess(process, &exit_code)) | |
| 24 return exit_code == STILL_ACTIVE; | |
| 25 return false; | |
| 26 } | |
| 27 | |
| 28 } // namespace | |
| 29 | |
| 30 namespace sandbox { | |
| 31 | |
| 32 // Constructs a full path to a file inside the system32 folder. | |
| 33 base::string16 MakePathToSys32(const wchar_t* name, bool is_obj_man_path) { | |
| 34 wchar_t windows_path[MAX_PATH] = {0}; | |
| 35 if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH)) | |
| 36 return base::string16(); | |
| 37 | |
| 38 base::string16 full_path(windows_path); | |
| 39 if (full_path.empty()) | |
| 40 return full_path; | |
| 41 | |
| 42 if (is_obj_man_path) | |
| 43 full_path.insert(0, L"\\??\\"); | |
| 44 | |
| 45 full_path += L"\\system32\\"; | |
| 46 full_path += name; | |
| 47 return full_path; | |
| 48 } | |
| 49 | |
| 50 // Constructs a full path to a file inside the syswow64 folder. | |
| 51 base::string16 MakePathToSysWow64(const wchar_t* name, bool is_obj_man_path) { | |
| 52 wchar_t windows_path[MAX_PATH] = {0}; | |
| 53 if (0 == ::GetSystemWindowsDirectoryW(windows_path, MAX_PATH)) | |
| 54 return base::string16(); | |
| 55 | |
| 56 base::string16 full_path(windows_path); | |
| 57 if (full_path.empty()) | |
| 58 return full_path; | |
| 59 | |
| 60 if (is_obj_man_path) | |
| 61 full_path.insert(0, L"\\??\\"); | |
| 62 | |
| 63 full_path += L"\\SysWOW64\\"; | |
| 64 full_path += name; | |
| 65 return full_path; | |
| 66 } | |
| 67 | |
| 68 base::string16 MakePathToSys(const wchar_t* name, bool is_obj_man_path) { | |
| 69 return (base::win::OSInfo::GetInstance()->wow64_status() == | |
| 70 base::win::OSInfo::WOW64_ENABLED) ? | |
| 71 MakePathToSysWow64(name, is_obj_man_path) : | |
| 72 MakePathToSys32(name, is_obj_man_path); | |
| 73 } | |
| 74 | |
| 75 BrokerServices* GetBroker() { | |
| 76 static BrokerServices* broker = SandboxFactory::GetBrokerServices(); | |
| 77 static bool is_initialized = false; | |
| 78 | |
| 79 if (!broker) { | |
| 80 return NULL; | |
| 81 } | |
| 82 | |
| 83 if (!is_initialized) { | |
| 84 if (SBOX_ALL_OK != broker->Init()) | |
| 85 return NULL; | |
| 86 | |
| 87 is_initialized = true; | |
| 88 } | |
| 89 | |
| 90 return broker; | |
| 91 } | |
| 92 | |
| 93 TestRunner::TestRunner(JobLevel job_level, | |
| 94 TokenLevel startup_token, | |
| 95 TokenLevel main_token) | |
| 96 : is_init_(false), | |
| 97 is_async_(false), | |
| 98 no_sandbox_(false), | |
| 99 disable_csrss_(true), | |
| 100 target_process_id_(0) { | |
| 101 Init(job_level, startup_token, main_token); | |
| 102 } | |
| 103 | |
| 104 TestRunner::TestRunner() | |
| 105 : is_init_(false), | |
| 106 is_async_(false), | |
| 107 no_sandbox_(false), | |
| 108 disable_csrss_(true), | |
| 109 target_process_id_(0) { | |
| 110 Init(JOB_LOCKDOWN, USER_RESTRICTED_SAME_ACCESS, USER_LOCKDOWN); | |
| 111 } | |
| 112 | |
| 113 void TestRunner::Init(JobLevel job_level, | |
| 114 TokenLevel startup_token, | |
| 115 TokenLevel main_token) { | |
| 116 broker_ = NULL; | |
| 117 policy_ = NULL; | |
| 118 timeout_ = kDefaultTimeout; | |
| 119 state_ = AFTER_REVERT; | |
| 120 is_async_= false; | |
| 121 kill_on_destruction_ = true; | |
| 122 target_process_id_ = 0; | |
| 123 | |
| 124 broker_ = GetBroker(); | |
| 125 if (!broker_) | |
| 126 return; | |
| 127 | |
| 128 policy_ = broker_->CreatePolicy(); | |
| 129 if (!policy_) | |
| 130 return; | |
| 131 | |
| 132 policy_->SetJobLevel(job_level, 0); | |
| 133 policy_->SetTokenLevel(startup_token, main_token); | |
| 134 | |
| 135 is_init_ = true; | |
| 136 } | |
| 137 | |
| 138 TargetPolicy* TestRunner::GetPolicy() { | |
| 139 return policy_; | |
| 140 } | |
| 141 | |
| 142 TestRunner::~TestRunner() { | |
| 143 if (target_process_.IsValid() && kill_on_destruction_) | |
| 144 ::TerminateProcess(target_process_.Get(), 0); | |
| 145 | |
| 146 if (policy_) | |
| 147 policy_->Release(); | |
| 148 } | |
| 149 | |
| 150 bool TestRunner::AddRule(TargetPolicy::SubSystem subsystem, | |
| 151 TargetPolicy::Semantics semantics, | |
| 152 const wchar_t* pattern) { | |
| 153 if (!is_init_) | |
| 154 return false; | |
| 155 | |
| 156 return (SBOX_ALL_OK == policy_->AddRule(subsystem, semantics, pattern)); | |
| 157 } | |
| 158 | |
| 159 bool TestRunner::AddRuleSys32(TargetPolicy::Semantics semantics, | |
| 160 const wchar_t* pattern) { | |
| 161 if (!is_init_) | |
| 162 return false; | |
| 163 | |
| 164 base::string16 win32_path = MakePathToSys32(pattern, false); | |
| 165 if (win32_path.empty()) | |
| 166 return false; | |
| 167 | |
| 168 if (!AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str())) | |
| 169 return false; | |
| 170 | |
| 171 if (base::win::OSInfo::GetInstance()->wow64_status() != | |
| 172 base::win::OSInfo::WOW64_ENABLED) | |
| 173 return true; | |
| 174 | |
| 175 win32_path = MakePathToSysWow64(pattern, false); | |
| 176 if (win32_path.empty()) | |
| 177 return false; | |
| 178 | |
| 179 return AddRule(TargetPolicy::SUBSYS_FILES, semantics, win32_path.c_str()); | |
| 180 } | |
| 181 | |
| 182 bool TestRunner::AddFsRule(TargetPolicy::Semantics semantics, | |
| 183 const wchar_t* pattern) { | |
| 184 if (!is_init_) | |
| 185 return false; | |
| 186 | |
| 187 return AddRule(TargetPolicy::SUBSYS_FILES, semantics, pattern); | |
| 188 } | |
| 189 | |
| 190 int TestRunner::RunTest(const wchar_t* command) { | |
| 191 if (MAX_STATE > 10) | |
| 192 return SBOX_TEST_INVALID_PARAMETER; | |
| 193 | |
| 194 wchar_t state_number[2]; | |
| 195 state_number[0] = static_cast<wchar_t>(L'0' + state_); | |
| 196 state_number[1] = L'\0'; | |
| 197 base::string16 full_command(state_number); | |
| 198 full_command += L" "; | |
| 199 full_command += command; | |
| 200 | |
| 201 return InternalRunTest(full_command.c_str()); | |
| 202 } | |
| 203 | |
| 204 int TestRunner::InternalRunTest(const wchar_t* command) { | |
| 205 if (!is_init_) | |
| 206 return SBOX_TEST_FAILED_TO_RUN_TEST; | |
| 207 | |
| 208 // For simplicity TestRunner supports only one process per instance. | |
| 209 if (target_process_.IsValid()) { | |
| 210 if (IsProcessRunning(target_process_.Get())) | |
| 211 return SBOX_TEST_FAILED_TO_RUN_TEST; | |
| 212 target_process_.Close(); | |
| 213 target_process_id_ = 0; | |
| 214 } | |
| 215 | |
| 216 if (disable_csrss_) | |
| 217 policy_->SetDisconnectCsrss(); | |
| 218 | |
| 219 // Get the path to the sandboxed process. | |
| 220 wchar_t prog_name[MAX_PATH]; | |
| 221 GetModuleFileNameW(NULL, prog_name, MAX_PATH); | |
| 222 | |
| 223 // Launch the sandboxed process. | |
| 224 ResultCode result = SBOX_ALL_OK; | |
| 225 PROCESS_INFORMATION target = {0}; | |
| 226 | |
| 227 base::string16 arguments(L"\""); | |
| 228 arguments += prog_name; | |
| 229 arguments += L"\" -child"; | |
| 230 arguments += no_sandbox_ ? L"-no-sandbox " : L" "; | |
| 231 arguments += command; | |
| 232 | |
| 233 if (no_sandbox_) { | |
| 234 STARTUPINFO startup_info = {sizeof(STARTUPINFO)}; | |
| 235 if (!::CreateProcessW(prog_name, &arguments[0], NULL, NULL, FALSE, 0, | |
| 236 NULL, NULL, &startup_info, &target)) { | |
| 237 return SBOX_ERROR_GENERIC; | |
| 238 } | |
| 239 broker_->AddTargetPeer(target.hProcess); | |
| 240 } else { | |
| 241 result = broker_->SpawnTarget(prog_name, arguments.c_str(), policy_, | |
| 242 &target); | |
| 243 } | |
| 244 | |
| 245 if (SBOX_ALL_OK != result) | |
| 246 return SBOX_TEST_FAILED_TO_RUN_TEST; | |
| 247 | |
| 248 ::ResumeThread(target.hThread); | |
| 249 | |
| 250 // For an asynchronous run we don't bother waiting. | |
| 251 if (is_async_) { | |
| 252 target_process_.Set(target.hProcess); | |
| 253 target_process_id_ = target.dwProcessId; | |
| 254 ::CloseHandle(target.hThread); | |
| 255 return SBOX_TEST_SUCCEEDED; | |
| 256 } | |
| 257 | |
| 258 if (::IsDebuggerPresent()) { | |
| 259 // Don't kill the target process on a time-out while we are debugging. | |
| 260 timeout_ = INFINITE; | |
| 261 } | |
| 262 | |
| 263 if (WAIT_TIMEOUT == ::WaitForSingleObject(target.hProcess, timeout_)) { | |
| 264 ::TerminateProcess(target.hProcess, static_cast<UINT>(SBOX_TEST_TIMED_OUT)); | |
| 265 ::CloseHandle(target.hProcess); | |
| 266 ::CloseHandle(target.hThread); | |
| 267 return SBOX_TEST_TIMED_OUT; | |
| 268 } | |
| 269 | |
| 270 DWORD exit_code = static_cast<DWORD>(SBOX_TEST_LAST_RESULT); | |
| 271 if (!::GetExitCodeProcess(target.hProcess, &exit_code)) { | |
| 272 ::CloseHandle(target.hProcess); | |
| 273 ::CloseHandle(target.hThread); | |
| 274 return SBOX_TEST_FAILED_TO_RUN_TEST; | |
| 275 } | |
| 276 | |
| 277 ::CloseHandle(target.hProcess); | |
| 278 ::CloseHandle(target.hThread); | |
| 279 | |
| 280 return exit_code; | |
| 281 } | |
| 282 | |
| 283 void TestRunner::SetTimeout(DWORD timeout_ms) { | |
| 284 timeout_ = timeout_ms; | |
| 285 } | |
| 286 | |
| 287 void TestRunner::SetTestState(SboxTestsState desired_state) { | |
| 288 state_ = desired_state; | |
| 289 } | |
| 290 | |
| 291 // This is the main procedure for the target (child) application. We'll find out | |
| 292 // the target test and call it. | |
| 293 // We expect the arguments to be: | |
| 294 // argv[1] = "-child" | |
| 295 // argv[2] = SboxTestsState when to run the command | |
| 296 // argv[3] = command to run | |
| 297 // argv[4...] = command arguments. | |
| 298 int DispatchCall(int argc, wchar_t **argv) { | |
| 299 if (argc < 4) | |
| 300 return SBOX_TEST_INVALID_PARAMETER; | |
| 301 | |
| 302 // We hard code two tests to avoid dispatch failures. | |
| 303 if (0 == _wcsicmp(argv[3], L"wait")) { | |
| 304 Sleep(INFINITE); | |
| 305 return SBOX_TEST_TIMED_OUT; | |
| 306 } | |
| 307 | |
| 308 if (0 == _wcsicmp(argv[3], L"ping")) | |
| 309 return SBOX_TEST_PING_OK; | |
| 310 | |
| 311 // If the caller shared a shared memory handle with us attempt to open it | |
| 312 // in read only mode and sleep infinitely if we succeed. | |
| 313 if (0 == _wcsicmp(argv[3], L"shared_memory_handle")) { | |
| 314 HANDLE raw_handle = nullptr; | |
| 315 base::StringToUint(argv[4], reinterpret_cast<unsigned int*>(&raw_handle)); | |
| 316 if (raw_handle == nullptr) | |
| 317 return SBOX_TEST_INVALID_PARAMETER; | |
| 318 base::SharedMemoryHandle shared_handle(raw_handle, | |
| 319 base::GetCurrentProcId()); | |
| 320 base::SharedMemory read_only_view(shared_handle, true); | |
| 321 if (!read_only_view.Map(0)) | |
| 322 return SBOX_TEST_INVALID_PARAMETER; | |
| 323 std::string contents(reinterpret_cast<char*>(read_only_view.memory())); | |
| 324 if (contents != "Hello World") | |
| 325 return SBOX_TEST_INVALID_PARAMETER; | |
| 326 Sleep(INFINITE); | |
| 327 return SBOX_TEST_TIMED_OUT; | |
| 328 } | |
| 329 | |
| 330 SboxTestsState state = static_cast<SboxTestsState>(_wtoi(argv[2])); | |
| 331 if ((state <= MIN_STATE) || (state >= MAX_STATE)) | |
| 332 return SBOX_TEST_INVALID_PARAMETER; | |
| 333 | |
| 334 HMODULE module; | |
| 335 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | | |
| 336 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, | |
| 337 reinterpret_cast<wchar_t*>(&DispatchCall), &module)) | |
| 338 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; | |
| 339 | |
| 340 std::string command_name = base::SysWideToMultiByte(argv[3], CP_UTF8); | |
| 341 CommandFunction command = reinterpret_cast<CommandFunction>( | |
| 342 ::GetProcAddress(module, command_name.c_str())); | |
| 343 if (!command) | |
| 344 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; | |
| 345 | |
| 346 if (BEFORE_INIT == state) | |
| 347 return command(argc - 4, argv + 4); | |
| 348 else if (EVERY_STATE == state) | |
| 349 command(argc - 4, argv + 4); | |
| 350 | |
| 351 TargetServices* target = SandboxFactory::GetTargetServices(); | |
| 352 if (target) { | |
| 353 if (SBOX_ALL_OK != target->Init()) | |
| 354 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; | |
| 355 | |
| 356 if (BEFORE_REVERT == state) | |
| 357 return command(argc - 4, argv + 4); | |
| 358 else if (EVERY_STATE == state) | |
| 359 command(argc - 4, argv + 4); | |
| 360 | |
| 361 #if defined(ADDRESS_SANITIZER) | |
| 362 // Bind and leak dbghelp.dll before the token is lowered, otherwise | |
| 363 // AddressSanitizer will crash when trying to symbolize a report. | |
| 364 if (!LoadLibraryA("dbghelp.dll")) | |
| 365 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; | |
| 366 #endif | |
| 367 | |
| 368 target->LowerToken(); | |
| 369 } else if (0 != _wcsicmp(argv[1], L"-child-no-sandbox")) { | |
| 370 return SBOX_TEST_FAILED_TO_EXECUTE_COMMAND; | |
| 371 } | |
| 372 | |
| 373 return command(argc - 4, argv + 4); | |
| 374 } | |
| 375 | |
| 376 } // namespace sandbox | |
| OLD | NEW |