Index: sandbox/win/src/broker_services.cc |
diff --git a/sandbox/win/src/broker_services.cc b/sandbox/win/src/broker_services.cc |
deleted file mode 100644 |
index 26b2561021dc2f32d9395aac7244990f56c84b22..0000000000000000000000000000000000000000 |
--- a/sandbox/win/src/broker_services.cc |
+++ /dev/null |
@@ -1,582 +0,0 @@ |
-// 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 <AclAPI.h> |
-#include <stddef.h> |
-#include <utility> |
- |
-#include "base/logging.h" |
-#include "base/macros.h" |
-#include "base/memory/scoped_ptr.h" |
-#include "base/stl_util.h" |
-#include "base/threading/platform_thread.h" |
-#include "base/win/scoped_handle.h" |
-#include "base/win/scoped_process_information.h" |
-#include "base/win/startup_information.h" |
-#include "base/win/windows_version.h" |
-#include "sandbox/win/src/app_container.h" |
-#include "sandbox/win/src/process_mitigations.h" |
-#include "sandbox/win/src/sandbox.h" |
-#include "sandbox/win/src/sandbox_policy_base.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 { |
- JobTracker(base::win::ScopedHandle job, sandbox::PolicyBase* policy) |
- : job(std::move(job)), policy(policy) {} |
- ~JobTracker() { |
- FreeResources(); |
- } |
- |
- // Releases the Job and notifies the associated Policy object to release its |
- // resources as well. |
- void FreeResources(); |
- |
- base::win::ScopedHandle job; |
- sandbox::PolicyBase* policy; |
-}; |
- |
-void JobTracker::FreeResources() { |
- if (policy) { |
- BOOL res = ::TerminateJobObject(job.Get(), sandbox::SBOX_ALL_OK); |
- DCHECK(res); |
- // Closing the job causes the target process to be destroyed so this needs |
- // to happen before calling OnJobEmpty(). |
- HANDLE stale_job_handle = job.Get(); |
- job.Close(); |
- |
- // In OnJobEmpty() we don't actually use the job handle directly. |
- policy->OnJobEmpty(stale_job_handle); |
- policy->Release(); |
- policy = NULL; |
- } |
-} |
- |
-// Helper structure that allows the broker to track peer processes |
-struct PeerTracker { |
- PeerTracker(DWORD process_id, HANDLE broker_job_port) |
- : wait_object(NULL), id(process_id), job_port(broker_job_port) { |
- } |
- |
- HANDLE wait_object; |
- base::win::ScopedHandle process; |
- DWORD id; |
- HANDLE 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) { |
-} |
- |
-// 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 (job_port_.IsValid() || (NULL != thread_pool_)) |
- return SBOX_ERROR_UNEXPECTED_CALL; |
- |
- ::InitializeCriticalSection(&lock_); |
- |
- job_port_.Set(::CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0)); |
- if (!job_port_.IsValid()) |
- return SBOX_ERROR_GENERIC; |
- |
- no_targets_.Set(::CreateEventW(NULL, TRUE, FALSE, NULL)); |
- |
- job_thread_.Set(::CreateThread(NULL, 0, // Default security and stack. |
- TargetEventsThread, this, NULL, NULL)); |
- if (!job_thread_.IsValid()) |
- 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_.IsValid()) |
- 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_.Get(), 0, THREAD_CTRL_QUIT, FALSE); |
- |
- if (job_thread_.IsValid() && |
- WAIT_TIMEOUT == ::WaitForSingleObject(job_thread_.Get(), 1000)) { |
- // Cannot clean broker services. |
- NOTREACHED(); |
- return; |
- } |
- |
- STLDeleteElements(&tracker_list_); |
- delete thread_pool_; |
- |
- // Cancel the wait events and delete remaining peer trackers. |
- for (PeerTrackerMap::iterator it = peer_map_.begin(); |
- it != peer_map_.end(); ++it) { |
- DeregisterPeerTracker(it->second); |
- } |
- |
- ::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; |
-} |
- |
-// 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_.Get(); |
- HANDLE no_targets = broker->no_targets_.Get(); |
- |
- int target_counter = 0; |
- int untracked_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. |
- tracker->FreeResources(); |
- break; |
- } |
- |
- case JOB_OBJECT_MSG_NEW_PROCESS: { |
- DWORD handle = static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl)); |
- { |
- AutoLock lock(&broker->lock_); |
- size_t count = broker->child_process_ids_.count(handle); |
- // Child process created from sandboxed process. |
- if (count == 0) |
- untracked_target_counter++; |
- } |
- ++target_counter; |
- if (1 == target_counter) { |
- ::ResetEvent(no_targets); |
- } |
- break; |
- } |
- |
- case JOB_OBJECT_MSG_EXIT_PROCESS: |
- case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: { |
- size_t erase_result = 0; |
- { |
- AutoLock lock(&broker->lock_); |
- erase_result = broker->child_process_ids_.erase( |
- static_cast<DWORD>(reinterpret_cast<uintptr_t>(ovl))); |
- } |
- if (erase_result != 1U) { |
- // The process was untracked e.g. a child process of the target. |
- --untracked_target_counter; |
- DCHECK(untracked_target_counter >= 0); |
- } |
- --target_counter; |
- if (0 == target_counter) |
- ::SetEvent(no_targets); |
- |
- DCHECK(target_counter >= 0); |
- break; |
- } |
- |
- case JOB_OBJECT_MSG_ACTIVE_PROCESS_LIMIT: { |
- // A child process attempted and failed to create a child process. |
- // Windows does not reveal the process id. |
- untracked_target_counter++; |
- target_counter++; |
- break; |
- } |
- |
- case JOB_OBJECT_MSG_PROCESS_MEMORY_LIMIT: { |
- BOOL res = ::TerminateJobObject(tracker->job.Get(), |
- SBOX_FATAL_MEMORY_EXCEEDED); |
- DCHECK(res); |
- 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( |
- static_cast<DWORD>(reinterpret_cast<uintptr_t>(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); |
- |
- if (policy_base->GetAppContainer() && policy_base->GetLowBoxSid()) |
- return SBOX_ERROR_BAD_PARAMS; |
- |
- // Construct the tokens and the job object that we are going to associate |
- // with the soon to be created target process. |
- base::win::ScopedHandle initial_token; |
- base::win::ScopedHandle lockdown_token; |
- base::win::ScopedHandle lowbox_token; |
- ResultCode result = SBOX_ALL_OK; |
- |
- result = |
- policy_base->MakeTokens(&initial_token, &lockdown_token, &lowbox_token); |
- if (SBOX_ALL_OK != result) |
- return result; |
- |
- base::win::ScopedHandle job; |
- result = policy_base->MakeJobObject(&job); |
- if (SBOX_ALL_OK != result) |
- return result; |
- |
- // Initialize the startup information from the policy. |
- base::win::StartupInformation startup_info; |
- // The liftime of |mitigations|, |inherit_handle_list| and |
- // |child_process_creation| have to be at least as long as |
- // |startup_info| because |UpdateProcThreadAttribute| requires that |
- // its |lpValue| parameter persist until |DeleteProcThreadAttributeList| is |
- // called; StartupInformation's destructor makes such a call. |
- DWORD64 mitigations; |
- std::vector<HANDLE> inherited_handle_list; |
- DWORD child_process_creation = PROCESS_CREATION_CHILD_PROCESS_RESTRICTED; |
- |
- base::string16 desktop = policy_base->GetAlternateDesktop(); |
- if (!desktop.empty()) { |
- startup_info.startup_info()->lpDesktop = |
- const_cast<wchar_t*>(desktop.c_str()); |
- } |
- |
- bool inherit_handles = false; |
- |
- int attribute_count = 0; |
- const AppContainerAttributes* app_container = |
- policy_base->GetAppContainer(); |
- if (app_container) |
- ++attribute_count; |
- |
- size_t mitigations_size; |
- ConvertProcessMitigationsToPolicy(policy_base->GetProcessMitigations(), |
- &mitigations, &mitigations_size); |
- if (mitigations) |
- ++attribute_count; |
- |
- bool restrict_child_process_creation = false; |
- if (base::win::GetVersion() >= base::win::VERSION_WIN10_TH2 && |
- policy_base->GetJobLevel() <= JOB_LIMITED_USER) { |
- restrict_child_process_creation = true; |
- ++attribute_count; |
- } |
- |
- HANDLE stdout_handle = policy_base->GetStdoutHandle(); |
- HANDLE stderr_handle = policy_base->GetStderrHandle(); |
- |
- if (stdout_handle != INVALID_HANDLE_VALUE) |
- inherited_handle_list.push_back(stdout_handle); |
- |
- // Handles in the list must be unique. |
- if (stderr_handle != stdout_handle && stderr_handle != INVALID_HANDLE_VALUE) |
- inherited_handle_list.push_back(stderr_handle); |
- |
- const base::HandlesToInheritVector& policy_handle_list = |
- policy_base->GetHandlesBeingShared(); |
- |
- for (HANDLE handle : policy_handle_list) |
- inherited_handle_list.push_back(handle); |
- |
- if (inherited_handle_list.size()) |
- ++attribute_count; |
- |
- if (!startup_info.InitializeProcThreadAttributeList(attribute_count)) |
- return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
- |
- if (app_container) { |
- result = app_container->ShareForStartup(&startup_info); |
- if (SBOX_ALL_OK != result) |
- return result; |
- } |
- |
- if (mitigations) { |
- if (!startup_info.UpdateProcThreadAttribute( |
- PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY, &mitigations, |
- mitigations_size)) { |
- return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
- } |
- } |
- |
- if (restrict_child_process_creation) { |
- if (!startup_info.UpdateProcThreadAttribute( |
- PROC_THREAD_ATTRIBUTE_CHILD_PROCESS_POLICY, |
- &child_process_creation, sizeof(child_process_creation))) { |
- return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
- } |
- } |
- |
- if (inherited_handle_list.size()) { |
- if (!startup_info.UpdateProcThreadAttribute( |
- PROC_THREAD_ATTRIBUTE_HANDLE_LIST, |
- &inherited_handle_list[0], |
- sizeof(HANDLE) * inherited_handle_list.size())) { |
- return SBOX_ERROR_PROC_THREAD_ATTRIBUTES; |
- } |
- startup_info.startup_info()->dwFlags |= STARTF_USESTDHANDLES; |
- startup_info.startup_info()->hStdInput = INVALID_HANDLE_VALUE; |
- startup_info.startup_info()->hStdOutput = stdout_handle; |
- startup_info.startup_info()->hStdError = stderr_handle; |
- // Allowing inheritance of handles is only secure now that we |
- // have limited which handles will be inherited. |
- inherit_handles = true; |
- } |
- |
- // 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 TargetProcess 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(std::move(initial_token), std::move(lockdown_token), |
- std::move(lowbox_token), job.Get(), thread_pool_); |
- |
- DWORD win_result = target->Create(exe_path, command_line, inherit_handles, |
- startup_info, &process_info); |
- |
- if (ERROR_SUCCESS != win_result) { |
- SpawnCleanup(target, win_result); |
- return SBOX_ERROR_CREATE_PROCESS; |
- } |
- |
- // 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(); |
- if (job.IsValid()) { |
- scoped_ptr<JobTracker> tracker(new JobTracker(std::move(job), policy_base)); |
- |
- // There is no obvious recovery after failure here. Previous version with |
- // SpawnCleanup() caused deletion of TargetProcess twice. crbug.com/480639 |
- CHECK(AssociateCompletionPort(tracker->job.Get(), job_port_.Get(), |
- tracker.get())); |
- |
- // 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()); |
- } else { |
- // We have to signal the event once here because the completion port will |
- // never get a message that this target is being terminated thus we should |
- // not block WaitForAllTargets until we have at least one target with job. |
- if (child_process_ids_.empty()) |
- ::SetEvent(no_targets_.Get()); |
- // We can not track the life time of such processes and it is responsibility |
- // of the host application to make sure that spawned targets without jobs |
- // are terminated when the main application don't need them anymore. |
- // Sandbox policy engine needs to know that these processes are valid |
- // targets for e.g. BrokerDuplicateHandle so track them as peer processes. |
- AddTargetPeer(process_info.process_handle()); |
- } |
- |
- *target_info = process_info.Take(); |
- return SBOX_ALL_OK; |
-} |
- |
- |
-ResultCode BrokerServicesBase::WaitForAllTargets() { |
- ::WaitForSingleObject(no_targets_.Get(), 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>(static_cast<uintptr_t>(peer->id))); |
-} |
- |
-ResultCode BrokerServicesBase::AddTargetPeer(HANDLE peer_process) { |
- scoped_ptr<PeerTracker> peer(new PeerTracker(::GetProcessId(peer_process), |
- job_port_.Get())); |
- 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.Get(), 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. |
- ignore_result(peer.release()); |
- return SBOX_ALL_OK; |
-} |
- |
-ResultCode BrokerServicesBase::InstallAppContainer(const wchar_t* sid, |
- const wchar_t* name) { |
- if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) |
- return SBOX_ERROR_UNSUPPORTED; |
- |
- base::string16 old_name = LookupAppContainer(sid); |
- if (old_name.empty()) |
- return CreateAppContainer(sid, name); |
- |
- if (old_name != name) |
- return SBOX_ERROR_INVALID_APP_CONTAINER; |
- |
- return SBOX_ALL_OK; |
-} |
- |
-ResultCode BrokerServicesBase::UninstallAppContainer(const wchar_t* sid) { |
- if (base::win::OSInfo::GetInstance()->version() < base::win::VERSION_WIN8) |
- return SBOX_ERROR_UNSUPPORTED; |
- |
- base::string16 name = LookupAppContainer(sid); |
- if (name.empty()) |
- return SBOX_ERROR_INVALID_APP_CONTAINER; |
- |
- return DeleteAppContainer(sid); |
-} |
- |
-} // namespace sandbox |