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 // For information about interceptions as a whole see | |
6 // http://dev.chromium.org/developers/design-documents/sandbox . | |
7 | |
8 #include <stddef.h> | |
9 | |
10 #include <set> | |
11 | |
12 #include "sandbox/win/src/interception.h" | |
13 | |
14 #include "base/logging.h" | |
15 #include "base/memory/scoped_ptr.h" | |
16 #include "base/strings/string16.h" | |
17 #include "base/win/pe_image.h" | |
18 #include "base/win/windows_version.h" | |
19 #include "sandbox/win/src/interception_internal.h" | |
20 #include "sandbox/win/src/interceptors.h" | |
21 #include "sandbox/win/src/sandbox.h" | |
22 #include "sandbox/win/src/sandbox_rand.h" | |
23 #include "sandbox/win/src/service_resolver.h" | |
24 #include "sandbox/win/src/target_interceptions.h" | |
25 #include "sandbox/win/src/target_process.h" | |
26 | |
27 namespace sandbox { | |
28 | |
29 namespace { | |
30 | |
31 // Standard allocation granularity and page size for Windows. | |
32 const size_t kAllocGranularity = 65536; | |
33 const size_t kPageSize = 4096; | |
34 | |
35 } // namespace | |
36 | |
37 namespace internal { | |
38 | |
39 // Find a random offset within 64k and aligned to ceil(log2(size)). | |
40 size_t GetGranularAlignedRandomOffset(size_t size) { | |
41 CHECK_LE(size, kAllocGranularity); | |
42 unsigned int offset; | |
43 | |
44 do { | |
45 GetRandom(&offset); | |
46 offset &= (kAllocGranularity - 1); | |
47 } while (offset > (kAllocGranularity - size)); | |
48 | |
49 // Find an alignment between 64 and the page size (4096). | |
50 size_t align_size = kPageSize; | |
51 for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) { | |
52 align_size = new_size; | |
53 } | |
54 return offset & ~(align_size - 1); | |
55 } | |
56 | |
57 } // namespace internal | |
58 | |
59 SANDBOX_INTERCEPT SharedMemory* g_interceptions; | |
60 | |
61 // Table of the unpatched functions that we intercept. Mapped from the parent. | |
62 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL }; | |
63 | |
64 // Magic constant that identifies that this function is not to be patched. | |
65 const char kUnloadDLLDummyFunction[] = "@"; | |
66 | |
67 InterceptionManager::InterceptionData::InterceptionData() { | |
68 } | |
69 | |
70 InterceptionManager::InterceptionData::~InterceptionData() { | |
71 } | |
72 | |
73 InterceptionManager::InterceptionManager(TargetProcess* child_process, | |
74 bool relaxed) | |
75 : child_(child_process), names_used_(false), relaxed_(relaxed) { | |
76 child_->AddRef(); | |
77 } | |
78 InterceptionManager::~InterceptionManager() { | |
79 child_->Release(); | |
80 } | |
81 | |
82 bool InterceptionManager::AddToPatchedFunctions( | |
83 const wchar_t* dll_name, const char* function_name, | |
84 InterceptionType interception_type, const void* replacement_code_address, | |
85 InterceptorId id) { | |
86 InterceptionData function; | |
87 function.type = interception_type; | |
88 function.id = id; | |
89 function.dll = dll_name; | |
90 function.function = function_name; | |
91 function.interceptor_address = replacement_code_address; | |
92 | |
93 interceptions_.push_back(function); | |
94 return true; | |
95 } | |
96 | |
97 bool InterceptionManager::AddToPatchedFunctions( | |
98 const wchar_t* dll_name, const char* function_name, | |
99 InterceptionType interception_type, const char* replacement_function_name, | |
100 InterceptorId id) { | |
101 InterceptionData function; | |
102 function.type = interception_type; | |
103 function.id = id; | |
104 function.dll = dll_name; | |
105 function.function = function_name; | |
106 function.interceptor = replacement_function_name; | |
107 function.interceptor_address = NULL; | |
108 | |
109 interceptions_.push_back(function); | |
110 names_used_ = true; | |
111 return true; | |
112 } | |
113 | |
114 bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) { | |
115 InterceptionData module_to_unload; | |
116 module_to_unload.type = INTERCEPTION_UNLOAD_MODULE; | |
117 module_to_unload.dll = dll_name; | |
118 // The next two are dummy values that make the structures regular, instead | |
119 // of having special cases. They should not be used. | |
120 module_to_unload.function = kUnloadDLLDummyFunction; | |
121 module_to_unload.interceptor_address = reinterpret_cast<void*>(1); | |
122 | |
123 interceptions_.push_back(module_to_unload); | |
124 return true; | |
125 } | |
126 | |
127 bool InterceptionManager::InitializeInterceptions() { | |
128 if (interceptions_.empty()) | |
129 return true; // Nothing to do here | |
130 | |
131 size_t buffer_bytes = GetBufferSize(); | |
132 scoped_ptr<char[]> local_buffer(new char[buffer_bytes]); | |
133 | |
134 if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes)) | |
135 return false; | |
136 | |
137 void* remote_buffer; | |
138 if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer)) | |
139 return false; | |
140 | |
141 bool hot_patch_needed = (0 != buffer_bytes); | |
142 if (!PatchNtdll(hot_patch_needed)) | |
143 return false; | |
144 | |
145 g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer); | |
146 ResultCode rc = child_->TransferVariable("g_interceptions", | |
147 &g_interceptions, | |
148 sizeof(g_interceptions)); | |
149 return (SBOX_ALL_OK == rc); | |
150 } | |
151 | |
152 size_t InterceptionManager::GetBufferSize() const { | |
153 std::set<base::string16> dlls; | |
154 size_t buffer_bytes = 0; | |
155 | |
156 std::list<InterceptionData>::const_iterator it = interceptions_.begin(); | |
157 for (; it != interceptions_.end(); ++it) { | |
158 // skip interceptions that are performed from the parent | |
159 if (!IsInterceptionPerformedByChild(*it)) | |
160 continue; | |
161 | |
162 if (!dlls.count(it->dll)) { | |
163 // NULL terminate the dll name on the structure | |
164 size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t); | |
165 | |
166 // include the dll related size | |
167 buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) + | |
168 dll_name_bytes, sizeof(size_t)); | |
169 dlls.insert(it->dll); | |
170 } | |
171 | |
172 // we have to NULL terminate the strings on the structure | |
173 size_t strings_chars = it->function.size() + it->interceptor.size() + 2; | |
174 | |
175 // a new FunctionInfo is required per function | |
176 size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars; | |
177 record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t)); | |
178 buffer_bytes += record_bytes; | |
179 } | |
180 | |
181 if (0 != buffer_bytes) | |
182 // add the part of SharedMemory that we have not counted yet | |
183 buffer_bytes += offsetof(SharedMemory, dll_list); | |
184 | |
185 return buffer_bytes; | |
186 } | |
187 | |
188 // Basically, walk the list of interceptions moving them to the config buffer, | |
189 // but keeping together all interceptions that belong to the same dll. | |
190 // The config buffer is a local buffer, not the one allocated on the child. | |
191 bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) { | |
192 if (0 == buffer_bytes) | |
193 return true; | |
194 | |
195 DCHECK(buffer_bytes > sizeof(SharedMemory)); | |
196 | |
197 SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer); | |
198 DllPatchInfo* dll_info = shared_memory->dll_list; | |
199 int num_dlls = 0; | |
200 | |
201 shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL; | |
202 | |
203 buffer_bytes -= offsetof(SharedMemory, dll_list); | |
204 buffer = dll_info; | |
205 | |
206 std::list<InterceptionData>::iterator it = interceptions_.begin(); | |
207 for (; it != interceptions_.end();) { | |
208 // skip interceptions that are performed from the parent | |
209 if (!IsInterceptionPerformedByChild(*it)) { | |
210 ++it; | |
211 continue; | |
212 } | |
213 | |
214 const base::string16 dll = it->dll; | |
215 if (!SetupDllInfo(*it, &buffer, &buffer_bytes)) | |
216 return false; | |
217 | |
218 // walk the interceptions from this point, saving the ones that are | |
219 // performed on this dll, and removing the entry from the list. | |
220 // advance the iterator before removing the element from the list | |
221 std::list<InterceptionData>::iterator rest = it; | |
222 for (; rest != interceptions_.end();) { | |
223 if (rest->dll == dll) { | |
224 if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info)) | |
225 return false; | |
226 if (it == rest) | |
227 ++it; | |
228 rest = interceptions_.erase(rest); | |
229 } else { | |
230 ++rest; | |
231 } | |
232 } | |
233 dll_info = reinterpret_cast<DllPatchInfo*>(buffer); | |
234 ++num_dlls; | |
235 } | |
236 | |
237 shared_memory->num_intercepted_dlls = num_dlls; | |
238 return true; | |
239 } | |
240 | |
241 // Fills up just the part that depends on the dll, not the info that depends on | |
242 // the actual interception. | |
243 bool InterceptionManager::SetupDllInfo(const InterceptionData& data, | |
244 void** buffer, | |
245 size_t* buffer_bytes) const { | |
246 DCHECK(buffer_bytes); | |
247 DCHECK(buffer); | |
248 DCHECK(*buffer); | |
249 | |
250 DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer); | |
251 | |
252 // the strings have to be zero terminated | |
253 size_t required = offsetof(DllPatchInfo, dll_name) + | |
254 (data.dll.size() + 1) * sizeof(wchar_t); | |
255 required = RoundUpToMultiple(required, sizeof(size_t)); | |
256 if (*buffer_bytes < required) | |
257 return false; | |
258 | |
259 *buffer_bytes -= required; | |
260 *buffer = reinterpret_cast<char*>(*buffer) + required; | |
261 | |
262 // set up the dll info to be what we know about it at this time | |
263 dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE); | |
264 dll_info->record_bytes = required; | |
265 dll_info->offset_to_functions = required; | |
266 dll_info->num_functions = 0; | |
267 data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size()); | |
268 dll_info->dll_name[data.dll.size()] = L'\0'; | |
269 | |
270 return true; | |
271 } | |
272 | |
273 bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data, | |
274 void** buffer, | |
275 size_t* buffer_bytes, | |
276 DllPatchInfo* dll_info) const { | |
277 DCHECK(buffer_bytes); | |
278 DCHECK(buffer); | |
279 DCHECK(*buffer); | |
280 | |
281 if ((dll_info->unload_module) && | |
282 (data.function != kUnloadDLLDummyFunction)) { | |
283 // Can't specify a dll for both patch and unload. | |
284 NOTREACHED(); | |
285 } | |
286 | |
287 FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer); | |
288 | |
289 size_t name_bytes = data.function.size(); | |
290 size_t interceptor_bytes = data.interceptor.size(); | |
291 | |
292 // the strings at the end of the structure are zero terminated | |
293 size_t required = offsetof(FunctionInfo, function) + | |
294 name_bytes + interceptor_bytes + 2; | |
295 required = RoundUpToMultiple(required, sizeof(size_t)); | |
296 if (*buffer_bytes < required) | |
297 return false; | |
298 | |
299 // update the caller's values | |
300 *buffer_bytes -= required; | |
301 *buffer = reinterpret_cast<char*>(*buffer) + required; | |
302 | |
303 function->record_bytes = required; | |
304 function->type = data.type; | |
305 function->id = data.id; | |
306 function->interceptor_address = data.interceptor_address; | |
307 char* names = function->function; | |
308 | |
309 data.function._Copy_s(names, name_bytes, name_bytes); | |
310 names += name_bytes; | |
311 *names++ = '\0'; | |
312 | |
313 // interceptor follows the function_name | |
314 data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes); | |
315 names += interceptor_bytes; | |
316 *names++ = '\0'; | |
317 | |
318 // update the dll table | |
319 dll_info->num_functions++; | |
320 dll_info->record_bytes += required; | |
321 | |
322 return true; | |
323 } | |
324 | |
325 bool InterceptionManager::CopyDataToChild(const void* local_buffer, | |
326 size_t buffer_bytes, | |
327 void** remote_buffer) const { | |
328 DCHECK(NULL != remote_buffer); | |
329 if (0 == buffer_bytes) { | |
330 *remote_buffer = NULL; | |
331 return true; | |
332 } | |
333 | |
334 HANDLE child = child_->Process(); | |
335 | |
336 // Allocate memory on the target process without specifying the address | |
337 void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes, | |
338 MEM_COMMIT, PAGE_READWRITE); | |
339 if (NULL == remote_data) | |
340 return false; | |
341 | |
342 SIZE_T bytes_written; | |
343 BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer, | |
344 buffer_bytes, &bytes_written); | |
345 if (FALSE == success || bytes_written != buffer_bytes) { | |
346 ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); | |
347 return false; | |
348 } | |
349 | |
350 *remote_buffer = remote_data; | |
351 | |
352 return true; | |
353 } | |
354 | |
355 // Only return true if the child should be able to perform this interception. | |
356 bool InterceptionManager::IsInterceptionPerformedByChild( | |
357 const InterceptionData& data) const { | |
358 if (INTERCEPTION_INVALID == data.type) | |
359 return false; | |
360 | |
361 if (INTERCEPTION_SERVICE_CALL == data.type) | |
362 return false; | |
363 | |
364 if (data.type >= INTERCEPTION_LAST) | |
365 return false; | |
366 | |
367 base::string16 ntdll(kNtdllName); | |
368 if (ntdll == data.dll) | |
369 return false; // ntdll has to be intercepted from the parent | |
370 | |
371 return true; | |
372 } | |
373 | |
374 bool InterceptionManager::PatchNtdll(bool hot_patch_needed) { | |
375 // Maybe there is nothing to do | |
376 if (!hot_patch_needed && interceptions_.empty()) | |
377 return true; | |
378 | |
379 if (hot_patch_needed) { | |
380 #if SANDBOX_EXPORTS | |
381 // Make sure the functions are not excluded by the linker. | |
382 #if defined(_WIN64) | |
383 #pragma comment(linker, "/include:TargetNtMapViewOfSection64") | |
384 #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64") | |
385 #else | |
386 #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44") | |
387 #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12") | |
388 #endif | |
389 #endif | |
390 ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44); | |
391 ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12); | |
392 } | |
393 | |
394 // Reserve a full 64k memory range in the child process. | |
395 HANDLE child = child_->Process(); | |
396 BYTE* thunk_base = reinterpret_cast<BYTE*>( | |
397 ::VirtualAllocEx(child, NULL, kAllocGranularity, | |
398 MEM_RESERVE, PAGE_NOACCESS)); | |
399 | |
400 // Find an aligned, random location within the reserved range. | |
401 size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) + | |
402 sizeof(DllInterceptionData); | |
403 size_t thunk_offset = internal::GetGranularAlignedRandomOffset(thunk_bytes); | |
404 | |
405 // Split the base and offset along page boundaries. | |
406 thunk_base += thunk_offset & ~(kPageSize - 1); | |
407 thunk_offset &= kPageSize - 1; | |
408 | |
409 // Make an aligned, padded allocation, and move the pointer to our chunk. | |
410 size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1); | |
411 thunk_base = reinterpret_cast<BYTE*>( | |
412 ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, | |
413 MEM_COMMIT, PAGE_EXECUTE_READWRITE)); | |
414 CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access. | |
415 DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>( | |
416 thunk_base + thunk_offset); | |
417 | |
418 DllInterceptionData dll_data; | |
419 dll_data.data_bytes = thunk_bytes; | |
420 dll_data.num_thunks = 0; | |
421 dll_data.used_bytes = offsetof(DllInterceptionData, thunks); | |
422 | |
423 // Reset all helpers for a new child. | |
424 memset(g_originals, 0, sizeof(g_originals)); | |
425 | |
426 // this should write all the individual thunks to the child's memory | |
427 if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data)) | |
428 return false; | |
429 | |
430 // and now write the first part of the table to the child's memory | |
431 SIZE_T written; | |
432 bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data, | |
433 offsetof(DllInterceptionData, thunks), | |
434 &written); | |
435 | |
436 if (!ok || (offsetof(DllInterceptionData, thunks) != written)) | |
437 return false; | |
438 | |
439 // Attempt to protect all the thunks, but ignore failure | |
440 DWORD old_protection; | |
441 ::VirtualProtectEx(child, thunks, thunk_bytes, | |
442 PAGE_EXECUTE_READ, &old_protection); | |
443 | |
444 ResultCode ret = child_->TransferVariable("g_originals", g_originals, | |
445 sizeof(g_originals)); | |
446 return (SBOX_ALL_OK == ret); | |
447 } | |
448 | |
449 bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks, | |
450 size_t thunk_bytes, | |
451 DllInterceptionData* dll_data) { | |
452 DCHECK(NULL != thunks); | |
453 DCHECK(NULL != dll_data); | |
454 | |
455 HMODULE ntdll_base = ::GetModuleHandle(kNtdllName); | |
456 if (!ntdll_base) | |
457 return false; | |
458 | |
459 base::win::PEImage ntdll_image(ntdll_base); | |
460 | |
461 // Bypass purify's interception. | |
462 wchar_t* loader_get = reinterpret_cast<wchar_t*>( | |
463 ntdll_image.GetProcAddress("LdrGetDllHandle")); | |
464 if (loader_get) { | |
465 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | | |
466 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, | |
467 loader_get, &ntdll_base)) | |
468 return false; | |
469 } | |
470 | |
471 char* interceptor_base = NULL; | |
472 | |
473 #if SANDBOX_EXPORTS | |
474 interceptor_base = reinterpret_cast<char*>(child_->MainModule()); | |
475 HMODULE local_interceptor = ::LoadLibrary(child_->Name()); | |
476 #endif | |
477 | |
478 ServiceResolverThunk* thunk; | |
479 #if defined(_WIN64) | |
480 thunk = new ServiceResolverThunk(child_->Process(), relaxed_); | |
481 #else | |
482 base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); | |
483 if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { | |
484 if (os_info->version() >= base::win::VERSION_WIN10) | |
485 thunk = new Wow64W10ResolverThunk(child_->Process(), relaxed_); | |
486 else if (os_info->version() >= base::win::VERSION_WIN8) | |
487 thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_); | |
488 else | |
489 thunk = new Wow64ResolverThunk(child_->Process(), relaxed_); | |
490 } else if (os_info->version() >= base::win::VERSION_WIN8) { | |
491 thunk = new Win8ResolverThunk(child_->Process(), relaxed_); | |
492 } else { | |
493 thunk = new ServiceResolverThunk(child_->Process(), relaxed_); | |
494 } | |
495 #endif | |
496 | |
497 std::list<InterceptionData>::iterator it = interceptions_.begin(); | |
498 for (; it != interceptions_.end(); ++it) { | |
499 const base::string16 ntdll(kNtdllName); | |
500 if (it->dll != ntdll) | |
501 break; | |
502 | |
503 if (INTERCEPTION_SERVICE_CALL != it->type) | |
504 break; | |
505 | |
506 #if SANDBOX_EXPORTS | |
507 // We may be trying to patch by function name. | |
508 if (NULL == it->interceptor_address) { | |
509 const char* address; | |
510 NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor, | |
511 it->interceptor.c_str(), | |
512 reinterpret_cast<const void**>( | |
513 &address)); | |
514 if (!NT_SUCCESS(ret)) | |
515 break; | |
516 | |
517 // Translate the local address to an address on the child. | |
518 it->interceptor_address = interceptor_base + (address - | |
519 reinterpret_cast<char*>(local_interceptor)); | |
520 } | |
521 #endif | |
522 NTSTATUS ret = thunk->Setup(ntdll_base, | |
523 interceptor_base, | |
524 it->function.c_str(), | |
525 it->interceptor.c_str(), | |
526 it->interceptor_address, | |
527 &thunks->thunks[dll_data->num_thunks], | |
528 thunk_bytes - dll_data->used_bytes, | |
529 NULL); | |
530 if (!NT_SUCCESS(ret)) | |
531 break; | |
532 | |
533 DCHECK(!g_originals[it->id]); | |
534 g_originals[it->id] = &thunks->thunks[dll_data->num_thunks]; | |
535 | |
536 dll_data->num_thunks++; | |
537 dll_data->used_bytes += sizeof(ThunkData); | |
538 } | |
539 | |
540 delete(thunk); | |
541 | |
542 #if SANDBOX_EXPORTS | |
543 if (NULL != local_interceptor) | |
544 ::FreeLibrary(local_interceptor); | |
545 #endif | |
546 | |
547 if (it != interceptions_.end()) | |
548 return false; | |
549 | |
550 return true; | |
551 } | |
552 | |
553 } // namespace sandbox | |
OLD | NEW |