// Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // For information about interceptions as a whole see // http://dev.chromium.org/developers/design-documents/sandbox . #include "sandbox/win/src/interception_agent.h" #include "sandbox/win/src/interception_internal.h" #include "sandbox/win/src/interceptors.h" #include "sandbox/win/src/eat_resolver.h" #include "sandbox/win/src/sidestep_resolver.h" #include "sandbox/win/src/sandbox_nt_util.h" namespace { // Returns true if target lies between base and base + range. bool IsWithinRange(const void* base, size_t range, const void* target) { const char* end = reinterpret_cast(base) + range; return reinterpret_cast(target) < end; } } // namespace namespace sandbox { // This is the list of all imported symbols from ntdll.dll. SANDBOX_INTERCEPT NtExports g_nt; // The list of intercepted functions back-pointers. SANDBOX_INTERCEPT OriginalFunctions g_originals; // Memory buffer mapped from the parent, with the list of interceptions. SANDBOX_INTERCEPT SharedMemory* g_interceptions = NULL; InterceptionAgent* InterceptionAgent::GetInterceptionAgent() { static InterceptionAgent* s_singleton = NULL; if (!s_singleton) { if (!g_interceptions) return NULL; size_t array_bytes = g_interceptions->num_intercepted_dlls * sizeof(void*); s_singleton = reinterpret_cast( new(NT_ALLOC) char[array_bytes + sizeof(InterceptionAgent)]); bool success = s_singleton->Init(g_interceptions); if (!success) { operator delete(s_singleton, NT_ALLOC); s_singleton = NULL; } } return s_singleton; } bool InterceptionAgent::Init(SharedMemory* shared_memory) { interceptions_ = shared_memory; for (int i = 0 ; i < shared_memory->num_intercepted_dlls; i++) dlls_[i] = NULL; return true; } bool InterceptionAgent::DllMatch(const UNICODE_STRING* full_path, const UNICODE_STRING* name, const DllPatchInfo* dll_info) { UNICODE_STRING current_name; current_name.Length = static_cast(g_nt.wcslen(dll_info->dll_name) * sizeof(wchar_t)); current_name.MaximumLength = current_name.Length; current_name.Buffer = const_cast(dll_info->dll_name); BOOLEAN case_insensitive = TRUE; if (full_path && !g_nt.RtlCompareUnicodeString(¤t_name, full_path, case_insensitive)) return true; if (name && !g_nt.RtlCompareUnicodeString(¤t_name, name, case_insensitive)) return true; return false; } bool InterceptionAgent::OnDllLoad(const UNICODE_STRING* full_path, const UNICODE_STRING* name, void* base_address) { DllPatchInfo* dll_info = interceptions_->dll_list; int i = 0; for (; i < interceptions_->num_intercepted_dlls; i++) { if (DllMatch(full_path, name, dll_info)) break; dll_info = reinterpret_cast( reinterpret_cast(dll_info) + dll_info->record_bytes); } // Return now if the dll is not in our list of interest. if (i == interceptions_->num_intercepted_dlls) return true; // The dll must be unloaded. if (dll_info->unload_module) return false; // Purify causes this condition to trigger. if (dlls_[i]) return true; size_t buffer_bytes = offsetof(DllInterceptionData, thunks) + dll_info->num_functions * sizeof(ThunkData); dlls_[i] = reinterpret_cast( new(NT_PAGE, base_address) char[buffer_bytes]); DCHECK_NT(dlls_[i]); if (!dlls_[i]) return true; dlls_[i]->data_bytes = buffer_bytes; dlls_[i]->num_thunks = 0; dlls_[i]->base = base_address; dlls_[i]->used_bytes = offsetof(DllInterceptionData, thunks); VERIFY(PatchDll(dll_info, dlls_[i])); ULONG old_protect; SIZE_T real_size = buffer_bytes; void* to_protect = dlls_[i]; VERIFY_SUCCESS(g_nt.ProtectVirtualMemory(NtCurrentProcess, &to_protect, &real_size, PAGE_EXECUTE_READ, &old_protect)); return true; } void InterceptionAgent::OnDllUnload(void* base_address) { for (int i = 0; i < interceptions_->num_intercepted_dlls; i++) { if (dlls_[i] && dlls_[i]->base == base_address) { operator delete(dlls_[i], NT_PAGE); dlls_[i] = NULL; break; } } } // TODO(rvargas): We have to deal with prebinded dlls. I see two options: change // the timestamp of the patched dll, or modify the info on the prebinded dll. // the first approach messes matching of debug symbols, the second one is more // complicated. bool InterceptionAgent::PatchDll(const DllPatchInfo* dll_info, DllInterceptionData* thunks) { DCHECK_NT(NULL != thunks); DCHECK_NT(NULL != dll_info); const FunctionInfo* function = reinterpret_cast( reinterpret_cast(dll_info) + dll_info->offset_to_functions); for (int i = 0; i < dll_info->num_functions; i++) { if (!IsWithinRange(dll_info, dll_info->record_bytes, function->function)) { NOTREACHED_NT(); return false; } ResolverThunk* resolver = GetResolver(function->type); if (!resolver) return false; const char* interceptor = function->function + g_nt.strlen(function->function) + 1; if (!IsWithinRange(function, function->record_bytes, interceptor) || !IsWithinRange(dll_info, dll_info->record_bytes, interceptor)) { NOTREACHED_NT(); return false; } NTSTATUS ret = resolver->Setup(thunks->base, interceptions_->interceptor_base, function->function, interceptor, function->interceptor_address, &thunks->thunks[i], sizeof(ThunkData), NULL); if (!NT_SUCCESS(ret)) { NOTREACHED_NT(); return false; } DCHECK_NT(!g_originals[function->id]); g_originals[function->id] = &thunks->thunks[i]; thunks->num_thunks++; thunks->used_bytes += sizeof(ThunkData); function = reinterpret_cast( reinterpret_cast(function) + function->record_bytes); } return true; } // This method is called from within the loader lock ResolverThunk* InterceptionAgent::GetResolver(InterceptionType type) { static EatResolverThunk* eat_resolver = NULL; static SidestepResolverThunk* sidestep_resolver = NULL; static SmartSidestepResolverThunk* smart_sidestep_resolver = NULL; if (!eat_resolver) eat_resolver = new(NT_ALLOC) EatResolverThunk; #if !defined(_WIN64) // Sidestep is not supported for x64. if (!sidestep_resolver) sidestep_resolver = new(NT_ALLOC) SidestepResolverThunk; if (!smart_sidestep_resolver) smart_sidestep_resolver = new(NT_ALLOC) SmartSidestepResolverThunk; #endif switch (type) { case INTERCEPTION_EAT: return eat_resolver; case INTERCEPTION_SIDESTEP: return sidestep_resolver; case INTERCEPTION_SMART_SIDESTEP: return smart_sidestep_resolver; default: NOTREACHED_NT(); } return NULL; } } // namespace sandbox