| 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/src/target_process.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <stdint.h> | |
| 9 | |
| 10 #include <utility> | |
| 11 | |
| 12 #include "base/macros.h" | |
| 13 #include "base/memory/free_deleter.h" | |
| 14 #include "base/memory/scoped_ptr.h" | |
| 15 #include "base/win/pe_image.h" | |
| 16 #include "base/win/startup_information.h" | |
| 17 #include "base/win/windows_version.h" | |
| 18 #include "sandbox/win/src/crosscall_client.h" | |
| 19 #include "sandbox/win/src/crosscall_server.h" | |
| 20 #include "sandbox/win/src/policy_low_level.h" | |
| 21 #include "sandbox/win/src/sandbox_types.h" | |
| 22 #include "sandbox/win/src/sharedmem_ipc_server.h" | |
| 23 #include "sandbox/win/src/win_utils.h" | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 27 void CopyPolicyToTarget(const void* source, size_t size, void* dest) { | |
| 28 if (!source || !size) | |
| 29 return; | |
| 30 memcpy(dest, source, size); | |
| 31 sandbox::PolicyGlobal* policy = | |
| 32 reinterpret_cast<sandbox::PolicyGlobal*>(dest); | |
| 33 | |
| 34 size_t offset = reinterpret_cast<size_t>(source); | |
| 35 | |
| 36 for (size_t i = 0; i < sandbox::kMaxServiceCount; i++) { | |
| 37 size_t buffer = reinterpret_cast<size_t>(policy->entry[i]); | |
| 38 if (buffer) { | |
| 39 buffer -= offset; | |
| 40 policy->entry[i] = reinterpret_cast<sandbox::PolicyBuffer*>(buffer); | |
| 41 } | |
| 42 } | |
| 43 } | |
| 44 | |
| 45 } // namespace | |
| 46 | |
| 47 namespace sandbox { | |
| 48 | |
| 49 SANDBOX_INTERCEPT HANDLE g_shared_section; | |
| 50 SANDBOX_INTERCEPT size_t g_shared_IPC_size; | |
| 51 SANDBOX_INTERCEPT size_t g_shared_policy_size; | |
| 52 | |
| 53 // Returns the address of the main exe module in memory taking in account | |
| 54 // address space layout randomization. | |
| 55 void* GetBaseAddress(const wchar_t* exe_name, void* entry_point) { | |
| 56 HMODULE exe = ::LoadLibrary(exe_name); | |
| 57 if (NULL == exe) | |
| 58 return exe; | |
| 59 | |
| 60 base::win::PEImage pe(exe); | |
| 61 if (!pe.VerifyMagic()) { | |
| 62 ::FreeLibrary(exe); | |
| 63 return exe; | |
| 64 } | |
| 65 PIMAGE_NT_HEADERS nt_header = pe.GetNTHeaders(); | |
| 66 char* base = reinterpret_cast<char*>(entry_point) - | |
| 67 nt_header->OptionalHeader.AddressOfEntryPoint; | |
| 68 | |
| 69 ::FreeLibrary(exe); | |
| 70 return base; | |
| 71 } | |
| 72 | |
| 73 TargetProcess::TargetProcess(base::win::ScopedHandle initial_token, | |
| 74 base::win::ScopedHandle lockdown_token, | |
| 75 base::win::ScopedHandle lowbox_token, | |
| 76 HANDLE job, | |
| 77 ThreadProvider* thread_pool) | |
| 78 // This object owns everything initialized here except thread_pool and | |
| 79 // the job_ handle. The Job handle is closed by BrokerServices and results | |
| 80 // eventually in a call to our dtor. | |
| 81 : lockdown_token_(std::move(lockdown_token)), | |
| 82 initial_token_(std::move(initial_token)), | |
| 83 lowbox_token_(std::move(lowbox_token)), | |
| 84 job_(job), | |
| 85 thread_pool_(thread_pool), | |
| 86 base_address_(NULL) {} | |
| 87 | |
| 88 TargetProcess::~TargetProcess() { | |
| 89 DWORD exit_code = 0; | |
| 90 // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE | |
| 91 // will take effect only when the context changes. As far as the testing went, | |
| 92 // this wait was enough to switch context and kill the processes in the job. | |
| 93 // If this process is already dead, the function will return without waiting. | |
| 94 // TODO(nsylvain): If the process is still alive at the end, we should kill | |
| 95 // it. http://b/893891 | |
| 96 // For now, this wait is there only to do a best effort to prevent some leaks | |
| 97 // from showing up in purify. | |
| 98 if (sandbox_process_info_.IsValid()) { | |
| 99 ::WaitForSingleObject(sandbox_process_info_.process_handle(), 50); | |
| 100 // At this point, the target process should have been killed. Check. | |
| 101 if (!::GetExitCodeProcess(sandbox_process_info_.process_handle(), | |
| 102 &exit_code) || (STILL_ACTIVE == exit_code)) { | |
| 103 // Something went wrong. We don't know if the target is in a state where | |
| 104 // it can manage to do another IPC call. If it can, and we've destroyed | |
| 105 // the |ipc_server_|, it will crash the broker. So we intentionally leak | |
| 106 // that. | |
| 107 if (shared_section_.IsValid()) | |
| 108 shared_section_.Take(); | |
| 109 ignore_result(ipc_server_.release()); | |
| 110 sandbox_process_info_.TakeProcessHandle(); | |
| 111 return; | |
| 112 } | |
| 113 } | |
| 114 | |
| 115 // ipc_server_ references our process handle, so make sure the former is shut | |
| 116 // down before the latter is closed (by ScopedProcessInformation). | |
| 117 ipc_server_.reset(); | |
| 118 } | |
| 119 | |
| 120 // Creates the target (child) process suspended and assigns it to the job | |
| 121 // object. | |
| 122 DWORD TargetProcess::Create(const wchar_t* exe_path, | |
| 123 const wchar_t* command_line, | |
| 124 bool inherit_handles, | |
| 125 const base::win::StartupInformation& startup_info, | |
| 126 base::win::ScopedProcessInformation* target_info) { | |
| 127 if (lowbox_token_.IsValid() && | |
| 128 base::win::GetVersion() < base::win::VERSION_WIN8) { | |
| 129 // We don't allow lowbox_token below Windows 8. | |
| 130 return ERROR_INVALID_PARAMETER; | |
| 131 } | |
| 132 | |
| 133 exe_name_.reset(_wcsdup(exe_path)); | |
| 134 | |
| 135 // the command line needs to be writable by CreateProcess(). | |
| 136 scoped_ptr<wchar_t, base::FreeDeleter> cmd_line(_wcsdup(command_line)); | |
| 137 | |
| 138 // Start the target process suspended. | |
| 139 DWORD flags = | |
| 140 CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS; | |
| 141 | |
| 142 if (startup_info.has_extended_startup_info()) | |
| 143 flags |= EXTENDED_STARTUPINFO_PRESENT; | |
| 144 | |
| 145 if (job_ && base::win::GetVersion() < base::win::VERSION_WIN8) { | |
| 146 // Windows 8 implements nested jobs, but for older systems we need to | |
| 147 // break out of any job we're in to enforce our restrictions. | |
| 148 flags |= CREATE_BREAKAWAY_FROM_JOB; | |
| 149 } | |
| 150 | |
| 151 PROCESS_INFORMATION temp_process_info = {}; | |
| 152 if (!::CreateProcessAsUserW(lockdown_token_.Get(), exe_path, cmd_line.get(), | |
| 153 NULL, // No security attribute. | |
| 154 NULL, // No thread attribute. | |
| 155 inherit_handles, flags, | |
| 156 NULL, // Use the environment of the caller. | |
| 157 NULL, // Use current directory of the caller. | |
| 158 startup_info.startup_info(), | |
| 159 &temp_process_info)) { | |
| 160 return ::GetLastError(); | |
| 161 } | |
| 162 base::win::ScopedProcessInformation process_info(temp_process_info); | |
| 163 | |
| 164 DWORD win_result = ERROR_SUCCESS; | |
| 165 | |
| 166 if (job_) { | |
| 167 // Assign the suspended target to the windows job object. | |
| 168 if (!::AssignProcessToJobObject(job_, process_info.process_handle())) { | |
| 169 win_result = ::GetLastError(); | |
| 170 ::TerminateProcess(process_info.process_handle(), 0); | |
| 171 return win_result; | |
| 172 } | |
| 173 } | |
| 174 | |
| 175 if (initial_token_.IsValid()) { | |
| 176 // Change the token of the main thread of the new process for the | |
| 177 // impersonation token with more rights. This allows the target to start; | |
| 178 // otherwise it will crash too early for us to help. | |
| 179 HANDLE temp_thread = process_info.thread_handle(); | |
| 180 if (!::SetThreadToken(&temp_thread, initial_token_.Get())) { | |
| 181 win_result = ::GetLastError(); | |
| 182 // It might be a security breach if we let the target run outside the job | |
| 183 // so kill it before it causes damage. | |
| 184 ::TerminateProcess(process_info.process_handle(), 0); | |
| 185 return win_result; | |
| 186 } | |
| 187 initial_token_.Close(); | |
| 188 } | |
| 189 | |
| 190 CONTEXT context; | |
| 191 context.ContextFlags = CONTEXT_ALL; | |
| 192 if (!::GetThreadContext(process_info.thread_handle(), &context)) { | |
| 193 win_result = ::GetLastError(); | |
| 194 ::TerminateProcess(process_info.process_handle(), 0); | |
| 195 return win_result; | |
| 196 } | |
| 197 | |
| 198 #if defined(_WIN64) | |
| 199 void* entry_point = reinterpret_cast<void*>(context.Rcx); | |
| 200 #else | |
| 201 #pragma warning(push) | |
| 202 #pragma warning(disable: 4312) | |
| 203 // This cast generates a warning because it is 32 bit specific. | |
| 204 void* entry_point = reinterpret_cast<void*>(context.Eax); | |
| 205 #pragma warning(pop) | |
| 206 #endif // _WIN64 | |
| 207 | |
| 208 if (!target_info->DuplicateFrom(process_info)) { | |
| 209 win_result = ::GetLastError(); // This may or may not be correct. | |
| 210 ::TerminateProcess(process_info.process_handle(), 0); | |
| 211 return win_result; | |
| 212 } | |
| 213 | |
| 214 if (lowbox_token_.IsValid()) { | |
| 215 PROCESS_ACCESS_TOKEN process_access_token; | |
| 216 process_access_token.thread = process_info.thread_handle(); | |
| 217 process_access_token.token = lowbox_token_.Get(); | |
| 218 | |
| 219 NtSetInformationProcess SetInformationProcess = NULL; | |
| 220 ResolveNTFunctionPtr("NtSetInformationProcess", &SetInformationProcess); | |
| 221 | |
| 222 NTSTATUS status = SetInformationProcess( | |
| 223 process_info.process_handle(), | |
| 224 static_cast<PROCESS_INFORMATION_CLASS>(NtProcessInformationAccessToken), | |
| 225 &process_access_token, sizeof(process_access_token)); | |
| 226 if (!NT_SUCCESS(status)) { | |
| 227 win_result = ERROR_INVALID_TOKEN; | |
| 228 ::TerminateProcess(process_info.process_handle(), 0); // exit code | |
| 229 return win_result; | |
| 230 } | |
| 231 } | |
| 232 | |
| 233 base_address_ = GetBaseAddress(exe_path, entry_point); | |
| 234 sandbox_process_info_.Set(process_info.Take()); | |
| 235 return win_result; | |
| 236 } | |
| 237 | |
| 238 ResultCode TargetProcess::TransferVariable(const char* name, void* address, | |
| 239 size_t size) { | |
| 240 if (!sandbox_process_info_.IsValid()) | |
| 241 return SBOX_ERROR_UNEXPECTED_CALL; | |
| 242 | |
| 243 void* child_var = address; | |
| 244 | |
| 245 #if SANDBOX_EXPORTS | |
| 246 HMODULE module = ::LoadLibrary(exe_name_.get()); | |
| 247 if (NULL == module) | |
| 248 return SBOX_ERROR_GENERIC; | |
| 249 | |
| 250 child_var = ::GetProcAddress(module, name); | |
| 251 ::FreeLibrary(module); | |
| 252 | |
| 253 if (NULL == child_var) | |
| 254 return SBOX_ERROR_GENERIC; | |
| 255 | |
| 256 size_t offset = reinterpret_cast<char*>(child_var) - | |
| 257 reinterpret_cast<char*>(module); | |
| 258 child_var = reinterpret_cast<char*>(MainModule()) + offset; | |
| 259 #endif | |
| 260 | |
| 261 SIZE_T written; | |
| 262 if (!::WriteProcessMemory(sandbox_process_info_.process_handle(), | |
| 263 child_var, address, size, &written)) | |
| 264 return SBOX_ERROR_GENERIC; | |
| 265 | |
| 266 if (written != size) | |
| 267 return SBOX_ERROR_GENERIC; | |
| 268 | |
| 269 return SBOX_ALL_OK; | |
| 270 } | |
| 271 | |
| 272 // Construct the IPC server and the IPC dispatcher. When the target does | |
| 273 // an IPC it will eventually call the dispatcher. | |
| 274 DWORD TargetProcess::Init(Dispatcher* ipc_dispatcher, | |
| 275 void* policy, | |
| 276 uint32_t shared_IPC_size, | |
| 277 uint32_t shared_policy_size) { | |
| 278 // We need to map the shared memory on the target. This is necessary for | |
| 279 // any IPC that needs to take place, even if the target has not yet hit | |
| 280 // the main( ) function or even has initialized the CRT. So here we set | |
| 281 // the handle to the shared section. The target on the first IPC must do | |
| 282 // the rest, which boils down to calling MapViewofFile() | |
| 283 | |
| 284 // We use this single memory pool for IPC and for policy. | |
| 285 DWORD shared_mem_size = static_cast<DWORD>(shared_IPC_size + | |
| 286 shared_policy_size); | |
| 287 shared_section_.Set(::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, | |
| 288 PAGE_READWRITE | SEC_COMMIT, | |
| 289 0, shared_mem_size, NULL)); | |
| 290 if (!shared_section_.IsValid()) { | |
| 291 return ::GetLastError(); | |
| 292 } | |
| 293 | |
| 294 DWORD access = FILE_MAP_READ | FILE_MAP_WRITE | SECTION_QUERY; | |
| 295 HANDLE target_shared_section; | |
| 296 if (!::DuplicateHandle(::GetCurrentProcess(), shared_section_.Get(), | |
| 297 sandbox_process_info_.process_handle(), | |
| 298 &target_shared_section, access, FALSE, 0)) { | |
| 299 return ::GetLastError(); | |
| 300 } | |
| 301 | |
| 302 void* shared_memory = ::MapViewOfFile(shared_section_.Get(), | |
| 303 FILE_MAP_WRITE|FILE_MAP_READ, | |
| 304 0, 0, 0); | |
| 305 if (NULL == shared_memory) { | |
| 306 return ::GetLastError(); | |
| 307 } | |
| 308 | |
| 309 CopyPolicyToTarget(policy, shared_policy_size, | |
| 310 reinterpret_cast<char*>(shared_memory) + shared_IPC_size); | |
| 311 | |
| 312 ResultCode ret; | |
| 313 // Set the global variables in the target. These are not used on the broker. | |
| 314 g_shared_section = target_shared_section; | |
| 315 ret = TransferVariable("g_shared_section", &g_shared_section, | |
| 316 sizeof(g_shared_section)); | |
| 317 g_shared_section = NULL; | |
| 318 if (SBOX_ALL_OK != ret) { | |
| 319 return (SBOX_ERROR_GENERIC == ret)? | |
| 320 ::GetLastError() : ERROR_INVALID_FUNCTION; | |
| 321 } | |
| 322 g_shared_IPC_size = shared_IPC_size; | |
| 323 ret = TransferVariable("g_shared_IPC_size", &g_shared_IPC_size, | |
| 324 sizeof(g_shared_IPC_size)); | |
| 325 g_shared_IPC_size = 0; | |
| 326 if (SBOX_ALL_OK != ret) { | |
| 327 return (SBOX_ERROR_GENERIC == ret) ? | |
| 328 ::GetLastError() : ERROR_INVALID_FUNCTION; | |
| 329 } | |
| 330 g_shared_policy_size = shared_policy_size; | |
| 331 ret = TransferVariable("g_shared_policy_size", &g_shared_policy_size, | |
| 332 sizeof(g_shared_policy_size)); | |
| 333 g_shared_policy_size = 0; | |
| 334 if (SBOX_ALL_OK != ret) { | |
| 335 return (SBOX_ERROR_GENERIC == ret) ? | |
| 336 ::GetLastError() : ERROR_INVALID_FUNCTION; | |
| 337 } | |
| 338 | |
| 339 ipc_server_.reset( | |
| 340 new SharedMemIPCServer(sandbox_process_info_.process_handle(), | |
| 341 sandbox_process_info_.process_id(), | |
| 342 thread_pool_, ipc_dispatcher)); | |
| 343 | |
| 344 if (!ipc_server_->Init(shared_memory, shared_IPC_size, kIPCChannelSize)) | |
| 345 return ERROR_NOT_ENOUGH_MEMORY; | |
| 346 | |
| 347 // After this point we cannot use this handle anymore. | |
| 348 ::CloseHandle(sandbox_process_info_.TakeThreadHandle()); | |
| 349 | |
| 350 return ERROR_SUCCESS; | |
| 351 } | |
| 352 | |
| 353 void TargetProcess::Terminate() { | |
| 354 if (!sandbox_process_info_.IsValid()) | |
| 355 return; | |
| 356 | |
| 357 ::TerminateProcess(sandbox_process_info_.process_handle(), 0); | |
| 358 } | |
| 359 | |
| 360 TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) { | |
| 361 TargetProcess* target = | |
| 362 new TargetProcess(base::win::ScopedHandle(), base::win::ScopedHandle(), | |
| 363 base::win::ScopedHandle(), NULL, NULL); | |
| 364 PROCESS_INFORMATION process_info = {}; | |
| 365 process_info.hProcess = process; | |
| 366 target->sandbox_process_info_.Set(process_info); | |
| 367 target->base_address_ = base_address; | |
| 368 return target; | |
| 369 } | |
| 370 | |
| 371 } // namespace sandbox | |
| OLD | NEW |