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
24namespace {
25
26// Utility function to associate a completion port to a job object.
27bool 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.
36sandbox::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().
48enum {
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.
57struct 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
66struct 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
76void 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.
86uint32_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
110namespace sandbox {
111
112BrokerServicesBase::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.
119ResultCode 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.
144BrokerServicesBase::~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
190TargetPolicy* 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
196void 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.
215DWORD 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.
315ResultCode 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
500ResultCode BrokerServicesBase::WaitForAllTargets() {
501  ::WaitForSingleObject(no_targets_, INFINITE);
502  return SBOX_ALL_OK;
503}
504
505bool 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
511VOID 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
518ResultCode 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
548ResultCode 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
563ResultCode 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