| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2011 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. | |
| 4 | |
| 5 #include "chrome_frame/vtable_patch_manager.h" | |
| 6 | |
| 7 #include <atlcomcli.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 | |
| 11 #include "base/atomicops.h" | |
| 12 #include "base/logging.h" | |
| 13 #include "base/memory/scoped_ptr.h" | |
| 14 #include "base/synchronization/lock.h" | |
| 15 #include "chrome_frame/function_stub.h" | |
| 16 #include "chrome_frame/pin_module.h" | |
| 17 | |
| 18 namespace vtable_patch { | |
| 19 | |
| 20 // The number of times we retry a patch/unpatch operation in case of | |
| 21 // VM races with other 3rd party software trying to patch the same thing. | |
| 22 const int kMaxRetries = 3; | |
| 23 | |
| 24 // We hold a lock over all patching operations to make sure that we don't | |
| 25 // e.g. race on VM operations to the same patches, or to physical pages | |
| 26 // shared across different VTABLEs. | |
| 27 base::Lock patch_lock_; | |
| 28 | |
| 29 namespace internal { | |
| 30 // Because other parties in our process might be attempting to patch the same | |
| 31 // virtual tables at the same time, we have a race to modify the VM protections | |
| 32 // on the pages. We also need to do a compare/swap type operation when we | |
| 33 // modify the function, so as to be sure that we grab the most recent value. | |
| 34 // Hence the SEH blocks and the nasty-looking compare/swap operation. | |
| 35 bool ReplaceFunctionPointer(void** entry, void* new_proc, void* curr_proc) { | |
| 36 __try { | |
| 37 base::subtle::Atomic32 prev_value; | |
| 38 | |
| 39 prev_value = base::subtle::NoBarrier_CompareAndSwap( | |
| 40 reinterpret_cast<base::subtle::Atomic32 volatile*>(entry), | |
| 41 reinterpret_cast<base::subtle::Atomic32>(curr_proc), | |
| 42 reinterpret_cast<base::subtle::Atomic32>(new_proc)); | |
| 43 | |
| 44 return curr_proc == reinterpret_cast<void*>(prev_value); | |
| 45 } __except(EXCEPTION_EXECUTE_HANDLER) { | |
| 46 // Oops, we took exception on access. | |
| 47 } | |
| 48 | |
| 49 return false; | |
| 50 } | |
| 51 | |
| 52 } // namespace | |
| 53 | |
| 54 // Convenient definition of a VTABLE | |
| 55 typedef PROC* Vtable; | |
| 56 | |
| 57 // Returns a pointer to the VTable of a COM interface. | |
| 58 // @param unknown [in] The pointer of the COM interface. | |
| 59 inline Vtable GetIFVTable(void* unknown) { | |
| 60 return reinterpret_cast<Vtable>(*reinterpret_cast<void**>(unknown)); | |
| 61 } | |
| 62 | |
| 63 HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches) { | |
| 64 // Do some sanity checking of the input arguments. | |
| 65 if (NULL == unknown || NULL == patches) { | |
| 66 NOTREACHED(); | |
| 67 return E_INVALIDARG; | |
| 68 } | |
| 69 | |
| 70 Vtable vtable = GetIFVTable(unknown); | |
| 71 DCHECK(vtable); | |
| 72 | |
| 73 // All VM operations, patching and manipulation of MethodPatchInfo | |
| 74 // is done under a global lock, to ensure multiple threads don't | |
| 75 // race, whether on an individual patch, or on VM operations to | |
| 76 // the same physical pages. | |
| 77 base::AutoLock lock(patch_lock_); | |
| 78 | |
| 79 for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { | |
| 80 if (it->stub_ != NULL) { | |
| 81 // If this DCHECK fires it means that we are using the same VTable | |
| 82 // information to patch two different interfaces, or we've lost a | |
| 83 // race with another thread who's patching the same interface. | |
| 84 DLOG(WARNING) << "Attempting to patch two different VTables with the " | |
| 85 "same VTable information, or patching the same interface on " | |
| 86 "multiple threads"; | |
| 87 continue; | |
| 88 } | |
| 89 | |
| 90 PROC original_fn = vtable[it->index_]; | |
| 91 FunctionStub* stub = NULL; | |
| 92 | |
| 93 #ifndef NDEBUG | |
| 94 stub = FunctionStub::FromCode(original_fn); | |
| 95 if (stub != NULL) { | |
| 96 DLOG(ERROR) << "attempt to patch a function that's already patched"; | |
| 97 DCHECK(stub->destination_function() == | |
| 98 reinterpret_cast<uintptr_t>(it->method_)) << | |
| 99 "patching the same method multiple times with different hooks?"; | |
| 100 continue; | |
| 101 } | |
| 102 #endif | |
| 103 | |
| 104 stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn), | |
| 105 it->method_); | |
| 106 if (!stub) { | |
| 107 NOTREACHED(); | |
| 108 return E_OUTOFMEMORY; | |
| 109 } | |
| 110 | |
| 111 // Do the VM operations and the patching in a loop, to try and ensure | |
| 112 // we succeed even if there's a VM operation or a patch race against | |
| 113 // other 3rd parties patching. | |
| 114 bool succeeded = false; | |
| 115 for (int i = 0; !succeeded && i < kMaxRetries; ++i) { | |
| 116 DWORD protect = 0; | |
| 117 if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), | |
| 118 PAGE_EXECUTE_READWRITE, &protect)) { | |
| 119 HRESULT hr = AtlHresultFromLastError(); | |
| 120 DLOG(ERROR) << "VirtualProtect failed 0x" << std::hex << hr; | |
| 121 | |
| 122 // Go around again in the feeble hope that this is | |
| 123 // a temporary problem. | |
| 124 continue; | |
| 125 } | |
| 126 original_fn = vtable[it->index_]; | |
| 127 stub->set_argument(reinterpret_cast<uintptr_t>(original_fn)); | |
| 128 succeeded = internal::ReplaceFunctionPointer( | |
| 129 reinterpret_cast<void**>(&vtable[it->index_]), stub->code(), | |
| 130 original_fn); | |
| 131 | |
| 132 if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect, | |
| 133 &protect)) { | |
| 134 DLOG(ERROR) << "VirtualProtect failed to restore protection"; | |
| 135 } | |
| 136 } | |
| 137 | |
| 138 if (!succeeded) { | |
| 139 FunctionStub::Destroy(stub); | |
| 140 stub = NULL; | |
| 141 | |
| 142 DLOG(ERROR) << "Failed to patch VTable."; | |
| 143 return E_FAIL; | |
| 144 } else { | |
| 145 // Success, save the stub we created. | |
| 146 it->stub_ = stub; | |
| 147 chrome_frame::PinModule(); | |
| 148 } | |
| 149 } | |
| 150 | |
| 151 return S_OK; | |
| 152 } | |
| 153 | |
| 154 HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) { | |
| 155 base::AutoLock lock(patch_lock_); | |
| 156 | |
| 157 for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { | |
| 158 if (it->stub_) { | |
| 159 DCHECK(it->stub_->destination_function() == | |
| 160 reinterpret_cast<uintptr_t>(it->method_)); | |
| 161 // Modify the stub to just jump directly to the original function. | |
| 162 it->stub_->BypassStub(reinterpret_cast<void*>(it->stub_->argument())); | |
| 163 it->stub_ = NULL; | |
| 164 // Leave the stub in memory so that we won't break any possible chains. | |
| 165 | |
| 166 // TODO(siggi): why not restore the original VTBL pointer here, provided | |
| 167 // we haven't been chained? | |
| 168 } else { | |
| 169 DLOG(WARNING) << "attempt to unpatch a function that wasn't patched"; | |
| 170 } | |
| 171 } | |
| 172 | |
| 173 return S_OK; | |
| 174 } | |
| 175 | |
| 176 // Disabled for now as we're not using it atm. | |
| 177 #if 0 | |
| 178 | |
| 179 DynamicPatchManager::DynamicPatchManager(const MethodPatchInfo* patch_prototype) | |
| 180 : patch_prototype_(patch_prototype) { | |
| 181 DCHECK(patch_prototype_); | |
| 182 DCHECK(patch_prototype_->stub_ == NULL); | |
| 183 } | |
| 184 | |
| 185 DynamicPatchManager::~DynamicPatchManager() { | |
| 186 UnpatchAll(); | |
| 187 } | |
| 188 | |
| 189 HRESULT DynamicPatchManager::PatchObject(void* unknown) { | |
| 190 int patched_methods = 0; | |
| 191 for (; patch_prototype_[patched_methods].index_ != -1; patched_methods++) { | |
| 192 // If you hit this, then you are likely using the prototype instance for | |
| 193 // patching in _addition_ to this class. This is not a good idea :) | |
| 194 DCHECK(patch_prototype_[patched_methods].stub_ == NULL); | |
| 195 } | |
| 196 | |
| 197 // Prepare a new patch object using the patch info from the prototype. | |
| 198 int mem_size = sizeof(PatchedObject) + | |
| 199 sizeof(MethodPatchInfo) * patched_methods; | |
| 200 PatchedObject* entry = reinterpret_cast<PatchedObject*>(new char[mem_size]); | |
| 201 entry->vtable_ = GetIFVTable(unknown); | |
| 202 memcpy(entry->patch_info_, patch_prototype_, | |
| 203 sizeof(MethodPatchInfo) * (patched_methods + 1)); | |
| 204 | |
| 205 patch_list_lock_.Acquire(); | |
| 206 | |
| 207 // See if we've already patched this vtable before. | |
| 208 // The search is done via the == operator of the PatchedObject class. | |
| 209 PatchList::const_iterator it = std::find(patch_list_.begin(), | |
| 210 patch_list_.end(), entry); | |
| 211 HRESULT hr; | |
| 212 if (it == patch_list_.end()) { | |
| 213 hr = PatchInterfaceMethods(unknown, entry->patch_info_); | |
| 214 if (SUCCEEDED(hr)) { | |
| 215 patch_list_.push_back(entry); | |
| 216 entry = NULL; // Ownership transferred to the array. | |
| 217 } | |
| 218 } else { | |
| 219 hr = S_FALSE; | |
| 220 } | |
| 221 | |
| 222 patch_list_lock_.Release(); | |
| 223 | |
| 224 delete entry; | |
| 225 | |
| 226 return hr; | |
| 227 } | |
| 228 | |
| 229 bool DynamicPatchManager::UnpatchAll() { | |
| 230 patch_list_lock_.Acquire(); | |
| 231 PatchList::iterator it; | |
| 232 for (it = patch_list_.begin(); it != patch_list_.end(); it++) { | |
| 233 UnpatchInterfaceMethods((*it)->patch_info_); | |
| 234 delete (*it); | |
| 235 } | |
| 236 patch_list_.clear(); | |
| 237 patch_list_lock_.Release(); | |
| 238 | |
| 239 return true; | |
| 240 } | |
| 241 | |
| 242 #endif // disabled DynamicPatchManager | |
| 243 | |
| 244 } // namespace vtable_patch | |
| OLD | NEW |