michael@0: // Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: // Use of this source code is governed by a BSD-style license that can be michael@0: // found in the LICENSE file. michael@0: michael@0: // For information about interceptions as a whole see michael@0: // http://dev.chromium.org/developers/design-documents/sandbox . michael@0: michael@0: #include michael@0: michael@0: #include "sandbox/win/src/interception.h" michael@0: michael@0: #include "base/logging.h" michael@0: #include "base/memory/scoped_ptr.h" michael@0: #include "base/win/pe_image.h" michael@0: #include "base/win/windows_version.h" michael@0: #include "sandbox/win/src/interception_internal.h" michael@0: #include "sandbox/win/src/interceptors.h" michael@0: #include "sandbox/win/src/sandbox.h" michael@0: #include "sandbox/win/src/sandbox_utils.h" michael@0: #include "sandbox/win/src/service_resolver.h" michael@0: #include "sandbox/win/src/target_interceptions.h" michael@0: #include "sandbox/win/src/target_process.h" michael@0: #include "sandbox/win/src/wow64.h" michael@0: michael@0: namespace { michael@0: michael@0: const char kMapViewOfSectionName[] = "NtMapViewOfSection"; michael@0: const char kUnmapViewOfSectionName[] = "NtUnmapViewOfSection"; michael@0: michael@0: // Standard allocation granularity and page size for Windows. michael@0: const size_t kAllocGranularity = 65536; michael@0: const size_t kPageSize = 4096; michael@0: michael@0: // Find a random offset within 64k and aligned to ceil(log2(size)). michael@0: size_t GetGranularAlignedRandomOffset(size_t size) { michael@0: CHECK_LE(size, kAllocGranularity); michael@0: unsigned int offset; michael@0: michael@0: do { michael@0: rand_s(&offset); michael@0: offset &= (kAllocGranularity - 1); michael@0: } while (offset > (kAllocGranularity - size)); michael@0: michael@0: // Find an alignment between 64 and the page size (4096). michael@0: size_t align_size = kPageSize; michael@0: for (size_t new_size = align_size / 2; new_size >= size; new_size /= 2) { michael@0: align_size = new_size; michael@0: } michael@0: return offset & ~(align_size - 1); michael@0: } michael@0: michael@0: } // namespace michael@0: michael@0: namespace sandbox { michael@0: michael@0: SANDBOX_INTERCEPT SharedMemory* g_interceptions; michael@0: michael@0: // Table of the unpatched functions that we intercept. Mapped from the parent. michael@0: SANDBOX_INTERCEPT OriginalFunctions g_originals = { NULL }; michael@0: michael@0: // Magic constant that identifies that this function is not to be patched. michael@0: const char kUnloadDLLDummyFunction[] = "@"; michael@0: michael@0: InterceptionManager::InterceptionManager(TargetProcess* child_process, michael@0: bool relaxed) michael@0: : child_(child_process), names_used_(false), relaxed_(relaxed) { michael@0: child_->AddRef(); michael@0: } michael@0: InterceptionManager::~InterceptionManager() { michael@0: child_->Release(); michael@0: } michael@0: michael@0: bool InterceptionManager::AddToPatchedFunctions( michael@0: const wchar_t* dll_name, const char* function_name, michael@0: InterceptionType interception_type, const void* replacement_code_address, michael@0: InterceptorId id) { michael@0: InterceptionData function; michael@0: function.type = interception_type; michael@0: function.id = id; michael@0: function.dll = dll_name; michael@0: function.function = function_name; michael@0: function.interceptor_address = replacement_code_address; michael@0: michael@0: interceptions_.push_back(function); michael@0: return true; michael@0: } michael@0: michael@0: bool InterceptionManager::AddToPatchedFunctions( michael@0: const wchar_t* dll_name, const char* function_name, michael@0: InterceptionType interception_type, const char* replacement_function_name, michael@0: InterceptorId id) { michael@0: InterceptionData function; michael@0: function.type = interception_type; michael@0: function.id = id; michael@0: function.dll = dll_name; michael@0: function.function = function_name; michael@0: function.interceptor = replacement_function_name; michael@0: function.interceptor_address = NULL; michael@0: michael@0: interceptions_.push_back(function); michael@0: names_used_ = true; michael@0: return true; michael@0: } michael@0: michael@0: bool InterceptionManager::AddToUnloadModules(const wchar_t* dll_name) { michael@0: InterceptionData module_to_unload; michael@0: module_to_unload.type = INTERCEPTION_UNLOAD_MODULE; michael@0: module_to_unload.dll = dll_name; michael@0: // The next two are dummy values that make the structures regular, instead michael@0: // of having special cases. They should not be used. michael@0: module_to_unload.function = kUnloadDLLDummyFunction; michael@0: module_to_unload.interceptor_address = reinterpret_cast(1); michael@0: michael@0: interceptions_.push_back(module_to_unload); michael@0: return true; michael@0: } michael@0: michael@0: bool InterceptionManager::InitializeInterceptions() { michael@0: if (interceptions_.empty()) michael@0: return true; // Nothing to do here michael@0: michael@0: size_t buffer_bytes = GetBufferSize(); michael@0: scoped_ptr local_buffer(new char[buffer_bytes]); michael@0: michael@0: if (!SetupConfigBuffer(local_buffer.get(), buffer_bytes)) michael@0: return false; michael@0: michael@0: void* remote_buffer; michael@0: if (!CopyDataToChild(local_buffer.get(), buffer_bytes, &remote_buffer)) michael@0: return false; michael@0: michael@0: bool hot_patch_needed = (0 != buffer_bytes); michael@0: if (!PatchNtdll(hot_patch_needed)) michael@0: return false; michael@0: michael@0: g_interceptions = reinterpret_cast(remote_buffer); michael@0: ResultCode rc = child_->TransferVariable("g_interceptions", michael@0: &g_interceptions, michael@0: sizeof(g_interceptions)); michael@0: return (SBOX_ALL_OK == rc); michael@0: } michael@0: michael@0: size_t InterceptionManager::GetBufferSize() const { michael@0: std::set dlls; michael@0: size_t buffer_bytes = 0; michael@0: michael@0: std::list::const_iterator it = interceptions_.begin(); michael@0: for (; it != interceptions_.end(); ++it) { michael@0: // skip interceptions that are performed from the parent michael@0: if (!IsInterceptionPerformedByChild(*it)) michael@0: continue; michael@0: michael@0: if (!dlls.count(it->dll)) { michael@0: // NULL terminate the dll name on the structure michael@0: size_t dll_name_bytes = (it->dll.size() + 1) * sizeof(wchar_t); michael@0: michael@0: // include the dll related size michael@0: buffer_bytes += RoundUpToMultiple(offsetof(DllPatchInfo, dll_name) + michael@0: dll_name_bytes, sizeof(size_t)); michael@0: dlls.insert(it->dll); michael@0: } michael@0: michael@0: // we have to NULL terminate the strings on the structure michael@0: size_t strings_chars = it->function.size() + it->interceptor.size() + 2; michael@0: michael@0: // a new FunctionInfo is required per function michael@0: size_t record_bytes = offsetof(FunctionInfo, function) + strings_chars; michael@0: record_bytes = RoundUpToMultiple(record_bytes, sizeof(size_t)); michael@0: buffer_bytes += record_bytes; michael@0: } michael@0: michael@0: if (0 != buffer_bytes) michael@0: // add the part of SharedMemory that we have not counted yet michael@0: buffer_bytes += offsetof(SharedMemory, dll_list); michael@0: michael@0: return buffer_bytes; michael@0: } michael@0: michael@0: // Basically, walk the list of interceptions moving them to the config buffer, michael@0: // but keeping together all interceptions that belong to the same dll. michael@0: // The config buffer is a local buffer, not the one allocated on the child. michael@0: bool InterceptionManager::SetupConfigBuffer(void* buffer, size_t buffer_bytes) { michael@0: if (0 == buffer_bytes) michael@0: return true; michael@0: michael@0: DCHECK(buffer_bytes > sizeof(SharedMemory)); michael@0: michael@0: SharedMemory* shared_memory = reinterpret_cast(buffer); michael@0: DllPatchInfo* dll_info = shared_memory->dll_list; michael@0: int num_dlls = 0; michael@0: michael@0: shared_memory->interceptor_base = names_used_ ? child_->MainModule() : NULL; michael@0: michael@0: buffer_bytes -= offsetof(SharedMemory, dll_list); michael@0: buffer = dll_info; michael@0: michael@0: std::list::iterator it = interceptions_.begin(); michael@0: for (; it != interceptions_.end();) { michael@0: // skip interceptions that are performed from the parent michael@0: if (!IsInterceptionPerformedByChild(*it)) { michael@0: ++it; michael@0: continue; michael@0: } michael@0: michael@0: const std::wstring dll = it->dll; michael@0: if (!SetupDllInfo(*it, &buffer, &buffer_bytes)) michael@0: return false; michael@0: michael@0: // walk the interceptions from this point, saving the ones that are michael@0: // performed on this dll, and removing the entry from the list. michael@0: // advance the iterator before removing the element from the list michael@0: std::list::iterator rest = it; michael@0: for (; rest != interceptions_.end();) { michael@0: if (rest->dll == dll) { michael@0: if (!SetupInterceptionInfo(*rest, &buffer, &buffer_bytes, dll_info)) michael@0: return false; michael@0: if (it == rest) michael@0: ++it; michael@0: rest = interceptions_.erase(rest); michael@0: } else { michael@0: ++rest; michael@0: } michael@0: } michael@0: dll_info = reinterpret_cast(buffer); michael@0: ++num_dlls; michael@0: } michael@0: michael@0: shared_memory->num_intercepted_dlls = num_dlls; michael@0: return true; michael@0: } michael@0: michael@0: // Fills up just the part that depends on the dll, not the info that depends on michael@0: // the actual interception. michael@0: bool InterceptionManager::SetupDllInfo(const InterceptionData& data, michael@0: void** buffer, michael@0: size_t* buffer_bytes) const { michael@0: DCHECK(buffer_bytes); michael@0: DCHECK(buffer); michael@0: DCHECK(*buffer); michael@0: michael@0: DllPatchInfo* dll_info = reinterpret_cast(*buffer); michael@0: michael@0: // the strings have to be zero terminated michael@0: size_t required = offsetof(DllPatchInfo, dll_name) + michael@0: (data.dll.size() + 1) * sizeof(wchar_t); michael@0: required = RoundUpToMultiple(required, sizeof(size_t)); michael@0: if (*buffer_bytes < required) michael@0: return false; michael@0: michael@0: *buffer_bytes -= required; michael@0: *buffer = reinterpret_cast(*buffer) + required; michael@0: michael@0: // set up the dll info to be what we know about it at this time michael@0: dll_info->unload_module = (data.type == INTERCEPTION_UNLOAD_MODULE); michael@0: dll_info->record_bytes = required; michael@0: dll_info->offset_to_functions = required; michael@0: dll_info->num_functions = 0; michael@0: data.dll._Copy_s(dll_info->dll_name, data.dll.size(), data.dll.size()); michael@0: dll_info->dll_name[data.dll.size()] = L'\0'; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool InterceptionManager::SetupInterceptionInfo(const InterceptionData& data, michael@0: void** buffer, michael@0: size_t* buffer_bytes, michael@0: DllPatchInfo* dll_info) const { michael@0: DCHECK(buffer_bytes); michael@0: DCHECK(buffer); michael@0: DCHECK(*buffer); michael@0: michael@0: if ((dll_info->unload_module) && michael@0: (data.function != kUnloadDLLDummyFunction)) { michael@0: // Can't specify a dll for both patch and unload. michael@0: NOTREACHED(); michael@0: } michael@0: michael@0: FunctionInfo* function = reinterpret_cast(*buffer); michael@0: michael@0: size_t name_bytes = data.function.size(); michael@0: size_t interceptor_bytes = data.interceptor.size(); michael@0: michael@0: // the strings at the end of the structure are zero terminated michael@0: size_t required = offsetof(FunctionInfo, function) + michael@0: name_bytes + interceptor_bytes + 2; michael@0: required = RoundUpToMultiple(required, sizeof(size_t)); michael@0: if (*buffer_bytes < required) michael@0: return false; michael@0: michael@0: // update the caller's values michael@0: *buffer_bytes -= required; michael@0: *buffer = reinterpret_cast(*buffer) + required; michael@0: michael@0: function->record_bytes = required; michael@0: function->type = data.type; michael@0: function->id = data.id; michael@0: function->interceptor_address = data.interceptor_address; michael@0: char* names = function->function; michael@0: michael@0: data.function._Copy_s(names, name_bytes, name_bytes); michael@0: names += name_bytes; michael@0: *names++ = '\0'; michael@0: michael@0: // interceptor follows the function_name michael@0: data.interceptor._Copy_s(names, interceptor_bytes, interceptor_bytes); michael@0: names += interceptor_bytes; michael@0: *names++ = '\0'; michael@0: michael@0: // update the dll table michael@0: dll_info->num_functions++; michael@0: dll_info->record_bytes += required; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool InterceptionManager::CopyDataToChild(const void* local_buffer, michael@0: size_t buffer_bytes, michael@0: void** remote_buffer) const { michael@0: DCHECK(NULL != remote_buffer); michael@0: if (0 == buffer_bytes) { michael@0: *remote_buffer = NULL; michael@0: return true; michael@0: } michael@0: michael@0: HANDLE child = child_->Process(); michael@0: michael@0: // Allocate memory on the target process without specifying the address michael@0: void* remote_data = ::VirtualAllocEx(child, NULL, buffer_bytes, michael@0: MEM_COMMIT, PAGE_READWRITE); michael@0: if (NULL == remote_data) michael@0: return false; michael@0: michael@0: SIZE_T bytes_written; michael@0: BOOL success = ::WriteProcessMemory(child, remote_data, local_buffer, michael@0: buffer_bytes, &bytes_written); michael@0: if (FALSE == success || bytes_written != buffer_bytes) { michael@0: ::VirtualFreeEx(child, remote_data, 0, MEM_RELEASE); michael@0: return false; michael@0: } michael@0: michael@0: *remote_buffer = remote_data; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: // Only return true if the child should be able to perform this interception. michael@0: bool InterceptionManager::IsInterceptionPerformedByChild( michael@0: const InterceptionData& data) const { michael@0: if (INTERCEPTION_INVALID == data.type) michael@0: return false; michael@0: michael@0: if (INTERCEPTION_SERVICE_CALL == data.type) michael@0: return false; michael@0: michael@0: if (data.type >= INTERCEPTION_LAST) michael@0: return false; michael@0: michael@0: std::wstring ntdll(kNtdllName); michael@0: if (ntdll == data.dll) michael@0: return false; // ntdll has to be intercepted from the parent michael@0: michael@0: return true; michael@0: } michael@0: michael@0: bool InterceptionManager::PatchNtdll(bool hot_patch_needed) { michael@0: // Maybe there is nothing to do michael@0: if (!hot_patch_needed && interceptions_.empty()) michael@0: return true; michael@0: michael@0: if (hot_patch_needed) { michael@0: #if SANDBOX_EXPORTS michael@0: // Make sure the functions are not excluded by the linker. michael@0: #if defined(_WIN64) michael@0: #pragma comment(linker, "/include:TargetNtMapViewOfSection64") michael@0: #pragma comment(linker, "/include:TargetNtUnmapViewOfSection64") michael@0: #else michael@0: #pragma comment(linker, "/include:_TargetNtMapViewOfSection@44") michael@0: #pragma comment(linker, "/include:_TargetNtUnmapViewOfSection@12") michael@0: #endif michael@0: #endif michael@0: ADD_NT_INTERCEPTION(NtMapViewOfSection, MAP_VIEW_OF_SECTION_ID, 44); michael@0: ADD_NT_INTERCEPTION(NtUnmapViewOfSection, UNMAP_VIEW_OF_SECTION_ID, 12); michael@0: } michael@0: michael@0: // Reserve a full 64k memory range in the child process. michael@0: HANDLE child = child_->Process(); michael@0: BYTE* thunk_base = reinterpret_cast( michael@0: ::VirtualAllocEx(child, NULL, kAllocGranularity, michael@0: MEM_RESERVE, PAGE_NOACCESS)); michael@0: michael@0: // Find an aligned, random location within the reserved range. michael@0: size_t thunk_bytes = interceptions_.size() * sizeof(ThunkData) + michael@0: sizeof(DllInterceptionData); michael@0: size_t thunk_offset = GetGranularAlignedRandomOffset(thunk_bytes); michael@0: michael@0: // Split the base and offset along page boundaries. michael@0: thunk_base += thunk_offset & ~(kPageSize - 1); michael@0: thunk_offset &= kPageSize - 1; michael@0: michael@0: // Make an aligned, padded allocation, and move the pointer to our chunk. michael@0: size_t thunk_bytes_padded = (thunk_bytes + kPageSize - 1) & kPageSize; michael@0: thunk_base = reinterpret_cast( michael@0: ::VirtualAllocEx(child, thunk_base, thunk_bytes_padded, michael@0: MEM_COMMIT, PAGE_EXECUTE_READWRITE)); michael@0: CHECK(thunk_base); // If this fails we'd crash anyway on an invalid access. michael@0: DllInterceptionData* thunks = reinterpret_cast( michael@0: thunk_base + thunk_offset); michael@0: michael@0: DllInterceptionData dll_data; michael@0: dll_data.data_bytes = thunk_bytes; michael@0: dll_data.num_thunks = 0; michael@0: dll_data.used_bytes = offsetof(DllInterceptionData, thunks); michael@0: michael@0: // Reset all helpers for a new child. michael@0: memset(g_originals, 0, sizeof(g_originals)); michael@0: michael@0: // this should write all the individual thunks to the child's memory michael@0: if (!PatchClientFunctions(thunks, thunk_bytes, &dll_data)) michael@0: return false; michael@0: michael@0: // and now write the first part of the table to the child's memory michael@0: SIZE_T written; michael@0: bool ok = FALSE != ::WriteProcessMemory(child, thunks, &dll_data, michael@0: offsetof(DllInterceptionData, thunks), michael@0: &written); michael@0: michael@0: if (!ok || (offsetof(DllInterceptionData, thunks) != written)) michael@0: return false; michael@0: michael@0: // Attempt to protect all the thunks, but ignore failure michael@0: DWORD old_protection; michael@0: ::VirtualProtectEx(child, thunks, thunk_bytes, michael@0: PAGE_EXECUTE_READ, &old_protection); michael@0: michael@0: ResultCode ret = child_->TransferVariable("g_originals", g_originals, michael@0: sizeof(g_originals)); michael@0: return (SBOX_ALL_OK == ret); michael@0: } michael@0: michael@0: bool InterceptionManager::PatchClientFunctions(DllInterceptionData* thunks, michael@0: size_t thunk_bytes, michael@0: DllInterceptionData* dll_data) { michael@0: DCHECK(NULL != thunks); michael@0: DCHECK(NULL != dll_data); michael@0: michael@0: HMODULE ntdll_base = ::GetModuleHandle(kNtdllName); michael@0: if (!ntdll_base) michael@0: return false; michael@0: michael@0: base::win::PEImage ntdll_image(ntdll_base); michael@0: michael@0: // Bypass purify's interception. michael@0: wchar_t* loader_get = reinterpret_cast( michael@0: ntdll_image.GetProcAddress("LdrGetDllHandle")); michael@0: if (loader_get) { michael@0: if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | michael@0: GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, michael@0: loader_get, &ntdll_base)) michael@0: return false; michael@0: } michael@0: michael@0: if (base::win::GetVersion() <= base::win::VERSION_VISTA) { michael@0: Wow64 WowHelper(child_, ntdll_base); michael@0: if (!WowHelper.WaitForNtdll()) michael@0: return false; michael@0: } michael@0: michael@0: char* interceptor_base = NULL; michael@0: michael@0: #if SANDBOX_EXPORTS michael@0: interceptor_base = reinterpret_cast(child_->MainModule()); michael@0: HMODULE local_interceptor = ::LoadLibrary(child_->Name()); michael@0: #endif michael@0: michael@0: ServiceResolverThunk* thunk; michael@0: #if defined(_WIN64) michael@0: thunk = new ServiceResolverThunk(child_->Process(), relaxed_); michael@0: #else michael@0: base::win::OSInfo* os_info = base::win::OSInfo::GetInstance(); michael@0: if (os_info->wow64_status() == base::win::OSInfo::WOW64_ENABLED) { michael@0: if (os_info->version() >= base::win::VERSION_WIN8) michael@0: thunk = new Wow64W8ResolverThunk(child_->Process(), relaxed_); michael@0: else michael@0: thunk = new Wow64ResolverThunk(child_->Process(), relaxed_); michael@0: } else if (!IsXPSP2OrLater()) { michael@0: thunk = new Win2kResolverThunk(child_->Process(), relaxed_); michael@0: } else if (os_info->version() >= base::win::VERSION_WIN8) { michael@0: thunk = new Win8ResolverThunk(child_->Process(), relaxed_); michael@0: } else { michael@0: thunk = new ServiceResolverThunk(child_->Process(), relaxed_); michael@0: } michael@0: #endif michael@0: michael@0: std::list::iterator it = interceptions_.begin(); michael@0: for (; it != interceptions_.end(); ++it) { michael@0: const std::wstring ntdll(kNtdllName); michael@0: if (it->dll != ntdll) michael@0: break; michael@0: michael@0: if (INTERCEPTION_SERVICE_CALL != it->type) michael@0: break; michael@0: michael@0: #if SANDBOX_EXPORTS michael@0: // We may be trying to patch by function name. michael@0: if (NULL == it->interceptor_address) { michael@0: const char* address; michael@0: NTSTATUS ret = thunk->ResolveInterceptor(local_interceptor, michael@0: it->interceptor.c_str(), michael@0: reinterpret_cast( michael@0: &address)); michael@0: if (!NT_SUCCESS(ret)) michael@0: break; michael@0: michael@0: // Translate the local address to an address on the child. michael@0: it->interceptor_address = interceptor_base + (address - michael@0: reinterpret_cast(local_interceptor)); michael@0: } michael@0: #endif michael@0: NTSTATUS ret = thunk->Setup(ntdll_base, michael@0: interceptor_base, michael@0: it->function.c_str(), michael@0: it->interceptor.c_str(), michael@0: it->interceptor_address, michael@0: &thunks->thunks[dll_data->num_thunks], michael@0: thunk_bytes - dll_data->used_bytes, michael@0: NULL); michael@0: if (!NT_SUCCESS(ret)) michael@0: break; michael@0: michael@0: DCHECK(!g_originals[it->id]); michael@0: g_originals[it->id] = &thunks->thunks[dll_data->num_thunks]; michael@0: michael@0: dll_data->num_thunks++; michael@0: dll_data->used_bytes += sizeof(ThunkData); michael@0: } michael@0: michael@0: delete(thunk); michael@0: michael@0: #if SANDBOX_EXPORTS michael@0: if (NULL != local_interceptor) michael@0: ::FreeLibrary(local_interceptor); michael@0: #endif michael@0: michael@0: if (it != interceptions_.end()) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: } // namespace sandbox