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
)) {
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
;
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())
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.
175 STLDeleteElements(&tracker_list_
);
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
) {
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
);
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.
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
);
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();
238 case JOB_OBJECT_MSG_NEW_PROCESS
: {
240 if (1 == target_counter
) {
241 ::ResetEvent(no_targets
);
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
)));
254 if (0 == target_counter
)
255 ::SetEvent(no_targets
);
257 DCHECK(target_counter
>= 0);
261 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT
: {
265 case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT
: {
266 BOOL res
= ::TerminateJobObject(tracker
->job
.Get(),
267 SBOX_FATAL_MEMORY_EXCEEDED
);
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.
288 // We have not implemented more commands.
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
) {
304 return SBOX_ERROR_BAD_PARAMS
;
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
)
334 base::win::ScopedHandle job
;
335 result
= policy_base
->MakeJobObject(&job
);
336 if (SBOX_ALL_OK
!= 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.
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();
364 size_t mitigations_size
;
365 ConvertProcessMitigationsToPolicy(policy
->GetProcessMitigations(),
366 &mitigations
, &mitigations_size
);
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())
388 if (!startup_info
.InitializeProcThreadAttributeList(attribute_count
))
389 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES
;
392 result
= app_container
->ShareForStartup(&startup_info
);
393 if (SBOX_ALL_OK
!= result
)
398 if (!startup_info
.UpdateProcThreadAttribute(
399 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY
, &mitigations
,
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
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(),
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();
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(),
486 // Save the tracker because in cleanup we might need to force closing
488 tracker_list_
.push_back(tracker
.release());
489 child_process_ids_
.insert(process_info
.process_id());
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();
509 ResultCode
BrokerServicesBase::WaitForAllTargets() {
510 ::WaitForSingleObject(no_targets_
.Get(), INFINITE
);
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
),
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.
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
;
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
);
579 return SBOX_ERROR_INVALID_APP_CONTAINER
;
581 return DeleteAppContainer(sid
);
584 } // namespace sandbox