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