security/sandbox/win/src/target_process.cc

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/security/sandbox/win/src/target_process.cc	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,337 @@
     1.4 +// Copyright (c) 2012 The Chromium Authors. All rights reserved.
     1.5 +// Use of this source code is governed by a BSD-style license that can be
     1.6 +// found in the LICENSE file.
     1.7 +
     1.8 +#include "sandbox/win/src/target_process.h"
     1.9 +
    1.10 +#include "base/basictypes.h"
    1.11 +#include "base/memory/scoped_ptr.h"
    1.12 +#include "base/win/pe_image.h"
    1.13 +#include "base/win/startup_information.h"
    1.14 +#include "base/win/windows_version.h"
    1.15 +#include "sandbox/win/src/crosscall_server.h"
    1.16 +#include "sandbox/win/src/crosscall_client.h"
    1.17 +#include "sandbox/win/src/policy_low_level.h"
    1.18 +#include "sandbox/win/src/sandbox_types.h"
    1.19 +#include "sandbox/win/src/sharedmem_ipc_server.h"
    1.20 +
    1.21 +namespace {
    1.22 +
    1.23 +void CopyPolicyToTarget(const void* source, size_t size, void* dest) {
    1.24 +  if (!source || !size)
    1.25 +    return;
    1.26 +  memcpy(dest, source, size);
    1.27 +  sandbox::PolicyGlobal* policy =
    1.28 +      reinterpret_cast<sandbox::PolicyGlobal*>(dest);
    1.29 +
    1.30 +  size_t offset = reinterpret_cast<size_t>(source);
    1.31 +
    1.32 +  for (size_t i = 0; i < sandbox::kMaxServiceCount; i++) {
    1.33 +    size_t buffer = reinterpret_cast<size_t>(policy->entry[i]);
    1.34 +    if (buffer) {
    1.35 +      buffer -= offset;
    1.36 +      policy->entry[i] = reinterpret_cast<sandbox::PolicyBuffer*>(buffer);
    1.37 +    }
    1.38 +  }
    1.39 +}
    1.40 +
    1.41 +}
    1.42 +
    1.43 +namespace sandbox {
    1.44 +
    1.45 +SANDBOX_INTERCEPT HANDLE g_shared_section;
    1.46 +SANDBOX_INTERCEPT size_t g_shared_IPC_size;
    1.47 +SANDBOX_INTERCEPT size_t g_shared_policy_size;
    1.48 +
    1.49 +// Returns the address of the main exe module in memory taking in account
    1.50 +// address space layout randomization.
    1.51 +void* GetBaseAddress(const wchar_t* exe_name, void* entry_point) {
    1.52 +  HMODULE exe = ::LoadLibrary(exe_name);
    1.53 +  if (NULL == exe)
    1.54 +    return exe;
    1.55 +
    1.56 +  base::win::PEImage pe(exe);
    1.57 +  if (!pe.VerifyMagic()) {
    1.58 +    ::FreeLibrary(exe);
    1.59 +    return exe;
    1.60 +  }
    1.61 +  PIMAGE_NT_HEADERS nt_header = pe.GetNTHeaders();
    1.62 +  char* base = reinterpret_cast<char*>(entry_point) -
    1.63 +    nt_header->OptionalHeader.AddressOfEntryPoint;
    1.64 +
    1.65 +  ::FreeLibrary(exe);
    1.66 +  return base;
    1.67 +}
    1.68 +
    1.69 +
    1.70 +TargetProcess::TargetProcess(HANDLE initial_token, HANDLE lockdown_token,
    1.71 +                             HANDLE job, ThreadProvider* thread_pool)
    1.72 +  // This object owns everything initialized here except thread_pool and
    1.73 +  // the job_ handle. The Job handle is closed by BrokerServices and results
    1.74 +  // eventually in a call to our dtor.
    1.75 +    : lockdown_token_(lockdown_token),
    1.76 +      initial_token_(initial_token),
    1.77 +      job_(job),
    1.78 +      thread_pool_(thread_pool),
    1.79 +      base_address_(NULL) {
    1.80 +}
    1.81 +
    1.82 +TargetProcess::~TargetProcess() {
    1.83 +  DWORD exit_code = 0;
    1.84 +  // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE
    1.85 +  // will take effect only when the context changes. As far as the testing went,
    1.86 +  // this wait was enough to switch context and kill the processes in the job.
    1.87 +  // If this process is already dead, the function will return without waiting.
    1.88 +  // TODO(nsylvain):  If the process is still alive at the end, we should kill
    1.89 +  // it. http://b/893891
    1.90 +  // For now, this wait is there only to do a best effort to prevent some leaks
    1.91 +  // from showing up in purify.
    1.92 +  if (sandbox_process_info_.IsValid()) {
    1.93 +    ::WaitForSingleObject(sandbox_process_info_.process_handle(), 50);
    1.94 +    if (!::GetExitCodeProcess(sandbox_process_info_.process_handle(),
    1.95 +                              &exit_code) || (STILL_ACTIVE == exit_code)) {
    1.96 +      // It is an error to destroy this object while the target process is still
    1.97 +      // alive because we need to destroy the IPC subsystem and cannot risk to
    1.98 +      // have an IPC reach us after this point.
    1.99 +      if (shared_section_.IsValid())
   1.100 +        shared_section_.Take();
   1.101 +      SharedMemIPCServer* server = ipc_server_.release();
   1.102 +      sandbox_process_info_.TakeProcessHandle();
   1.103 +      return;
   1.104 +    }
   1.105 +  }
   1.106 +
   1.107 +  // ipc_server_ references our process handle, so make sure the former is shut
   1.108 +  // down before the latter is closed (by ScopedProcessInformation).
   1.109 +  ipc_server_.reset();
   1.110 +}
   1.111 +
   1.112 +// Creates the target (child) process suspended and assigns it to the job
   1.113 +// object.
   1.114 +DWORD TargetProcess::Create(const wchar_t* exe_path,
   1.115 +                            const wchar_t* command_line,
   1.116 +                            bool inherit_handles,
   1.117 +                            const base::win::StartupInformation& startup_info,
   1.118 +                            base::win::ScopedProcessInformation* target_info) {
   1.119 +  exe_name_.reset(_wcsdup(exe_path));
   1.120 +
   1.121 +  // the command line needs to be writable by CreateProcess().
   1.122 +  scoped_ptr_malloc<wchar_t> cmd_line(_wcsdup(command_line));
   1.123 +
   1.124 +  // Start the target process suspended.
   1.125 +  DWORD flags =
   1.126 +      CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS;
   1.127 +
   1.128 +  if (startup_info.has_extended_startup_info())
   1.129 +    flags |= EXTENDED_STARTUPINFO_PRESENT;
   1.130 +
   1.131 +  if (job_ && base::win::GetVersion() < base::win::VERSION_WIN8) {
   1.132 +    // Windows 8 implements nested jobs, but for older systems we need to
   1.133 +    // break out of any job we're in to enforce our restrictions.
   1.134 +    flags |= CREATE_BREAKAWAY_FROM_JOB;
   1.135 +  }
   1.136 +
   1.137 +  base::win::ScopedProcessInformation process_info;
   1.138 +
   1.139 +  if (!::CreateProcessAsUserW(lockdown_token_,
   1.140 +                              exe_path,
   1.141 +                              cmd_line.get(),
   1.142 +                              NULL,   // No security attribute.
   1.143 +                              NULL,   // No thread attribute.
   1.144 +                              inherit_handles,
   1.145 +                              flags,
   1.146 +                              NULL,   // Use the environment of the caller.
   1.147 +                              NULL,   // Use current directory of the caller.
   1.148 +                              startup_info.startup_info(),
   1.149 +                              process_info.Receive())) {
   1.150 +    return ::GetLastError();
   1.151 +  }
   1.152 +  lockdown_token_.Close();
   1.153 +
   1.154 +  DWORD win_result = ERROR_SUCCESS;
   1.155 +
   1.156 +  if (job_) {
   1.157 +    // Assign the suspended target to the windows job object.
   1.158 +    if (!::AssignProcessToJobObject(job_, process_info.process_handle())) {
   1.159 +      win_result = ::GetLastError();
   1.160 +      ::TerminateProcess(process_info.process_handle(), 0);
   1.161 +      return win_result;
   1.162 +    }
   1.163 +  }
   1.164 +
   1.165 +  if (initial_token_.IsValid()) {
   1.166 +    // Change the token of the main thread of the new process for the
   1.167 +    // impersonation token with more rights. This allows the target to start;
   1.168 +    // otherwise it will crash too early for us to help.
   1.169 +    HANDLE temp_thread = process_info.thread_handle();
   1.170 +    if (!::SetThreadToken(&temp_thread, initial_token_)) {
   1.171 +      win_result = ::GetLastError();
   1.172 +      // It might be a security breach if we let the target run outside the job
   1.173 +      // so kill it before it causes damage.
   1.174 +      ::TerminateProcess(process_info.process_handle(), 0);
   1.175 +      return win_result;
   1.176 +    }
   1.177 +    initial_token_.Close();
   1.178 +  }
   1.179 +
   1.180 +  CONTEXT context;
   1.181 +  context.ContextFlags = CONTEXT_ALL;
   1.182 +  if (!::GetThreadContext(process_info.thread_handle(), &context)) {
   1.183 +    win_result = ::GetLastError();
   1.184 +    ::TerminateProcess(process_info.process_handle(), 0);
   1.185 +    return win_result;
   1.186 +  }
   1.187 +
   1.188 +#if defined(_WIN64)
   1.189 +  void* entry_point = reinterpret_cast<void*>(context.Rcx);
   1.190 +#else
   1.191 +#pragma warning(push)
   1.192 +#pragma warning(disable: 4312)
   1.193 +  // This cast generates a warning because it is 32 bit specific.
   1.194 +  void* entry_point = reinterpret_cast<void*>(context.Eax);
   1.195 +#pragma warning(pop)
   1.196 +#endif  // _WIN64
   1.197 +
   1.198 +  if (!target_info->DuplicateFrom(process_info)) {
   1.199 +    win_result = ::GetLastError();  // This may or may not be correct.
   1.200 +    ::TerminateProcess(process_info.process_handle(), 0);
   1.201 +    return win_result;
   1.202 +  }
   1.203 +
   1.204 +  base_address_ = GetBaseAddress(exe_path, entry_point);
   1.205 +  sandbox_process_info_.Set(process_info.Take());
   1.206 +  return win_result;
   1.207 +}
   1.208 +
   1.209 +ResultCode TargetProcess::TransferVariable(const char* name, void* address,
   1.210 +                                           size_t size) {
   1.211 +  if (!sandbox_process_info_.IsValid())
   1.212 +    return SBOX_ERROR_UNEXPECTED_CALL;
   1.213 +
   1.214 +  void* child_var = address;
   1.215 +
   1.216 +#if SANDBOX_EXPORTS
   1.217 +  HMODULE module = ::LoadLibrary(exe_name_.get());
   1.218 +  if (NULL == module)
   1.219 +    return SBOX_ERROR_GENERIC;
   1.220 +
   1.221 +  child_var = ::GetProcAddress(module, name);
   1.222 +  ::FreeLibrary(module);
   1.223 +
   1.224 +  if (NULL == child_var)
   1.225 +    return SBOX_ERROR_GENERIC;
   1.226 +
   1.227 +  size_t offset = reinterpret_cast<char*>(child_var) -
   1.228 +                  reinterpret_cast<char*>(module);
   1.229 +  child_var = reinterpret_cast<char*>(MainModule()) + offset;
   1.230 +#else
   1.231 +  UNREFERENCED_PARAMETER(name);
   1.232 +#endif
   1.233 +
   1.234 +  SIZE_T written;
   1.235 +  if (!::WriteProcessMemory(sandbox_process_info_.process_handle(),
   1.236 +                            child_var, address, size, &written))
   1.237 +    return SBOX_ERROR_GENERIC;
   1.238 +
   1.239 +  if (written != size)
   1.240 +    return SBOX_ERROR_GENERIC;
   1.241 +
   1.242 +  return SBOX_ALL_OK;
   1.243 +}
   1.244 +
   1.245 +// Construct the IPC server and the IPC dispatcher. When the target does
   1.246 +// an IPC it will eventually call the dispatcher.
   1.247 +DWORD TargetProcess::Init(Dispatcher* ipc_dispatcher, void* policy,
   1.248 +                          uint32 shared_IPC_size, uint32 shared_policy_size) {
   1.249 +  // We need to map the shared memory on the target. This is necessary for
   1.250 +  // any IPC that needs to take place, even if the target has not yet hit
   1.251 +  // the main( ) function or even has initialized the CRT. So here we set
   1.252 +  // the handle to the shared section. The target on the first IPC must do
   1.253 +  // the rest, which boils down to calling MapViewofFile()
   1.254 +
   1.255 +  // We use this single memory pool for IPC and for policy.
   1.256 +  DWORD shared_mem_size = static_cast<DWORD>(shared_IPC_size +
   1.257 +                                             shared_policy_size);
   1.258 +  shared_section_.Set(::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
   1.259 +                                           PAGE_READWRITE | SEC_COMMIT,
   1.260 +                                           0, shared_mem_size, NULL));
   1.261 +  if (!shared_section_.IsValid()) {
   1.262 +    return ::GetLastError();
   1.263 +  }
   1.264 +
   1.265 +  DWORD access = FILE_MAP_READ | FILE_MAP_WRITE;
   1.266 +  HANDLE target_shared_section;
   1.267 +  if (!::DuplicateHandle(::GetCurrentProcess(), shared_section_,
   1.268 +                         sandbox_process_info_.process_handle(),
   1.269 +                         &target_shared_section, access, FALSE, 0)) {
   1.270 +    return ::GetLastError();
   1.271 +  }
   1.272 +
   1.273 +  void* shared_memory = ::MapViewOfFile(shared_section_,
   1.274 +                                        FILE_MAP_WRITE|FILE_MAP_READ,
   1.275 +                                        0, 0, 0);
   1.276 +  if (NULL == shared_memory) {
   1.277 +    return ::GetLastError();
   1.278 +  }
   1.279 +
   1.280 +  CopyPolicyToTarget(policy, shared_policy_size,
   1.281 +                     reinterpret_cast<char*>(shared_memory) + shared_IPC_size);
   1.282 +
   1.283 +  ResultCode ret;
   1.284 +  // Set the global variables in the target. These are not used on the broker.
   1.285 +  g_shared_section = target_shared_section;
   1.286 +  ret = TransferVariable("g_shared_section", &g_shared_section,
   1.287 +                         sizeof(g_shared_section));
   1.288 +  g_shared_section = NULL;
   1.289 +  if (SBOX_ALL_OK != ret) {
   1.290 +    return (SBOX_ERROR_GENERIC == ret)?
   1.291 +           ::GetLastError() : ERROR_INVALID_FUNCTION;
   1.292 +  }
   1.293 +  g_shared_IPC_size = shared_IPC_size;
   1.294 +  ret = TransferVariable("g_shared_IPC_size", &g_shared_IPC_size,
   1.295 +                         sizeof(g_shared_IPC_size));
   1.296 +  g_shared_IPC_size = 0;
   1.297 +  if (SBOX_ALL_OK != ret) {
   1.298 +    return (SBOX_ERROR_GENERIC == ret) ?
   1.299 +           ::GetLastError() : ERROR_INVALID_FUNCTION;
   1.300 +  }
   1.301 +  g_shared_policy_size = shared_policy_size;
   1.302 +  ret = TransferVariable("g_shared_policy_size", &g_shared_policy_size,
   1.303 +                         sizeof(g_shared_policy_size));
   1.304 +  g_shared_policy_size = 0;
   1.305 +  if (SBOX_ALL_OK != ret) {
   1.306 +    return (SBOX_ERROR_GENERIC == ret) ?
   1.307 +           ::GetLastError() : ERROR_INVALID_FUNCTION;
   1.308 +  }
   1.309 +
   1.310 +  ipc_server_.reset(
   1.311 +      new SharedMemIPCServer(sandbox_process_info_.process_handle(),
   1.312 +                             sandbox_process_info_.process_id(),
   1.313 +                             job_, thread_pool_, ipc_dispatcher));
   1.314 +
   1.315 +  if (!ipc_server_->Init(shared_memory, shared_IPC_size, kIPCChannelSize))
   1.316 +    return ERROR_NOT_ENOUGH_MEMORY;
   1.317 +
   1.318 +  // After this point we cannot use this handle anymore.
   1.319 +  ::CloseHandle(sandbox_process_info_.TakeThreadHandle());
   1.320 +
   1.321 +  return ERROR_SUCCESS;
   1.322 +}
   1.323 +
   1.324 +void TargetProcess::Terminate() {
   1.325 +  if (!sandbox_process_info_.IsValid())
   1.326 +    return;
   1.327 +
   1.328 +  ::TerminateProcess(sandbox_process_info_.process_handle(), 0);
   1.329 +}
   1.330 +
   1.331 +TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) {
   1.332 +  TargetProcess* target = new TargetProcess(NULL, NULL, NULL, NULL);
   1.333 +  PROCESS_INFORMATION process_info = {};
   1.334 +  process_info.hProcess = process;
   1.335 +  target->sandbox_process_info_.Set(process_info);
   1.336 +  target->base_address_ = base_address;
   1.337 +  return target;
   1.338 +}
   1.339 +
   1.340 +}  // namespace sandbox

mercurial