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"
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"
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
) {
39 error
= ::GetLastError();
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().
51 THREAD_CTRL_REMOVE_PEER
,
56 // Helper structure that allows the Broker to associate a job notification
57 // with a job object and with a policy.
59 JobTracker(base::win::ScopedHandle job
, sandbox::PolicyBase
* policy
)
60 : job(job
.Pass()), policy(policy
) {
66 // Releases the Job and notifies the associated Policy object to release its
70 base::win::ScopedHandle job
;
71 sandbox::PolicyBase
* policy
;
74 void JobTracker::FreeResources() {
76 BOOL res
= ::TerminateJobObject(job
.Get(), sandbox::SBOX_ALL_OK
);
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();
83 // In OnJobEmpty() we don't actually use the job handle directly.
84 policy
->OnJobEmpty(stale_job_handle
);
90 // Helper structure that allows the broker to track peer processes
92 PeerTracker(DWORD process_id
, HANDLE broker_job_port
)
93 : wait_object(NULL
), id(process_id
), job_port(broker_job_port
) {
97 base::win::ScopedHandle process
;
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
)) {
111 // Utility function to determine whether a token for the specified policy can
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())
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;
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();
142 key
|= policy
->GetLockdownTokenLevel();
144 key
|= policy
->GetIntegrityLevel();
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
;
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())
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.
213 STLDeleteElements(&tracker_list_
);
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
) {
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
);
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.
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
);
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();
279 case JOB_OBJECT_MSG_NEW_PROCESS
: {
281 if (1 == target_counter
) {
282 ::ResetEvent(no_targets
);
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
)));
295 if (0 == target_counter
)
296 ::SetEvent(no_targets
);
298 DCHECK(target_counter
>= 0);
302 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT
: {
306 case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT
: {
307 BOOL res
= ::TerminateJobObject(tracker
->job
.Get(),
308 SBOX_FATAL_MEMORY_EXCEEDED
);
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.
329 // We have not implemented more commands.
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
) {
345 return SBOX_ERROR_BAD_PARAMS
;
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
375 uint32_t token_key
= GenerateTokenCacheKey(policy_base
);
376 TokenCacheMap::iterator it
= token_cache_
.find(token_key
);
378 if (it
!= token_cache_
.end()) {
381 result
= policy_base
->MakeTokens(&initial_token
, &lockdown_token
);
382 if (SBOX_ALL_OK
!= result
)
385 tokens
= new TokenPair(initial_token
.Pass(), lockdown_token
.Pass());
386 token_cache_
[token_key
] = tokens
;
390 if (!::DuplicateToken(tokens
->initial
.Get(), SecurityImpersonation
,
392 return SBOX_ERROR_GENERIC
;
394 initial_token
.Set(temp_token
);
396 if (!::DuplicateTokenEx(tokens
->lockdown
.Get(), TOKEN_ALL_ACCESS
, 0,
397 SecurityIdentification
, TokenPrimary
,
399 return SBOX_ERROR_GENERIC
;
401 lockdown_token
.Set(temp_token
);
403 result
= policy_base
->MakeTokens(&initial_token
, &lockdown_token
);
404 if (SBOX_ALL_OK
!= result
)
409 result
= policy_base
->MakeJobObject(&job_temp
);
410 if (SBOX_ALL_OK
!= result
)
413 base::win::ScopedHandle
job(job_temp
);
415 // Initialize the startup information from the policy.
416 base::win::StartupInformation startup_info
;
417 // The liftime of |mitigations| and |inherit_handle_list| have to be at least
418 // as long as |startup_info| because |UpdateProcThreadAttribute| requires that
419 // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is
420 // called; StartupInformation's destructor makes such a call.
423 std::vector
<HANDLE
> inherited_handle_list
;
425 base::string16 desktop
= policy_base
->GetAlternateDesktop();
426 if (!desktop
.empty()) {
427 startup_info
.startup_info()->lpDesktop
=
428 const_cast<wchar_t*>(desktop
.c_str());
431 bool inherit_handles
= false;
433 if (base::win::GetVersion() >= base::win::VERSION_VISTA
) {
434 int attribute_count
= 0;
435 const AppContainerAttributes
* app_container
=
436 policy_base
->GetAppContainer();
440 size_t mitigations_size
;
441 ConvertProcessMitigationsToPolicy(policy
->GetProcessMitigations(),
442 &mitigations
, &mitigations_size
);
446 HANDLE stdout_handle
= policy_base
->GetStdoutHandle();
447 HANDLE stderr_handle
= policy_base
->GetStderrHandle();
449 if (stdout_handle
!= INVALID_HANDLE_VALUE
)
450 inherited_handle_list
.push_back(stdout_handle
);
452 // Handles in the list must be unique.
453 if (stderr_handle
!= stdout_handle
&& stderr_handle
!= INVALID_HANDLE_VALUE
)
454 inherited_handle_list
.push_back(stderr_handle
);
456 const HandleList
& policy_handle_list
= policy_base
->GetHandlesBeingShared();
458 for (auto handle
: policy_handle_list
)
459 inherited_handle_list
.push_back(handle
->Get());
461 if (inherited_handle_list
.size())
464 if (!startup_info
.InitializeProcThreadAttributeList(attribute_count
))
465 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
468 result
= app_container
->ShareForStartup(&startup_info
);
469 if (SBOX_ALL_OK
!= result
)
474 if (!startup_info
.UpdateProcThreadAttribute(
475 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
, &mitigations
,
477 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
481 if (inherited_handle_list
.size()) {
482 if (!startup_info
.UpdateProcThreadAttribute(
483 PROC_THREAD_ATTRIBUTE_HANDLE_LIST
,
484 &inherited_handle_list
[0],
485 sizeof(HANDLE
) * inherited_handle_list
.size())) {
486 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
488 startup_info
.startup_info()->dwFlags
|= STARTF_USESTDHANDLES
;
489 startup_info
.startup_info()->hStdInput
= INVALID_HANDLE_VALUE
;
490 startup_info
.startup_info()->hStdOutput
= stdout_handle
;
491 startup_info
.startup_info()->hStdError
= stderr_handle
;
492 // Allowing inheritance of handles is only secure now that we
493 // have limited which handles will be inherited.
494 inherit_handles
= true;
498 // Construct the thread pool here in case it is expensive.
499 // The thread pool is shared by all the targets
500 if (NULL
== thread_pool_
)
501 thread_pool_
= new Win2kThreadPool();
503 // Create the TargetProces object and spawn the target suspended. Note that
504 // Brokerservices does not own the target object. It is owned by the Policy.
505 base::win::ScopedProcessInformation process_info
;
506 TargetProcess
* target
= new TargetProcess(initial_token
.Pass(),
507 lockdown_token
.Pass(),
511 DWORD win_result
= target
->Create(exe_path
, command_line
, inherit_handles
,
512 policy_base
->GetLowBoxSid() ? true : false,
513 startup_info
, &process_info
);
515 policy_base
->ClearSharedHandles();
517 if (ERROR_SUCCESS
!= win_result
) {
518 SpawnCleanup(target
, win_result
);
519 return SBOX_ERROR_CREATE_PROCESS
;
522 // Now the policy is the owner of the target.
523 if (!policy_base
->AddTarget(target
)) {
524 return SpawnCleanup(target
, 0);
527 // We are going to keep a pointer to the policy because we'll call it when
528 // the job object generates notifications using the completion port.
529 policy_base
->AddRef();
531 scoped_ptr
<JobTracker
> tracker(new JobTracker(job
.Pass(), policy_base
));
533 // There is no obvious recovery after failure here. Previous version with
534 // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639
535 CHECK(AssociateCompletionPort(tracker
->job
.Get(), job_port_
.Get(),
538 // Save the tracker because in cleanup we might need to force closing
540 tracker_list_
.push_back(tracker
.release());
541 child_process_ids_
.insert(process_info
.process_id());
543 // We have to signal the event once here because the completion port will
544 // never get a message that this target is being terminated thus we should
545 // not block WaitForAllTargets until we have at least one target with job.
546 if (child_process_ids_
.empty())
547 ::SetEvent(no_targets_
.Get());
548 // We can not track the life time of such processes and it is responsibility
549 // of the host application to make sure that spawned targets without jobs
550 // are terminated when the main application don't need them anymore.
551 // Sandbox policy engine needs to know that these processes are valid
552 // targets for e.g. BrokerDuplicateHandle so track them as peer processes.
553 AddTargetPeer(process_info
.process_handle());
556 *target_info
= process_info
.Take();
561 ResultCode
BrokerServicesBase::WaitForAllTargets() {
562 ::WaitForSingleObject(no_targets_
.Get(), INFINITE
);
566 bool BrokerServicesBase::IsActiveTarget(DWORD process_id
) {
567 AutoLock
lock(&lock_
);
568 return child_process_ids_
.find(process_id
) != child_process_ids_
.end() ||
569 peer_map_
.find(process_id
) != peer_map_
.end();
572 VOID CALLBACK
BrokerServicesBase::RemovePeer(PVOID parameter
, BOOLEAN timeout
) {
573 PeerTracker
* peer
= reinterpret_cast<PeerTracker
*>(parameter
);
574 // Don't check the return code because we this may fail (safely) at shutdown.
575 ::PostQueuedCompletionStatus(
576 peer
->job_port
, 0, THREAD_CTRL_REMOVE_PEER
,
577 reinterpret_cast<LPOVERLAPPED
>(static_cast<uintptr_t>(peer
->id
)));
580 ResultCode
BrokerServicesBase::AddTargetPeer(HANDLE peer_process
) {
581 scoped_ptr
<PeerTracker
> peer(new PeerTracker(::GetProcessId(peer_process
),
584 return SBOX_ERROR_GENERIC
;
586 HANDLE process_handle
;
587 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process
,
588 ::GetCurrentProcess(), &process_handle
,
589 SYNCHRONIZE
, FALSE
, 0)) {
590 return SBOX_ERROR_GENERIC
;
592 peer
->process
.Set(process_handle
);
594 AutoLock
lock(&lock_
);
595 if (!peer_map_
.insert(std::make_pair(peer
->id
, peer
.get())).second
)
596 return SBOX_ERROR_BAD_PARAMS
;
598 if (!::RegisterWaitForSingleObject(
599 &peer
->wait_object
, peer
->process
.Get(), RemovePeer
, peer
.get(),
600 INFINITE
, WT_EXECUTEONLYONCE
| WT_EXECUTEINWAITTHREAD
)) {
601 peer_map_
.erase(peer
->id
);
602 return SBOX_ERROR_GENERIC
;
605 // Release the pointer since it will be cleaned up by the callback.
610 ResultCode
BrokerServicesBase::InstallAppContainer(const wchar_t* sid
,
611 const wchar_t* name
) {
612 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8
)
613 return SBOX_ERROR_UNSUPPORTED
;
615 base::string16 old_name
= LookupAppContainer(sid
);
616 if (old_name
.empty())
617 return CreateAppContainer(sid
, name
);
619 if (old_name
!= name
)
620 return SBOX_ERROR_INVALID_APP_CONTAINER
;
625 ResultCode
BrokerServicesBase::UninstallAppContainer(const wchar_t* sid
) {
626 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8
)
627 return SBOX_ERROR_UNSUPPORTED
;
629 base::string16 name
= LookupAppContainer(sid
);
631 return SBOX_ERROR_INVALID_APP_CONTAINER
;
633 return DeleteAppContainer(sid
);
636 } // namespace sandbox