Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / sandbox / win / src / broker_services.cc
blob92cd0e05bc490115de3817e29cbbdcf61fed9271
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.
5 #include "sandbox/win/src/broker_services.h"
7 #include <AclAPI.h>
9 #include "base/logging.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/stl_util.h"
12 #include "base/threading/platform_thread.h"
13 #include "base/win/scoped_handle.h"
14 #include "base/win/scoped_process_information.h"
15 #include "base/win/startup_information.h"
16 #include "base/win/windows_version.h"
17 #include "sandbox/win/src/app_container.h"
18 #include "sandbox/win/src/process_mitigations.h"
19 #include "sandbox/win/src/sandbox_policy_base.h"
20 #include "sandbox/win/src/sandbox.h"
21 #include "sandbox/win/src/target_process.h"
22 #include "sandbox/win/src/win2k_threadpool.h"
23 #include "sandbox/win/src/win_utils.h"
25 namespace {
27 // Utility function to associate a completion port to a job object.
28 bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) {
29 JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port };
30 return ::SetInformationJobObject(job,
31 JobObjectAssociateCompletionPortInformation,
32 &job_acp, sizeof(job_acp))? true : false;
35 // Utility function to do the cleanup necessary when something goes wrong
36 // while in SpawnTarget and we must terminate the target process.
37 sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) {
38 if (0 == error)
39 error = ::GetLastError();
41 target->Terminate();
42 delete target;
43 ::SetLastError(error);
44 return sandbox::SBOX_ERROR_GENERIC;
47 // the different commands that you can send to the worker thread that
48 // executes TargetEventsThread().
49 enum {
50 THREAD_CTRL_NONE,
51 THREAD_CTRL_REMOVE_PEER,
52 THREAD_CTRL_QUIT,
53 THREAD_CTRL_LAST,
56 // Helper structure that allows the Broker to associate a job notification
57 // with a job object and with a policy.
58 struct JobTracker {
59 JobTracker(base::win::ScopedHandle job, sandbox::PolicyBase* policy)
60 : job(job.Pass()), policy(policy) {
62 ~JobTracker() {
63 FreeResources();
66 // Releases the Job and notifies the associated Policy object to release its
67 // resources as well.
68 void FreeResources();
70 base::win::ScopedHandle job;
71 sandbox::PolicyBase* policy;
74 void JobTracker::FreeResources() {
75 if (policy) {
76 BOOL res = ::TerminateJobObject(job.Get(), sandbox::SBOX_ALL_OK);
77 DCHECK(res);
78 // Closing the job causes the target process to be destroyed so this needs
79 // to happen before calling OnJobEmpty().
80 HANDLE stale_job_handle = job.Get();
81 job.Close();
83 // In OnJobEmpty() we don't actually use the job handle directly.
84 policy->OnJobEmpty(stale_job_handle);
85 policy->Release();
86 policy = NULL;
90 // Helper structure that allows the broker to track peer processes
91 struct PeerTracker {
92 PeerTracker(DWORD process_id, HANDLE broker_job_port)
93 : wait_object(NULL), id(process_id), job_port(broker_job_port) {
96 HANDLE wait_object;
97 base::win::ScopedHandle process;
98 DWORD id;
99 HANDLE job_port;
102 void DeregisterPeerTracker(PeerTracker* peer) {
103 // Deregistration shouldn't fail, but we leak rather than crash if it does.
104 if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) {
105 delete peer;
106 } else {
107 NOTREACHED();
111 } // namespace
113 namespace sandbox {
115 // TODO(rvargas): Replace this structure with a std::pair of ScopedHandles.
116 struct BrokerServicesBase::TokenPair {
117 TokenPair(base::win::ScopedHandle initial_token,
118 base::win::ScopedHandle lockdown_token)
119 : initial(initial_token.Pass()),
120 lockdown(lockdown_token.Pass()) {
123 base::win::ScopedHandle initial;
124 base::win::ScopedHandle lockdown;
127 BrokerServicesBase::BrokerServicesBase() : thread_pool_(NULL) {
130 // The broker uses a dedicated worker thread that services the job completion
131 // port to perform policy notifications and associated cleanup tasks.
132 ResultCode BrokerServicesBase::Init() {
133 if (job_port_.IsValid() || (NULL != thread_pool_))
134 return SBOX_ERROR_UNEXPECTED_CALL;
136 ::InitializeCriticalSection(&lock_);
138 job_port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0));
139 if (!job_port_.IsValid())
140 return SBOX_ERROR_GENERIC;
142 no_targets_.Set(::CreateEventW(NULL, TRUE, FALSE, NULL));
144 job_thread_.Set(::CreateThread(NULL, 0, // Default security and stack.
145 TargetEventsThread, this, NULL, NULL));
146 if (!job_thread_.IsValid())
147 return SBOX_ERROR_GENERIC;
149 return SBOX_ALL_OK;
152 // The destructor should only be called when the Broker process is terminating.
153 // Since BrokerServicesBase is a singleton, this is called from the CRT
154 // termination handlers, if this code lives on a DLL it is called during
155 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
156 // wait for threads here.
157 BrokerServicesBase::~BrokerServicesBase() {
158 // If there is no port Init() was never called successfully.
159 if (!job_port_.IsValid())
160 return;
162 // Closing the port causes, that no more Job notifications are delivered to
163 // the worker thread and also causes the thread to exit. This is what we
164 // want to do since we are going to close all outstanding Jobs and notifying
165 // the policy objects ourselves.
166 ::PostQueuedCompletionStatus(job_port_.Get(), 0, THREAD_CTRL_QUIT, FALSE);
168 if (job_thread_.IsValid() &&
169 WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_.Get(), 1000)) {
170 // Cannot clean broker services.
171 NOTREACHED();
172 return;
175 STLDeleteElements(&tracker_list_);
176 delete thread_pool_;
178 // Cancel the wait events and delete remaining peer trackers.
179 for (PeerTrackerMap::iterator it = peer_map_.begin();
180 it != peer_map_.end(); ++it) {
181 DeregisterPeerTracker(it->second);
184 ::DeleteCriticalSection(&lock_);
187 TargetPolicy* BrokerServicesBase::CreatePolicy() {
188 // If you change the type of the object being created here you must also
189 // change the downcast to it in SpawnTarget().
190 return new PolicyBase;
193 // The worker thread stays in a loop waiting for asynchronous notifications
194 // from the job objects. Right now we only care about knowing when the last
195 // process on a job terminates, but in general this is the place to tell
196 // the policy about events.
197 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
198 if (NULL == param)
199 return 1;
201 base::PlatformThread::SetName("BrokerEvent");
203 BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
204 HANDLE port = broker->job_port_.Get();
205 HANDLE no_targets = broker->no_targets_.Get();
207 int target_counter = 0;
208 ::ResetEvent(no_targets);
210 while (true) {
211 DWORD events = 0;
212 ULONG_PTR key = 0;
213 LPOVERLAPPED ovl = NULL;
215 if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) {
216 // this call fails if the port has been closed before we have a
217 // chance to service the last packet which is 'exit' anyway so
218 // this is not an error.
219 return 1;
222 if (key > THREAD_CTRL_LAST) {
223 // The notification comes from a job object. There are nine notifications
224 // that jobs can send and some of them depend on the job attributes set.
225 JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
227 switch (events) {
228 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
229 // The job object has signaled that the last process associated
230 // with it has terminated. Assuming there is no way for a process
231 // to appear out of thin air in this job, it safe to assume that
232 // we can tell the policy to destroy the target object, and for
233 // us to release our reference to the policy object.
234 tracker->FreeResources();
235 break;
238 case JOB_OBJECT_MSG_NEW_PROCESS: {
239 ++target_counter;
240 if (1 == target_counter) {
241 ::ResetEvent(no_targets);
243 break;
246 case JOB_OBJECT_MSG_EXIT_PROCESS:
247 case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
249 AutoLock lock(&broker->lock_);
250 broker->child_process_ids_.erase(
251 static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
253 --target_counter;
254 if (0 == target_counter)
255 ::SetEvent(no_targets);
257 DCHECK(target_counter >= 0);
258 break;
261 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
262 break;
265 case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
266 BOOL res = ::TerminateJobObject(tracker->job.Get(),
267 SBOX_FATAL_MEMORY_EXCEEDED);
268 DCHECK(res);
269 break;
272 default: {
273 NOTREACHED();
274 break;
277 } else if (THREAD_CTRL_REMOVE_PEER == key) {
278 // Remove a process from our list of peers.
279 AutoLock lock(&broker->lock_);
280 PeerTrackerMap::iterator it = broker->peer_map_.find(
281 static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
282 DeregisterPeerTracker(it->second);
283 broker->peer_map_.erase(it);
284 } else if (THREAD_CTRL_QUIT == key) {
285 // The broker object is being destroyed so the thread needs to exit.
286 return 0;
287 } else {
288 // We have not implemented more commands.
289 NOTREACHED();
293 NOTREACHED();
294 return 0;
297 // SpawnTarget does all the interesting sandbox setup and creates the target
298 // process inside the sandbox.
299 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
300 const wchar_t* command_line,
301 TargetPolicy* policy,
302 PROCESS_INFORMATION* target_info) {
303 if (!exe_path)
304 return SBOX_ERROR_BAD_PARAMS;
306 if (!policy)
307 return SBOX_ERROR_BAD_PARAMS;
309 // Even though the resources touched by SpawnTarget can be accessed in
310 // multiple threads, the method itself cannot be called from more than
311 // 1 thread. This is to protect the global variables used while setting up
312 // the child process.
313 static DWORD thread_id = ::GetCurrentThreadId();
314 DCHECK(thread_id == ::GetCurrentThreadId());
316 AutoLock lock(&lock_);
318 // This downcast is safe as long as we control CreatePolicy()
319 PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
321 if (policy_base->GetAppContainer() && policy_base->GetLowBoxSid())
322 return SBOX_ERROR_BAD_PARAMS;
324 // Construct the tokens and the job object that we are going to associate
325 // with the soon to be created target process.
326 base::win::ScopedHandle initial_token;
327 base::win::ScopedHandle lockdown_token;
328 ResultCode result = SBOX_ALL_OK;
330 result = policy_base->MakeTokens(&initial_token, &lockdown_token);
331 if (SBOX_ALL_OK != result)
332 return result;
334 base::win::ScopedHandle job;
335 result = policy_base->MakeJobObject(&job);
336 if (SBOX_ALL_OK != result)
337 return result;
339 // Initialize the startup information from the policy.
340 base::win::StartupInformation startup_info;
341 // The liftime of |mitigations| and |inherit_handle_list| have to be at least
342 // as long as |startup_info| because |UpdateProcThreadAttribute| requires that
343 // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is
344 // called; StartupInformation's destructor makes such a call.
345 DWORD64 mitigations;
347 std::vector<HANDLE> inherited_handle_list;
349 base::string16 desktop = policy_base->GetAlternateDesktop();
350 if (!desktop.empty()) {
351 startup_info.startup_info()->lpDesktop =
352 const_cast<wchar_t*>(desktop.c_str());
355 bool inherit_handles = false;
357 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
358 int attribute_count = 0;
359 const AppContainerAttributes* app_container =
360 policy_base->GetAppContainer();
361 if (app_container)
362 ++attribute_count;
364 size_t mitigations_size;
365 ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
366 &mitigations, &mitigations_size);
367 if (mitigations)
368 ++attribute_count;
370 HANDLE stdout_handle = policy_base->GetStdoutHandle();
371 HANDLE stderr_handle = policy_base->GetStderrHandle();
373 if (stdout_handle != INVALID_HANDLE_VALUE)
374 inherited_handle_list.push_back(stdout_handle);
376 // Handles in the list must be unique.
377 if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE)
378 inherited_handle_list.push_back(stderr_handle);
380 const HandleList& policy_handle_list = policy_base->GetHandlesBeingShared();
382 for (auto handle : policy_handle_list)
383 inherited_handle_list.push_back(handle->Get());
385 if (inherited_handle_list.size())
386 ++attribute_count;
388 if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
389 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
391 if (app_container) {
392 result = app_container->ShareForStartup(&startup_info);
393 if (SBOX_ALL_OK != result)
394 return result;
397 if (mitigations) {
398 if (!startup_info.UpdateProcThreadAttribute(
399 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations,
400 mitigations_size)) {
401 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
405 if (inherited_handle_list.size()) {
406 if (!startup_info.UpdateProcThreadAttribute(
407 PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
408 &inherited_handle_list[0],
409 sizeof(HANDLE) * inherited_handle_list.size())) {
410 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
412 startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES;
413 startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE;
414 startup_info.startup_info()->hStdOutput = stdout_handle;
415 startup_info.startup_info()->hStdError = stderr_handle;
416 // Allowing inheritance of handles is only secure now that we
417 // have limited which handles will be inherited.
418 inherit_handles = true;
422 // Construct the thread pool here in case it is expensive.
423 // The thread pool is shared by all the targets
424 if (NULL == thread_pool_)
425 thread_pool_ = new Win2kThreadPool();
427 // We need to temporarily mark all inherited handles as closeable. The handle
428 // tracker may have marked the handles we're passing to the child as
429 // non-closeable, but the child is getting new copies that it's allowed to
430 // close. We're about to mark these handles as closeable for this process
431 // (when we close them below in ClearSharedHandles()) but that will be too
432 // late -- there will already another copy in the child that's non-closeable.
433 // After launching we restore the non-closability of these handles. We don't
434 // have any way here to affect *only* the child's copy, as the process
435 // launching mechanism takes care of doing the duplication-with-the-same-value
436 // into the child.
437 std::vector<DWORD> inherited_handle_information(inherited_handle_list.size());
438 for (size_t i = 0; i < inherited_handle_list.size(); ++i) {
439 const HANDLE& inherited_handle = inherited_handle_list[i];
440 ::GetHandleInformation(inherited_handle, &inherited_handle_information[i]);
441 ::SetHandleInformation(inherited_handle, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0);
444 // Create the TargetProces object and spawn the target suspended. Note that
445 // Brokerservices does not own the target object. It is owned by the Policy.
446 base::win::ScopedProcessInformation process_info;
447 TargetProcess* target = new TargetProcess(initial_token.Pass(),
448 lockdown_token.Pass(),
449 job.Get(),
450 thread_pool_);
452 DWORD win_result = target->Create(exe_path, command_line, inherit_handles,
453 policy_base->GetLowBoxSid() ? true : false,
454 startup_info, &process_info);
456 // Restore the previous handle protection values.
457 for (size_t i = 0; i < inherited_handle_list.size(); ++i) {
458 ::SetHandleInformation(inherited_handle_list[i],
459 HANDLE_FLAG_PROTECT_FROM_CLOSE,
460 inherited_handle_information[i]);
463 policy_base->ClearSharedHandles();
465 if (ERROR_SUCCESS != win_result) {
466 SpawnCleanup(target, win_result);
467 return SBOX_ERROR_CREATE_PROCESS;
470 // Now the policy is the owner of the target.
471 if (!policy_base->AddTarget(target)) {
472 return SpawnCleanup(target, 0);
475 // We are going to keep a pointer to the policy because we'll call it when
476 // the job object generates notifications using the completion port.
477 policy_base->AddRef();
478 if (job.IsValid()) {
479 scoped_ptr<JobTracker> tracker(new JobTracker(job.Pass(), policy_base));
481 // There is no obvious recovery after failure here. Previous version with
482 // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639
483 CHECK(AssociateCompletionPort(tracker->job.Get(), job_port_.Get(),
484 tracker.get()));
486 // Save the tracker because in cleanup we might need to force closing
487 // the Jobs.
488 tracker_list_.push_back(tracker.release());
489 child_process_ids_.insert(process_info.process_id());
490 } else {
491 // We have to signal the event once here because the completion port will
492 // never get a message that this target is being terminated thus we should
493 // not block WaitForAllTargets until we have at least one target with job.
494 if (child_process_ids_.empty())
495 ::SetEvent(no_targets_.Get());
496 // We can not track the life time of such processes and it is responsibility
497 // of the host application to make sure that spawned targets without jobs
498 // are terminated when the main application don't need them anymore.
499 // Sandbox policy engine needs to know that these processes are valid
500 // targets for e.g. BrokerDuplicateHandle so track them as peer processes.
501 AddTargetPeer(process_info.process_handle());
504 *target_info = process_info.Take();
505 return SBOX_ALL_OK;
509 ResultCode BrokerServicesBase::WaitForAllTargets() {
510 ::WaitForSingleObject(no_targets_.Get(), INFINITE);
511 return SBOX_ALL_OK;
514 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) {
515 AutoLock lock(&lock_);
516 return child_process_ids_.find(process_id) != child_process_ids_.end() ||
517 peer_map_.find(process_id) != peer_map_.end();
520 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) {
521 PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter);
522 // Don't check the return code because we this may fail (safely) at shutdown.
523 ::PostQueuedCompletionStatus(
524 peer->job_port, 0, THREAD_CTRL_REMOVE_PEER,
525 reinterpret_cast<LPOVERLAPPED>(static_cast<uintptr_t>(peer->id)));
528 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
529 scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
530 job_port_.Get()));
531 if (!peer->id)
532 return SBOX_ERROR_GENERIC;
534 HANDLE process_handle;
535 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
536 ::GetCurrentProcess(), &process_handle,
537 SYNCHRONIZE, FALSE, 0)) {
538 return SBOX_ERROR_GENERIC;
540 peer->process.Set(process_handle);
542 AutoLock lock(&lock_);
543 if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
544 return SBOX_ERROR_BAD_PARAMS;
546 if (!::RegisterWaitForSingleObject(
547 &peer->wait_object, peer->process.Get(), RemovePeer, peer.get(),
548 INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
549 peer_map_.erase(peer->id);
550 return SBOX_ERROR_GENERIC;
553 // Release the pointer since it will be cleaned up by the callback.
554 peer.release();
555 return SBOX_ALL_OK;
558 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid,
559 const wchar_t* name) {
560 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
561 return SBOX_ERROR_UNSUPPORTED;
563 base::string16 old_name = LookupAppContainer(sid);
564 if (old_name.empty())
565 return CreateAppContainer(sid, name);
567 if (old_name != name)
568 return SBOX_ERROR_INVALID_APP_CONTAINER;
570 return SBOX_ALL_OK;
573 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
574 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
575 return SBOX_ERROR_UNSUPPORTED;
577 base::string16 name = LookupAppContainer(sid);
578 if (name.empty())
579 return SBOX_ERROR_INVALID_APP_CONTAINER;
581 return DeleteAppContainer(sid);
584 } // namespace sandbox