Add ICU message format support
[chromium-blink-merge.git] / sandbox / win / src / interception.cc
blob283c9422352a083cad14a0fdbfd0a38fffcf7704
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.
5 // For information about interceptions as a whole see
6 // http://dev.chromium.org/developers/design-documents/sandbox .
8 #include <set>
10 #include "sandbox/win/src/interception.h"
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/service_resolver.h"
21 #include "sandbox/win/src/target_interceptions.h"
22 #include "sandbox/win/src/target_process.h"
23 #include "sandbox/win/src/wow64.h"
25 namespace {
27 // Standard allocation granularity and page size for Windows.
28 const size_t kAllocGranularity = 65536;
29 const size_t kPageSize = 4096;
31 // Find a random offset within 64k and aligned to ceil(log2(size)).
32 size_t GetGranularAlignedRandomOffset(size_t size) {
33 CHECK_LE(size, kAllocGranularity);
34 unsigned int offset;
36 do {
37 rand_s(&offset);
38 offset &= (kAllocGranularity - 1);
39 } while (offset > (kAllocGranularity - size));
41 // Find an alignment between 64 and the page size (4096).
42 size_t align_size = kPageSize;
43 for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) {
44 align_size = new_size;
46 return offset & ~(align_size - 1);
49 } // namespace
51 namespace sandbox {
53 SANDBOX_INTERCEPT SharedMemory* g_interceptions;
55 // Table of the unpatched functions that we intercept. Mapped from the parent.
56 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL };
58 // Magic constant that identifies that this function is not to be patched.
59 const char kUnloadDLLDummyFunction[] = "@";
61 InterceptionManager::InterceptionData::InterceptionData() {
64 InterceptionManager::InterceptionData::~InterceptionData() {
67 InterceptionManager::InterceptionManager(TargetProcess* child_process,
68 bool relaxed)
69 : child_(child_process), names_used_(false), relaxed_(relaxed) {
70 child_->AddRef();
72 InterceptionManager::~InterceptionManager() {
73 child_->Release();
76 bool InterceptionManager::AddToPatchedFunctions(
77 const wchar_t* dll_name, const char* function_name,
78 InterceptionType interception_type, const void* replacement_code_address,
79 InterceptorId id) {
80 InterceptionData function;
81 function.type = interception_type;
82 function.id = id;
83 function.dll = dll_name;
84 function.function = function_name;
85 function.interceptor_address = replacement_code_address;
87 interceptions_.push_back(function);
88 return true;
91 bool InterceptionManager::AddToPatchedFunctions(
92 const wchar_t* dll_name, const char* function_name,
93 InterceptionType interception_type, const char* replacement_function_name,
94 InterceptorId id) {
95 InterceptionData function;
96 function.type = interception_type;
97 function.id = id;
98 function.dll = dll_name;
99 function.function = function_name;
100 function.interceptor = replacement_function_name;
101 function.interceptor_address = NULL;
103 interceptions_.push_back(function);
104 names_used_ = true;
105 return true;
108 bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) {
109 InterceptionData module_to_unload;
110 module_to_unload.type = INTERCEPTION_UNLOAD_MODULE;
111 module_to_unload.dll = dll_name;
112 // The next two are dummy values that make the structures regular, instead
113 // of having special cases. They should not be used.
114 module_to_unload.function = kUnloadDLLDummyFunction;
115 module_to_unload.interceptor_address = reinterpret_cast<void*>(1);
117 interceptions_.push_back(module_to_unload);
118 return true;
121 bool InterceptionManager::InitializeInterceptions() {
122 if (interceptions_.empty())
123 return true; // Nothing to do here
125 size_t buffer_bytes = GetBufferSize();
126 scoped_ptr<char[]> local_buffer(new char[buffer_bytes]);
128 if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes))
129 return false;
131 void* remote_buffer;
132 if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer))
133 return false;
135 bool hot_patch_needed = (0 != buffer_bytes);
136 if (!PatchNtdll(hot_patch_needed))
137 return false;
139 g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer);
140 ResultCode rc = child_->TransferVariable("g_interceptions",
141 &g_interceptions,
142 sizeof(g_interceptions));
143 return (SBOX_ALL_OK == rc);
146 size_t InterceptionManager::GetBufferSize() const {
147 std::set<base::string16> dlls;
148 size_t buffer_bytes = 0;
150 std::list<InterceptionData>::const_iterator it = interceptions_.begin();
151 for (; it != interceptions_.end(); ++it) {
152 // skip interceptions that are performed from the parent
153 if (!IsInterceptionPerformedByChild(*it))
154 continue;
156 if (!dlls.count(it->dll)) {
157 // NULL terminate the dll name on the structure
158 size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t);
160 // include the dll related size
161 buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) +
162 dll_name_bytes, sizeof(size_t));
163 dlls.insert(it->dll);
166 // we have to NULL terminate the strings on the structure
167 size_t strings_chars = it->function.size() + it->interceptor.size() + 2;
169 // a new FunctionInfo is required per function
170 size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars;
171 record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t));
172 buffer_bytes += record_bytes;
175 if (0 != buffer_bytes)
176 // add the part of SharedMemory that we have not counted yet
177 buffer_bytes += offsetof(SharedMemory, dll_list);
179 return buffer_bytes;
182 // Basically, walk the list of interceptions moving them to the config buffer,
183 // but keeping together all interceptions that belong to the same dll.
184 // The config buffer is a local buffer, not the one allocated on the child.
185 bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) {
186 if (0 == buffer_bytes)
187 return true;
189 DCHECK(buffer_bytes > sizeof(SharedMemory));
191 SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer);
192 DllPatchInfo* dll_info = shared_memory->dll_list;
193 int num_dlls = 0;
195 shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL;
197 buffer_bytes -= offsetof(SharedMemory, dll_list);
198 buffer = dll_info;
200 std::list<InterceptionData>::iterator it = interceptions_.begin();
201 for (; it != interceptions_.end();) {
202 // skip interceptions that are performed from the parent
203 if (!IsInterceptionPerformedByChild(*it)) {
204 ++it;
205 continue;
208 const base::string16 dll = it->dll;
209 if (!SetupDllInfo(*it, &buffer, &buffer_bytes))
210 return false;
212 // walk the interceptions from this point, saving the ones that are
213 // performed on this dll, and removing the entry from the list.
214 // advance the iterator before removing the element from the list
215 std::list<InterceptionData>::iterator rest = it;
216 for (; rest != interceptions_.end();) {
217 if (rest->dll == dll) {
218 if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info))
219 return false;
220 if (it == rest)
221 ++it;
222 rest = interceptions_.erase(rest);
223 } else {
224 ++rest;
227 dll_info = reinterpret_cast<DllPatchInfo*>(buffer);
228 ++num_dlls;
231 shared_memory->num_intercepted_dlls = num_dlls;
232 return true;
235 // Fills up just the part that depends on the dll, not the info that depends on
236 // the actual interception.
237 bool InterceptionManager::SetupDllInfo(const InterceptionData& data,
238 void** buffer,
239 size_t* buffer_bytes) const {
240 DCHECK(buffer_bytes);
241 DCHECK(buffer);
242 DCHECK(*buffer);
244 DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer);
246 // the strings have to be zero terminated
247 size_t required = offsetof(DllPatchInfo, dll_name) +
248 (data.dll.size() + 1) * sizeof(wchar_t);
249 required = RoundUpToMultiple(required, sizeof(size_t));
250 if (*buffer_bytes < required)
251 return false;
253 *buffer_bytes -= required;
254 *buffer = reinterpret_cast<char*>(*buffer) + required;
256 // set up the dll info to be what we know about it at this time
257 dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE);
258 dll_info->record_bytes = required;
259 dll_info->offset_to_functions = required;
260 dll_info->num_functions = 0;
261 data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size());
262 dll_info->dll_name[data.dll.size()] = L'\0';
264 return true;
267 bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data,
268 void** buffer,
269 size_t* buffer_bytes,
270 DllPatchInfo* dll_info) const {
271 DCHECK(buffer_bytes);
272 DCHECK(buffer);
273 DCHECK(*buffer);
275 if ((dll_info->unload_module) &&
276 (data.function != kUnloadDLLDummyFunction)) {
277 // Can't specify a dll for both patch and unload.
278 NOTREACHED();
281 FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer);
283 size_t name_bytes = data.function.size();
284 size_t interceptor_bytes = data.interceptor.size();
286 // the strings at the end of the structure are zero terminated
287 size_t required = offsetof(FunctionInfo, function) +
288 name_bytes + interceptor_bytes + 2;
289 required = RoundUpToMultiple(required, sizeof(size_t));
290 if (*buffer_bytes < required)
291 return false;
293 // update the caller's values
294 *buffer_bytes -= required;
295 *buffer = reinterpret_cast<char*>(*buffer) + required;
297 function->record_bytes = required;
298 function->type = data.type;
299 function->id = data.id;
300 function->interceptor_address = data.interceptor_address;
301 char* names = function->function;
303 data.function._Copy_s(names, name_bytes, name_bytes);
304 names += name_bytes;
305 *names++ = '\0';
307 // interceptor follows the function_name
308 data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes);
309 names += interceptor_bytes;
310 *names++ = '\0';
312 // update the dll table
313 dll_info->num_functions++;
314 dll_info->record_bytes += required;
316 return true;
319 bool InterceptionManager::CopyDataToChild(const void* local_buffer,
320 size_t buffer_bytes,
321 void** remote_buffer) const {
322 DCHECK(NULL != remote_buffer);
323 if (0 == buffer_bytes) {
324 *remote_buffer = NULL;
325 return true;
328 HANDLE child = child_->Process();
330 // Allocate memory on the target process without specifying the address
331 void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes,
332 MEM_COMMIT, PAGE_READWRITE);
333 if (NULL == remote_data)
334 return false;
336 SIZE_T bytes_written;
337 BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer,
338 buffer_bytes, &bytes_written);
339 if (FALSE == success || bytes_written != buffer_bytes) {
340 ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE);
341 return false;
344 *remote_buffer = remote_data;
346 return true;
349 // Only return true if the child should be able to perform this interception.
350 bool InterceptionManager::IsInterceptionPerformedByChild(
351 const InterceptionData& data) const {
352 if (INTERCEPTION_INVALID == data.type)
353 return false;
355 if (INTERCEPTION_SERVICE_CALL == data.type)
356 return false;
358 if (data.type >= INTERCEPTION_LAST)
359 return false;
361 base::string16 ntdll(kNtdllName);
362 if (ntdll == data.dll)
363 return false; // ntdll has to be intercepted from the parent
365 return true;
368 bool InterceptionManager::PatchNtdll(bool hot_patch_needed) {
369 // Maybe there is nothing to do
370 if (!hot_patch_needed && interceptions_.empty())
371 return true;
373 if (hot_patch_needed) {
374 #if SANDBOX_EXPORTS
375 // Make sure the functions are not excluded by the linker.
376 #if defined(_WIN64)
377 #pragma comment(linker, "/include:TargetNtMapViewOfSection64")
378 #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64")
379 #else
380 #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44")
381 #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12")
382 #endif
383 #endif
384 ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44);
385 ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12);
388 // Reserve a full 64k memory range in the child process.
389 HANDLE child = child_->Process();
390 BYTE* thunk_base = reinterpret_cast<BYTE*>(
391 ::VirtualAllocEx(child, NULL, kAllocGranularity,
392 MEM_RESERVE, PAGE_NOACCESS));
394 // Find an aligned, random location within the reserved range.
395 size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) +
396 sizeof(DllInterceptionData);
397 size_t thunk_offset = GetGranularAlignedRandomOffset(thunk_bytes);
399 // Split the base and offset along page boundaries.
400 thunk_base += thunk_offset & ~(kPageSize - 1);
401 thunk_offset &= kPageSize - 1;
403 // Make an aligned, padded allocation, and move the pointer to our chunk.
404 size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & ~(kPageSize - 1);
405 thunk_base = reinterpret_cast<BYTE*>(
406 ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded,
407 MEM_COMMIT, PAGE_EXECUTE_READWRITE));
408 CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access.
409 DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>(
410 thunk_base + thunk_offset);
412 DllInterceptionData dll_data;
413 dll_data.data_bytes = thunk_bytes;
414 dll_data.num_thunks = 0;
415 dll_data.used_bytes = offsetof(DllInterceptionData, thunks);
417 // Reset all helpers for a new child.
418 memset(g_originals, 0, sizeof(g_originals));
420 // this should write all the individual thunks to the child's memory
421 if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data))
422 return false;
424 // and now write the first part of the table to the child's memory
425 SIZE_T written;
426 bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data,
427 offsetof(DllInterceptionData, thunks),
428 &written);
430 if (!ok || (offsetof(DllInterceptionData, thunks) != written))
431 return false;
433 // Attempt to protect all the thunks, but ignore failure
434 DWORD old_protection;
435 ::VirtualProtectEx(child, thunks, thunk_bytes,
436 PAGE_EXECUTE_READ, &old_protection);
438 ResultCode ret = child_->TransferVariable("g_originals", g_originals,
439 sizeof(g_originals));
440 return (SBOX_ALL_OK == ret);
443 bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks,
444 size_t thunk_bytes,
445 DllInterceptionData* dll_data) {
446 DCHECK(NULL != thunks);
447 DCHECK(NULL != dll_data);
449 HMODULE ntdll_base = ::GetModuleHandle(kNtdllName);
450 if (!ntdll_base)
451 return false;
453 base::win::PEImage ntdll_image(ntdll_base);
455 // Bypass purify's interception.
456 wchar_t* loader_get = reinterpret_cast<wchar_t*>(
457 ntdll_image.GetProcAddress("LdrGetDllHandle"));
458 if (loader_get) {
459 if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
460 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
461 loader_get, &ntdll_base))
462 return false;
465 if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
466 Wow64 WowHelper(child_, ntdll_base);
467 if (!WowHelper.WaitForNtdll())
468 return false;
471 char* interceptor_base = NULL;
473 #if SANDBOX_EXPORTS
474 interceptor_base = reinterpret_cast<char*>(child_->MainModule());
475 HMODULE local_interceptor = ::LoadLibrary(child_->Name());
476 #endif
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_WIN8)
485 thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_);
486 else
487 thunk = new Wow64ResolverThunk(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_);
493 #endif
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;
501 if (INTERCEPTION_SERVICE_CALL != it->type)
502 break;
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;
515 // Translate the local address to an address on the child.
516 it->interceptor_address = interceptor_base + (address -
517 reinterpret_cast<char*>(local_interceptor));
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;
531 DCHECK(!g_originals[it->id]);
532 g_originals[it->id] = &thunks->thunks[dll_data->num_thunks];
534 dll_data->num_thunks++;
535 dll_data->used_bytes += sizeof(ThunkData);
538 delete(thunk);
540 #if SANDBOX_EXPORTS
541 if (NULL != local_interceptor)
542 ::FreeLibrary(local_interceptor);
543 #endif
545 if (it != interceptions_.end())
546 return false;
548 return true;
551 } // namespace sandbox