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