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