Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | // Copyright (c) 2006-2010 The Chromium Authors. All rights reserved. |
michael@0 | 2 | // Use of this source code is governed by a BSD-style license that can be |
michael@0 | 3 | // found in the LICENSE file. |
michael@0 | 4 | |
michael@0 | 5 | // For information about interceptions as a whole see |
michael@0 | 6 | // http://dev.chromium.org/developers/design-documents/sandbox . |
michael@0 | 7 | |
michael@0 | 8 | #include "sandbox/win/src/interception_agent.h" |
michael@0 | 9 | |
michael@0 | 10 | #include "sandbox/win/src/interception_internal.h" |
michael@0 | 11 | #include "sandbox/win/src/interceptors.h" |
michael@0 | 12 | #include "sandbox/win/src/eat_resolver.h" |
michael@0 | 13 | #include "sandbox/win/src/sidestep_resolver.h" |
michael@0 | 14 | #include "sandbox/win/src/sandbox_nt_util.h" |
michael@0 | 15 | |
michael@0 | 16 | namespace { |
michael@0 | 17 | |
michael@0 | 18 | // Returns true if target lies between base and base + range. |
michael@0 | 19 | bool IsWithinRange(const void* base, size_t range, const void* target) { |
michael@0 | 20 | const char* end = reinterpret_cast<const char*>(base) + range; |
michael@0 | 21 | return reinterpret_cast<const char*>(target) < end; |
michael@0 | 22 | } |
michael@0 | 23 | |
michael@0 | 24 | } // namespace |
michael@0 | 25 | |
michael@0 | 26 | namespace sandbox { |
michael@0 | 27 | |
michael@0 | 28 | // This is the list of all imported symbols from ntdll.dll. |
michael@0 | 29 | SANDBOX_INTERCEPT NtExports g_nt; |
michael@0 | 30 | |
michael@0 | 31 | // The list of intercepted functions back-pointers. |
michael@0 | 32 | SANDBOX_INTERCEPT OriginalFunctions g_originals; |
michael@0 | 33 | |
michael@0 | 34 | // Memory buffer mapped from the parent, with the list of interceptions. |
michael@0 | 35 | SANDBOX_INTERCEPT SharedMemory* g_interceptions = NULL; |
michael@0 | 36 | |
michael@0 | 37 | InterceptionAgent* InterceptionAgent::GetInterceptionAgent() { |
michael@0 | 38 | static InterceptionAgent* s_singleton = NULL; |
michael@0 | 39 | if (!s_singleton) { |
michael@0 | 40 | if (!g_interceptions) |
michael@0 | 41 | return NULL; |
michael@0 | 42 | |
michael@0 | 43 | size_t array_bytes = g_interceptions->num_intercepted_dlls * sizeof(void*); |
michael@0 | 44 | s_singleton = reinterpret_cast<InterceptionAgent*>( |
michael@0 | 45 | new(NT_ALLOC) char[array_bytes + sizeof(InterceptionAgent)]); |
michael@0 | 46 | |
michael@0 | 47 | bool success = s_singleton->Init(g_interceptions); |
michael@0 | 48 | if (!success) { |
michael@0 | 49 | operator delete(s_singleton, NT_ALLOC); |
michael@0 | 50 | s_singleton = NULL; |
michael@0 | 51 | } |
michael@0 | 52 | } |
michael@0 | 53 | return s_singleton; |
michael@0 | 54 | } |
michael@0 | 55 | |
michael@0 | 56 | bool InterceptionAgent::Init(SharedMemory* shared_memory) { |
michael@0 | 57 | interceptions_ = shared_memory; |
michael@0 | 58 | for (int i = 0 ; i < shared_memory->num_intercepted_dlls; i++) |
michael@0 | 59 | dlls_[i] = NULL; |
michael@0 | 60 | return true; |
michael@0 | 61 | } |
michael@0 | 62 | |
michael@0 | 63 | bool InterceptionAgent::DllMatch(const UNICODE_STRING* full_path, |
michael@0 | 64 | const UNICODE_STRING* name, |
michael@0 | 65 | const DllPatchInfo* dll_info) { |
michael@0 | 66 | UNICODE_STRING current_name; |
michael@0 | 67 | current_name.Length = static_cast<USHORT>(g_nt.wcslen(dll_info->dll_name) * |
michael@0 | 68 | sizeof(wchar_t)); |
michael@0 | 69 | current_name.MaximumLength = current_name.Length; |
michael@0 | 70 | current_name.Buffer = const_cast<wchar_t*>(dll_info->dll_name); |
michael@0 | 71 | |
michael@0 | 72 | BOOLEAN case_insensitive = TRUE; |
michael@0 | 73 | if (full_path && |
michael@0 | 74 | !g_nt.RtlCompareUnicodeString(¤t_name, full_path, case_insensitive)) |
michael@0 | 75 | return true; |
michael@0 | 76 | |
michael@0 | 77 | if (name && |
michael@0 | 78 | !g_nt.RtlCompareUnicodeString(¤t_name, name, case_insensitive)) |
michael@0 | 79 | return true; |
michael@0 | 80 | |
michael@0 | 81 | return false; |
michael@0 | 82 | } |
michael@0 | 83 | |
michael@0 | 84 | bool InterceptionAgent::OnDllLoad(const UNICODE_STRING* full_path, |
michael@0 | 85 | const UNICODE_STRING* name, |
michael@0 | 86 | void* base_address) { |
michael@0 | 87 | DllPatchInfo* dll_info = interceptions_->dll_list; |
michael@0 | 88 | int i = 0; |
michael@0 | 89 | for (; i < interceptions_->num_intercepted_dlls; i++) { |
michael@0 | 90 | if (DllMatch(full_path, name, dll_info)) |
michael@0 | 91 | break; |
michael@0 | 92 | |
michael@0 | 93 | dll_info = reinterpret_cast<DllPatchInfo*>( |
michael@0 | 94 | reinterpret_cast<char*>(dll_info) + dll_info->record_bytes); |
michael@0 | 95 | } |
michael@0 | 96 | |
michael@0 | 97 | // Return now if the dll is not in our list of interest. |
michael@0 | 98 | if (i == interceptions_->num_intercepted_dlls) |
michael@0 | 99 | return true; |
michael@0 | 100 | |
michael@0 | 101 | // The dll must be unloaded. |
michael@0 | 102 | if (dll_info->unload_module) |
michael@0 | 103 | return false; |
michael@0 | 104 | |
michael@0 | 105 | // Purify causes this condition to trigger. |
michael@0 | 106 | if (dlls_[i]) |
michael@0 | 107 | return true; |
michael@0 | 108 | |
michael@0 | 109 | size_t buffer_bytes = offsetof(DllInterceptionData, thunks) + |
michael@0 | 110 | dll_info->num_functions * sizeof(ThunkData); |
michael@0 | 111 | dlls_[i] = reinterpret_cast<DllInterceptionData*>( |
michael@0 | 112 | new(NT_PAGE, base_address) char[buffer_bytes]); |
michael@0 | 113 | |
michael@0 | 114 | DCHECK_NT(dlls_[i]); |
michael@0 | 115 | if (!dlls_[i]) |
michael@0 | 116 | return true; |
michael@0 | 117 | |
michael@0 | 118 | dlls_[i]->data_bytes = buffer_bytes; |
michael@0 | 119 | dlls_[i]->num_thunks = 0; |
michael@0 | 120 | dlls_[i]->base = base_address; |
michael@0 | 121 | dlls_[i]->used_bytes = offsetof(DllInterceptionData, thunks); |
michael@0 | 122 | |
michael@0 | 123 | VERIFY(PatchDll(dll_info, dlls_[i])); |
michael@0 | 124 | |
michael@0 | 125 | ULONG old_protect; |
michael@0 | 126 | SIZE_T real_size = buffer_bytes; |
michael@0 | 127 | void* to_protect = dlls_[i]; |
michael@0 | 128 | VERIFY_SUCCESS(g_nt.ProtectVirtualMemory(NtCurrentProcess, &to_protect, |
michael@0 | 129 | &real_size, PAGE_EXECUTE_READ, |
michael@0 | 130 | &old_protect)); |
michael@0 | 131 | return true; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | void InterceptionAgent::OnDllUnload(void* base_address) { |
michael@0 | 135 | for (int i = 0; i < interceptions_->num_intercepted_dlls; i++) { |
michael@0 | 136 | if (dlls_[i] && dlls_[i]->base == base_address) { |
michael@0 | 137 | operator delete(dlls_[i], NT_PAGE); |
michael@0 | 138 | dlls_[i] = NULL; |
michael@0 | 139 | break; |
michael@0 | 140 | } |
michael@0 | 141 | } |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | // TODO(rvargas): We have to deal with prebinded dlls. I see two options: change |
michael@0 | 145 | // the timestamp of the patched dll, or modify the info on the prebinded dll. |
michael@0 | 146 | // the first approach messes matching of debug symbols, the second one is more |
michael@0 | 147 | // complicated. |
michael@0 | 148 | bool InterceptionAgent::PatchDll(const DllPatchInfo* dll_info, |
michael@0 | 149 | DllInterceptionData* thunks) { |
michael@0 | 150 | DCHECK_NT(NULL != thunks); |
michael@0 | 151 | DCHECK_NT(NULL != dll_info); |
michael@0 | 152 | |
michael@0 | 153 | const FunctionInfo* function = reinterpret_cast<const FunctionInfo*>( |
michael@0 | 154 | reinterpret_cast<const char*>(dll_info) + dll_info->offset_to_functions); |
michael@0 | 155 | |
michael@0 | 156 | for (int i = 0; i < dll_info->num_functions; i++) { |
michael@0 | 157 | if (!IsWithinRange(dll_info, dll_info->record_bytes, function->function)) { |
michael@0 | 158 | NOTREACHED_NT(); |
michael@0 | 159 | return false; |
michael@0 | 160 | } |
michael@0 | 161 | |
michael@0 | 162 | ResolverThunk* resolver = GetResolver(function->type); |
michael@0 | 163 | if (!resolver) |
michael@0 | 164 | return false; |
michael@0 | 165 | |
michael@0 | 166 | const char* interceptor = function->function + |
michael@0 | 167 | g_nt.strlen(function->function) + 1; |
michael@0 | 168 | |
michael@0 | 169 | if (!IsWithinRange(function, function->record_bytes, interceptor) || |
michael@0 | 170 | !IsWithinRange(dll_info, dll_info->record_bytes, interceptor)) { |
michael@0 | 171 | NOTREACHED_NT(); |
michael@0 | 172 | return false; |
michael@0 | 173 | } |
michael@0 | 174 | |
michael@0 | 175 | NTSTATUS ret = resolver->Setup(thunks->base, |
michael@0 | 176 | interceptions_->interceptor_base, |
michael@0 | 177 | function->function, |
michael@0 | 178 | interceptor, |
michael@0 | 179 | function->interceptor_address, |
michael@0 | 180 | &thunks->thunks[i], |
michael@0 | 181 | sizeof(ThunkData), |
michael@0 | 182 | NULL); |
michael@0 | 183 | if (!NT_SUCCESS(ret)) { |
michael@0 | 184 | NOTREACHED_NT(); |
michael@0 | 185 | return false; |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | DCHECK_NT(!g_originals[function->id]); |
michael@0 | 189 | g_originals[function->id] = &thunks->thunks[i]; |
michael@0 | 190 | |
michael@0 | 191 | thunks->num_thunks++; |
michael@0 | 192 | thunks->used_bytes += sizeof(ThunkData); |
michael@0 | 193 | |
michael@0 | 194 | function = reinterpret_cast<const FunctionInfo*>( |
michael@0 | 195 | reinterpret_cast<const char*>(function) + function->record_bytes); |
michael@0 | 196 | } |
michael@0 | 197 | |
michael@0 | 198 | return true; |
michael@0 | 199 | } |
michael@0 | 200 | |
michael@0 | 201 | // This method is called from within the loader lock |
michael@0 | 202 | ResolverThunk* InterceptionAgent::GetResolver(InterceptionType type) { |
michael@0 | 203 | static EatResolverThunk* eat_resolver = NULL; |
michael@0 | 204 | static SidestepResolverThunk* sidestep_resolver = NULL; |
michael@0 | 205 | static SmartSidestepResolverThunk* smart_sidestep_resolver = NULL; |
michael@0 | 206 | |
michael@0 | 207 | if (!eat_resolver) |
michael@0 | 208 | eat_resolver = new(NT_ALLOC) EatResolverThunk; |
michael@0 | 209 | |
michael@0 | 210 | #if !defined(_WIN64) |
michael@0 | 211 | // Sidestep is not supported for x64. |
michael@0 | 212 | if (!sidestep_resolver) |
michael@0 | 213 | sidestep_resolver = new(NT_ALLOC) SidestepResolverThunk; |
michael@0 | 214 | |
michael@0 | 215 | if (!smart_sidestep_resolver) |
michael@0 | 216 | smart_sidestep_resolver = new(NT_ALLOC) SmartSidestepResolverThunk; |
michael@0 | 217 | #endif |
michael@0 | 218 | |
michael@0 | 219 | switch (type) { |
michael@0 | 220 | case INTERCEPTION_EAT: |
michael@0 | 221 | return eat_resolver; |
michael@0 | 222 | case INTERCEPTION_SIDESTEP: |
michael@0 | 223 | return sidestep_resolver; |
michael@0 | 224 | case INTERCEPTION_SMART_SIDESTEP: |
michael@0 | 225 | return smart_sidestep_resolver; |
michael@0 | 226 | default: |
michael@0 | 227 | NOTREACHED_NT(); |
michael@0 | 228 | } |
michael@0 | 229 | |
michael@0 | 230 | return NULL; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | } // namespace sandbox |