Don't show supervised user as "already on this device" while they're being imported.
[chromium-blink-merge.git] / sandbox / win / src / broker_services.cc
blob57aa51a52d7280b63d2405db9dd9f747b6aac598
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/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"
24 namespace {
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;
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();
40 target->Terminate();
41 delete target;
42 ::SetLastError(error);
43 return sandbox::SBOX_ERROR_GENERIC;
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,
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) {
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) {
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();
85 // Utility function to determine whether a token for the specified policy can
86 // be cached.
87 bool IsTokenCacheable(const sandbox::PolicyBase* policy) {
88 const sandbox::AppContainerAttributes* app_container =
89 policy->GetAppContainer();
91 // We cannot cache tokens with an app container or lowbox.
92 if (app_container || policy->GetLowBoxSid())
93 return false;
95 return true;
98 // Utility function to pack token values into a key for the cache map.
99 uint32_t GenerateTokenCacheKey(const sandbox::PolicyBase* policy) {
100 const size_t kTokenShift = 3;
101 uint32_t key;
103 DCHECK(IsTokenCacheable(policy));
105 // Make sure our token values aren't too large to pack into the key.
106 static_assert(sandbox::USER_LAST <= (1 << kTokenShift),
107 "TokenLevel too large");
108 static_assert(sandbox::INTEGRITY_LEVEL_LAST <= (1 << kTokenShift),
109 "IntegrityLevel too large");
110 static_assert(sizeof(key) < (kTokenShift * 3),
111 "Token key type too small");
113 // The key is the enum values shifted to avoid overlap and OR'd together.
114 key = policy->GetInitialTokenLevel();
115 key <<= kTokenShift;
116 key |= policy->GetLockdownTokenLevel();
117 key <<= kTokenShift;
118 key |= policy->GetIntegrityLevel();
120 return key;
123 } // namespace
125 namespace sandbox {
127 BrokerServicesBase::BrokerServicesBase()
128 : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL),
129 job_thread_(NULL) {
132 // The broker uses a dedicated worker thread that services the job completion
133 // port to perform policy notifications and associated cleanup tasks.
134 ResultCode BrokerServicesBase::Init() {
135 if ((NULL != job_port_) || (NULL != thread_pool_))
136 return SBOX_ERROR_UNEXPECTED_CALL;
138 ::InitializeCriticalSection(&lock_);
140 job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
141 if (NULL == job_port_)
142 return SBOX_ERROR_GENERIC;
144 no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL);
146 job_thread_ = ::CreateThread(NULL, 0, // Default security and stack.
147 TargetEventsThread, this, NULL, NULL);
148 if (NULL == job_thread_)
149 return SBOX_ERROR_GENERIC;
151 return SBOX_ALL_OK;
154 // The destructor should only be called when the Broker process is terminating.
155 // Since BrokerServicesBase is a singleton, this is called from the CRT
156 // termination handlers, if this code lives on a DLL it is called during
157 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
158 // wait for threads here.
159 BrokerServicesBase::~BrokerServicesBase() {
160 // If there is no port Init() was never called successfully.
161 if (!job_port_)
162 return;
164 // Closing the port causes, that no more Job notifications are delivered to
165 // the worker thread and also causes the thread to exit. This is what we
166 // want to do since we are going to close all outstanding Jobs and notifying
167 // the policy objects ourselves.
168 ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE);
169 ::CloseHandle(job_port_);
171 if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) {
172 // Cannot clean broker services.
173 NOTREACHED();
174 return;
177 JobTrackerList::iterator it;
178 for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) {
179 JobTracker* tracker = (*it);
180 FreeResources(tracker);
181 delete tracker;
183 ::CloseHandle(job_thread_);
184 delete thread_pool_;
185 ::CloseHandle(no_targets_);
187 // Cancel the wait events and delete remaining peer trackers.
188 for (PeerTrackerMap::iterator it = peer_map_.begin();
189 it != peer_map_.end(); ++it) {
190 DeregisterPeerTracker(it->second);
193 // If job_port_ isn't NULL, assumes that the lock has been initialized.
194 if (job_port_)
195 ::DeleteCriticalSection(&lock_);
197 // Close any token in the cache.
198 for (TokenCacheMap::iterator it = token_cache_.begin();
199 it != token_cache_.end(); ++it) {
200 ::CloseHandle(it->second.first);
201 ::CloseHandle(it->second.second);
205 TargetPolicy* BrokerServicesBase::CreatePolicy() {
206 // If you change the type of the object being created here you must also
207 // change the downcast to it in SpawnTarget().
208 return new PolicyBase;
211 void BrokerServicesBase::FreeResources(JobTracker* tracker) {
212 if (NULL != tracker->policy) {
213 BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK);
214 DCHECK(res);
215 // Closing the job causes the target process to be destroyed so this
216 // needs to happen before calling OnJobEmpty().
217 res = ::CloseHandle(tracker->job);
218 DCHECK(res);
219 // In OnJobEmpty() we don't actually use the job handle directly.
220 tracker->policy->OnJobEmpty(tracker->job);
221 tracker->policy->Release();
222 tracker->policy = NULL;
226 // The worker thread stays in a loop waiting for asynchronous notifications
227 // from the job objects. Right now we only care about knowing when the last
228 // process on a job terminates, but in general this is the place to tell
229 // the policy about events.
230 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
231 if (NULL == param)
232 return 1;
234 base::PlatformThread::SetName("BrokerEvent");
236 BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
237 HANDLE port = broker->job_port_;
238 HANDLE no_targets = broker->no_targets_;
240 int target_counter = 0;
241 ::ResetEvent(no_targets);
243 while (true) {
244 DWORD events = 0;
245 ULONG_PTR key = 0;
246 LPOVERLAPPED ovl = NULL;
248 if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE))
249 // this call fails if the port has been closed before we have a
250 // chance to service the last packet which is 'exit' anyway so
251 // this is not an error.
252 return 1;
254 if (key > THREAD_CTRL_LAST) {
255 // The notification comes from a job object. There are nine notifications
256 // that jobs can send and some of them depend on the job attributes set.
257 JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
259 switch (events) {
260 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
261 // The job object has signaled that the last process associated
262 // with it has terminated. Assuming there is no way for a process
263 // to appear out of thin air in this job, it safe to assume that
264 // we can tell the policy to destroy the target object, and for
265 // us to release our reference to the policy object.
266 FreeResources(tracker);
267 break;
270 case JOB_OBJECT_MSG_NEW_PROCESS: {
271 ++target_counter;
272 if (1 == target_counter) {
273 ::ResetEvent(no_targets);
275 break;
278 case JOB_OBJECT_MSG_EXIT_PROCESS:
279 case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
281 AutoLock lock(&broker->lock_);
282 broker->child_process_ids_.erase(
283 static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
285 --target_counter;
286 if (0 == target_counter)
287 ::SetEvent(no_targets);
289 DCHECK(target_counter >= 0);
290 break;
293 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
294 break;
297 case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
298 BOOL res = ::TerminateJobObject(tracker->job,
299 SBOX_FATAL_MEMORY_EXCEEDED);
300 DCHECK(res);
301 break;
304 default: {
305 NOTREACHED();
306 break;
309 } else if (THREAD_CTRL_REMOVE_PEER == key) {
310 // Remove a process from our list of peers.
311 AutoLock lock(&broker->lock_);
312 PeerTrackerMap::iterator it = broker->peer_map_.find(
313 static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)));
314 DeregisterPeerTracker(it->second);
315 broker->peer_map_.erase(it);
316 } else if (THREAD_CTRL_QUIT == key) {
317 // The broker object is being destroyed so the thread needs to exit.
318 return 0;
319 } else {
320 // We have not implemented more commands.
321 NOTREACHED();
325 NOTREACHED();
326 return 0;
329 // SpawnTarget does all the interesting sandbox setup and creates the target
330 // process inside the sandbox.
331 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
332 const wchar_t* command_line,
333 TargetPolicy* policy,
334 PROCESS_INFORMATION* target_info) {
335 if (!exe_path)
336 return SBOX_ERROR_BAD_PARAMS;
338 if (!policy)
339 return SBOX_ERROR_BAD_PARAMS;
341 // Even though the resources touched by SpawnTarget can be accessed in
342 // multiple threads, the method itself cannot be called from more than
343 // 1 thread. This is to protect the global variables used while setting up
344 // the child process.
345 static DWORD thread_id = ::GetCurrentThreadId();
346 DCHECK(thread_id == ::GetCurrentThreadId());
348 AutoLock lock(&lock_);
350 // This downcast is safe as long as we control CreatePolicy()
351 PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
353 if (policy_base->GetAppContainer() && policy_base->GetLowBoxSid())
354 return SBOX_ERROR_BAD_PARAMS;
356 // Construct the tokens and the job object that we are going to associate
357 // with the soon to be created target process.
358 HANDLE initial_token_temp;
359 HANDLE lockdown_token_temp;
360 ResultCode result = SBOX_ALL_OK;
362 if (IsTokenCacheable(policy_base)) {
363 // Create the master tokens only once and save them in a cache. That way
364 // can just duplicate them to avoid hammering LSASS on every sandboxed
365 // process launch.
366 uint32_t token_key = GenerateTokenCacheKey(policy_base);
367 TokenCacheMap::iterator it = token_cache_.find(token_key);
368 if (it != token_cache_.end()) {
369 initial_token_temp = it->second.first;
370 lockdown_token_temp = it->second.second;
371 } else {
372 result =
373 policy_base->MakeTokens(&initial_token_temp, &lockdown_token_temp);
374 if (SBOX_ALL_OK != result)
375 return result;
376 token_cache_[token_key] =
377 std::pair<HANDLE, HANDLE>(initial_token_temp, lockdown_token_temp);
380 if (!::DuplicateToken(initial_token_temp, SecurityImpersonation,
381 &initial_token_temp)) {
382 return SBOX_ERROR_GENERIC;
385 if (!::DuplicateTokenEx(lockdown_token_temp, TOKEN_ALL_ACCESS, 0,
386 SecurityIdentification, TokenPrimary,
387 &lockdown_token_temp)) {
388 return SBOX_ERROR_GENERIC;
390 } else {
391 result = policy_base->MakeTokens(&initial_token_temp, &lockdown_token_temp);
392 if (SBOX_ALL_OK != result)
393 return result;
396 base::win::ScopedHandle initial_token(initial_token_temp);
397 base::win::ScopedHandle lockdown_token(lockdown_token_temp);
399 HANDLE job_temp;
400 result = policy_base->MakeJobObject(&job_temp);
401 if (SBOX_ALL_OK != result)
402 return result;
404 base::win::ScopedHandle job(job_temp);
406 // Initialize the startup information from the policy.
407 base::win::StartupInformation startup_info;
408 // The liftime of |mitigations| and |inherit_handle_list| have to be at least
409 // as long as |startup_info| because |UpdateProcThreadAttribute| requires that
410 // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is
411 // called; StartupInformation's destructor makes such a call.
412 DWORD64 mitigations;
414 std::vector<HANDLE> inherited_handle_list;
416 base::string16 desktop = policy_base->GetAlternateDesktop();
417 if (!desktop.empty()) {
418 startup_info.startup_info()->lpDesktop =
419 const_cast<wchar_t*>(desktop.c_str());
422 bool inherit_handles = false;
424 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
425 int attribute_count = 0;
426 const AppContainerAttributes* app_container =
427 policy_base->GetAppContainer();
428 if (app_container)
429 ++attribute_count;
431 size_t mitigations_size;
432 ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
433 &mitigations, &mitigations_size);
434 if (mitigations)
435 ++attribute_count;
437 HANDLE stdout_handle = policy_base->GetStdoutHandle();
438 HANDLE stderr_handle = policy_base->GetStderrHandle();
440 if (stdout_handle != INVALID_HANDLE_VALUE)
441 inherited_handle_list.push_back(stdout_handle);
443 // Handles in the list must be unique.
444 if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE)
445 inherited_handle_list.push_back(stderr_handle);
447 HandleList policy_handle_list = policy_base->GetHandlesBeingShared();
449 for (auto handle : policy_handle_list)
450 inherited_handle_list.push_back(handle);
452 if (inherited_handle_list.size())
453 ++attribute_count;
455 if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
456 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
458 if (app_container) {
459 result = app_container->ShareForStartup(&startup_info);
460 if (SBOX_ALL_OK != result)
461 return result;
464 if (mitigations) {
465 if (!startup_info.UpdateProcThreadAttribute(
466 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations,
467 mitigations_size)) {
468 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
472 if (inherited_handle_list.size()) {
473 if (!startup_info.UpdateProcThreadAttribute(
474 PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
475 &inherited_handle_list[0],
476 sizeof(HANDLE) * inherited_handle_list.size())) {
477 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
479 startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES;
480 startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE;
481 startup_info.startup_info()->hStdOutput = stdout_handle;
482 startup_info.startup_info()->hStdError = stderr_handle;
483 // Allowing inheritance of handles is only secure now that we
484 // have limited which handles will be inherited.
485 inherit_handles = true;
489 // Construct the thread pool here in case it is expensive.
490 // The thread pool is shared by all the targets
491 if (NULL == thread_pool_)
492 thread_pool_ = new Win2kThreadPool();
494 // Create the TargetProces object and spawn the target suspended. Note that
495 // Brokerservices does not own the target object. It is owned by the Policy.
496 base::win::ScopedProcessInformation process_info;
497 TargetProcess* target = new TargetProcess(initial_token.Take(),
498 lockdown_token.Take(),
499 job.Get(),
500 thread_pool_);
502 DWORD win_result = target->Create(exe_path, command_line, inherit_handles,
503 policy_base->GetLowBoxSid() ? true : false,
504 startup_info, &process_info);
506 policy_base->ClearSharedHandles();
508 if (ERROR_SUCCESS != win_result) {
509 SpawnCleanup(target, win_result);
510 return SBOX_ERROR_CREATE_PROCESS;
513 // Now the policy is the owner of the target.
514 if (!policy_base->AddTarget(target)) {
515 return SpawnCleanup(target, 0);
518 // We are going to keep a pointer to the policy because we'll call it when
519 // the job object generates notifications using the completion port.
520 policy_base->AddRef();
521 if (job.IsValid()) {
522 scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base));
524 // There is no obvious recovery after failure here. Previous version with
525 // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639
526 CHECK(AssociateCompletionPort(tracker->job, job_port_, tracker.get()));
528 // Save the tracker because in cleanup we might need to force closing
529 // the Jobs.
530 tracker_list_.push_back(tracker.release());
531 child_process_ids_.insert(process_info.process_id());
532 } else {
533 // We have to signal the event once here because the completion port will
534 // never get a message that this target is being terminated thus we should
535 // not block WaitForAllTargets until we have at least one target with job.
536 if (child_process_ids_.empty())
537 ::SetEvent(no_targets_);
538 // We can not track the life time of such processes and it is responsibility
539 // of the host application to make sure that spawned targets without jobs
540 // are terminated when the main application don't need them anymore.
543 *target_info = process_info.Take();
544 return SBOX_ALL_OK;
548 ResultCode BrokerServicesBase::WaitForAllTargets() {
549 ::WaitForSingleObject(no_targets_, INFINITE);
550 return SBOX_ALL_OK;
553 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) {
554 AutoLock lock(&lock_);
555 return child_process_ids_.find(process_id) != child_process_ids_.end() ||
556 peer_map_.find(process_id) != peer_map_.end();
559 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) {
560 PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter);
561 // Don't check the return code because we this may fail (safely) at shutdown.
562 ::PostQueuedCompletionStatus(
563 peer->job_port, 0, THREAD_CTRL_REMOVE_PEER,
564 reinterpret_cast<LPOVERLAPPED>(static_cast<uintptr_t>(peer->id)));
567 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
568 scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
569 job_port_));
570 if (!peer->id)
571 return SBOX_ERROR_GENERIC;
573 HANDLE process_handle;
574 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
575 ::GetCurrentProcess(), &process_handle,
576 SYNCHRONIZE, FALSE, 0)) {
577 return SBOX_ERROR_GENERIC;
579 peer->process.Set(process_handle);
581 AutoLock lock(&lock_);
582 if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
583 return SBOX_ERROR_BAD_PARAMS;
585 if (!::RegisterWaitForSingleObject(
586 &peer->wait_object, peer->process.Get(), RemovePeer, peer.get(),
587 INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
588 peer_map_.erase(peer->id);
589 return SBOX_ERROR_GENERIC;
592 // Release the pointer since it will be cleaned up by the callback.
593 peer.release();
594 return SBOX_ALL_OK;
597 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid,
598 const wchar_t* name) {
599 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
600 return SBOX_ERROR_UNSUPPORTED;
602 base::string16 old_name = LookupAppContainer(sid);
603 if (old_name.empty())
604 return CreateAppContainer(sid, name);
606 if (old_name != name)
607 return SBOX_ERROR_INVALID_APP_CONTAINER;
609 return SBOX_ALL_OK;
612 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
613 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
614 return SBOX_ERROR_UNSUPPORTED;
616 base::string16 name = LookupAppContainer(sid);
617 if (name.empty())
618 return SBOX_ERROR_INVALID_APP_CONTAINER;
620 return DeleteAppContainer(sid);
623 } // namespace sandbox