1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/security/sandbox/win/src/broker_services.cc Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,510 @@ 1.4 +// Copyright (c) 2012 The Chromium Authors. All rights reserved. 1.5 +// Use of this source code is governed by a BSD-style license that can be 1.6 +// found in the LICENSE file. 1.7 + 1.8 +#include "sandbox/win/src/broker_services.h" 1.9 + 1.10 +#include "base/logging.h" 1.11 +#include "base/memory/scoped_ptr.h" 1.12 +#include "base/threading/platform_thread.h" 1.13 +#include "base/win/scoped_handle.h" 1.14 +#include "base/win/scoped_process_information.h" 1.15 +#include "base/win/startup_information.h" 1.16 +#include "base/win/windows_version.h" 1.17 +#include "sandbox/win/src/app_container.h" 1.18 +#include "sandbox/win/src/process_mitigations.h" 1.19 +#include "sandbox/win/src/sandbox_policy_base.h" 1.20 +#include "sandbox/win/src/sandbox.h" 1.21 +#include "sandbox/win/src/target_process.h" 1.22 +#include "sandbox/win/src/win2k_threadpool.h" 1.23 +#include "sandbox/win/src/win_utils.h" 1.24 + 1.25 +namespace { 1.26 + 1.27 +// Utility function to associate a completion port to a job object. 1.28 +bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) { 1.29 + JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port }; 1.30 + return ::SetInformationJobObject(job, 1.31 + JobObjectAssociateCompletionPortInformation, 1.32 + &job_acp, sizeof(job_acp))? true : false; 1.33 +} 1.34 + 1.35 +// Utility function to do the cleanup necessary when something goes wrong 1.36 +// while in SpawnTarget and we must terminate the target process. 1.37 +sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) { 1.38 + if (0 == error) 1.39 + error = ::GetLastError(); 1.40 + 1.41 + target->Terminate(); 1.42 + delete target; 1.43 + ::SetLastError(error); 1.44 + return sandbox::SBOX_ERROR_GENERIC; 1.45 +} 1.46 + 1.47 +// the different commands that you can send to the worker thread that 1.48 +// executes TargetEventsThread(). 1.49 +enum { 1.50 + THREAD_CTRL_NONE, 1.51 + THREAD_CTRL_REMOVE_PEER, 1.52 + THREAD_CTRL_QUIT, 1.53 + THREAD_CTRL_LAST, 1.54 +}; 1.55 + 1.56 +// Helper structure that allows the Broker to associate a job notification 1.57 +// with a job object and with a policy. 1.58 +struct JobTracker { 1.59 + HANDLE job; 1.60 + sandbox::PolicyBase* policy; 1.61 + JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy) 1.62 + : job(cjob), policy(cpolicy) { 1.63 + } 1.64 +}; 1.65 + 1.66 +// Helper structure that allows the broker to track peer processes 1.67 +struct PeerTracker { 1.68 + HANDLE wait_object; 1.69 + base::win::ScopedHandle process; 1.70 + DWORD id; 1.71 + HANDLE job_port; 1.72 + PeerTracker(DWORD process_id, HANDLE broker_job_port) 1.73 + : wait_object(NULL), id(process_id), job_port(broker_job_port) { 1.74 + } 1.75 +}; 1.76 + 1.77 +void DeregisterPeerTracker(PeerTracker* peer) { 1.78 + // Deregistration shouldn't fail, but we leak rather than crash if it does. 1.79 + if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) { 1.80 + delete peer; 1.81 + } else { 1.82 + NOTREACHED(); 1.83 + } 1.84 +} 1.85 + 1.86 +} // namespace 1.87 + 1.88 +namespace sandbox { 1.89 + 1.90 +BrokerServicesBase::BrokerServicesBase() 1.91 + : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL), 1.92 + job_thread_(NULL) { 1.93 +} 1.94 + 1.95 +// The broker uses a dedicated worker thread that services the job completion 1.96 +// port to perform policy notifications and associated cleanup tasks. 1.97 +ResultCode BrokerServicesBase::Init() { 1.98 + if ((NULL != job_port_) || (NULL != thread_pool_)) 1.99 + return SBOX_ERROR_UNEXPECTED_CALL; 1.100 + 1.101 + ::InitializeCriticalSection(&lock_); 1.102 + 1.103 + job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0); 1.104 + if (NULL == job_port_) 1.105 + return SBOX_ERROR_GENERIC; 1.106 + 1.107 + no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL); 1.108 + 1.109 + job_thread_ = ::CreateThread(NULL, 0, // Default security and stack. 1.110 + TargetEventsThread, this, NULL, NULL); 1.111 + if (NULL == job_thread_) 1.112 + return SBOX_ERROR_GENERIC; 1.113 + 1.114 + return SBOX_ALL_OK; 1.115 +} 1.116 + 1.117 +// The destructor should only be called when the Broker process is terminating. 1.118 +// Since BrokerServicesBase is a singleton, this is called from the CRT 1.119 +// termination handlers, if this code lives on a DLL it is called during 1.120 +// DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot 1.121 +// wait for threads here. 1.122 +BrokerServicesBase::~BrokerServicesBase() { 1.123 + // If there is no port Init() was never called successfully. 1.124 + if (!job_port_) 1.125 + return; 1.126 + 1.127 + // Closing the port causes, that no more Job notifications are delivered to 1.128 + // the worker thread and also causes the thread to exit. This is what we 1.129 + // want to do since we are going to close all outstanding Jobs and notifying 1.130 + // the policy objects ourselves. 1.131 + ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE); 1.132 + ::CloseHandle(job_port_); 1.133 + 1.134 + if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) { 1.135 + // Cannot clean broker services. 1.136 + NOTREACHED(); 1.137 + return; 1.138 + } 1.139 + 1.140 + JobTrackerList::iterator it; 1.141 + for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) { 1.142 + JobTracker* tracker = (*it); 1.143 + FreeResources(tracker); 1.144 + delete tracker; 1.145 + } 1.146 + ::CloseHandle(job_thread_); 1.147 + delete thread_pool_; 1.148 + ::CloseHandle(no_targets_); 1.149 + 1.150 + // Cancel the wait events and delete remaining peer trackers. 1.151 + for (PeerTrackerMap::iterator it = peer_map_.begin(); 1.152 + it != peer_map_.end(); ++it) { 1.153 + DeregisterPeerTracker(it->second); 1.154 + } 1.155 + 1.156 + // If job_port_ isn't NULL, assumes that the lock has been initialized. 1.157 + if (job_port_) 1.158 + ::DeleteCriticalSection(&lock_); 1.159 +} 1.160 + 1.161 +TargetPolicy* BrokerServicesBase::CreatePolicy() { 1.162 + // If you change the type of the object being created here you must also 1.163 + // change the downcast to it in SpawnTarget(). 1.164 + return new PolicyBase; 1.165 +} 1.166 + 1.167 +void BrokerServicesBase::FreeResources(JobTracker* tracker) { 1.168 + if (NULL != tracker->policy) { 1.169 + BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK); 1.170 + DCHECK(res); 1.171 + // Closing the job causes the target process to be destroyed so this 1.172 + // needs to happen before calling OnJobEmpty(). 1.173 + res = ::CloseHandle(tracker->job); 1.174 + DCHECK(res); 1.175 + // In OnJobEmpty() we don't actually use the job handle directly. 1.176 + tracker->policy->OnJobEmpty(tracker->job); 1.177 + tracker->policy->Release(); 1.178 + tracker->policy = NULL; 1.179 + } 1.180 +} 1.181 + 1.182 +// The worker thread stays in a loop waiting for asynchronous notifications 1.183 +// from the job objects. Right now we only care about knowing when the last 1.184 +// process on a job terminates, but in general this is the place to tell 1.185 +// the policy about events. 1.186 +DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) { 1.187 + if (NULL == param) 1.188 + return 1; 1.189 + 1.190 + base::PlatformThread::SetName("BrokerEvent"); 1.191 + 1.192 + BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param); 1.193 + HANDLE port = broker->job_port_; 1.194 + HANDLE no_targets = broker->no_targets_; 1.195 + 1.196 + int target_counter = 0; 1.197 + ::ResetEvent(no_targets); 1.198 + 1.199 + while (true) { 1.200 + DWORD events = 0; 1.201 + ULONG_PTR key = 0; 1.202 + LPOVERLAPPED ovl = NULL; 1.203 + 1.204 + if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) 1.205 + // this call fails if the port has been closed before we have a 1.206 + // chance to service the last packet which is 'exit' anyway so 1.207 + // this is not an error. 1.208 + return 1; 1.209 + 1.210 + if (key > THREAD_CTRL_LAST) { 1.211 + // The notification comes from a job object. There are nine notifications 1.212 + // that jobs can send and some of them depend on the job attributes set. 1.213 + JobTracker* tracker = reinterpret_cast<JobTracker*>(key); 1.214 + 1.215 + switch (events) { 1.216 + case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: { 1.217 + // The job object has signaled that the last process associated 1.218 + // with it has terminated. Assuming there is no way for a process 1.219 + // to appear out of thin air in this job, it safe to assume that 1.220 + // we can tell the policy to destroy the target object, and for 1.221 + // us to release our reference to the policy object. 1.222 + FreeResources(tracker); 1.223 + break; 1.224 + } 1.225 + 1.226 + case JOB_OBJECT_MSG_NEW_PROCESS: { 1.227 + ++target_counter; 1.228 + if (1 == target_counter) { 1.229 + ::ResetEvent(no_targets); 1.230 + } 1.231 + break; 1.232 + } 1.233 + 1.234 + case JOB_OBJECT_MSG_EXIT_PROCESS: 1.235 + case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { 1.236 + { 1.237 + AutoLock lock(&broker->lock_); 1.238 + broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl)); 1.239 + } 1.240 + --target_counter; 1.241 + if (0 == target_counter) 1.242 + ::SetEvent(no_targets); 1.243 + 1.244 + DCHECK(target_counter >= 0); 1.245 + break; 1.246 + } 1.247 + 1.248 + case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { 1.249 + break; 1.250 + } 1.251 + 1.252 + default: { 1.253 + NOTREACHED(); 1.254 + break; 1.255 + } 1.256 + } 1.257 + } else if (THREAD_CTRL_REMOVE_PEER == key) { 1.258 + // Remove a process from our list of peers. 1.259 + AutoLock lock(&broker->lock_); 1.260 + PeerTrackerMap::iterator it = 1.261 + broker->peer_map_.find(reinterpret_cast<DWORD>(ovl)); 1.262 + DeregisterPeerTracker(it->second); 1.263 + broker->peer_map_.erase(it); 1.264 + } else if (THREAD_CTRL_QUIT == key) { 1.265 + // The broker object is being destroyed so the thread needs to exit. 1.266 + return 0; 1.267 + } else { 1.268 + // We have not implemented more commands. 1.269 + NOTREACHED(); 1.270 + } 1.271 + } 1.272 + 1.273 + NOTREACHED(); 1.274 + return 0; 1.275 +} 1.276 + 1.277 +// SpawnTarget does all the interesting sandbox setup and creates the target 1.278 +// process inside the sandbox. 1.279 +ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path, 1.280 + const wchar_t* command_line, 1.281 + TargetPolicy* policy, 1.282 + PROCESS_INFORMATION* target_info) { 1.283 + if (!exe_path) 1.284 + return SBOX_ERROR_BAD_PARAMS; 1.285 + 1.286 + if (!policy) 1.287 + return SBOX_ERROR_BAD_PARAMS; 1.288 + 1.289 + // Even though the resources touched by SpawnTarget can be accessed in 1.290 + // multiple threads, the method itself cannot be called from more than 1.291 + // 1 thread. This is to protect the global variables used while setting up 1.292 + // the child process. 1.293 + static DWORD thread_id = ::GetCurrentThreadId(); 1.294 + DCHECK(thread_id == ::GetCurrentThreadId()); 1.295 + 1.296 + AutoLock lock(&lock_); 1.297 + 1.298 + // This downcast is safe as long as we control CreatePolicy() 1.299 + PolicyBase* policy_base = static_cast<PolicyBase*>(policy); 1.300 + 1.301 + // Construct the tokens and the job object that we are going to associate 1.302 + // with the soon to be created target process. 1.303 + HANDLE initial_token_temp; 1.304 + HANDLE lockdown_token_temp; 1.305 + ResultCode result = policy_base->MakeTokens(&initial_token_temp, 1.306 + &lockdown_token_temp); 1.307 + if (SBOX_ALL_OK != result) 1.308 + return result; 1.309 + 1.310 + base::win::ScopedHandle initial_token(initial_token_temp); 1.311 + base::win::ScopedHandle lockdown_token(lockdown_token_temp); 1.312 + 1.313 + HANDLE job_temp; 1.314 + result = policy_base->MakeJobObject(&job_temp); 1.315 + if (SBOX_ALL_OK != result) 1.316 + return result; 1.317 + 1.318 + base::win::ScopedHandle job(job_temp); 1.319 + 1.320 + // Initialize the startup information from the policy. 1.321 + base::win::StartupInformation startup_info; 1.322 + string16 desktop = policy_base->GetAlternateDesktop(); 1.323 + if (!desktop.empty()) { 1.324 + startup_info.startup_info()->lpDesktop = 1.325 + const_cast<wchar_t*>(desktop.c_str()); 1.326 + } 1.327 + 1.328 + bool inherit_handles = false; 1.329 + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { 1.330 + int attribute_count = 0; 1.331 + const AppContainerAttributes* app_container = 1.332 + policy_base->GetAppContainer(); 1.333 + if (app_container) 1.334 + ++attribute_count; 1.335 + 1.336 + DWORD64 mitigations; 1.337 + size_t mitigations_size; 1.338 + ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(), 1.339 + &mitigations, &mitigations_size); 1.340 + if (mitigations) 1.341 + ++attribute_count; 1.342 + 1.343 + HANDLE stdout_handle = policy_base->GetStdoutHandle(); 1.344 + HANDLE stderr_handle = policy_base->GetStderrHandle(); 1.345 + HANDLE inherit_handle_list[2]; 1.346 + int inherit_handle_count = 0; 1.347 + if (stdout_handle != INVALID_HANDLE_VALUE) 1.348 + inherit_handle_list[inherit_handle_count++] = stdout_handle; 1.349 + // Handles in the list must be unique. 1.350 + if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) 1.351 + inherit_handle_list[inherit_handle_count++] = stderr_handle; 1.352 + if (inherit_handle_count) 1.353 + ++attribute_count; 1.354 + 1.355 + if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) 1.356 + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; 1.357 + 1.358 + if (app_container) { 1.359 + result = app_container->ShareForStartup(&startup_info); 1.360 + if (SBOX_ALL_OK != result) 1.361 + return result; 1.362 + } 1.363 + 1.364 + if (mitigations) { 1.365 + if (!startup_info.UpdateProcThreadAttribute( 1.366 + PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations, 1.367 + mitigations_size)) { 1.368 + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; 1.369 + } 1.370 + } 1.371 + 1.372 + if (inherit_handle_count) { 1.373 + if (!startup_info.UpdateProcThreadAttribute( 1.374 + PROC_THREAD_ATTRIBUTE_HANDLE_LIST, 1.375 + inherit_handle_list, 1.376 + sizeof(inherit_handle_list[0]) * inherit_handle_count)) { 1.377 + return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; 1.378 + } 1.379 + startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; 1.380 + startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; 1.381 + startup_info.startup_info()->hStdOutput = stdout_handle; 1.382 + startup_info.startup_info()->hStdError = stderr_handle; 1.383 + // Allowing inheritance of handles is only secure now that we 1.384 + // have limited which handles will be inherited. 1.385 + inherit_handles = true; 1.386 + } 1.387 + } 1.388 + 1.389 + // Construct the thread pool here in case it is expensive. 1.390 + // The thread pool is shared by all the targets 1.391 + if (NULL == thread_pool_) 1.392 + thread_pool_ = new Win2kThreadPool(); 1.393 + 1.394 + // Create the TargetProces object and spawn the target suspended. Note that 1.395 + // Brokerservices does not own the target object. It is owned by the Policy. 1.396 + base::win::ScopedProcessInformation process_info; 1.397 + TargetProcess* target = new TargetProcess(initial_token.Take(), 1.398 + lockdown_token.Take(), 1.399 + job, 1.400 + thread_pool_); 1.401 + 1.402 + DWORD win_result = target->Create(exe_path, command_line, inherit_handles, 1.403 + startup_info, &process_info); 1.404 + if (ERROR_SUCCESS != win_result) 1.405 + return SpawnCleanup(target, win_result); 1.406 + 1.407 + // Now the policy is the owner of the target. 1.408 + if (!policy_base->AddTarget(target)) { 1.409 + return SpawnCleanup(target, 0); 1.410 + } 1.411 + 1.412 + // We are going to keep a pointer to the policy because we'll call it when 1.413 + // the job object generates notifications using the completion port. 1.414 + policy_base->AddRef(); 1.415 + if (job.IsValid()) { 1.416 + scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base)); 1.417 + if (!AssociateCompletionPort(tracker->job, job_port_, tracker.get())) 1.418 + return SpawnCleanup(target, 0); 1.419 + // Save the tracker because in cleanup we might need to force closing 1.420 + // the Jobs. 1.421 + tracker_list_.push_back(tracker.release()); 1.422 + child_process_ids_.insert(process_info.process_id()); 1.423 + } else { 1.424 + // We have to signal the event once here because the completion port will 1.425 + // never get a message that this target is being terminated thus we should 1.426 + // not block WaitForAllTargets until we have at least one target with job. 1.427 + if (child_process_ids_.empty()) 1.428 + ::SetEvent(no_targets_); 1.429 + // We can not track the life time of such processes and it is responsibility 1.430 + // of the host application to make sure that spawned targets without jobs 1.431 + // are terminated when the main application don't need them anymore. 1.432 + } 1.433 + 1.434 + *target_info = process_info.Take(); 1.435 + return SBOX_ALL_OK; 1.436 +} 1.437 + 1.438 + 1.439 +ResultCode BrokerServicesBase::WaitForAllTargets() { 1.440 + ::WaitForSingleObject(no_targets_, INFINITE); 1.441 + return SBOX_ALL_OK; 1.442 +} 1.443 + 1.444 +bool BrokerServicesBase::IsActiveTarget(DWORD process_id) { 1.445 + AutoLock lock(&lock_); 1.446 + return child_process_ids_.find(process_id) != child_process_ids_.end() || 1.447 + peer_map_.find(process_id) != peer_map_.end(); 1.448 +} 1.449 + 1.450 +VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) { 1.451 + PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter); 1.452 + // Don't check the return code because we this may fail (safely) at shutdown. 1.453 + ::PostQueuedCompletionStatus(peer->job_port, 0, THREAD_CTRL_REMOVE_PEER, 1.454 + reinterpret_cast<LPOVERLAPPED>(peer->id)); 1.455 +} 1.456 + 1.457 +ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { 1.458 + scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process), 1.459 + job_port_)); 1.460 + if (!peer->id) 1.461 + return SBOX_ERROR_GENERIC; 1.462 + 1.463 + HANDLE process_handle; 1.464 + if (!::DuplicateHandle(::GetCurrentProcess(), peer_process, 1.465 + ::GetCurrentProcess(), &process_handle, 1.466 + SYNCHRONIZE, FALSE, 0)) { 1.467 + return SBOX_ERROR_GENERIC; 1.468 + } 1.469 + peer->process.Set(process_handle); 1.470 + 1.471 + AutoLock lock(&lock_); 1.472 + if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second) 1.473 + return SBOX_ERROR_BAD_PARAMS; 1.474 + 1.475 + if (!::RegisterWaitForSingleObject( 1.476 + &peer->wait_object, peer->process, RemovePeer, peer.get(), INFINITE, 1.477 + WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) { 1.478 + peer_map_.erase(peer->id); 1.479 + return SBOX_ERROR_GENERIC; 1.480 + } 1.481 + 1.482 + // Release the pointer since it will be cleaned up by the callback. 1.483 + peer.release(); 1.484 + return SBOX_ALL_OK; 1.485 +} 1.486 + 1.487 +ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid, 1.488 + const wchar_t* name) { 1.489 + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) 1.490 + return SBOX_ERROR_UNSUPPORTED; 1.491 + 1.492 + string16 old_name = LookupAppContainer(sid); 1.493 + if (old_name.empty()) 1.494 + return CreateAppContainer(sid, name); 1.495 + 1.496 + if (old_name != name) 1.497 + return SBOX_ERROR_INVALID_APP_CONTAINER; 1.498 + 1.499 + return SBOX_ALL_OK; 1.500 +} 1.501 + 1.502 +ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) { 1.503 + if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) 1.504 + return SBOX_ERROR_UNSUPPORTED; 1.505 + 1.506 + string16 name = LookupAppContainer(sid); 1.507 + if (name.empty()) 1.508 + return SBOX_ERROR_INVALID_APP_CONTAINER; 1.509 + 1.510 + return DeleteAppContainer(sid); 1.511 +} 1.512 + 1.513 +} // namespace sandbox