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 "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"
21
22namespace {
23
24// Utility function to associate a completion port to a job object.
25bool 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;
30}
31
32// Utility function to do the cleanup necessary when something goes wrong
33// while in SpawnTarget and we must terminate the target process.
34sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) {
35  if (0 == error)
36    error = ::GetLastError();
37
38  target->Terminate();
39  delete target;
40  ::SetLastError(error);
41  return sandbox::SBOX_ERROR_GENERIC;
42}
43
44// the different commands that you can send to the worker thread that
45// executes TargetEventsThread().
46enum {
47  THREAD_CTRL_NONE,
48  THREAD_CTRL_REMOVE_PEER,
49  THREAD_CTRL_QUIT,
50  THREAD_CTRL_LAST,
51};
52
53// Helper structure that allows the Broker to associate a job notification
54// with a job object and with a policy.
55struct JobTracker {
56  HANDLE job;
57  sandbox::PolicyBase* policy;
58  JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy)
59      : job(cjob), policy(cpolicy) {
60  }
61};
62
63// Helper structure that allows the broker to track peer processes
64struct 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) {
71  }
72};
73
74void 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();
80  }
81}
82
83}  // namespace
84
85namespace sandbox {
86
87BrokerServicesBase::BrokerServicesBase()
88    : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL),
89      job_thread_(NULL) {
90}
91
92// The broker uses a dedicated worker thread that services the job completion
93// port to perform policy notifications and associated cleanup tasks.
94ResultCode BrokerServicesBase::Init() {
95  if ((NULL != job_port_) || (NULL != thread_pool_))
96    return SBOX_ERROR_UNEXPECTED_CALL;
97
98  ::InitializeCriticalSection(&lock_);
99
100  job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
101  if (NULL == job_port_)
102    return SBOX_ERROR_GENERIC;
103
104  no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL);
105
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;
110
111  return SBOX_ALL_OK;
112}
113
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.
119BrokerServicesBase::~BrokerServicesBase() {
120  // If there is no port Init() was never called successfully.
121  if (!job_port_)
122    return;
123
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_);
130
131  if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) {
132    // Cannot clean broker services.
133    NOTREACHED();
134    return;
135  }
136
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;
142  }
143  ::CloseHandle(job_thread_);
144  delete thread_pool_;
145  ::CloseHandle(no_targets_);
146
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);
151  }
152
153  // If job_port_ isn't NULL, assumes that the lock has been initialized.
154  if (job_port_)
155    ::DeleteCriticalSection(&lock_);
156}
157
158TargetPolicy* 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;
162}
163
164void 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;
176  }
177}
178
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.
183DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
184  if (NULL == param)
185    return 1;
186
187  base::PlatformThread::SetName("BrokerEvent");
188
189  BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
190  HANDLE port = broker->job_port_;
191  HANDLE no_targets = broker->no_targets_;
192
193  int target_counter = 0;
194  ::ResetEvent(no_targets);
195
196  while (true) {
197    DWORD events = 0;
198    ULONG_PTR key = 0;
199    LPOVERLAPPED ovl = NULL;
200
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;
206
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);
211
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;
221        }
222
223        case JOB_OBJECT_MSG_NEW_PROCESS: {
224          ++target_counter;
225          if (1 == target_counter) {
226            ::ResetEvent(no_targets);
227          }
228          break;
229        }
230
231        case JOB_OBJECT_MSG_EXIT_PROCESS:
232        case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
233          {
234            AutoLock lock(&broker->lock_);
235            broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl));
236          }
237          --target_counter;
238          if (0 == target_counter)
239            ::SetEvent(no_targets);
240
241          DCHECK(target_counter >= 0);
242          break;
243        }
244
245        case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
246          break;
247        }
248
249        default: {
250          NOTREACHED();
251          break;
252        }
253      }
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();
267    }
268  }
269
270  NOTREACHED();
271  return 0;
272}
273
274// SpawnTarget does all the interesting sandbox setup and creates the target
275// process inside the sandbox.
276ResultCode 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;
282
283  if (!policy)
284    return SBOX_ERROR_BAD_PARAMS;
285
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());
292
293  AutoLock lock(&lock_);
294
295  // This downcast is safe as long as we control CreatePolicy()
296  PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
297
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;
306
307  base::win::ScopedHandle initial_token(initial_token_temp);
308  base::win::ScopedHandle lockdown_token(lockdown_token_temp);
309
310  HANDLE job_temp;
311  result = policy_base->MakeJobObject(&job_temp);
312  if (SBOX_ALL_OK != result)
313    return result;
314
315  base::win::ScopedHandle job(job_temp);
316
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());
323  }
324
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;
332
333    DWORD64 mitigations;
334    size_t mitigations_size;
335    ConvertProcessMitigationsToPolicy(policy->GetProcessMitigations(),
336                                      &mitigations, &mitigations_size);
337    if (mitigations)
338      ++attribute_count;
339
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;
351
352    if (!startup_info.InitializeProcThreadAttributeList(attribute_count))
353      return SBOX_ERROR_PROC_THREAD_ATTRIBUTES;
354
355    if (app_container) {
356      result = app_container->ShareForStartup(&startup_info);
357      if (SBOX_ALL_OK != result)
358        return result;
359    }
360
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;
366      }
367    }
368
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;
375      }
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;
383    }
384  }
385
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();
390
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_);
398
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);
403
404  // Now the policy is the owner of the target.
405  if (!policy_base->AddTarget(target)) {
406    return SpawnCleanup(target, 0);
407  }
408
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.
429  }
430
431  *target_info = process_info.Take();
432  return SBOX_ALL_OK;
433}
434
435
436ResultCode BrokerServicesBase::WaitForAllTargets() {
437  ::WaitForSingleObject(no_targets_, INFINITE);
438  return SBOX_ALL_OK;
439}
440
441bool 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();
445}
446
447VOID 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));
452}
453
454ResultCode 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;
459
460  HANDLE process_handle;
461  if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
462                         ::GetCurrentProcess(), &process_handle,
463                         SYNCHRONIZE, FALSE, 0)) {
464    return SBOX_ERROR_GENERIC;
465  }
466  peer->process.Set(process_handle);
467
468  AutoLock lock(&lock_);
469  if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
470    return SBOX_ERROR_BAD_PARAMS;
471
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;
477  }
478
479  // Release the pointer since it will be cleaned up by the callback.
480  peer.release();
481  return SBOX_ALL_OK;
482}
483
484ResultCode 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;
488
489  string16 old_name = LookupAppContainer(sid);
490  if (old_name.empty())
491    return CreateAppContainer(sid, name);
492
493  if (old_name != name)
494    return SBOX_ERROR_INVALID_APP_CONTAINER;
495
496  return SBOX_ALL_OK;
497}
498
499ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) {
500  if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8)
501    return SBOX_ERROR_UNSUPPORTED;
502
503  string16 name =  LookupAppContainer(sid);
504  if (name.empty())
505    return SBOX_ERROR_INVALID_APP_CONTAINER;
506
507  return DeleteAppContainer(sid);
508}
509
510}  // namespace sandbox
511