security/sandbox/win/src/target_process.cc

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
michael@0 2 // Use of this source code is governed by a BSD-style license that can be
michael@0 3 // found in the LICENSE file.
michael@0 4
michael@0 5 #include "sandbox/win/src/target_process.h"
michael@0 6
michael@0 7 #include "base/basictypes.h"
michael@0 8 #include "base/memory/scoped_ptr.h"
michael@0 9 #include "base/win/pe_image.h"
michael@0 10 #include "base/win/startup_information.h"
michael@0 11 #include "base/win/windows_version.h"
michael@0 12 #include "sandbox/win/src/crosscall_server.h"
michael@0 13 #include "sandbox/win/src/crosscall_client.h"
michael@0 14 #include "sandbox/win/src/policy_low_level.h"
michael@0 15 #include "sandbox/win/src/sandbox_types.h"
michael@0 16 #include "sandbox/win/src/sharedmem_ipc_server.h"
michael@0 17
michael@0 18 namespace {
michael@0 19
michael@0 20 void CopyPolicyToTarget(const void* source, size_t size, void* dest) {
michael@0 21 if (!source || !size)
michael@0 22 return;
michael@0 23 memcpy(dest, source, size);
michael@0 24 sandbox::PolicyGlobal* policy =
michael@0 25 reinterpret_cast<sandbox::PolicyGlobal*>(dest);
michael@0 26
michael@0 27 size_t offset = reinterpret_cast<size_t>(source);
michael@0 28
michael@0 29 for (size_t i = 0; i < sandbox::kMaxServiceCount; i++) {
michael@0 30 size_t buffer = reinterpret_cast<size_t>(policy->entry[i]);
michael@0 31 if (buffer) {
michael@0 32 buffer -= offset;
michael@0 33 policy->entry[i] = reinterpret_cast<sandbox::PolicyBuffer*>(buffer);
michael@0 34 }
michael@0 35 }
michael@0 36 }
michael@0 37
michael@0 38 }
michael@0 39
michael@0 40 namespace sandbox {
michael@0 41
michael@0 42 SANDBOX_INTERCEPT HANDLE g_shared_section;
michael@0 43 SANDBOX_INTERCEPT size_t g_shared_IPC_size;
michael@0 44 SANDBOX_INTERCEPT size_t g_shared_policy_size;
michael@0 45
michael@0 46 // Returns the address of the main exe module in memory taking in account
michael@0 47 // address space layout randomization.
michael@0 48 void* GetBaseAddress(const wchar_t* exe_name, void* entry_point) {
michael@0 49 HMODULE exe = ::LoadLibrary(exe_name);
michael@0 50 if (NULL == exe)
michael@0 51 return exe;
michael@0 52
michael@0 53 base::win::PEImage pe(exe);
michael@0 54 if (!pe.VerifyMagic()) {
michael@0 55 ::FreeLibrary(exe);
michael@0 56 return exe;
michael@0 57 }
michael@0 58 PIMAGE_NT_HEADERS nt_header = pe.GetNTHeaders();
michael@0 59 char* base = reinterpret_cast<char*>(entry_point) -
michael@0 60 nt_header->OptionalHeader.AddressOfEntryPoint;
michael@0 61
michael@0 62 ::FreeLibrary(exe);
michael@0 63 return base;
michael@0 64 }
michael@0 65
michael@0 66
michael@0 67 TargetProcess::TargetProcess(HANDLE initial_token, HANDLE lockdown_token,
michael@0 68 HANDLE job, ThreadProvider* thread_pool)
michael@0 69 // This object owns everything initialized here except thread_pool and
michael@0 70 // the job_ handle. The Job handle is closed by BrokerServices and results
michael@0 71 // eventually in a call to our dtor.
michael@0 72 : lockdown_token_(lockdown_token),
michael@0 73 initial_token_(initial_token),
michael@0 74 job_(job),
michael@0 75 thread_pool_(thread_pool),
michael@0 76 base_address_(NULL) {
michael@0 77 }
michael@0 78
michael@0 79 TargetProcess::~TargetProcess() {
michael@0 80 DWORD exit_code = 0;
michael@0 81 // Give a chance to the process to die. In most cases the JOB_KILL_ON_CLOSE
michael@0 82 // will take effect only when the context changes. As far as the testing went,
michael@0 83 // this wait was enough to switch context and kill the processes in the job.
michael@0 84 // If this process is already dead, the function will return without waiting.
michael@0 85 // TODO(nsylvain): If the process is still alive at the end, we should kill
michael@0 86 // it. http://b/893891
michael@0 87 // For now, this wait is there only to do a best effort to prevent some leaks
michael@0 88 // from showing up in purify.
michael@0 89 if (sandbox_process_info_.IsValid()) {
michael@0 90 ::WaitForSingleObject(sandbox_process_info_.process_handle(), 50);
michael@0 91 if (!::GetExitCodeProcess(sandbox_process_info_.process_handle(),
michael@0 92 &exit_code) || (STILL_ACTIVE == exit_code)) {
michael@0 93 // It is an error to destroy this object while the target process is still
michael@0 94 // alive because we need to destroy the IPC subsystem and cannot risk to
michael@0 95 // have an IPC reach us after this point.
michael@0 96 if (shared_section_.IsValid())
michael@0 97 shared_section_.Take();
michael@0 98 SharedMemIPCServer* server = ipc_server_.release();
michael@0 99 sandbox_process_info_.TakeProcessHandle();
michael@0 100 return;
michael@0 101 }
michael@0 102 }
michael@0 103
michael@0 104 // ipc_server_ references our process handle, so make sure the former is shut
michael@0 105 // down before the latter is closed (by ScopedProcessInformation).
michael@0 106 ipc_server_.reset();
michael@0 107 }
michael@0 108
michael@0 109 // Creates the target (child) process suspended and assigns it to the job
michael@0 110 // object.
michael@0 111 DWORD TargetProcess::Create(const wchar_t* exe_path,
michael@0 112 const wchar_t* command_line,
michael@0 113 bool inherit_handles,
michael@0 114 const base::win::StartupInformation& startup_info,
michael@0 115 base::win::ScopedProcessInformation* target_info) {
michael@0 116 exe_name_.reset(_wcsdup(exe_path));
michael@0 117
michael@0 118 // the command line needs to be writable by CreateProcess().
michael@0 119 scoped_ptr_malloc<wchar_t> cmd_line(_wcsdup(command_line));
michael@0 120
michael@0 121 // Start the target process suspended.
michael@0 122 DWORD flags =
michael@0 123 CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT | DETACHED_PROCESS;
michael@0 124
michael@0 125 if (startup_info.has_extended_startup_info())
michael@0 126 flags |= EXTENDED_STARTUPINFO_PRESENT;
michael@0 127
michael@0 128 if (job_ && base::win::GetVersion() < base::win::VERSION_WIN8) {
michael@0 129 // Windows 8 implements nested jobs, but for older systems we need to
michael@0 130 // break out of any job we're in to enforce our restrictions.
michael@0 131 flags |= CREATE_BREAKAWAY_FROM_JOB;
michael@0 132 }
michael@0 133
michael@0 134 base::win::ScopedProcessInformation process_info;
michael@0 135
michael@0 136 if (!::CreateProcessAsUserW(lockdown_token_,
michael@0 137 exe_path,
michael@0 138 cmd_line.get(),
michael@0 139 NULL, // No security attribute.
michael@0 140 NULL, // No thread attribute.
michael@0 141 inherit_handles,
michael@0 142 flags,
michael@0 143 NULL, // Use the environment of the caller.
michael@0 144 NULL, // Use current directory of the caller.
michael@0 145 startup_info.startup_info(),
michael@0 146 process_info.Receive())) {
michael@0 147 return ::GetLastError();
michael@0 148 }
michael@0 149 lockdown_token_.Close();
michael@0 150
michael@0 151 DWORD win_result = ERROR_SUCCESS;
michael@0 152
michael@0 153 if (job_) {
michael@0 154 // Assign the suspended target to the windows job object.
michael@0 155 if (!::AssignProcessToJobObject(job_, process_info.process_handle())) {
michael@0 156 win_result = ::GetLastError();
michael@0 157 ::TerminateProcess(process_info.process_handle(), 0);
michael@0 158 return win_result;
michael@0 159 }
michael@0 160 }
michael@0 161
michael@0 162 if (initial_token_.IsValid()) {
michael@0 163 // Change the token of the main thread of the new process for the
michael@0 164 // impersonation token with more rights. This allows the target to start;
michael@0 165 // otherwise it will crash too early for us to help.
michael@0 166 HANDLE temp_thread = process_info.thread_handle();
michael@0 167 if (!::SetThreadToken(&temp_thread, initial_token_)) {
michael@0 168 win_result = ::GetLastError();
michael@0 169 // It might be a security breach if we let the target run outside the job
michael@0 170 // so kill it before it causes damage.
michael@0 171 ::TerminateProcess(process_info.process_handle(), 0);
michael@0 172 return win_result;
michael@0 173 }
michael@0 174 initial_token_.Close();
michael@0 175 }
michael@0 176
michael@0 177 CONTEXT context;
michael@0 178 context.ContextFlags = CONTEXT_ALL;
michael@0 179 if (!::GetThreadContext(process_info.thread_handle(), &context)) {
michael@0 180 win_result = ::GetLastError();
michael@0 181 ::TerminateProcess(process_info.process_handle(), 0);
michael@0 182 return win_result;
michael@0 183 }
michael@0 184
michael@0 185 #if defined(_WIN64)
michael@0 186 void* entry_point = reinterpret_cast<void*>(context.Rcx);
michael@0 187 #else
michael@0 188 #pragma warning(push)
michael@0 189 #pragma warning(disable: 4312)
michael@0 190 // This cast generates a warning because it is 32 bit specific.
michael@0 191 void* entry_point = reinterpret_cast<void*>(context.Eax);
michael@0 192 #pragma warning(pop)
michael@0 193 #endif // _WIN64
michael@0 194
michael@0 195 if (!target_info->DuplicateFrom(process_info)) {
michael@0 196 win_result = ::GetLastError(); // This may or may not be correct.
michael@0 197 ::TerminateProcess(process_info.process_handle(), 0);
michael@0 198 return win_result;
michael@0 199 }
michael@0 200
michael@0 201 base_address_ = GetBaseAddress(exe_path, entry_point);
michael@0 202 sandbox_process_info_.Set(process_info.Take());
michael@0 203 return win_result;
michael@0 204 }
michael@0 205
michael@0 206 ResultCode TargetProcess::TransferVariable(const char* name, void* address,
michael@0 207 size_t size) {
michael@0 208 if (!sandbox_process_info_.IsValid())
michael@0 209 return SBOX_ERROR_UNEXPECTED_CALL;
michael@0 210
michael@0 211 void* child_var = address;
michael@0 212
michael@0 213 #if SANDBOX_EXPORTS
michael@0 214 HMODULE module = ::LoadLibrary(exe_name_.get());
michael@0 215 if (NULL == module)
michael@0 216 return SBOX_ERROR_GENERIC;
michael@0 217
michael@0 218 child_var = ::GetProcAddress(module, name);
michael@0 219 ::FreeLibrary(module);
michael@0 220
michael@0 221 if (NULL == child_var)
michael@0 222 return SBOX_ERROR_GENERIC;
michael@0 223
michael@0 224 size_t offset = reinterpret_cast<char*>(child_var) -
michael@0 225 reinterpret_cast<char*>(module);
michael@0 226 child_var = reinterpret_cast<char*>(MainModule()) + offset;
michael@0 227 #else
michael@0 228 UNREFERENCED_PARAMETER(name);
michael@0 229 #endif
michael@0 230
michael@0 231 SIZE_T written;
michael@0 232 if (!::WriteProcessMemory(sandbox_process_info_.process_handle(),
michael@0 233 child_var, address, size, &written))
michael@0 234 return SBOX_ERROR_GENERIC;
michael@0 235
michael@0 236 if (written != size)
michael@0 237 return SBOX_ERROR_GENERIC;
michael@0 238
michael@0 239 return SBOX_ALL_OK;
michael@0 240 }
michael@0 241
michael@0 242 // Construct the IPC server and the IPC dispatcher. When the target does
michael@0 243 // an IPC it will eventually call the dispatcher.
michael@0 244 DWORD TargetProcess::Init(Dispatcher* ipc_dispatcher, void* policy,
michael@0 245 uint32 shared_IPC_size, uint32 shared_policy_size) {
michael@0 246 // We need to map the shared memory on the target. This is necessary for
michael@0 247 // any IPC that needs to take place, even if the target has not yet hit
michael@0 248 // the main( ) function or even has initialized the CRT. So here we set
michael@0 249 // the handle to the shared section. The target on the first IPC must do
michael@0 250 // the rest, which boils down to calling MapViewofFile()
michael@0 251
michael@0 252 // We use this single memory pool for IPC and for policy.
michael@0 253 DWORD shared_mem_size = static_cast<DWORD>(shared_IPC_size +
michael@0 254 shared_policy_size);
michael@0 255 shared_section_.Set(::CreateFileMappingW(INVALID_HANDLE_VALUE, NULL,
michael@0 256 PAGE_READWRITE | SEC_COMMIT,
michael@0 257 0, shared_mem_size, NULL));
michael@0 258 if (!shared_section_.IsValid()) {
michael@0 259 return ::GetLastError();
michael@0 260 }
michael@0 261
michael@0 262 DWORD access = FILE_MAP_READ | FILE_MAP_WRITE;
michael@0 263 HANDLE target_shared_section;
michael@0 264 if (!::DuplicateHandle(::GetCurrentProcess(), shared_section_,
michael@0 265 sandbox_process_info_.process_handle(),
michael@0 266 &target_shared_section, access, FALSE, 0)) {
michael@0 267 return ::GetLastError();
michael@0 268 }
michael@0 269
michael@0 270 void* shared_memory = ::MapViewOfFile(shared_section_,
michael@0 271 FILE_MAP_WRITE|FILE_MAP_READ,
michael@0 272 0, 0, 0);
michael@0 273 if (NULL == shared_memory) {
michael@0 274 return ::GetLastError();
michael@0 275 }
michael@0 276
michael@0 277 CopyPolicyToTarget(policy, shared_policy_size,
michael@0 278 reinterpret_cast<char*>(shared_memory) + shared_IPC_size);
michael@0 279
michael@0 280 ResultCode ret;
michael@0 281 // Set the global variables in the target. These are not used on the broker.
michael@0 282 g_shared_section = target_shared_section;
michael@0 283 ret = TransferVariable("g_shared_section", &g_shared_section,
michael@0 284 sizeof(g_shared_section));
michael@0 285 g_shared_section = NULL;
michael@0 286 if (SBOX_ALL_OK != ret) {
michael@0 287 return (SBOX_ERROR_GENERIC == ret)?
michael@0 288 ::GetLastError() : ERROR_INVALID_FUNCTION;
michael@0 289 }
michael@0 290 g_shared_IPC_size = shared_IPC_size;
michael@0 291 ret = TransferVariable("g_shared_IPC_size", &g_shared_IPC_size,
michael@0 292 sizeof(g_shared_IPC_size));
michael@0 293 g_shared_IPC_size = 0;
michael@0 294 if (SBOX_ALL_OK != ret) {
michael@0 295 return (SBOX_ERROR_GENERIC == ret) ?
michael@0 296 ::GetLastError() : ERROR_INVALID_FUNCTION;
michael@0 297 }
michael@0 298 g_shared_policy_size = shared_policy_size;
michael@0 299 ret = TransferVariable("g_shared_policy_size", &g_shared_policy_size,
michael@0 300 sizeof(g_shared_policy_size));
michael@0 301 g_shared_policy_size = 0;
michael@0 302 if (SBOX_ALL_OK != ret) {
michael@0 303 return (SBOX_ERROR_GENERIC == ret) ?
michael@0 304 ::GetLastError() : ERROR_INVALID_FUNCTION;
michael@0 305 }
michael@0 306
michael@0 307 ipc_server_.reset(
michael@0 308 new SharedMemIPCServer(sandbox_process_info_.process_handle(),
michael@0 309 sandbox_process_info_.process_id(),
michael@0 310 job_, thread_pool_, ipc_dispatcher));
michael@0 311
michael@0 312 if (!ipc_server_->Init(shared_memory, shared_IPC_size, kIPCChannelSize))
michael@0 313 return ERROR_NOT_ENOUGH_MEMORY;
michael@0 314
michael@0 315 // After this point we cannot use this handle anymore.
michael@0 316 ::CloseHandle(sandbox_process_info_.TakeThreadHandle());
michael@0 317
michael@0 318 return ERROR_SUCCESS;
michael@0 319 }
michael@0 320
michael@0 321 void TargetProcess::Terminate() {
michael@0 322 if (!sandbox_process_info_.IsValid())
michael@0 323 return;
michael@0 324
michael@0 325 ::TerminateProcess(sandbox_process_info_.process_handle(), 0);
michael@0 326 }
michael@0 327
michael@0 328 TargetProcess* MakeTestTargetProcess(HANDLE process, HMODULE base_address) {
michael@0 329 TargetProcess* target = new TargetProcess(NULL, NULL, NULL, NULL);
michael@0 330 PROCESS_INFORMATION process_info = {};
michael@0 331 process_info.hProcess = process;
michael@0 332 target->sandbox_process_info_.Set(process_info);
michael@0 333 target->base_address_ = base_address;
michael@0 334 return target;
michael@0 335 }
michael@0 336
michael@0 337 } // namespace sandbox

mercurial