ash: Extend launcher first button to include leading inset.
[chromium-blink-merge.git] / sandbox / src / interception.cc
bloba873f4ae6eeb045a8893ad6f9241b2fcd9beeaa7
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/src/interception.h"
12 #include "base/logging.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/win/pe_image.h"
15 #include "base/win/windows_version.h"
16 #include "sandbox/src/interception_internal.h"
17 #include "sandbox/src/interceptors.h"
18 #include "sandbox/src/sandbox.h"
19 #include "sandbox/src/sandbox_utils.h"
20 #include "sandbox/src/service_resolver.h"
21 #include "sandbox/src/target_interceptions.h"
22 #include "sandbox/src/target_process.h"
23 #include "sandbox/src/wow64.h"
25 namespace {
27 const char kMapViewOfSectionName[] = "NtMapViewOfSection";
28 const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection";
30 } // namespace
32 namespace sandbox {
34 SANDBOX_INTERCEPT SharedMemory* g_interceptions;
36 // Table of the unpatched functions that we intercept. Mapped from the parent.
37 SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL };
39 // Magic constant that identifies that this function is not to be patched.
40 const char kUnloadDLLDummyFunction[] = "@";
42 InterceptionManager::InterceptionManager(TargetProcess* child_process,
43 bool relaxed)
44 : child_(child_process), names_used_(false), relaxed_(relaxed) {
45 child_->AddRef();
47 InterceptionManager::~InterceptionManager() {
48 child_->Release();
51 bool InterceptionManager::AddToPatchedFunctions(
52 const wchar_t* dll_name, const char* function_name,
53 InterceptionType interception_type, const void* replacement_code_address,
54 InterceptorId id) {
55 InterceptionData function;
56 function.type = interception_type;
57 function.id = id;
58 function.dll = dll_name;
59 function.function = function_name;
60 function.interceptor_address = replacement_code_address;
62 interceptions_.push_back(function);
63 return true;
66 bool InterceptionManager::AddToPatchedFunctions(
67 const wchar_t* dll_name, const char* function_name,
68 InterceptionType interception_type, const char* replacement_function_name,
69 InterceptorId id) {
70 InterceptionData function;
71 function.type = interception_type;
72 function.id = id;
73 function.dll = dll_name;
74 function.function = function_name;
75 function.interceptor = replacement_function_name;
76 function.interceptor_address = NULL;
78 interceptions_.push_back(function);
79 names_used_ = true;
80 return true;
83 bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) {
84 InterceptionData module_to_unload;
85 module_to_unload.type = INTERCEPTION_UNLOAD_MODULE;
86 module_to_unload.dll = dll_name;
87 // The next two are dummy values that make the structures regular, instead
88 // of having special cases. They should not be used.
89 module_to_unload.function = kUnloadDLLDummyFunction;
90 module_to_unload.interceptor_address = reinterpret_cast<void*>(1);
92 interceptions_.push_back(module_to_unload);
93 return true;
96 bool InterceptionManager::InitializeInterceptions() {
97 if (interceptions_.empty())
98 return true; // Nothing to do here
100 size_t buffer_bytes = GetBufferSize();
101 scoped_array<char> local_buffer(new char[buffer_bytes]);
103 if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes))
104 return false;
106 void* remote_buffer;
107 if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer))
108 return false;
110 bool hot_patch_needed = (0 != buffer_bytes);
111 if (!PatchNtdll(hot_patch_needed))
112 return false;
114 g_interceptions = reinterpret_cast<SharedMemory*>(remote_buffer);
115 ResultCode rc = child_->TransferVariable("g_interceptions",
116 &g_interceptions,
117 sizeof(g_interceptions));
118 return (SBOX_ALL_OK == rc);
121 size_t InterceptionManager::GetBufferSize() const {
122 std::set<std::wstring> dlls;
123 size_t buffer_bytes = 0;
125 std::list<InterceptionData>::const_iterator it = interceptions_.begin();
126 for (; it != interceptions_.end(); ++it) {
127 // skip interceptions that are performed from the parent
128 if (!IsInterceptionPerformedByChild(*it))
129 continue;
131 if (!dlls.count(it->dll)) {
132 // NULL terminate the dll name on the structure
133 size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t);
135 // include the dll related size
136 buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) +
137 dll_name_bytes, sizeof(size_t));
138 dlls.insert(it->dll);
141 // we have to NULL terminate the strings on the structure
142 size_t strings_chars = it->function.size() + it->interceptor.size() + 2;
144 // a new FunctionInfo is required per function
145 size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars;
146 record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t));
147 buffer_bytes += record_bytes;
150 if (0 != buffer_bytes)
151 // add the part of SharedMemory that we have not counted yet
152 buffer_bytes += offsetof(SharedMemory, dll_list);
154 return buffer_bytes;
157 // Basically, walk the list of interceptions moving them to the config buffer,
158 // but keeping together all interceptions that belong to the same dll.
159 // The config buffer is a local buffer, not the one allocated on the child.
160 bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) {
161 if (0 == buffer_bytes)
162 return true;
164 DCHECK(buffer_bytes > sizeof(SharedMemory));
166 SharedMemory* shared_memory = reinterpret_cast<SharedMemory*>(buffer);
167 DllPatchInfo* dll_info = shared_memory->dll_list;
168 int num_dlls = 0;
170 shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL;
172 buffer_bytes -= offsetof(SharedMemory, dll_list);
173 buffer = dll_info;
175 std::list<InterceptionData>::iterator it = interceptions_.begin();
176 for (; it != interceptions_.end();) {
177 // skip interceptions that are performed from the parent
178 if (!IsInterceptionPerformedByChild(*it)) {
179 ++it;
180 continue;
183 const std::wstring dll = it->dll;
184 if (!SetupDllInfo(*it, &buffer, &buffer_bytes))
185 return false;
187 // walk the interceptions from this point, saving the ones that are
188 // performed on this dll, and removing the entry from the list.
189 // advance the iterator before removing the element from the list
190 std::list<InterceptionData>::iterator rest = it;
191 for (; rest != interceptions_.end();) {
192 if (rest->dll == dll) {
193 if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info))
194 return false;
195 if (it == rest)
196 ++it;
197 rest = interceptions_.erase(rest);
198 } else {
199 ++rest;
202 dll_info = reinterpret_cast<DllPatchInfo*>(buffer);
203 ++num_dlls;
206 shared_memory->num_intercepted_dlls = num_dlls;
207 return true;
210 // Fills up just the part that depends on the dll, not the info that depends on
211 // the actual interception.
212 bool InterceptionManager::SetupDllInfo(const InterceptionData& data,
213 void** buffer,
214 size_t* buffer_bytes) const {
215 DCHECK(buffer_bytes);
216 DCHECK(buffer);
217 DCHECK(*buffer);
219 DllPatchInfo* dll_info = reinterpret_cast<DllPatchInfo*>(*buffer);
221 // the strings have to be zero terminated
222 size_t required = offsetof(DllPatchInfo, dll_name) +
223 (data.dll.size() + 1) * sizeof(wchar_t);
224 required = RoundUpToMultiple(required, sizeof(size_t));
225 if (*buffer_bytes < required)
226 return false;
228 *buffer_bytes -= required;
229 *buffer = reinterpret_cast<char*>(*buffer) + required;
231 // set up the dll info to be what we know about it at this time
232 dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE);
233 dll_info->record_bytes = required;
234 dll_info->offset_to_functions = required;
235 dll_info->num_functions = 0;
236 data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size());
237 dll_info->dll_name[data.dll.size()] = L'\0';
239 return true;
242 bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data,
243 void** buffer,
244 size_t* buffer_bytes,
245 DllPatchInfo* dll_info) const {
246 DCHECK(buffer_bytes);
247 DCHECK(buffer);
248 DCHECK(*buffer);
250 if ((dll_info->unload_module) &&
251 (data.function != kUnloadDLLDummyFunction)) {
252 // Can't specify a dll for both patch and unload.
253 NOTREACHED();
256 FunctionInfo* function = reinterpret_cast<FunctionInfo*>(*buffer);
258 size_t name_bytes = data.function.size();
259 size_t interceptor_bytes = data.interceptor.size();
261 // the strings at the end of the structure are zero terminated
262 size_t required = offsetof(FunctionInfo, function) +
263 name_bytes + interceptor_bytes + 2;
264 required = RoundUpToMultiple(required, sizeof(size_t));
265 if (*buffer_bytes < required)
266 return false;
268 // update the caller's values
269 *buffer_bytes -= required;
270 *buffer = reinterpret_cast<char*>(*buffer) + required;
272 function->record_bytes = required;
273 function->type = data.type;
274 function->id = data.id;
275 function->interceptor_address = data.interceptor_address;
276 char* names = function->function;
278 data.function._Copy_s(names, name_bytes, name_bytes);
279 names += name_bytes;
280 *names++ = '\0';
282 // interceptor follows the function_name
283 data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes);
284 names += interceptor_bytes;
285 *names++ = '\0';
287 // update the dll table
288 dll_info->num_functions++;
289 dll_info->record_bytes += required;
291 return true;
294 bool InterceptionManager::CopyDataToChild(const void* local_buffer,
295 size_t buffer_bytes,
296 void** remote_buffer) const {
297 DCHECK(NULL != remote_buffer);
298 if (0 == buffer_bytes) {
299 *remote_buffer = NULL;
300 return true;
303 HANDLE child = child_->Process();
305 // Allocate memory on the target process without specifying the address
306 void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes,
307 MEM_COMMIT, PAGE_READWRITE);
308 if (NULL == remote_data)
309 return false;
311 SIZE_T bytes_written;
312 BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer,
313 buffer_bytes, &bytes_written);
314 if (FALSE == success || bytes_written != buffer_bytes) {
315 ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE);
316 return false;
319 *remote_buffer = remote_data;
321 return true;
324 // Only return true if the child should be able to perform this interception.
325 bool InterceptionManager::IsInterceptionPerformedByChild(
326 const InterceptionData& data) const {
327 if (INTERCEPTION_INVALID == data.type)
328 return false;
330 if (INTERCEPTION_SERVICE_CALL == data.type)
331 return false;
333 if (data.type >= INTERCEPTION_LAST)
334 return false;
336 std::wstring ntdll(kNtdllName);
337 if (ntdll == data.dll)
338 return false; // ntdll has to be intercepted from the parent
340 return true;
343 bool InterceptionManager::PatchNtdll(bool hot_patch_needed) {
344 // Maybe there is nothing to do
345 if (!hot_patch_needed && interceptions_.empty())
346 return true;
348 if (hot_patch_needed) {
349 #if SANDBOX_EXPORTS
350 // Make sure the functions are not excluded by the linker.
351 #if defined(_WIN64)
352 #pragma comment(linker, "/include:TargetNtMapViewOfSection64")
353 #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64")
354 #else
355 #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44")
356 #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12")
357 #endif
358 #endif
359 ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44);
360 ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12);
363 size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) +
364 sizeof(DllInterceptionData);
366 // Allocate memory on the child, without specifying the desired address
367 HANDLE child = child_->Process();
368 DllInterceptionData* thunks = reinterpret_cast<DllInterceptionData*>(
369 ::VirtualAllocEx(child, NULL, thunk_bytes,
370 MEM_COMMIT,
371 PAGE_EXECUTE_READWRITE));
373 DllInterceptionData dll_data;
374 dll_data.data_bytes = thunk_bytes;
375 dll_data.num_thunks = 0;
376 dll_data.used_bytes = offsetof(DllInterceptionData, thunks);
378 // Reset all helpers for a new child.
379 memset(g_originals, 0, sizeof(g_originals));
381 // this should write all the individual thunks to the child's memory
382 if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data))
383 return false;
385 // and now write the first part of the table to the child's memory
386 SIZE_T written;
387 bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data,
388 offsetof(DllInterceptionData, thunks),
389 &written);
391 if (!ok || (offsetof(DllInterceptionData, thunks) != written))
392 return false;
394 // Attempt to protect all the thunks, but ignore failure
395 DWORD old_protection;
396 ::VirtualProtectEx(child, thunks, thunk_bytes,
397 PAGE_EXECUTE_READ, &old_protection);
399 ResultCode ret = child_->TransferVariable("g_originals", g_originals,
400 sizeof(g_originals));
402 return SBOX_ALL_OK == ret ? true : false;
405 bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks,
406 size_t thunk_bytes,
407 DllInterceptionData* dll_data) {
408 DCHECK(NULL != thunks);
409 DCHECK(NULL != dll_data);
411 HMODULE ntdll_base = ::GetModuleHandle(kNtdllName);
412 if (!ntdll_base)
413 return false;
415 base::win::PEImage ntdll_image(ntdll_base);
417 // Bypass purify's interception.
418 wchar_t* loader_get = reinterpret_cast<wchar_t*>(
419 ntdll_image.GetProcAddress("LdrGetDllHandle"));
420 if (loader_get) {
421 if (!GetModuleHandleHelper(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
422 GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
423 loader_get, &ntdll_base))
424 return false;
427 if (base::win::GetVersion() <= base::win::VERSION_VISTA) {
428 Wow64 WowHelper(child_, ntdll_base);
429 if (!WowHelper.WaitForNtdll())
430 return false;
433 char* interceptor_base = NULL;
435 #if SANDBOX_EXPORTS
436 interceptor_base = reinterpret_cast<char*>(child_->MainModule());
437 HMODULE local_interceptor = ::LoadLibrary(child_->Name());
438 #endif
440 ServiceResolverThunk* thunk;
441 #if defined(_WIN64)
442 thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
443 #else
444 base::win::OSInfo* os_info = base::win::OSInfo::GetInstance();
445 if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) {
446 if (os_info->version() >= base::win::VERSION_WIN8)
447 thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_);
448 else
449 thunk = new Wow64ResolverThunk(child_->Process(), relaxed_);
450 } else if (!IsXPSP2OrLater()) {
451 thunk = new Win2kResolverThunk(child_->Process(), relaxed_);
452 } else if (os_info->version() >= base::win::VERSION_WIN8) {
453 thunk = new Win8ResolverThunk(child_->Process(), relaxed_);
454 } else {
455 thunk = new ServiceResolverThunk(child_->Process(), relaxed_);
457 #endif
459 std::list<InterceptionData>::iterator it = interceptions_.begin();
460 for (; it != interceptions_.end(); ++it) {
461 const std::wstring ntdll(kNtdllName);
462 if (it->dll != ntdll)
463 break;
465 if (INTERCEPTION_SERVICE_CALL != it->type)
466 break;
468 #if SANDBOX_EXPORTS
469 // We may be trying to patch by function name.
470 if (NULL == it->interceptor_address) {
471 const char* address;
472 NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor,
473 it->interceptor.c_str(),
474 reinterpret_cast<const void**>(
475 &address));
476 if (!NT_SUCCESS(ret))
477 break;
479 // Translate the local address to an address on the child.
480 it->interceptor_address = interceptor_base + (address -
481 reinterpret_cast<char*>(local_interceptor));
483 #endif
484 NTSTATUS ret = thunk->Setup(ntdll_base,
485 interceptor_base,
486 it->function.c_str(),
487 it->interceptor.c_str(),
488 it->interceptor_address,
489 &thunks->thunks[dll_data->num_thunks],
490 thunk_bytes - dll_data->used_bytes,
491 NULL);
492 if (!NT_SUCCESS(ret))
493 break;
495 DCHECK(!g_originals[it->id]);
496 g_originals[it->id] = &thunks->thunks[dll_data->num_thunks];
498 dll_data->num_thunks++;
499 dll_data->used_bytes += sizeof(ThunkData);
502 delete(thunk);
504 #if SANDBOX_EXPORTS
505 if (NULL != local_interceptor)
506 ::FreeLibrary(local_interceptor);
507 #endif
509 if (it != interceptions_.end())
510 return false;
512 return true;
515 } // namespace sandbox