Revert "Fix broken channel icon in chrome://help on CrOS" and try again
[chromium-blink-merge.git] / sandbox / win / src / broker_services.cc
blob1c72ef5200cc0e48cdf4aaf75270b5b26f4283f5
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 // Utility function to determine whether a token for the specified policy can
112 // be cached.
113 bool IsTokenCacheable(const sandbox::PolicyBase* policy) {
114 const sandbox::AppContainerAttributes* app_container =
115 policy->GetAppContainer();
117 // We cannot cache tokens with an app container or lowbox.
118 if (app_container || policy->GetLowBoxSid())
119 return false;
121 return true;
124 // Utility function to pack token values into a key for the cache map.
125 uint32_t GenerateTokenCacheKey(const sandbox::PolicyBase* policy) {
126 const size_t kTokenShift = 3;
127 uint32_t key;
129 DCHECK(IsTokenCacheable(policy));
131 // Make sure our token values aren't too large to pack into the key.
132 static_assert(sandbox::USER_LAST <= (1 << kTokenShift),
133 "TokenLevel too large");
134 static_assert(sandbox::INTEGRITY_LEVEL_LAST <= (1 << kTokenShift),
135 "IntegrityLevel too large");
136 static_assert(sizeof(key) < (kTokenShift * 3),
137 "Token key type too small");
139 // The key is the enum values shifted to avoid overlap and OR'd together.
140 key = policy->GetInitialTokenLevel();
141 key <<= kTokenShift;
142 key |= policy->GetLockdownTokenLevel();
143 key <<= kTokenShift;
144 key |= policy->GetIntegrityLevel();
146 return key;
149 } // namespace
151 namespace sandbox {
153 // TODO(rvargas): Replace this structure with a std::pair of ScopedHandles.
154 struct BrokerServicesBase::TokenPair {
155 TokenPair(base::win::ScopedHandle initial_token,
156 base::win::ScopedHandle lockdown_token)
157 : initial(initial_token.Pass()),
158 lockdown(lockdown_token.Pass()) {
161 base::win::ScopedHandle initial;
162 base::win::ScopedHandle lockdown;
165 BrokerServicesBase::BrokerServicesBase() : thread_pool_(NULL) {
168 // The broker uses a dedicated worker thread that services the job completion
169 // port to perform policy notifications and associated cleanup tasks.
170 ResultCode BrokerServicesBase::Init() {
171 if (job_port_.IsValid() || (NULL != thread_pool_))
172 return SBOX_ERROR_UNEXPECTED_CALL;
174 ::InitializeCriticalSection(&lock_);
176 job_port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0));
177 if (!job_port_.IsValid())
178 return SBOX_ERROR_GENERIC;
180 no_targets_.Set(::CreateEventW(NULL, TRUE, FALSE, NULL));
182 job_thread_.Set(::CreateThread(NULL, 0, // Default security and stack.
183 TargetEventsThread, this, NULL, NULL));
184 if (!job_thread_.IsValid())
185 return SBOX_ERROR_GENERIC;
187 return SBOX_ALL_OK;
190 // The destructor should only be called when the Broker process is terminating.
191 // Since BrokerServicesBase is a singleton, this is called from the CRT
192 // termination handlers, if this code lives on a DLL it is called during
193 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
194 // wait for threads here.
195 BrokerServicesBase::~BrokerServicesBase() {
196 // If there is no port Init() was never called successfully.
197 if (!job_port_.IsValid())
198 return;
200 // Closing the port causes, that no more Job notifications are delivered to
201 // the worker thread and also causes the thread to exit. This is what we
202 // want to do since we are going to close all outstanding Jobs and notifying
203 // the policy objects ourselves.
204 ::PostQueuedCompletionStatus(job_port_.Get(), 0, THREAD_CTRL_QUIT, FALSE);
206 if (job_thread_.IsValid() &&
207 WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_.Get(), 1000)) {
208 // Cannot clean broker services.
209 NOTREACHED();
210 return;
213 STLDeleteElements(&tracker_list_);
214 delete thread_pool_;
216 // Cancel the wait events and delete remaining peer trackers.
217 for (PeerTrackerMap::iterator it = peer_map_.begin();
218 it != peer_map_.end(); ++it) {
219 DeregisterPeerTracker(it->second);
222 ::DeleteCriticalSection(&lock_);
224 // Close any token in the cache.
225 STLDeleteValues(&token_cache_);
228 TargetPolicy* BrokerServicesBase::CreatePolicy() {
229 // If you change the type of the object being created here you must also
230 // change the downcast to it in SpawnTarget().
231 return new PolicyBase;
234 // The worker thread stays in a loop waiting for asynchronous notifications
235 // from the job objects. Right now we only care about knowing when the last
236 // process on a job terminates, but in general this is the place to tell
237 // the policy about events.
238 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
239 if (NULL == param)
240 return 1;
242 base::PlatformThread::SetName("BrokerEvent");
244 BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
245 HANDLE port = broker->job_port_.Get();
246 HANDLE no_targets = broker->no_targets_.Get();
248 int target_counter = 0;
249 ::ResetEvent(no_targets);
251 while (true) {
252 DWORD events = 0;
253 ULONG_PTR key = 0;
254 LPOVERLAPPED ovl = NULL;
256 if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE)) {
257 // this call fails if the port has been closed before we have a
258 // chance to service the last packet which is 'exit' anyway so
259 // this is not an error.
260 return 1;
263 if (key > THREAD_CTRL_LAST) {
264 // The notification comes from a job object. There are nine notifications
265 // that jobs can send and some of them depend on the job attributes set.
266 JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
268 switch (events) {
269 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
270 // The job object has signaled that the last process associated
271 // with it has terminated. Assuming there is no way for a process
272 // to appear out of thin air in this job, it safe to assume that
273 // we can tell the policy to destroy the target object, and for
274 // us to release our reference to the policy object.
275 tracker->FreeResources();
276 break;
279 case JOB_OBJECT_MSG_NEW_PROCESS: {
280 ++target_counter;
281 if (1 == target_counter) {
282 ::ResetEvent(no_targets);
284 break;
287 case JOB_OBJECT_MSG_EXIT_PROCESS:
288 case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
290 AutoLock lock(&broker->lock_);
291 broker->child_process_ids_.erase(
292 static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
294 --target_counter;
295 if (0 == target_counter)
296 ::SetEvent(no_targets);
298 DCHECK(target_counter >= 0);
299 break;
302 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
303 break;
306 case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
307 BOOL res = ::TerminateJobObject(tracker->job.Get(),
308 SBOX_FATAL_MEMORY_EXCEEDED);
309 DCHECK(res);
310 break;
313 default: {
314 NOTREACHED();
315 break;
318 } else if (THREAD_CTRL_REMOVE_PEER == key) {
319 // Remove a process from our list of peers.
320 AutoLock lock(&broker->lock_);
321 PeerTrackerMap::iterator it = broker->peer_map_.find(
322 static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
323 DeregisterPeerTracker(it->second);
324 broker->peer_map_.erase(it);
325 } else if (THREAD_CTRL_QUIT == key) {
326 // The broker object is being destroyed so the thread needs to exit.
327 return 0;
328 } else {
329 // We have not implemented more commands.
330 NOTREACHED();
334 NOTREACHED();
335 return 0;
338 // SpawnTarget does all the interesting sandbox setup and creates the target
339 // process inside the sandbox.
340 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
341 const wchar_t* command_line,
342 TargetPolicy* policy,
343 PROCESS_INFORMATION* target_info) {
344 if (!exe_path)
345 return SBOX_ERROR_BAD_PARAMS;
347 if (!policy)
348 return SBOX_ERROR_BAD_PARAMS;
350 // Even though the resources touched by SpawnTarget can be accessed in
351 // multiple threads, the method itself cannot be called from more than
352 // 1 thread. This is to protect the global variables used while setting up
353 // the child process.
354 static DWORD thread_id = ::GetCurrentThreadId();
355 DCHECK(thread_id == ::GetCurrentThreadId());
357 AutoLock lock(&lock_);
359 // This downcast is safe as long as we control CreatePolicy()
360 PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
362 if (policy_base->GetAppContainer() && policy_base->GetLowBoxSid())
363 return SBOX_ERROR_BAD_PARAMS;
365 // Construct the tokens and the job object that we are going to associate
366 // with the soon to be created target process.
367 base::win::ScopedHandle initial_token;
368 base::win::ScopedHandle lockdown_token;
369 ResultCode result = SBOX_ALL_OK;
371 if (IsTokenCacheable(policy_base)) {
372 // Create the master tokens only once and save them in a cache. That way
373 // can just duplicate them to avoid hammering LSASS on every sandboxed
374 // process launch.
375 uint32_t token_key = GenerateTokenCacheKey(policy_base);
376 TokenCacheMap::iterator it = token_cache_.find(token_key);
377 TokenPair* tokens;
378 if (it != token_cache_.end()) {
379 tokens = it->second;
380 } else {
381 result = policy_base->MakeTokens(&initial_token, &lockdown_token);
382 if (SBOX_ALL_OK != result)
383 return result;
385 tokens = new TokenPair(initial_token.Pass(), lockdown_token.Pass());
386 token_cache_[token_key] = tokens;
389 HANDLE temp_token;
390 if (!::DuplicateToken(tokens->initial.Get(), SecurityImpersonation,
391 &temp_token)) {
392 return SBOX_ERROR_GENERIC;
394 initial_token.Set(temp_token);
396 if (!::DuplicateTokenEx(tokens->lockdown.Get(), TOKEN_ALL_ACCESS, 0,
397 SecurityIdentification, TokenPrimary,
398 &temp_token)) {
399 return SBOX_ERROR_GENERIC;
401 lockdown_token.Set(temp_token);
402 } else {
403 result = policy_base->MakeTokens(&initial_token, &lockdown_token);
404 if (SBOX_ALL_OK != result)
405 return result;
408 base::win::ScopedHandle job;
409 result = policy_base->MakeJobObject(&job);
410 if (SBOX_ALL_OK != result)
411 return result;
413 // Initialize the startup information from the policy.
414 base::win::StartupInformation startup_info;
415 // The liftime of |mitigations| and |inherit_handle_list| have to be at least
416 // as long as |startup_info| because |UpdateProcThreadAttribute| requires that
417 // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is
418 // called; StartupInformation's destructor makes such a call.
419 DWORD64 mitigations;
421 std::vector<HANDLE> inherited_handle_list;
423 base::string16 desktop = policy_base->GetAlternateDesktop();
424 if (!desktop.empty()) {
425 startup_info.startup_info()->lpDesktop =
426 const_cast<wchar_t*>(desktop.c_str());
429 bool inherit_handles = false;
431 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
432 int attribute_count = 0;
433 const AppContainerAttributes* app_container =
434 policy_base->GetAppContainer();
435 if (app_container)
436 ++attribute_count;
438 size_t mitigations_size;
439 ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
440 &mitigations, &mitigations_size);
441 if (mitigations)
442 ++attribute_count;
444 HANDLE stdout_handle = policy_base->GetStdoutHandle();
445 HANDLE stderr_handle = policy_base->GetStderrHandle();
447 if (stdout_handle != INVALID_HANDLE_VALUE)
448 inherited_handle_list.push_back(stdout_handle);
450 // Handles in the list must be unique.
451 if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE)
452 inherited_handle_list.push_back(stderr_handle);
454 const HandleList& policy_handle_list = policy_base->GetHandlesBeingShared();
456 for (auto handle : policy_handle_list)
457 inherited_handle_list.push_back(handle->Get());
459 if (inherited_handle_list.size())
460 ++attribute_count;
462 if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
463 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
465 if (app_container) {
466 result = app_container->ShareForStartup(&startup_info);
467 if (SBOX_ALL_OK != result)
468 return result;
471 if (mitigations) {
472 if (!startup_info.UpdateProcThreadAttribute(
473 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations,
474 mitigations_size)) {
475 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
479 if (inherited_handle_list.size()) {
480 if (!startup_info.UpdateProcThreadAttribute(
481 PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
482 &inherited_handle_list[0],
483 sizeof(HANDLE) * inherited_handle_list.size())) {
484 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
486 startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES;
487 startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE;
488 startup_info.startup_info()->hStdOutput = stdout_handle;
489 startup_info.startup_info()->hStdError = stderr_handle;
490 // Allowing inheritance of handles is only secure now that we
491 // have limited which handles will be inherited.
492 inherit_handles = true;
496 // Construct the thread pool here in case it is expensive.
497 // The thread pool is shared by all the targets
498 if (NULL == thread_pool_)
499 thread_pool_ = new Win2kThreadPool();
501 // Create the TargetProces object and spawn the target suspended. Note that
502 // Brokerservices does not own the target object. It is owned by the Policy.
503 base::win::ScopedProcessInformation process_info;
504 TargetProcess* target = new TargetProcess(initial_token.Pass(),
505 lockdown_token.Pass(),
506 job.Get(),
507 thread_pool_);
509 DWORD win_result = target->Create(exe_path, command_line, inherit_handles,
510 policy_base->GetLowBoxSid() ? true : false,
511 startup_info, &process_info);
513 policy_base->ClearSharedHandles();
515 if (ERROR_SUCCESS != win_result) {
516 SpawnCleanup(target, win_result);
517 return SBOX_ERROR_CREATE_PROCESS;
520 // Now the policy is the owner of the target.
521 if (!policy_base->AddTarget(target)) {
522 return SpawnCleanup(target, 0);
525 // We are going to keep a pointer to the policy because we'll call it when
526 // the job object generates notifications using the completion port.
527 policy_base->AddRef();
528 if (job.IsValid()) {
529 scoped_ptr<JobTracker> tracker(new JobTracker(job.Pass(), policy_base));
531 // There is no obvious recovery after failure here. Previous version with
532 // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639
533 CHECK(AssociateCompletionPort(tracker->job.Get(), job_port_.Get(),
534 tracker.get()));
536 // Save the tracker because in cleanup we might need to force closing
537 // the Jobs.
538 tracker_list_.push_back(tracker.release());
539 child_process_ids_.insert(process_info.process_id());
540 } else {
541 // We have to signal the event once here because the completion port will
542 // never get a message that this target is being terminated thus we should
543 // not block WaitForAllTargets until we have at least one target with job.
544 if (child_process_ids_.empty())
545 ::SetEvent(no_targets_.Get());
546 // We can not track the life time of such processes and it is responsibility
547 // of the host application to make sure that spawned targets without jobs
548 // are terminated when the main application don't need them anymore.
549 // Sandbox policy engine needs to know that these processes are valid
550 // targets for e.g. BrokerDuplicateHandle so track them as peer processes.
551 AddTargetPeer(process_info.process_handle());
554 *target_info = process_info.Take();
555 return SBOX_ALL_OK;
559 ResultCode BrokerServicesBase::WaitForAllTargets() {
560 ::WaitForSingleObject(no_targets_.Get(), INFINITE);
561 return SBOX_ALL_OK;
564 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) {
565 AutoLock lock(&lock_);
566 return child_process_ids_.find(process_id) != child_process_ids_.end() ||
567 peer_map_.find(process_id) != peer_map_.end();
570 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) {
571 PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter);
572 // Don't check the return code because we this may fail (safely) at shutdown.
573 ::PostQueuedCompletionStatus(
574 peer->job_port, 0, THREAD_CTRL_REMOVE_PEER,
575 reinterpret_cast<LPOVERLAPPED>(static_cast<uintptr_t>(peer->id)));
578 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
579 scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
580 job_port_.Get()));
581 if (!peer->id)
582 return SBOX_ERROR_GENERIC;
584 HANDLE process_handle;
585 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
586 ::GetCurrentProcess(), &process_handle,
587 SYNCHRONIZE, FALSE, 0)) {
588 return SBOX_ERROR_GENERIC;
590 peer->process.Set(process_handle);
592 AutoLock lock(&lock_);
593 if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
594 return SBOX_ERROR_BAD_PARAMS;
596 if (!::RegisterWaitForSingleObject(
597 &peer->wait_object, peer->process.Get(), RemovePeer, peer.get(),
598 INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
599 peer_map_.erase(peer->id);
600 return SBOX_ERROR_GENERIC;
603 // Release the pointer since it will be cleaned up by the callback.
604 peer.release();
605 return SBOX_ALL_OK;
608 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid,
609 const wchar_t* name) {
610 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
611 return SBOX_ERROR_UNSUPPORTED;
613 base::string16 old_name = LookupAppContainer(sid);
614 if (old_name.empty())
615 return CreateAppContainer(sid, name);
617 if (old_name != name)
618 return SBOX_ERROR_INVALID_APP_CONTAINER;
620 return SBOX_ALL_OK;
623 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
624 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
625 return SBOX_ERROR_UNSUPPORTED;
627 base::string16 name = LookupAppContainer(sid);
628 if (name.empty())
629 return SBOX_ERROR_INVALID_APP_CONTAINER;
631 return DeleteAppContainer(sid);
634 } // namespace sandbox