Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / sandbox / win / src / broker_services.cc
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.
4
5 #include "sandbox/win/src/broker_services.h"
6
7 #include <AclAPI.h>
8
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"
23
24 namespace {
25
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;
32 }
33
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();
39
40   target->Terminate();
41   delete target;
42   ::SetLastError(error);
43   return sandbox::SBOX_ERROR_GENERIC;
44 }
45
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,
53 };
54
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) {
62   }
63 };
64
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) {
73   }
74 };
75
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();
82   }
83 }
84
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();
90
91   // We cannot cache tokens with an app container.
92   if (app_container)
93     return false;
94
95   return true;
96 }
97
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;
102
103   DCHECK(IsTokenCacheable(policy));
104
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");
112
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();
119
120   return key;
121 }
122
123 }  // namespace
124
125 namespace sandbox {
126
127 BrokerServicesBase::BrokerServicesBase()
128     : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL),
129       job_thread_(NULL) {
130 }
131
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;
137
138   ::InitializeCriticalSection(&lock_);
139
140   job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
141   if (NULL == job_port_)
142     return SBOX_ERROR_GENERIC;
143
144   no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL);
145
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;
150
151   return SBOX_ALL_OK;
152 }
153
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;
163
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_);
170
171   if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) {
172     // Cannot clean broker services.
173     NOTREACHED();
174     return;
175   }
176
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;
182   }
183   ::CloseHandle(job_thread_);
184   delete thread_pool_;
185   ::CloseHandle(no_targets_);
186
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);
191   }
192
193   // If job_port_ isn't NULL, assumes that the lock has been initialized.
194   if (job_port_)
195     ::DeleteCriticalSection(&lock_);
196
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);
202   }
203 }
204
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;
209 }
210
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;
223   }
224 }
225
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;
233
234   base::PlatformThread::SetName("BrokerEvent");
235
236   BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
237   HANDLE port = broker->job_port_;
238   HANDLE no_targets = broker->no_targets_;
239
240   int target_counter = 0;
241   ::ResetEvent(no_targets);
242
243   while (true) {
244     DWORD events = 0;
245     ULONG_PTR key = 0;
246     LPOVERLAPPED ovl = NULL;
247
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;
253
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);
258
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;
268         }
269
270         case JOB_OBJECT_MSG_NEW_PROCESS: {
271           ++target_counter;
272           if (1 == target_counter) {
273             ::ResetEvent(no_targets);
274           }
275           break;
276         }
277
278         case JOB_OBJECT_MSG_EXIT_PROCESS:
279         case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
280           {
281             AutoLock lock(&broker->lock_);
282             broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl));
283           }
284           --target_counter;
285           if (0 == target_counter)
286             ::SetEvent(no_targets);
287
288           DCHECK(target_counter >= 0);
289           break;
290         }
291
292         case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
293           break;
294         }
295
296         case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
297           BOOL res = ::TerminateJobObject(tracker->job,
298                                           SBOX_FATAL_MEMORY_EXCEEDED);
299           DCHECK(res);
300           break;
301         }
302
303         default: {
304           NOTREACHED();
305           break;
306         }
307       }
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.
317       return 0;
318     } else {
319       // We have not implemented more commands.
320       NOTREACHED();
321     }
322   }
323
324   NOTREACHED();
325   return 0;
326 }
327
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) {
334   if (!exe_path)
335     return SBOX_ERROR_BAD_PARAMS;
336
337   if (!policy)
338     return SBOX_ERROR_BAD_PARAMS;
339
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());
346
347   AutoLock lock(&lock_);
348
349   // This downcast is safe as long as we control CreatePolicy()
350   PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
351
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;
357
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
361     // process launch.
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;
367     } else {
368       result =
369           policy_base->MakeTokens(&initial_token_temp, &lockdown_token_temp);
370       if (SBOX_ALL_OK != result)
371         return result;
372       token_cache_[token_key] =
373           std::pair<HANDLE, HANDLE>(initial_token_temp, lockdown_token_temp);
374     }
375
376     if (!::DuplicateToken(initial_token_temp, SecurityImpersonation,
377                           &initial_token_temp)) {
378       return SBOX_ERROR_GENERIC;
379     }
380
381     if (!::DuplicateTokenEx(lockdown_token_temp, TOKEN_ALL_ACCESS, 0,
382                             SecurityIdentification, TokenPrimary,
383                             &lockdown_token_temp)) {
384       return SBOX_ERROR_GENERIC;
385     }
386   } else {
387     result = policy_base->MakeTokens(&initial_token_temp, &lockdown_token_temp);
388     if (SBOX_ALL_OK != result)
389       return result;
390   }
391
392   base::win::ScopedHandle initial_token(initial_token_temp);
393   base::win::ScopedHandle lockdown_token(lockdown_token_temp);
394
395   HANDLE job_temp;
396   result = policy_base->MakeJobObject(&job_temp);
397   if (SBOX_ALL_OK != result)
398     return result;
399
400   base::win::ScopedHandle job(job_temp);
401
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());
408   }
409
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();
415     if (app_container)
416       ++attribute_count;
417
418     DWORD64 mitigations;
419     size_t mitigations_size;
420     ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
421                                       &mitigations, &mitigations_size);
422     if (mitigations)
423       ++attribute_count;
424
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)
435       ++attribute_count;
436
437     if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
438       return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
439
440     if (app_container) {
441       result = app_container->ShareForStartup(&startup_info);
442       if (SBOX_ALL_OK != result)
443         return result;
444     }
445
446     if (mitigations) {
447       if (!startup_info.UpdateProcThreadAttribute(
448                PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations,
449                mitigations_size)) {
450         return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
451       }
452     }
453
454     if (inherit_handle_count) {
455       if (!startup_info.UpdateProcThreadAttribute(
456               PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
457               inherit_handle_list,
458               sizeof(inherit_handle_list[0]) * inherit_handle_count)) {
459         return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
460       }
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;
468     }
469   }
470
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();
475
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(),
481                                             job.Get(),
482                                             thread_pool_);
483
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);
488
489   // Now the policy is the owner of the target.
490   if (!policy_base->AddTarget(target)) {
491     return SpawnCleanup(target, 0);
492   }
493
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();
497   if (job.IsValid()) {
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
502     // the Jobs.
503     tracker_list_.push_back(tracker.release());
504     child_process_ids_.insert(process_info.process_id());
505   } else {
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.
514   }
515
516   *target_info = process_info.Take();
517   return SBOX_ALL_OK;
518 }
519
520
521 ResultCode BrokerServicesBase::WaitForAllTargets() {
522   ::WaitForSingleObject(no_targets_, INFINITE);
523   return SBOX_ALL_OK;
524 }
525
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();
530 }
531
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));
537 }
538
539 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
540   scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
541                                                job_port_));
542   if (!peer->id)
543     return SBOX_ERROR_GENERIC;
544
545   HANDLE process_handle;
546   if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
547                          ::GetCurrentProcess(), &process_handle,
548                          SYNCHRONIZE, FALSE, 0)) {
549     return SBOX_ERROR_GENERIC;
550   }
551   peer->process.Set(process_handle);
552
553   AutoLock lock(&lock_);
554   if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
555     return SBOX_ERROR_BAD_PARAMS;
556
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;
562   }
563
564   // Release the pointer since it will be cleaned up by the callback.
565   peer.release();
566   return SBOX_ALL_OK;
567 }
568
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;
573
574   base::string16 old_name = LookupAppContainer(sid);
575   if (old_name.empty())
576     return CreateAppContainer(sid, name);
577
578   if (old_name != name)
579     return SBOX_ERROR_INVALID_APP_CONTAINER;
580
581   return SBOX_ALL_OK;
582 }
583
584 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
585   if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
586     return SBOX_ERROR_UNSUPPORTED;
587
588   base::string16 name = LookupAppContainer(sid);
589   if (name.empty())
590     return SBOX_ERROR_INVALID_APP_CONTAINER;
591
592   return DeleteAppContainer(sid);
593 }
594
595 }  // namespace sandbox