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