summaryrefslogtreecommitdiffstats
path: root/sandbox/win/src/broker_services.cc
diff options
context:
space:
mode:
authorjln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-18 00:59:15 +0000
committerjln@chromium.org <jln@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-07-18 00:59:15 +0000
commit18149178646e45f3d7dde865efbeabbab431799a (patch)
treecc19ce0fc5cc1927695c789212fab93a195b9d9f /sandbox/win/src/broker_services.cc
parent4633cdb53427b31197d3c6f991f07bee2a04e0df (diff)
downloadchromium_src-18149178646e45f3d7dde865efbeabbab431799a.zip
chromium_src-18149178646e45f3d7dde865efbeabbab431799a.tar.gz
chromium_src-18149178646e45f3d7dde865efbeabbab431799a.tar.bz2
Move the Windows sandbox to sandbox/win
This is a rather large refactor to move the Windows sandbox to the right place. BUG= TEST= NOTRY=true TBR=sky@chromium.org Review URL: https://chromiumcodereview.appspot.com/10689170 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@147151 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'sandbox/win/src/broker_services.cc')
-rw-r--r--sandbox/win/src/broker_services.cc405
1 files changed, 405 insertions, 0 deletions
diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc
new file mode 100644
index 0000000..80837b3
--- /dev/null
+++ b/sandbox/win/src/broker_services.cc
@@ -0,0 +1,405 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "sandbox/win/src/broker_services.h"
+
+#include "base/logging.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/threading/platform_thread.h"
+#include "base/win/scoped_handle.h"
+#include "base/win/scoped_process_information.h"
+#include "sandbox/win/src/sandbox_policy_base.h"
+#include "sandbox/win/src/sandbox.h"
+#include "sandbox/win/src/target_process.h"
+#include "sandbox/win/src/win2k_threadpool.h"
+#include "sandbox/win/src/win_utils.h"
+
+namespace {
+
+// Utility function to associate a completion port to a job object.
+bool AssociateCompletionPort(HANDLE job, HANDLE port, void* key) {
+ JOBOBJECT_ASSOCIATE_COMPLETION_PORT job_acp = { key, port };
+ return ::SetInformationJobObject(job,
+ JobObjectAssociateCompletionPortInformation,
+ &job_acp, sizeof(job_acp))? true : false;
+}
+
+// Utility function to do the cleanup necessary when something goes wrong
+// while in SpawnTarget and we must terminate the target process.
+sandbox::ResultCode SpawnCleanup(sandbox::TargetProcess* target, DWORD error) {
+ if (0 == error)
+ error = ::GetLastError();
+
+ target->Terminate();
+ delete target;
+ ::SetLastError(error);
+ return sandbox::SBOX_ERROR_GENERIC;
+}
+
+// the different commands that you can send to the worker thread that
+// executes TargetEventsThread().
+enum {
+ THREAD_CTRL_NONE,
+ THREAD_CTRL_REMOVE_PEER,
+ THREAD_CTRL_QUIT,
+ THREAD_CTRL_LAST,
+};
+
+// Helper structure that allows the Broker to associate a job notification
+// with a job object and with a policy.
+struct JobTracker {
+ HANDLE job;
+ sandbox::PolicyBase* policy;
+ JobTracker(HANDLE cjob, sandbox::PolicyBase* cpolicy)
+ : job(cjob), policy(cpolicy) {
+ }
+};
+
+// Helper structure that allows the broker to track peer processes
+struct PeerTracker {
+ HANDLE wait_object;
+ base::win::ScopedHandle process;
+ DWORD id;
+ HANDLE job_port;
+ PeerTracker(DWORD process_id, HANDLE broker_job_port)
+ : wait_object(NULL), id(process_id), job_port(broker_job_port) {
+ }
+};
+
+void DeregisterPeerTracker(PeerTracker* peer) {
+ // Deregistration shouldn't fail, but we leak rather than crash if it does.
+ if (::UnregisterWaitEx(peer->wait_object, INVALID_HANDLE_VALUE)) {
+ delete peer;
+ } else {
+ NOTREACHED();
+ }
+}
+
+} // namespace
+
+namespace sandbox {
+
+BrokerServicesBase::BrokerServicesBase()
+ : thread_pool_(NULL), job_port_(NULL), no_targets_(NULL),
+ job_thread_(NULL) {
+}
+
+// The broker uses a dedicated worker thread that services the job completion
+// port to perform policy notifications and associated cleanup tasks.
+ResultCode BrokerServicesBase::Init() {
+ if ((NULL != job_port_) || (NULL != thread_pool_))
+ return SBOX_ERROR_UNEXPECTED_CALL;
+
+ ::InitializeCriticalSection(&lock_);
+
+ job_port_ = ::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
+ if (NULL == job_port_)
+ return SBOX_ERROR_GENERIC;
+
+ no_targets_ = ::CreateEventW(NULL, TRUE, FALSE, NULL);
+
+ job_thread_ = ::CreateThread(NULL, 0, // Default security and stack.
+ TargetEventsThread, this, NULL, NULL);
+ if (NULL == job_thread_)
+ return SBOX_ERROR_GENERIC;
+
+ return SBOX_ALL_OK;
+}
+
+// The destructor should only be called when the Broker process is terminating.
+// Since BrokerServicesBase is a singleton, this is called from the CRT
+// termination handlers, if this code lives on a DLL it is called during
+// DLL_PROCESS_DETACH in other words, holding the loader lock, so we cannot
+// wait for threads here.
+BrokerServicesBase::~BrokerServicesBase() {
+ // If there is no port Init() was never called successfully.
+ if (!job_port_)
+ return;
+
+ // Closing the port causes, that no more Job notifications are delivered to
+ // the worker thread and also causes the thread to exit. This is what we
+ // want to do since we are going to close all outstanding Jobs and notifying
+ // the policy objects ourselves.
+ ::PostQueuedCompletionStatus(job_port_, 0, THREAD_CTRL_QUIT, FALSE);
+ ::CloseHandle(job_port_);
+
+ if (WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_, 1000)) {
+ // Cannot clean broker services.
+ NOTREACHED();
+ return;
+ }
+
+ JobTrackerList::iterator it;
+ for (it = tracker_list_.begin(); it != tracker_list_.end(); ++it) {
+ JobTracker* tracker = (*it);
+ FreeResources(tracker);
+ delete tracker;
+ }
+ ::CloseHandle(job_thread_);
+ delete thread_pool_;
+ ::CloseHandle(no_targets_);
+
+ // Cancel the wait events and delete remaining peer trackers.
+ for (PeerTrackerMap::iterator it = peer_map_.begin();
+ it != peer_map_.end(); ++it) {
+ DeregisterPeerTracker(it->second);
+ }
+
+ // If job_port_ isn't NULL, assumes that the lock has been initialized.
+ if (job_port_)
+ ::DeleteCriticalSection(&lock_);
+}
+
+TargetPolicy* BrokerServicesBase::CreatePolicy() {
+ // If you change the type of the object being created here you must also
+ // change the downcast to it in SpawnTarget().
+ return new PolicyBase;
+}
+
+void BrokerServicesBase::FreeResources(JobTracker* tracker) {
+ if (NULL != tracker->policy) {
+ BOOL res = ::TerminateJobObject(tracker->job, SBOX_ALL_OK);
+ DCHECK(res);
+ // Closing the job causes the target process to be destroyed so this
+ // needs to happen before calling OnJobEmpty().
+ res = ::CloseHandle(tracker->job);
+ DCHECK(res);
+ // In OnJobEmpty() we don't actually use the job handle directly.
+ tracker->policy->OnJobEmpty(tracker->job);
+ tracker->policy->Release();
+ tracker->policy = NULL;
+ }
+}
+
+// The worker thread stays in a loop waiting for asynchronous notifications
+// from the job objects. Right now we only care about knowing when the last
+// process on a job terminates, but in general this is the place to tell
+// the policy about events.
+DWORD WINAPI BrokerServicesBase::TargetEventsThread(PVOID param) {
+ if (NULL == param)
+ return 1;
+
+ base::PlatformThread::SetName("BrokerEvent");
+
+ BrokerServicesBase* broker = reinterpret_cast<BrokerServicesBase*>(param);
+ HANDLE port = broker->job_port_;
+ HANDLE no_targets = broker->no_targets_;
+
+ int target_counter = 0;
+ ::ResetEvent(no_targets);
+
+ while (true) {
+ DWORD events = 0;
+ ULONG_PTR key = 0;
+ LPOVERLAPPED ovl = NULL;
+
+ if (!::GetQueuedCompletionStatus(port, &events, &key, &ovl, INFINITE))
+ // this call fails if the port has been closed before we have a
+ // chance to service the last packet which is 'exit' anyway so
+ // this is not an error.
+ return 1;
+
+ if (key > THREAD_CTRL_LAST) {
+ // The notification comes from a job object. There are nine notifications
+ // that jobs can send and some of them depend on the job attributes set.
+ JobTracker* tracker = reinterpret_cast<JobTracker*>(key);
+
+ switch (events) {
+ case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: {
+ // The job object has signaled that the last process associated
+ // with it has terminated. Assuming there is no way for a process
+ // to appear out of thin air in this job, it safe to assume that
+ // we can tell the policy to destroy the target object, and for
+ // us to release our reference to the policy object.
+ FreeResources(tracker);
+ break;
+ }
+
+ case JOB_OBJECT_MSG_NEW_PROCESS: {
+ ++target_counter;
+ if (1 == target_counter) {
+ ::ResetEvent(no_targets);
+ }
+ break;
+ }
+
+ case JOB_OBJECT_MSG_EXIT_PROCESS:
+ case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: {
+ {
+ AutoLock lock(&broker->lock_);
+ broker->child_process_ids_.erase(reinterpret_cast<DWORD>(ovl));
+ }
+ --target_counter;
+ if (0 == target_counter)
+ ::SetEvent(no_targets);
+
+ DCHECK(target_counter >= 0);
+ break;
+ }
+
+ case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: {
+ break;
+ }
+
+ default: {
+ NOTREACHED();
+ break;
+ }
+ }
+ } else if (THREAD_CTRL_REMOVE_PEER == key) {
+ // Remove a process from our list of peers.
+ AutoLock lock(&broker->lock_);
+ PeerTrackerMap::iterator it =
+ broker->peer_map_.find(reinterpret_cast<DWORD>(ovl));
+ DeregisterPeerTracker(it->second);
+ broker->peer_map_.erase(it);
+ } else if (THREAD_CTRL_QUIT == key) {
+ // The broker object is being destroyed so the thread needs to exit.
+ return 0;
+ } else {
+ // We have not implemented more commands.
+ NOTREACHED();
+ }
+ }
+
+ NOTREACHED();
+ return 0;
+}
+
+// SpawnTarget does all the interesting sandbox setup and creates the target
+// process inside the sandbox.
+ResultCode BrokerServicesBase::SpawnTarget(const wchar_t* exe_path,
+ const wchar_t* command_line,
+ TargetPolicy* policy,
+ PROCESS_INFORMATION* target_info) {
+ if (!exe_path)
+ return SBOX_ERROR_BAD_PARAMS;
+
+ if (!policy)
+ return SBOX_ERROR_BAD_PARAMS;
+
+ // Even though the resources touched by SpawnTarget can be accessed in
+ // multiple threads, the method itself cannot be called from more than
+ // 1 thread. This is to protect the global variables used while setting up
+ // the child process.
+ static DWORD thread_id = ::GetCurrentThreadId();
+ DCHECK(thread_id == ::GetCurrentThreadId());
+
+ AutoLock lock(&lock_);
+
+ // This downcast is safe as long as we control CreatePolicy()
+ PolicyBase* policy_base = static_cast<PolicyBase*>(policy);
+
+ // Construct the tokens and the job object that we are going to associate
+ // with the soon to be created target process.
+ HANDLE initial_token_temp;
+ HANDLE lockdown_token_temp;
+ DWORD win_result = policy_base->MakeTokens(&initial_token_temp,
+ &lockdown_token_temp);
+ base::win::ScopedHandle initial_token(initial_token_temp);
+ base::win::ScopedHandle lockdown_token(lockdown_token_temp);
+
+ if (ERROR_SUCCESS != win_result)
+ return SBOX_ERROR_GENERIC;
+
+ HANDLE job_temp;
+ win_result = policy_base->MakeJobObject(&job_temp);
+ base::win::ScopedHandle job(job_temp);
+ if (ERROR_SUCCESS != win_result)
+ return SBOX_ERROR_GENERIC;
+
+ if (ERROR_ALREADY_EXISTS == ::GetLastError())
+ return SBOX_ERROR_GENERIC;
+
+ // Construct the thread pool here in case it is expensive.
+ // The thread pool is shared by all the targets
+ if (NULL == thread_pool_)
+ thread_pool_ = new Win2kThreadPool();
+
+ // Create the TargetProces object and spawn the target suspended. Note that
+ // Brokerservices does not own the target object. It is owned by the Policy.
+ base::win::ScopedProcessInformation process_info;
+ TargetProcess* target = new TargetProcess(initial_token.Take(),
+ lockdown_token.Take(),
+ job,
+ thread_pool_);
+
+ std::wstring desktop = policy_base->GetAlternateDesktop();
+
+ win_result = target->Create(exe_path, command_line,
+ desktop.empty() ? NULL : desktop.c_str(),
+ &process_info);
+ if (ERROR_SUCCESS != win_result)
+ return SpawnCleanup(target, win_result);
+
+ // Now the policy is the owner of the target.
+ if (!policy_base->AddTarget(target)) {
+ return SpawnCleanup(target, 0);
+ }
+
+ // We are going to keep a pointer to the policy because we'll call it when
+ // the job object generates notifications using the completion port.
+ policy_base->AddRef();
+ scoped_ptr<JobTracker> tracker(new JobTracker(job.Take(), policy_base));
+ if (!AssociateCompletionPort(tracker->job, job_port_, tracker.get()))
+ return SpawnCleanup(target, 0);
+ // Save the tracker because in cleanup we might need to force closing
+ // the Jobs.
+ tracker_list_.push_back(tracker.release());
+ child_process_ids_.insert(process_info.process_id());
+
+ *target_info = process_info.Take();
+ return SBOX_ALL_OK;
+}
+
+
+ResultCode BrokerServicesBase::WaitForAllTargets() {
+ ::WaitForSingleObject(no_targets_, INFINITE);
+ return SBOX_ALL_OK;
+}
+
+bool BrokerServicesBase::IsActiveTarget(DWORD process_id) {
+ AutoLock lock(&lock_);
+ return child_process_ids_.find(process_id) != child_process_ids_.end() ||
+ peer_map_.find(process_id) != peer_map_.end();
+}
+
+VOID CALLBACK BrokerServicesBase::RemovePeer(PVOID parameter, BOOLEAN timeout) {
+ PeerTracker* peer = reinterpret_cast<PeerTracker*>(parameter);
+ // Don't check the return code because we this may fail (safely) at shutdown.
+ ::PostQueuedCompletionStatus(peer->job_port, 0, THREAD_CTRL_REMOVE_PEER,
+ reinterpret_cast<LPOVERLAPPED>(peer->id));
+}
+
+ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) {
+ scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process),
+ job_port_));
+ if (!peer->id)
+ return SBOX_ERROR_GENERIC;
+
+ HANDLE process_handle;
+ if (!::DuplicateHandle(::GetCurrentProcess(), peer_process,
+ ::GetCurrentProcess(), &process_handle,
+ SYNCHRONIZE, FALSE, 0)) {
+ return SBOX_ERROR_GENERIC;
+ }
+ peer->process.Set(process_handle);
+
+ AutoLock lock(&lock_);
+ if (!peer_map_.insert(std::make_pair(peer->id, peer.get())).second)
+ return SBOX_ERROR_BAD_PARAMS;
+
+ if (!::RegisterWaitForSingleObject(
+ &peer->wait_object, peer->process, RemovePeer, peer.get(), INFINITE,
+ WT_EXECUTEONLYONCE | WT_EXECUTEINWAITTHREAD)) {
+ peer_map_.erase(peer->id);
+ return SBOX_ERROR_GENERIC;
+ }
+
+ // Release the pointer since it will be cleaned up by the callback.
+ peer.release();
+ return SBOX_ALL_OK;
+}
+
+} // namespace sandbox