Added GetState, GetManagedProperties, CreateNetwork methods to WiFiService.
[chromium-blink-merge.git] / sandbox / win / src / broker_services.cc
blob921eb4f89e0bc2a84a5792219fa44eebbd5b67e2
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 "base/logging.h"
8 #include "base/memory/scoped_ptr.h"
9 #include "base/threading/platform_thread.h"
10 #include "base/win/scoped_handle.h"
11 #include "base/win/scoped_process_information.h"
12 #include "base/win/startup_information.h"
13 #include "base/win/windows_version.h"
14 #include "sandbox/win/src/app_container.h"
15 #include "sandbox/win/src/process_mitigations.h"
16 #include "sandbox/win/src/sandbox_policy_base.h"
17 #include "sandbox/win/src/sandbox.h"
18 #include "sandbox/win/src/target_process.h"
19 #include "sandbox/win/src/win2k_threadpool.h"
20 #include "sandbox/win/src/win_utils.h"
22 namespace {
24 // Utility function to associate a completion port to a job object.
25 bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) {
26 JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port };
27 return ::SetInformationJobObject(job,
28 JobObjectAssociateCompletionPortInformation,
29 &job_acp, sizeof(job_acp))? true : false;
32 // Utility function to do the cleanup necessary when something goes wrong
33 // while in SpawnTarget and we must terminate the target process.
34 sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) {
35 if (0 == error)
36 error = ::GetLastError();
38 target->Terminate();
39 delete target;
40 ::SetLastError(error);
41 return sandbox::SBOX_ERROR_GENERIC;
44 // the different commands that you can send to the worker thread that
45 // executes TargetEventsThread().
46 enum {
47 THREAD_CTRL_NONE,
48 THREAD_CTRL_REMOVE_PEER,
49 THREAD_CTRL_QUIT,
50 THREAD_CTRL_LAST,
53 // Helper structure that allows the Broker to associate a job notification
54 // with a job object and with a policy.
55 struct JobTracker {
56 HANDLE job;
57 sandbox::PolicyBase* policy;
58 JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy)
59 : job(cjob), policy(cpolicy) {
63 // Helper structure that allows the broker to track peer processes
64 struct PeerTracker {
65 HANDLE wait_object;
66 base::win::ScopedHandle process;
67 DWORD id;
68 HANDLE job_port;
69 PeerTracker(DWORD process_id, HANDLE broker_job_port)
70 : wait_object(NULL), id(process_id), job_port(broker_job_port) {
74 void DeregisterPeerTracker(PeerTracker* peer) {
75 // Deregistration shouldn't fail, but we leak rather than crash if it does.
76 if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) {
77 delete peer;
78 } else {
79 NOTREACHED();
83 } // namespace
85 namespace sandbox {
87 BrokerServicesBase::BrokerServicesBase()
88 : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL),
89 job_thread_(NULL) {
92 // The broker uses a dedicated worker thread that services the job completion
93 // port to perform policy notifications and associated cleanup tasks.
94 ResultCode BrokerServicesBase::Init() {
95 if ((NULL != job_port_) || (NULL != thread_pool_))
96 return SBOX_ERROR_UNEXPECTED_CALL;
98 ::InitializeCriticalSection(&lock_);
100 job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
101 if (NULL == job_port_)
102 return SBOX_ERROR_GENERIC;
104 no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL);
106 job_thread_ = ::CreateThread(NULL, 0, // Default security and stack.
107 TargetEventsThread, this, NULL, NULL);
108 if (NULL == job_thread_)
109 return SBOX_ERROR_GENERIC;
111 return SBOX_ALL_OK;
114 // The destructor should only be called when the Broker process is terminating.
115 // Since BrokerServicesBase is a singleton, this is called from the CRT
116 // termination handlers, if this code lives on a DLL it is called during
117 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
118 // wait for threads here.
119 BrokerServicesBase::~BrokerServicesBase() {
120 // If there is no port Init() was never called successfully.
121 if (!job_port_)
122 return;
124 // Closing the port causes, that no more Job notifications are delivered to
125 // the worker thread and also causes the thread to exit. This is what we
126 // want to do since we are going to close all outstanding Jobs and notifying
127 // the policy objects ourselves.
128 ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE);
129 ::CloseHandle(job_port_);
131 if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) {
132 // Cannot clean broker services.
133 NOTREACHED();
134 return;
137 JobTrackerList::iterator it;
138 for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) {
139 JobTracker* tracker = (*it);
140 FreeResources(tracker);
141 delete tracker;
143 ::CloseHandle(job_thread_);
144 delete thread_pool_;
145 ::CloseHandle(no_targets_);
147 // Cancel the wait events and delete remaining peer trackers.
148 for (PeerTrackerMap::iterator it = peer_map_.begin();
149 it != peer_map_.end(); ++it) {
150 DeregisterPeerTracker(it->second);
153 // If job_port_ isn't NULL, assumes that the lock has been initialized.
154 if (job_port_)
155 ::DeleteCriticalSection(&lock_);
158 TargetPolicy* BrokerServicesBase::CreatePolicy() {
159 // If you change the type of the object being created here you must also
160 // change the downcast to it in SpawnTarget().
161 return new PolicyBase;
164 void BrokerServicesBase::FreeResources(JobTracker* tracker) {
165 if (NULL != tracker->policy) {
166 BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK);
167 DCHECK(res);
168 // Closing the job causes the target process to be destroyed so this
169 // needs to happen before calling OnJobEmpty().
170 res = ::CloseHandle(tracker->job);
171 DCHECK(res);
172 // In OnJobEmpty() we don't actually use the job handle directly.
173 tracker->policy->OnJobEmpty(tracker->job);
174 tracker->policy->Release();
175 tracker->policy = NULL;
179 // The worker thread stays in a loop waiting for asynchronous notifications
180 // from the job objects. Right now we only care about knowing when the last
181 // process on a job terminates, but in general this is the place to tell
182 // the policy about events.
183 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
184 if (NULL == param)
185 return 1;
187 base::PlatformThread::SetName("BrokerEvent");
189 BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
190 HANDLE port = broker->job_port_;
191 HANDLE no_targets = broker->no_targets_;
193 int target_counter = 0;
194 ::ResetEvent(no_targets);
196 while (true) {
197 DWORD events = 0;
198 ULONG_PTR key = 0;
199 LPOVERLAPPED ovl = NULL;
201 if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE))
202 // this call fails if the port has been closed before we have a
203 // chance to service the last packet which is 'exit' anyway so
204 // this is not an error.
205 return 1;
207 if (key > THREAD_CTRL_LAST) {
208 // The notification comes from a job object. There are nine notifications
209 // that jobs can send and some of them depend on the job attributes set.
210 JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
212 switch (events) {
213 case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
214 // The job object has signaled that the last process associated
215 // with it has terminated. Assuming there is no way for a process
216 // to appear out of thin air in this job, it safe to assume that
217 // we can tell the policy to destroy the target object, and for
218 // us to release our reference to the policy object.
219 FreeResources(tracker);
220 break;
223 case JOB_OBJECT_MSG_NEW_PROCESS: {
224 ++target_counter;
225 if (1 == target_counter) {
226 ::ResetEvent(no_targets);
228 break;
231 case JOB_OBJECT_MSG_EXIT_PROCESS:
232 case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
234 AutoLock lock(&broker->lock_);
235 broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl));
237 --target_counter;
238 if (0 == target_counter)
239 ::SetEvent(no_targets);
241 DCHECK(target_counter >= 0);
242 break;
245 case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
246 break;
249 default: {
250 NOTREACHED();
251 break;
254 } else if (THREAD_CTRL_REMOVE_PEER == key) {
255 // Remove a process from our list of peers.
256 AutoLock lock(&broker->lock_);
257 PeerTrackerMap::iterator it =
258 broker->peer_map_.find(reinterpret_cast<DWORD>(ovl));
259 DeregisterPeerTracker(it->second);
260 broker->peer_map_.erase(it);
261 } else if (THREAD_CTRL_QUIT == key) {
262 // The broker object is being destroyed so the thread needs to exit.
263 return 0;
264 } else {
265 // We have not implemented more commands.
266 NOTREACHED();
270 NOTREACHED();
271 return 0;
274 // SpawnTarget does all the interesting sandbox setup and creates the target
275 // process inside the sandbox.
276 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
277 const wchar_t* command_line,
278 TargetPolicy* policy,
279 PROCESS_INFORMATION* target_info) {
280 if (!exe_path)
281 return SBOX_ERROR_BAD_PARAMS;
283 if (!policy)
284 return SBOX_ERROR_BAD_PARAMS;
286 // Even though the resources touched by SpawnTarget can be accessed in
287 // multiple threads, the method itself cannot be called from more than
288 // 1 thread. This is to protect the global variables used while setting up
289 // the child process.
290 static DWORD thread_id = ::GetCurrentThreadId();
291 DCHECK(thread_id == ::GetCurrentThreadId());
293 AutoLock lock(&lock_);
295 // This downcast is safe as long as we control CreatePolicy()
296 PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
298 // Construct the tokens and the job object that we are going to associate
299 // with the soon to be created target process.
300 HANDLE initial_token_temp;
301 HANDLE lockdown_token_temp;
302 ResultCode result = policy_base->MakeTokens(&initial_token_temp,
303 &lockdown_token_temp);
304 if (SBOX_ALL_OK != result)
305 return result;
307 base::win::ScopedHandle initial_token(initial_token_temp);
308 base::win::ScopedHandle lockdown_token(lockdown_token_temp);
310 HANDLE job_temp;
311 result = policy_base->MakeJobObject(&job_temp);
312 if (SBOX_ALL_OK != result)
313 return result;
315 base::win::ScopedHandle job(job_temp);
317 // Initialize the startup information from the policy.
318 base::win::StartupInformation startup_info;
319 string16 desktop = policy_base->GetAlternateDesktop();
320 if (!desktop.empty()) {
321 startup_info.startup_info()->lpDesktop =
322 const_cast<wchar_t*>(desktop.c_str());
325 bool inherit_handles = false;
326 if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
327 int attribute_count = 0;
328 const AppContainerAttributes* app_container =
329 policy_base->GetAppContainer();
330 if (app_container)
331 ++attribute_count;
333 DWORD64 mitigations;
334 size_t mitigations_size;
335 ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
336 &mitigations, &mitigations_size);
337 if (mitigations)
338 ++attribute_count;
340 HANDLE stdout_handle = policy_base->GetStdoutHandle();
341 HANDLE stderr_handle = policy_base->GetStderrHandle();
342 HANDLE inherit_handle_list[2];
343 int inherit_handle_count = 0;
344 if (stdout_handle != INVALID_HANDLE_VALUE)
345 inherit_handle_list[inherit_handle_count++] = stdout_handle;
346 // Handles in the list must be unique.
347 if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE)
348 inherit_handle_list[inherit_handle_count++] = stderr_handle;
349 if (inherit_handle_count)
350 ++attribute_count;
352 if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
353 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
355 if (app_container) {
356 result = app_container->ShareForStartup(&startup_info);
357 if (SBOX_ALL_OK != result)
358 return result;
361 if (mitigations) {
362 if (!startup_info.UpdateProcThreadAttribute(
363 PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations,
364 mitigations_size)) {
365 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
369 if (inherit_handle_count) {
370 if (!startup_info.UpdateProcThreadAttribute(
371 PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
372 inherit_handle_list,
373 sizeof(inherit_handle_list[0]) * inherit_handle_count)) {
374 return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
376 startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES;
377 startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE;
378 startup_info.startup_info()->hStdOutput = stdout_handle;
379 startup_info.startup_info()->hStdError = stderr_handle;
380 // Allowing inheritance of handles is only secure now that we
381 // have limited which handles will be inherited.
382 inherit_handles = true;
386 // Construct the thread pool here in case it is expensive.
387 // The thread pool is shared by all the targets
388 if (NULL == thread_pool_)
389 thread_pool_ = new Win2kThreadPool();
391 // Create the TargetProces object and spawn the target suspended. Note that
392 // Brokerservices does not own the target object. It is owned by the Policy.
393 base::win::ScopedProcessInformation process_info;
394 TargetProcess* target = new TargetProcess(initial_token.Take(),
395 lockdown_token.Take(),
396 job,
397 thread_pool_);
399 DWORD win_result = target->Create(exe_path, command_line, inherit_handles,
400 startup_info, &process_info);
401 if (ERROR_SUCCESS != win_result)
402 return SpawnCleanup(target, win_result);
404 // Now the policy is the owner of the target.
405 if (!policy_base->AddTarget(target)) {
406 return SpawnCleanup(target, 0);
409 // We are going to keep a pointer to the policy because we'll call it when
410 // the job object generates notifications using the completion port.
411 policy_base->AddRef();
412 if (job.IsValid()) {
413 scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base));
414 if (!AssociateCompletionPort(tracker->job, job_port_, tracker.get()))
415 return SpawnCleanup(target, 0);
416 // Save the tracker because in cleanup we might need to force closing
417 // the Jobs.
418 tracker_list_.push_back(tracker.release());
419 child_process_ids_.insert(process_info.process_id());
420 } else {
421 // We have to signal the event once here because the completion port will
422 // never get a message that this target is being terminated thus we should
423 // not block WaitForAllTargets until we have at least one target with job.
424 if (child_process_ids_.empty())
425 ::SetEvent(no_targets_);
426 // We can not track the life time of such processes and it is responsibility
427 // of the host application to make sure that spawned targets without jobs
428 // are terminated when the main application don't need them anymore.
431 *target_info = process_info.Take();
432 return SBOX_ALL_OK;
436 ResultCode BrokerServicesBase::WaitForAllTargets() {
437 ::WaitForSingleObject(no_targets_, INFINITE);
438 return SBOX_ALL_OK;
441 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) {
442 AutoLock lock(&lock_);
443 return child_process_ids_.find(process_id) != child_process_ids_.end() ||
444 peer_map_.find(process_id) != peer_map_.end();
447 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) {
448 PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter);
449 // Don't check the return code because we this may fail (safely) at shutdown.
450 ::PostQueuedCompletionStatus(peer->job_port, 0, THREAD_CTRL_REMOVE_PEER,
451 reinterpret_cast<LPOVERLAPPED>(peer->id));
454 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
455 scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
456 job_port_));
457 if (!peer->id)
458 return SBOX_ERROR_GENERIC;
460 HANDLE process_handle;
461 if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
462 ::GetCurrentProcess(), &process_handle,
463 SYNCHRONIZE, FALSE, 0)) {
464 return SBOX_ERROR_GENERIC;
466 peer->process.Set(process_handle);
468 AutoLock lock(&lock_);
469 if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
470 return SBOX_ERROR_BAD_PARAMS;
472 if (!::RegisterWaitForSingleObject(
473 &peer->wait_object, peer->process, RemovePeer, peer.get(), INFINITE,
474 WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
475 peer_map_.erase(peer->id);
476 return SBOX_ERROR_GENERIC;
479 // Release the pointer since it will be cleaned up by the callback.
480 peer.release();
481 return SBOX_ALL_OK;
484 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid,
485 const wchar_t* name) {
486 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
487 return SBOX_ERROR_UNSUPPORTED;
489 string16 old_name = LookupAppContainer(sid);
490 if (old_name.empty())
491 return CreateAppContainer(sid, name);
493 if (old_name != name)
494 return SBOX_ERROR_INVALID_APP_CONTAINER;
496 return SBOX_ALL_OK;
499 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
500 if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
501 return SBOX_ERROR_UNSUPPORTED;
503 string16 name = LookupAppContainer(sid);
504 if (name.empty())
505 return SBOX_ERROR_INVALID_APP_CONTAINER;
507 return DeleteAppContainer(sid);
510 } // namespace sandbox