Home | History | Annotate | Download | only in src
      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.
     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.
     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;
     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
     66 struct 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 
     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.
     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 
    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.
    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.
    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 
    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 
    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.
    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.
    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 
    500 ResultCode BrokerServicesBase::WaitForAllTargets() {
    501   ::WaitForSingleObject(no_targets_, INFINITE);
    502   return SBOX_ALL_OK;
    503 }
    504 
    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 
    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 
    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 
    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 
    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