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/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"
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
) {
38 error
= ::GetLastError();
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().
50 THREAD_CTRL_REMOVE_PEER
,
55 // Helper structure that allows the Broker to associate a job notification
56 // with a job object and with a policy.
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
68 base::win::ScopedHandle process
;
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
)) {
85 // Utility function to determine whether a token for the specified policy can
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.
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;
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();
116 key
|= policy
->GetLockdownTokenLevel();
118 key
|= policy
->GetIntegrityLevel();
127 BrokerServicesBase::BrokerServicesBase()
128 : thread_pool_(NULL
), job_port_(NULL
), no_targets_(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
;
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.
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.
177 JobTrackerList::iterator it
;
178 for (it
= tracker_list_
.begin(); it
!= tracker_list_
.end(); ++it
) {
179 JobTracker
* tracker
= (*it
);
180 FreeResources(tracker
);
183 ::CloseHandle(job_thread_
);
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.
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
);
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
);
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
) {
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
);
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.
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
);
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
);
270 case JOB_OBJECT_MSG_NEW_PROCESS
: {
272 if (1 == target_counter
) {
273 ::ResetEvent(no_targets
);
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(reinterpret_cast<DWORD
>(ovl
));
285 if (0 == target_counter
)
286 ::SetEvent(no_targets
);
288 DCHECK(target_counter
>= 0);
292 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT
: {
296 case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT
: {
297 BOOL res
= ::TerminateJobObject(tracker
->job
,
298 SBOX_FATAL_MEMORY_EXCEEDED
);
308 } else if (THREAD_CTRL_REMOVE_PEER
== key
) {
309 // Remove a process from our list of peers.
310 AutoLock
lock(&broker
->lock_
);
311 PeerTrackerMap::iterator it
=
312 broker
->peer_map_
.find(reinterpret_cast<DWORD
>(ovl
));
313 DeregisterPeerTracker(it
->second
);
314 broker
->peer_map_
.erase(it
);
315 } else if (THREAD_CTRL_QUIT
== key
) {
316 // The broker object is being destroyed so the thread needs to exit.
319 // We have not implemented more commands.
328 // SpawnTarget does all the interesting sandbox setup and creates the target
329 // process inside the sandbox.
330 ResultCode
BrokerServicesBase::SpawnTarget(const wchar_t* exe_path
,
331 const wchar_t* command_line
,
332 TargetPolicy
* policy
,
333 PROCESS_INFORMATION
* target_info
) {
335 return SBOX_ERROR_BAD_PARAMS
;
338 return SBOX_ERROR_BAD_PARAMS
;
340 // Even though the resources touched by SpawnTarget can be accessed in
341 // multiple threads, the method itself cannot be called from more than
342 // 1 thread. This is to protect the global variables used while setting up
343 // the child process.
344 static DWORD thread_id
= ::GetCurrentThreadId();
345 DCHECK(thread_id
== ::GetCurrentThreadId());
347 AutoLock
lock(&lock_
);
349 // This downcast is safe as long as we control CreatePolicy()
350 PolicyBase
* policy_base
= static_cast<PolicyBase
*>(policy
);
352 // Construct the tokens and the job object that we are going to associate
353 // with the soon to be created target process.
354 HANDLE initial_token_temp
;
355 HANDLE lockdown_token_temp
;
356 ResultCode result
= SBOX_ALL_OK
;
358 if (IsTokenCacheable(policy_base
)) {
359 // Create the master tokens only once and save them in a cache. That way
360 // can just duplicate them to avoid hammering LSASS on every sandboxed
362 uint32_t token_key
= GenerateTokenCacheKey(policy_base
);
363 TokenCacheMap::iterator it
= token_cache_
.find(token_key
);
364 if (it
!= token_cache_
.end()) {
365 initial_token_temp
= it
->second
.first
;
366 lockdown_token_temp
= it
->second
.second
;
369 policy_base
->MakeTokens(&initial_token_temp
, &lockdown_token_temp
);
370 if (SBOX_ALL_OK
!= result
)
372 token_cache_
[token_key
] =
373 std::pair
<HANDLE
, HANDLE
>(initial_token_temp
, lockdown_token_temp
);
376 if (!::DuplicateToken(initial_token_temp
, SecurityImpersonation
,
377 &initial_token_temp
)) {
378 return SBOX_ERROR_GENERIC
;
381 if (!::DuplicateTokenEx(lockdown_token_temp
, TOKEN_ALL_ACCESS
, 0,
382 SecurityIdentification
, TokenPrimary
,
383 &lockdown_token_temp
)) {
384 return SBOX_ERROR_GENERIC
;
387 result
= policy_base
->MakeTokens(&initial_token_temp
, &lockdown_token_temp
);
388 if (SBOX_ALL_OK
!= result
)
392 base::win::ScopedHandle
initial_token(initial_token_temp
);
393 base::win::ScopedHandle
lockdown_token(lockdown_token_temp
);
396 result
= policy_base
->MakeJobObject(&job_temp
);
397 if (SBOX_ALL_OK
!= result
)
400 base::win::ScopedHandle
job(job_temp
);
402 // Initialize the startup information from the policy.
403 base::win::StartupInformation startup_info
;
404 base::string16 desktop
= policy_base
->GetAlternateDesktop();
405 if (!desktop
.empty()) {
406 startup_info
.startup_info()->lpDesktop
=
407 const_cast<wchar_t*>(desktop
.c_str());
410 bool inherit_handles
= false;
411 if (base::win::GetVersion() >= base::win::VERSION_VISTA
) {
412 int attribute_count
= 0;
413 const AppContainerAttributes
* app_container
=
414 policy_base
->GetAppContainer();
419 size_t mitigations_size
;
420 ConvertProcessMitigationsToPolicy(policy
->GetProcessMitigations(),
421 &mitigations
, &mitigations_size
);
425 HANDLE stdout_handle
= policy_base
->GetStdoutHandle();
426 HANDLE stderr_handle
= policy_base
->GetStderrHandle();
427 HANDLE inherit_handle_list
[2];
428 int inherit_handle_count
= 0;
429 if (stdout_handle
!= INVALID_HANDLE_VALUE
)
430 inherit_handle_list
[inherit_handle_count
++] = stdout_handle
;
431 // Handles in the list must be unique.
432 if (stderr_handle
!= stdout_handle
&& stderr_handle
!= INVALID_HANDLE_VALUE
)
433 inherit_handle_list
[inherit_handle_count
++] = stderr_handle
;
434 if (inherit_handle_count
)
437 if (!startup_info
.InitializeProcThreadAttributeList(attribute_count
))
438 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
441 result
= app_container
->ShareForStartup(&startup_info
);
442 if (SBOX_ALL_OK
!= result
)
447 if (!startup_info
.UpdateProcThreadAttribute(
448 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
, &mitigations
,
450 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
454 if (inherit_handle_count
) {
455 if (!startup_info
.UpdateProcThreadAttribute(
456 PROC_THREAD_ATTRIBUTE_HANDLE_LIST
,
458 sizeof(inherit_handle_list
[0]) * inherit_handle_count
)) {
459 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
461 startup_info
.startup_info()->dwFlags
|= STARTF_USESTDHANDLES
;
462 startup_info
.startup_info()->hStdInput
= INVALID_HANDLE_VALUE
;
463 startup_info
.startup_info()->hStdOutput
= stdout_handle
;
464 startup_info
.startup_info()->hStdError
= stderr_handle
;
465 // Allowing inheritance of handles is only secure now that we
466 // have limited which handles will be inherited.
467 inherit_handles
= true;
471 // Construct the thread pool here in case it is expensive.
472 // The thread pool is shared by all the targets
473 if (NULL
== thread_pool_
)
474 thread_pool_
= new Win2kThreadPool();
476 // Create the TargetProces object and spawn the target suspended. Note that
477 // Brokerservices does not own the target object. It is owned by the Policy.
478 base::win::ScopedProcessInformation process_info
;
479 TargetProcess
* target
= new TargetProcess(initial_token
.Take(),
480 lockdown_token
.Take(),
484 DWORD win_result
= target
->Create(exe_path
, command_line
, inherit_handles
,
485 startup_info
, &process_info
);
486 if (ERROR_SUCCESS
!= win_result
)
487 return SpawnCleanup(target
, win_result
);
489 // Now the policy is the owner of the target.
490 if (!policy_base
->AddTarget(target
)) {
491 return SpawnCleanup(target
, 0);
494 // We are going to keep a pointer to the policy because we'll call it when
495 // the job object generates notifications using the completion port.
496 policy_base
->AddRef();
498 scoped_ptr
<JobTracker
> tracker(new JobTracker(job
.Take(), policy_base
));
499 if (!AssociateCompletionPort(tracker
->job
, job_port_
, tracker
.get()))
500 return SpawnCleanup(target
, 0);
501 // Save the tracker because in cleanup we might need to force closing
503 tracker_list_
.push_back(tracker
.release());
504 child_process_ids_
.insert(process_info
.process_id());
506 // We have to signal the event once here because the completion port will
507 // never get a message that this target is being terminated thus we should
508 // not block WaitForAllTargets until we have at least one target with job.
509 if (child_process_ids_
.empty())
510 ::SetEvent(no_targets_
);
511 // We can not track the life time of such processes and it is responsibility
512 // of the host application to make sure that spawned targets without jobs
513 // are terminated when the main application don't need them anymore.
516 *target_info
= process_info
.Take();
521 ResultCode
BrokerServicesBase::WaitForAllTargets() {
522 ::WaitForSingleObject(no_targets_
, INFINITE
);
526 bool BrokerServicesBase::IsActiveTarget(DWORD process_id
) {
527 AutoLock
lock(&lock_
);
528 return child_process_ids_
.find(process_id
) != child_process_ids_
.end() ||
529 peer_map_
.find(process_id
) != peer_map_
.end();
532 VOID CALLBACK
BrokerServicesBase::RemovePeer(PVOID parameter
, BOOLEAN timeout
) {
533 PeerTracker
* peer
= reinterpret_cast<PeerTracker
*>(parameter
);
534 // Don't check the return code because we this may fail (safely) at shutdown.
535 ::PostQueuedCompletionStatus(peer
->job_port
, 0, THREAD_CTRL_REMOVE_PEER
,
536 reinterpret_cast<LPOVERLAPPED
>(peer
->id
));
539 ResultCode
BrokerServicesBase::AddTargetPeer(HANDLE peer_process
) {
540 scoped_ptr
<PeerTracker
> peer(new PeerTracker(::GetProcessId(peer_process
),
543 return SBOX_ERROR_GENERIC
;
545 HANDLE process_handle
;
546 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process
,
547 ::GetCurrentProcess(), &process_handle
,
548 SYNCHRONIZE
, FALSE
, 0)) {
549 return SBOX_ERROR_GENERIC
;
551 peer
->process
.Set(process_handle
);
553 AutoLock
lock(&lock_
);
554 if (!peer_map_
.insert(std::make_pair(peer
->id
, peer
.get())).second
)
555 return SBOX_ERROR_BAD_PARAMS
;
557 if (!::RegisterWaitForSingleObject(
558 &peer
->wait_object
, peer
->process
.Get(), RemovePeer
, peer
.get(),
559 INFINITE
, WT_EXECUTEONLYONCE
| WT_EXECUTEINWAITTHREAD
)) {
560 peer_map_
.erase(peer
->id
);
561 return SBOX_ERROR_GENERIC
;
564 // Release the pointer since it will be cleaned up by the callback.
569 ResultCode
BrokerServicesBase::InstallAppContainer(const wchar_t* sid
,
570 const wchar_t* name
) {
571 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8
)
572 return SBOX_ERROR_UNSUPPORTED
;
574 base::string16 old_name
= LookupAppContainer(sid
);
575 if (old_name
.empty())
576 return CreateAppContainer(sid
, name
);
578 if (old_name
!= name
)
579 return SBOX_ERROR_INVALID_APP_CONTAINER
;
584 ResultCode
BrokerServicesBase::UninstallAppContainer(const wchar_t* sid
) {
585 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8
)
586 return SBOX_ERROR_UNSUPPORTED
;
588 base::string16 name
= LookupAppContainer(sid
);
590 return SBOX_ERROR_INVALID_APP_CONTAINER
;
592 return DeleteAppContainer(sid
);
595 } // namespace sandbox