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/broker_services.h" 6 7 #include "base/logging.h" 8 #include "base/memory/scoped_ptr.h" 9 #include "base/threading/platform_thread.h" 10 #include "base/win/scoped_handle.h" 11 #include "base/win/scoped_process_information.h" 12 #include "base/win/startup_information.h" 13 #include "base/win/windows_version.h" 14 #include "sandbox/win/src/app_container.h" 15 #include "sandbox/win/src/process_mitigations.h" 16 #include "sandbox/win/src/sandbox_policy_base.h" 17 #include "sandbox/win/src/sandbox.h" 18 #include "sandbox/win/src/target_process.h" 19 #include "sandbox/win/src/win2k_threadpool.h" 20 #include "sandbox/win/src/win_utils.h" 21 22 namespace { 23 24 // Utility function to associate a completion port to a job object. 25 bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) { 26 JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port }; 27 return ::SetInformationJobObject(job, 28 JobObjectAssociateCompletionPortInformation, 29 &job_acp, sizeof(job_acp))? true : false; 30 } 31 32 // Utility function to do the cleanup necessary when something goes wrong 33 // while in SpawnTarget and we must terminate the target process. 34 sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) { 35 if (0 == error) 36 error = ::GetLastError(); 37 38 target->Terminate(); 39 delete target; 40 ::SetLastError(error); 41 return sandbox::SBOX_ERROR_GENERIC; 42 } 43 44 // the different commands that you can send to the worker thread that 45 // executes TargetEventsThread(). 46 enum { 47 THREAD_CTRL_NONE, 48 THREAD_CTRL_REMOVE_PEER, 49 THREAD_CTRL_QUIT, 50 THREAD_CTRL_LAST, 51 }; 52 53 // Helper structure that allows the Broker to associate a job notification 54 // with a job object and with a policy. 55 struct JobTracker { 56 HANDLE job; 57 sandbox::PolicyBase* policy; 58 JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy) 59 : job(cjob), policy(cpolicy) { 60 } 61 }; 62 63 // Helper structure that allows the broker to track peer processes 64 struct PeerTracker { 65 HANDLE wait_object; 66 base::win::ScopedHandle process; 67 DWORD id; 68 HANDLE job_port; 69 PeerTracker(DWORD process_id, HANDLE broker_job_port) 70 : wait_object(NULL), id(process_id), job_port(broker_job_port) { 71 } 72 }; 73 74 void DeregisterPeerTracker(PeerTracker* peer) { 75 // Deregistration shouldn't fail, but we leak rather than crash if it does. 76 if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) { 77 delete peer; 78 } else { 79 NOTREACHED(); 80 } 81 } 82 83 } // namespace 84 85 namespace sandbox { 86 87 BrokerServicesBase::BrokerServicesBase() 88 : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL), 89 job_thread_(NULL) { 90 } 91 92 // The broker uses a dedicated worker thread that services the job completion 93 // port to perform policy notifications and associated cleanup tasks. 94 ResultCode BrokerServicesBase::Init() { 95 if ((NULL != job_port_) || (NULL != thread_pool_)) 96 return SBOX_ERROR_UNEXPECTED_CALL; 97 98 ::InitializeCriticalSection(&lock_); 99 100 job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 101 if (NULL == job_port_) 102 return SBOX_ERROR_GENERIC; 103 104 no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL); 105 106 job_thread_ = ::CreateThread(NULL, 0, // Default security and stack. 107 TargetEventsThread, this, NULL, NULL); 108 if (NULL == job_thread_) 109 return SBOX_ERROR_GENERIC; 110 111 return SBOX_ALL_OK; 112 } 113 114 // The destructor should only be called when the Broker process is terminating. 115 // Since BrokerServicesBase is a singleton, this is called from the CRT 116 // termination handlers, if this code lives on a DLL it is called during 117 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot 118 // wait for threads here. 119 BrokerServicesBase::~BrokerServicesBase() { 120 // If there is no port Init() was never called successfully. 121 if (!job_port_) 122 return; 123 124 // Closing the port causes, that no more Job notifications are delivered to 125 // the worker thread and also causes the thread to exit. This is what we 126 // want to do since we are going to close all outstanding Jobs and notifying 127 // the policy objects ourselves. 128 ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE); 129 ::CloseHandle(job_port_); 130 131 if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) { 132 // Cannot clean broker services. 133 NOTREACHED(); 134 return; 135 } 136 137 JobTrackerList::iterator it; 138 for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) { 139 JobTracker* tracker = (*it); 140 FreeResources(tracker); 141 delete tracker; 142 } 143 ::CloseHandle(job_thread_); 144 delete thread_pool_; 145 ::CloseHandle(no_targets_); 146 147 // Cancel the wait events and delete remaining peer trackers. 148 for (PeerTrackerMap::iterator it = peer_map_.begin(); 149 it != peer_map_.end(); ++it) { 150 DeregisterPeerTracker(it->second); 151 } 152 153 // If job_port_ isn't NULL, assumes that the lock has been initialized. 154 if (job_port_) 155 ::DeleteCriticalSection(&lock_); 156 } 157 158 TargetPolicy* BrokerServicesBase::CreatePolicy() { 159 // If you change the type of the object being created here you must also 160 // change the downcast to it in SpawnTarget(). 161 return new PolicyBase; 162 } 163 164 void BrokerServicesBase::FreeResources(JobTracker* tracker) { 165 if (NULL != tracker->policy) { 166 BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK); 167 DCHECK(res); 168 // Closing the job causes the target process to be destroyed so this 169 // needs to happen before calling OnJobEmpty(). 170 res = ::CloseHandle(tracker->job); 171 DCHECK(res); 172 // In OnJobEmpty() we don't actually use the job handle directly. 173 tracker->policy->OnJobEmpty(tracker->job); 174 tracker->policy->Release(); 175 tracker->policy = NULL; 176 } 177 } 178 179 // The worker thread stays in a loop waiting for asynchronous notifications 180 // from the job objects. Right now we only care about knowing when the last 181 // process on a job terminates, but in general this is the place to tell 182 // the policy about events. 183 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { 184 if (NULL == param) 185 return 1; 186 187 base::PlatformThread::SetName("BrokerEvent"); 188 189 BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param); 190 HANDLE port = broker->job_port_; 191 HANDLE no_targets = broker->no_targets_; 192 193 int target_counter = 0; 194 ::ResetEvent(no_targets); 195 196 while (true) { 197 DWORD events = 0; 198 ULONG_PTR key = 0; 199 LPOVERLAPPED ovl = NULL; 200 201 if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) 202 // this call fails if the port has been closed before we have a 203 // chance to service the last packet which is 'exit' anyway so 204 // this is not an error. 205 return 1; 206 207 if (key > THREAD_CTRL_LAST) { 208 // The notification comes from a job object. There are nine notifications 209 // that jobs can send and some of them depend on the job attributes set. 210 JobTracker* tracker = reinterpret_cast<JobTracker*>(key); 211 212 switch (events) { 213 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { 214 // The job object has signaled that the last process associated 215 // with it has terminated. Assuming there is no way for a process 216 // to appear out of thin air in this job, it safe to assume that 217 // we can tell the policy to destroy the target object, and for 218 // us to release our reference to the policy object. 219 FreeResources(tracker); 220 break; 221 } 222 223 case JOB_OBJECT_MSG_NEW_PROCESS: { 224 ++target_counter; 225 if (1 == target_counter) { 226 ::ResetEvent(no_targets); 227 } 228 break; 229 } 230 231 case JOB_OBJECT_MSG_EXIT_PROCESS: 232 case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { 233 { 234 AutoLock lock(&broker->lock_); 235 broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl)); 236 } 237 --target_counter; 238 if (0 == target_counter) 239 ::SetEvent(no_targets); 240 241 DCHECK(target_counter >= 0); 242 break; 243 } 244 245 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { 246 break; 247 } 248 249 default: { 250 NOTREACHED(); 251 break; 252 } 253 } 254 } else if (THREAD_CTRL_REMOVE_PEER == key) { 255 // Remove a process from our list of peers. 256 AutoLock lock(&broker->lock_); 257 PeerTrackerMap::iterator it = 258 broker->peer_map_.find(reinterpret_cast<DWORD>(ovl)); 259 DeregisterPeerTracker(it->second); 260 broker->peer_map_.erase(it); 261 } else if (THREAD_CTRL_QUIT == key) { 262 // The broker object is being destroyed so the thread needs to exit. 263 return 0; 264 } else { 265 // We have not implemented more commands. 266 NOTREACHED(); 267 } 268 } 269 270 NOTREACHED(); 271 return 0; 272 } 273 274 // SpawnTarget does all the interesting sandbox setup and creates the target 275 // process inside the sandbox. 276 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, 277 const wchar_t* command_line, 278 TargetPolicy* policy, 279 PROCESS_INFORMATION* target_info) { 280 if (!exe_path) 281 return SBOX_ERROR_BAD_PARAMS; 282 283 if (!policy) 284 return SBOX_ERROR_BAD_PARAMS; 285 286 // Even though the resources touched by SpawnTarget can be accessed in 287 // multiple threads, the method itself cannot be called from more than 288 // 1 thread. This is to protect the global variables used while setting up 289 // the child process. 290 static DWORD thread_id = ::GetCurrentThreadId(); 291 DCHECK(thread_id == ::GetCurrentThreadId()); 292 293 AutoLock lock(&lock_); 294 295 // This downcast is safe as long as we control CreatePolicy() 296 PolicyBase* policy_base = static_cast<PolicyBase*>(policy); 297 298 // Construct the tokens and the job object that we are going to associate 299 // with the soon to be created target process. 300 HANDLE initial_token_temp; 301 HANDLE lockdown_token_temp; 302 ResultCode result = policy_base->MakeTokens(&initial_token_temp, 303 &lockdown_token_temp); 304 if (SBOX_ALL_OK != result) 305 return result; 306 307 base::win::ScopedHandle initial_token(initial_token_temp); 308 base::win::ScopedHandle lockdown_token(lockdown_token_temp); 309 310 HANDLE job_temp; 311 result = policy_base->MakeJobObject(&job_temp); 312 if (SBOX_ALL_OK != result) 313 return result; 314 315 base::win::ScopedHandle job(job_temp); 316 317 // Initialize the startup information from the policy. 318 base::win::StartupInformation startup_info; 319 string16 desktop = policy_base->GetAlternateDesktop(); 320 if (!desktop.empty()) { 321 startup_info.startup_info()->lpDesktop = 322 const_cast<wchar_t*>(desktop.c_str()); 323 } 324 325 bool inherit_handles = false; 326 if (base::win::GetVersion() >= base::win::VERSION_VISTA) { 327 int attribute_count = 0; 328 const AppContainerAttributes* app_container = 329 policy_base->GetAppContainer(); 330 if (app_container) 331 ++attribute_count; 332 333 DWORD64 mitigations; 334 size_t mitigations_size; 335 ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(), 336 &mitigations, &mitigations_size); 337 if (mitigations) 338 ++attribute_count; 339 340 HANDLE stdout_handle = policy_base->GetStdoutHandle(); 341 HANDLE stderr_handle = policy_base->GetStderrHandle(); 342 HANDLE inherit_handle_list[2]; 343 int inherit_handle_count = 0; 344 if (stdout_handle != INVALID_HANDLE_VALUE) 345 inherit_handle_list[inherit_handle_count++] = stdout_handle; 346 // Handles in the list must be unique. 347 if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) 348 inherit_handle_list[inherit_handle_count++] = stderr_handle; 349 if (inherit_handle_count) 350 ++attribute_count; 351 352 if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) 353 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; 354 355 if (app_container) { 356 result = app_container->ShareForStartup(&startup_info); 357 if (SBOX_ALL_OK != result) 358 return result; 359 } 360 361 if (mitigations) { 362 if (!startup_info.UpdateProcThreadAttribute( 363 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations, 364 mitigations_size)) { 365 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; 366 } 367 } 368 369 if (inherit_handle_count) { 370 if (!startup_info.UpdateProcThreadAttribute( 371 PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 372 inherit_handle_list, 373 sizeof(inherit_handle_list[0]) * inherit_handle_count)) { 374 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; 375 } 376 startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; 377 startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; 378 startup_info.startup_info()->hStdOutput = stdout_handle; 379 startup_info.startup_info()->hStdError = stderr_handle; 380 // Allowing inheritance of handles is only secure now that we 381 // have limited which handles will be inherited. 382 inherit_handles = true; 383 } 384 } 385 386 // Construct the thread pool here in case it is expensive. 387 // The thread pool is shared by all the targets 388 if (NULL == thread_pool_) 389 thread_pool_ = new Win2kThreadPool(); 390 391 // Create the TargetProces object and spawn the target suspended. Note that 392 // Brokerservices does not own the target object. It is owned by the Policy. 393 base::win::ScopedProcessInformation process_info; 394 TargetProcess* target = new TargetProcess(initial_token.Take(), 395 lockdown_token.Take(), 396 job, 397 thread_pool_); 398 399 DWORD win_result = target->Create(exe_path, command_line, inherit_handles, 400 startup_info, &process_info); 401 if (ERROR_SUCCESS != win_result) 402 return SpawnCleanup(target, win_result); 403 404 // Now the policy is the owner of the target. 405 if (!policy_base->AddTarget(target)) { 406 return SpawnCleanup(target, 0); 407 } 408 409 // We are going to keep a pointer to the policy because we'll call it when 410 // the job object generates notifications using the completion port. 411 policy_base->AddRef(); 412 if (job.IsValid()) { 413 scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base)); 414 if (!AssociateCompletionPort(tracker->job, job_port_, tracker.get())) 415 return SpawnCleanup(target, 0); 416 // Save the tracker because in cleanup we might need to force closing 417 // the Jobs. 418 tracker_list_.push_back(tracker.release()); 419 child_process_ids_.insert(process_info.process_id()); 420 } else { 421 // We have to signal the event once here because the completion port will 422 // never get a message that this target is being terminated thus we should 423 // not block WaitForAllTargets until we have at least one target with job. 424 if (child_process_ids_.empty()) 425 ::SetEvent(no_targets_); 426 // We can not track the life time of such processes and it is responsibility 427 // of the host application to make sure that spawned targets without jobs 428 // are terminated when the main application don't need them anymore. 429 } 430 431 *target_info = process_info.Take(); 432 return SBOX_ALL_OK; 433 } 434 435 436 ResultCode BrokerServicesBase::WaitForAllTargets() { 437 ::WaitForSingleObject(no_targets_, INFINITE); 438 return SBOX_ALL_OK; 439 } 440 441 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) { 442 AutoLock lock(&lock_); 443 return child_process_ids_.find(process_id) != child_process_ids_.end() || 444 peer_map_.find(process_id) != peer_map_.end(); 445 } 446 447 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) { 448 PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter); 449 // Don't check the return code because we this may fail (safely) at shutdown. 450 ::PostQueuedCompletionStatus(peer->job_port, 0, THREAD_CTRL_REMOVE_PEER, 451 reinterpret_cast<LPOVERLAPPED>(peer->id)); 452 } 453 454 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { 455 scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process), 456 job_port_)); 457 if (!peer->id) 458 return SBOX_ERROR_GENERIC; 459 460 HANDLE process_handle; 461 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process, 462 ::GetCurrentProcess(), &process_handle, 463 SYNCHRONIZE, FALSE, 0)) { 464 return SBOX_ERROR_GENERIC; 465 } 466 peer->process.Set(process_handle); 467 468 AutoLock lock(&lock_); 469 if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second) 470 return SBOX_ERROR_BAD_PARAMS; 471 472 if (!::RegisterWaitForSingleObject( 473 &peer->wait_object, peer->process, RemovePeer, peer.get(), INFINITE, 474 WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) { 475 peer_map_.erase(peer->id); 476 return SBOX_ERROR_GENERIC; 477 } 478 479 // Release the pointer since it will be cleaned up by the callback. 480 peer.release(); 481 return SBOX_ALL_OK; 482 } 483 484 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid, 485 const wchar_t* name) { 486 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) 487 return SBOX_ERROR_UNSUPPORTED; 488 489 string16 old_name = LookupAppContainer(sid); 490 if (old_name.empty()) 491 return CreateAppContainer(sid, name); 492 493 if (old_name != name) 494 return SBOX_ERROR_INVALID_APP_CONTAINER; 495 496 return SBOX_ALL_OK; 497 } 498 499 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) { 500 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) 501 return SBOX_ERROR_UNSUPPORTED; 502 503 string16 name = LookupAppContainer(sid); 504 if (name.empty()) 505 return SBOX_ERROR_INVALID_APP_CONTAINER; 506 507 return DeleteAppContainer(sid); 508 } 509 510 } // namespace sandbox 511