• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
AssociateCompletionPort(HANDLE job,HANDLE port,void * key)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.
SpawnCleanup(sandbox::TargetProcess * target,DWORD error)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;
JobTracker__anon23a1bc100111::JobTracker60   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;
PeerTracker__anon23a1bc100111::PeerTracker71   PeerTracker(DWORD process_id, HANDLE broker_job_port)
72       : wait_object(NULL), id(process_id), job_port(broker_job_port) {
73   }
74 };
75 
DeregisterPeerTracker(PeerTracker * peer)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 pack token values into a key for the cache map.
GenerateTokenCacheKey(const sandbox::PolicyBase * policy)86 uint32_t GenerateTokenCacheKey(const sandbox::PolicyBase* policy) {
87   const size_t kTokenShift = 3;
88   uint32_t key;
89 
90   // Make sure our token values aren't too large to pack into the key.
91   static_assert(sandbox::USER_LAST <= (1 << kTokenShift),
92                 "TokenLevel too large");
93   static_assert(sandbox::INTEGRITY_LEVEL_LAST <= (1 << kTokenShift),
94                 "IntegrityLevel too large");
95   static_assert(sizeof(key) < (kTokenShift * 3),
96                 "Token key type too small");
97 
98   // The key is the enum values shifted to avoid overlap and OR'd together.
99   key = policy->GetInitialTokenLevel();
100   key <<= kTokenShift;
101   key |= policy->GetLockdownTokenLevel();
102   key <<= kTokenShift;
103   key |= policy->GetIntegrityLevel();
104 
105   return key;
106 }
107 
108 }  // namespace
109 
110 namespace sandbox {
111 
BrokerServicesBase()112 BrokerServicesBase::BrokerServicesBase()
113     : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL),
114       job_thread_(NULL) {
115 }
116 
117 // The broker uses a dedicated worker thread that services the job completion
118 // port to perform policy notifications and associated cleanup tasks.
Init()119 ResultCode BrokerServicesBase::Init() {
120   if ((NULL != job_port_) || (NULL != thread_pool_))
121     return SBOX_ERROR_UNEXPECTED_CALL;
122 
123   ::InitializeCriticalSection(&lock_);
124 
125   job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
126   if (NULL == job_port_)
127     return SBOX_ERROR_GENERIC;
128 
129   no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL);
130 
131   job_thread_ = ::CreateThread(NULL, 0,  // Default security and stack.
132                                TargetEventsThread, this, NULL, NULL);
133   if (NULL == job_thread_)
134     return SBOX_ERROR_GENERIC;
135 
136   return SBOX_ALL_OK;
137 }
138 
139 // The destructor should only be called when the Broker process is terminating.
140 // Since BrokerServicesBase is a singleton, this is called from the CRT
141 // termination handlers, if this code lives on a DLL it is called during
142 // DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
143 // wait for threads here.
~BrokerServicesBase()144 BrokerServicesBase::~BrokerServicesBase() {
145   // If there is no port Init() was never called successfully.
146   if (!job_port_)
147     return;
148 
149   // Closing the port causes, that no more Job notifications are delivered to
150   // the worker thread and also causes the thread to exit. This is what we
151   // want to do since we are going to close all outstanding Jobs and notifying
152   // the policy objects ourselves.
153   ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE);
154   ::CloseHandle(job_port_);
155 
156   if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) {
157     // Cannot clean broker services.
158     NOTREACHED();
159     return;
160   }
161 
162   JobTrackerList::iterator it;
163   for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) {
164     JobTracker* tracker = (*it);
165     FreeResources(tracker);
166     delete tracker;
167   }
168   ::CloseHandle(job_thread_);
169   delete thread_pool_;
170   ::CloseHandle(no_targets_);
171 
172   // Cancel the wait events and delete remaining peer trackers.
173   for (PeerTrackerMap::iterator it = peer_map_.begin();
174        it != peer_map_.end(); ++it) {
175     DeregisterPeerTracker(it->second);
176   }
177 
178   // If job_port_ isn't NULL, assumes that the lock has been initialized.
179   if (job_port_)
180     ::DeleteCriticalSection(&lock_);
181 
182   // Close any token in the cache.
183   for (TokenCacheMap::iterator it = token_cache_.begin();
184        it != token_cache_.end(); ++it) {
185     ::CloseHandle(it->second.first);
186     ::CloseHandle(it->second.second);
187   }
188 }
189 
CreatePolicy()190 TargetPolicy* BrokerServicesBase::CreatePolicy() {
191   // If you change the type of the object being created here you must also
192   // change the downcast to it in SpawnTarget().
193   return new PolicyBase;
194 }
195 
FreeResources(JobTracker * tracker)196 void BrokerServicesBase::FreeResources(JobTracker* tracker) {
197   if (NULL != tracker->policy) {
198     BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK);
199     DCHECK(res);
200     // Closing the job causes the target process to be destroyed so this
201     // needs to happen before calling OnJobEmpty().
202     res = ::CloseHandle(tracker->job);
203     DCHECK(res);
204     // In OnJobEmpty() we don't actually use the job handle directly.
205     tracker->policy->OnJobEmpty(tracker->job);
206     tracker->policy->Release();
207     tracker->policy = NULL;
208   }
209 }
210 
211 // The worker thread stays in a loop waiting for asynchronous notifications
212 // from the job objects. Right now we only care about knowing when the last
213 // process on a job terminates, but in general this is the place to tell
214 // the policy about events.
TargetEventsThread(PVOID param)215 DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
216   if (NULL == param)
217     return 1;
218 
219   base::PlatformThread::SetName("BrokerEvent");
220 
221   BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
222   HANDLE port = broker->job_port_;
223   HANDLE no_targets = broker->no_targets_;
224 
225   int target_counter = 0;
226   ::ResetEvent(no_targets);
227 
228   while (true) {
229     DWORD events = 0;
230     ULONG_PTR key = 0;
231     LPOVERLAPPED ovl = NULL;
232 
233     if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE))
234       // this call fails if the port has been closed before we have a
235       // chance to service the last packet which is 'exit' anyway so
236       // this is not an error.
237       return 1;
238 
239     if (key > THREAD_CTRL_LAST) {
240       // The notification comes from a job object. There are nine notifications
241       // that jobs can send and some of them depend on the job attributes set.
242       JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
243 
244       switch (events) {
245         case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
246           // The job object has signaled that the last process associated
247           // with it has terminated. Assuming there is no way for a process
248           // to appear out of thin air in this job, it safe to assume that
249           // we can tell the policy to destroy the target object, and for
250           // us to release our reference to the policy object.
251           FreeResources(tracker);
252           break;
253         }
254 
255         case JOB_OBJECT_MSG_NEW_PROCESS: {
256           ++target_counter;
257           if (1 == target_counter) {
258             ::ResetEvent(no_targets);
259           }
260           break;
261         }
262 
263         case JOB_OBJECT_MSG_EXIT_PROCESS:
264         case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
265           {
266             AutoLock lock(&broker->lock_);
267             broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl));
268           }
269           --target_counter;
270           if (0 == target_counter)
271             ::SetEvent(no_targets);
272 
273           DCHECK(target_counter >= 0);
274           break;
275         }
276 
277         case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
278           break;
279         }
280 
281         case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: {
282           BOOL res = ::TerminateJobObject(tracker->job,
283                                           SBOX_FATAL_MEMORY_EXCEEDED);
284           DCHECK(res);
285           break;
286         }
287 
288         default: {
289           NOTREACHED();
290           break;
291         }
292       }
293     } else if (THREAD_CTRL_REMOVE_PEER == key) {
294       // Remove a process from our list of peers.
295       AutoLock lock(&broker->lock_);
296       PeerTrackerMap::iterator it =
297           broker->peer_map_.find(reinterpret_cast<DWORD>(ovl));
298       DeregisterPeerTracker(it->second);
299       broker->peer_map_.erase(it);
300     } else if (THREAD_CTRL_QUIT == key) {
301       // The broker object is being destroyed so the thread needs to exit.
302       return 0;
303     } else {
304       // We have not implemented more commands.
305       NOTREACHED();
306     }
307   }
308 
309   NOTREACHED();
310   return 0;
311 }
312 
313 // SpawnTarget does all the interesting sandbox setup and creates the target
314 // process inside the sandbox.
SpawnTarget(const wchar_t * exe_path,const wchar_t * command_line,TargetPolicy * policy,PROCESS_INFORMATION * target_info)315 ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
316                                            const wchar_t* command_line,
317                                            TargetPolicy* policy,
318                                            PROCESS_INFORMATION* target_info) {
319   if (!exe_path)
320     return SBOX_ERROR_BAD_PARAMS;
321 
322   if (!policy)
323     return SBOX_ERROR_BAD_PARAMS;
324 
325   // Even though the resources touched by SpawnTarget can be accessed in
326   // multiple threads, the method itself cannot be called from more than
327   // 1 thread. This is to protect the global variables used while setting up
328   // the child process.
329   static DWORD thread_id = ::GetCurrentThreadId();
330   DCHECK(thread_id == ::GetCurrentThreadId());
331 
332   AutoLock lock(&lock_);
333 
334   // This downcast is safe as long as we control CreatePolicy()
335   PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
336 
337   // Construct the tokens and the job object that we are going to associate
338   // with the soon to be created target process.
339   HANDLE initial_token_temp;
340   HANDLE lockdown_token_temp;
341   ResultCode result = SBOX_ALL_OK;
342 
343   // Create the master tokens only once and save them in a cache. That way
344   // can just duplicate them to avoid hammering LSASS on every sandboxed
345   // process launch.
346   uint32_t token_key = GenerateTokenCacheKey(policy_base);
347   TokenCacheMap::iterator it = token_cache_.find(token_key);
348   if (it != token_cache_.end()) {
349     initial_token_temp = it->second.first;
350     lockdown_token_temp = it->second.second;
351   } else {
352     result = policy_base->MakeTokens(&initial_token_temp,
353                                      &lockdown_token_temp);
354     if (SBOX_ALL_OK != result)
355       return result;
356     token_cache_[token_key] =
357         std::pair<HANDLE, HANDLE>(initial_token_temp, lockdown_token_temp);
358   }
359 
360   if (!::DuplicateToken(initial_token_temp, SecurityImpersonation,
361                         &initial_token_temp)) {
362     return SBOX_ERROR_GENERIC;
363   }
364 
365   if (!::DuplicateTokenEx(lockdown_token_temp, TOKEN_ALL_ACCESS, 0,
366                           SecurityIdentification, TokenPrimary,
367                           &lockdown_token_temp)) {
368     return SBOX_ERROR_GENERIC;
369   }
370 
371   base::win::ScopedHandle initial_token(initial_token_temp);
372   base::win::ScopedHandle lockdown_token(lockdown_token_temp);
373 
374   HANDLE job_temp;
375   result = policy_base->MakeJobObject(&job_temp);
376   if (SBOX_ALL_OK != result)
377     return result;
378 
379   base::win::ScopedHandle job(job_temp);
380 
381   // Initialize the startup information from the policy.
382   base::win::StartupInformation startup_info;
383   base::string16 desktop = policy_base->GetAlternateDesktop();
384   if (!desktop.empty()) {
385     startup_info.startup_info()->lpDesktop =
386         const_cast<wchar_t*>(desktop.c_str());
387   }
388 
389   bool inherit_handles = false;
390   if (base::win::GetVersion() >= base::win::VERSION_VISTA) {
391     int attribute_count = 0;
392     const AppContainerAttributes* app_container =
393         policy_base->GetAppContainer();
394     if (app_container)
395       ++attribute_count;
396 
397     DWORD64 mitigations;
398     size_t mitigations_size;
399     ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
400                                       &mitigations, &mitigations_size);
401     if (mitigations)
402       ++attribute_count;
403 
404     HANDLE stdout_handle = policy_base->GetStdoutHandle();
405     HANDLE stderr_handle = policy_base->GetStderrHandle();
406     HANDLE inherit_handle_list[2];
407     int inherit_handle_count = 0;
408     if (stdout_handle != INVALID_HANDLE_VALUE)
409       inherit_handle_list[inherit_handle_count++] = stdout_handle;
410     // Handles in the list must be unique.
411     if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE)
412       inherit_handle_list[inherit_handle_count++] = stderr_handle;
413     if (inherit_handle_count)
414       ++attribute_count;
415 
416     if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
417       return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
418 
419     if (app_container) {
420       result = app_container->ShareForStartup(&startup_info);
421       if (SBOX_ALL_OK != result)
422         return result;
423     }
424 
425     if (mitigations) {
426       if (!startup_info.UpdateProcThreadAttribute(
427                PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations,
428                mitigations_size)) {
429         return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
430       }
431     }
432 
433     if (inherit_handle_count) {
434       if (!startup_info.UpdateProcThreadAttribute(
435               PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
436               inherit_handle_list,
437               sizeof(inherit_handle_list[0]) * inherit_handle_count)) {
438         return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
439       }
440       startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES;
441       startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE;
442       startup_info.startup_info()->hStdOutput = stdout_handle;
443       startup_info.startup_info()->hStdError = stderr_handle;
444       // Allowing inheritance of handles is only secure now that we
445       // have limited which handles will be inherited.
446       inherit_handles = true;
447     }
448   }
449 
450   // Construct the thread pool here in case it is expensive.
451   // The thread pool is shared by all the targets
452   if (NULL == thread_pool_)
453     thread_pool_ = new Win2kThreadPool();
454 
455   // Create the TargetProces object and spawn the target suspended. Note that
456   // Brokerservices does not own the target object. It is owned by the Policy.
457   base::win::ScopedProcessInformation process_info;
458   TargetProcess* target = new TargetProcess(initial_token.Take(),
459                                             lockdown_token.Take(),
460                                             job.Get(),
461                                             thread_pool_);
462 
463   DWORD win_result = target->Create(exe_path, command_line, inherit_handles,
464                                     startup_info, &process_info);
465   if (ERROR_SUCCESS != win_result)
466     return SpawnCleanup(target, win_result);
467 
468   // Now the policy is the owner of the target.
469   if (!policy_base->AddTarget(target)) {
470     return SpawnCleanup(target, 0);
471   }
472 
473   // We are going to keep a pointer to the policy because we'll call it when
474   // the job object generates notifications using the completion port.
475   policy_base->AddRef();
476   if (job.IsValid()) {
477     scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base));
478     if (!AssociateCompletionPort(tracker->job, job_port_, tracker.get()))
479       return SpawnCleanup(target, 0);
480     // Save the tracker because in cleanup we might need to force closing
481     // the Jobs.
482     tracker_list_.push_back(tracker.release());
483     child_process_ids_.insert(process_info.process_id());
484   } else {
485     // We have to signal the event once here because the completion port will
486     // never get a message that this target is being terminated thus we should
487     // not block WaitForAllTargets until we have at least one target with job.
488     if (child_process_ids_.empty())
489       ::SetEvent(no_targets_);
490     // We can not track the life time of such processes and it is responsibility
491     // of the host application to make sure that spawned targets without jobs
492     // are terminated when the main application don't need them anymore.
493   }
494 
495   *target_info = process_info.Take();
496   return SBOX_ALL_OK;
497 }
498 
499 
WaitForAllTargets()500 ResultCode BrokerServicesBase::WaitForAllTargets() {
501   ::WaitForSingleObject(no_targets_, INFINITE);
502   return SBOX_ALL_OK;
503 }
504 
IsActiveTarget(DWORD process_id)505 bool BrokerServicesBase::IsActiveTarget(DWORD process_id) {
506   AutoLock lock(&lock_);
507   return child_process_ids_.find(process_id) != child_process_ids_.end() ||
508          peer_map_.find(process_id) != peer_map_.end();
509 }
510 
RemovePeer(PVOID parameter,BOOLEAN timeout)511 VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) {
512   PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter);
513   // Don't check the return code because we this may fail (safely) at shutdown.
514   ::PostQueuedCompletionStatus(peer->job_port, 0, THREAD_CTRL_REMOVE_PEER,
515                                reinterpret_cast<LPOVERLAPPED>(peer->id));
516 }
517 
AddTargetPeer(HANDLE peer_process)518 ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
519   scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
520                                                job_port_));
521   if (!peer->id)
522     return SBOX_ERROR_GENERIC;
523 
524   HANDLE process_handle;
525   if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
526                          ::GetCurrentProcess(), &process_handle,
527                          SYNCHRONIZE, FALSE, 0)) {
528     return SBOX_ERROR_GENERIC;
529   }
530   peer->process.Set(process_handle);
531 
532   AutoLock lock(&lock_);
533   if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
534     return SBOX_ERROR_BAD_PARAMS;
535 
536   if (!::RegisterWaitForSingleObject(
537           &peer->wait_object, peer->process.Get(), RemovePeer, peer.get(),
538           INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
539     peer_map_.erase(peer->id);
540     return SBOX_ERROR_GENERIC;
541   }
542 
543   // Release the pointer since it will be cleaned up by the callback.
544   peer.release();
545   return SBOX_ALL_OK;
546 }
547 
InstallAppContainer(const wchar_t * sid,const wchar_t * name)548 ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid,
549                                                    const wchar_t* name) {
550   if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
551     return SBOX_ERROR_UNSUPPORTED;
552 
553   base::string16 old_name = LookupAppContainer(sid);
554   if (old_name.empty())
555     return CreateAppContainer(sid, name);
556 
557   if (old_name != name)
558     return SBOX_ERROR_INVALID_APP_CONTAINER;
559 
560   return SBOX_ALL_OK;
561 }
562 
UninstallAppContainer(const wchar_t * sid)563 ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
564   if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
565     return SBOX_ERROR_UNSUPPORTED;
566 
567   base::string16 name = LookupAppContainer(sid);
568   if (name.empty())
569     return SBOX_ERROR_INVALID_APP_CONTAINER;
570 
571   return DeleteAppContainer(sid);
572 }
573 
574 }  // namespace sandbox
575