| // Copyright (c) 2011 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. |
| |
| #include "chrome_frame/vtable_patch_manager.h" |
| |
| #include <atlcomcli.h> |
| |
| #include <algorithm> |
| |
| #include "base/atomicops.h" |
| #include "base/logging.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/synchronization/lock.h" |
| #include "chrome_frame/function_stub.h" |
| #include "chrome_frame/pin_module.h" |
| |
| namespace vtable_patch { |
| |
| // The number of times we retry a patch/unpatch operation in case of |
| // VM races with other 3rd party software trying to patch the same thing. |
| const int kMaxRetries = 3; |
| |
| // We hold a lock over all patching operations to make sure that we don't |
| // e.g. race on VM operations to the same patches, or to physical pages |
| // shared across different VTABLEs. |
| base::Lock patch_lock_; |
| |
| namespace internal { |
| // Because other parties in our process might be attempting to patch the same |
| // virtual tables at the same time, we have a race to modify the VM protections |
| // on the pages. We also need to do a compare/swap type operation when we |
| // modify the function, so as to be sure that we grab the most recent value. |
| // Hence the SEH blocks and the nasty-looking compare/swap operation. |
| bool ReplaceFunctionPointer(void** entry, void* new_proc, void* curr_proc) { |
| __try { |
| base::subtle::Atomic32 prev_value; |
| |
| prev_value = base::subtle::NoBarrier_CompareAndSwap( |
| reinterpret_cast<base::subtle::Atomic32 volatile*>(entry), |
| reinterpret_cast<base::subtle::Atomic32>(curr_proc), |
| reinterpret_cast<base::subtle::Atomic32>(new_proc)); |
| |
| return curr_proc == reinterpret_cast<void*>(prev_value); |
| } __except(EXCEPTION_EXECUTE_HANDLER) { |
| // Oops, we took exception on access. |
| } |
| |
| return false; |
| } |
| |
| } // namespace |
| |
| // Convenient definition of a VTABLE |
| typedef PROC* Vtable; |
| |
| // Returns a pointer to the VTable of a COM interface. |
| // @param unknown [in] The pointer of the COM interface. |
| inline Vtable GetIFVTable(void* unknown) { |
| return reinterpret_cast<Vtable>(*reinterpret_cast<void**>(unknown)); |
| } |
| |
| HRESULT PatchInterfaceMethods(void* unknown, MethodPatchInfo* patches) { |
| // Do some sanity checking of the input arguments. |
| if (NULL == unknown || NULL == patches) { |
| NOTREACHED(); |
| return E_INVALIDARG; |
| } |
| |
| Vtable vtable = GetIFVTable(unknown); |
| DCHECK(vtable); |
| |
| // All VM operations, patching and manipulation of MethodPatchInfo |
| // is done under a global lock, to ensure multiple threads don't |
| // race, whether on an individual patch, or on VM operations to |
| // the same physical pages. |
| base::AutoLock lock(patch_lock_); |
| |
| for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { |
| if (it->stub_ != NULL) { |
| // If this DCHECK fires it means that we are using the same VTable |
| // information to patch two different interfaces, or we've lost a |
| // race with another thread who's patching the same interface. |
| DLOG(WARNING) << "Attempting to patch two different VTables with the " |
| "same VTable information, or patching the same interface on " |
| "multiple threads"; |
| continue; |
| } |
| |
| PROC original_fn = vtable[it->index_]; |
| FunctionStub* stub = NULL; |
| |
| #ifndef NDEBUG |
| stub = FunctionStub::FromCode(original_fn); |
| if (stub != NULL) { |
| DLOG(ERROR) << "attempt to patch a function that's already patched"; |
| DCHECK(stub->destination_function() == |
| reinterpret_cast<uintptr_t>(it->method_)) << |
| "patching the same method multiple times with different hooks?"; |
| continue; |
| } |
| #endif |
| |
| stub = FunctionStub::Create(reinterpret_cast<uintptr_t>(original_fn), |
| it->method_); |
| if (!stub) { |
| NOTREACHED(); |
| return E_OUTOFMEMORY; |
| } |
| |
| // Do the VM operations and the patching in a loop, to try and ensure |
| // we succeed even if there's a VM operation or a patch race against |
| // other 3rd parties patching. |
| bool succeeded = false; |
| for (int i = 0; !succeeded && i < kMaxRetries; ++i) { |
| DWORD protect = 0; |
| if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), |
| PAGE_EXECUTE_READWRITE, &protect)) { |
| HRESULT hr = AtlHresultFromLastError(); |
| DLOG(ERROR) << "VirtualProtect failed 0x" << std::hex << hr; |
| |
| // Go around again in the feeble hope that this is |
| // a temporary problem. |
| continue; |
| } |
| original_fn = vtable[it->index_]; |
| stub->set_argument(reinterpret_cast<uintptr_t>(original_fn)); |
| succeeded = internal::ReplaceFunctionPointer( |
| reinterpret_cast<void**>(&vtable[it->index_]), stub->code(), |
| original_fn); |
| |
| if (!::VirtualProtect(&vtable[it->index_], sizeof(PROC), protect, |
| &protect)) { |
| DLOG(ERROR) << "VirtualProtect failed to restore protection"; |
| } |
| } |
| |
| if (!succeeded) { |
| FunctionStub::Destroy(stub); |
| stub = NULL; |
| |
| DLOG(ERROR) << "Failed to patch VTable."; |
| return E_FAIL; |
| } else { |
| // Success, save the stub we created. |
| it->stub_ = stub; |
| chrome_frame::PinModule(); |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| HRESULT UnpatchInterfaceMethods(MethodPatchInfo* patches) { |
| base::AutoLock lock(patch_lock_); |
| |
| for (MethodPatchInfo* it = patches; it->index_ != -1; ++it) { |
| if (it->stub_) { |
| DCHECK(it->stub_->destination_function() == |
| reinterpret_cast<uintptr_t>(it->method_)); |
| // Modify the stub to just jump directly to the original function. |
| it->stub_->BypassStub(reinterpret_cast<void*>(it->stub_->argument())); |
| it->stub_ = NULL; |
| // Leave the stub in memory so that we won't break any possible chains. |
| |
| // TODO(siggi): why not restore the original VTBL pointer here, provided |
| // we haven't been chained? |
| } else { |
| DLOG(WARNING) << "attempt to unpatch a function that wasn't patched"; |
| } |
| } |
| |
| return S_OK; |
| } |
| |
| // Disabled for now as we're not using it atm. |
| #if 0 |
| |
| DynamicPatchManager::DynamicPatchManager(const MethodPatchInfo* patch_prototype) |
| : patch_prototype_(patch_prototype) { |
| DCHECK(patch_prototype_); |
| DCHECK(patch_prototype_->stub_ == NULL); |
| } |
| |
| DynamicPatchManager::~DynamicPatchManager() { |
| UnpatchAll(); |
| } |
| |
| HRESULT DynamicPatchManager::PatchObject(void* unknown) { |
| int patched_methods = 0; |
| for (; patch_prototype_[patched_methods].index_ != -1; patched_methods++) { |
| // If you hit this, then you are likely using the prototype instance for |
| // patching in _addition_ to this class. This is not a good idea :) |
| DCHECK(patch_prototype_[patched_methods].stub_ == NULL); |
| } |
| |
| // Prepare a new patch object using the patch info from the prototype. |
| int mem_size = sizeof(PatchedObject) + |
| sizeof(MethodPatchInfo) * patched_methods; |
| PatchedObject* entry = reinterpret_cast<PatchedObject*>(new char[mem_size]); |
| entry->vtable_ = GetIFVTable(unknown); |
| memcpy(entry->patch_info_, patch_prototype_, |
| sizeof(MethodPatchInfo) * (patched_methods + 1)); |
| |
| patch_list_lock_.Acquire(); |
| |
| // See if we've already patched this vtable before. |
| // The search is done via the == operator of the PatchedObject class. |
| PatchList::const_iterator it = std::find(patch_list_.begin(), |
| patch_list_.end(), entry); |
| HRESULT hr; |
| if (it == patch_list_.end()) { |
| hr = PatchInterfaceMethods(unknown, entry->patch_info_); |
| if (SUCCEEDED(hr)) { |
| patch_list_.push_back(entry); |
| entry = NULL; // Ownership transferred to the array. |
| } |
| } else { |
| hr = S_FALSE; |
| } |
| |
| patch_list_lock_.Release(); |
| |
| delete entry; |
| |
| return hr; |
| } |
| |
| bool DynamicPatchManager::UnpatchAll() { |
| patch_list_lock_.Acquire(); |
| PatchList::iterator it; |
| for (it = patch_list_.begin(); it != patch_list_.end(); it++) { |
| UnpatchInterfaceMethods((*it)->patch_info_); |
| delete (*it); |
| } |
| patch_list_.clear(); |
| patch_list_lock_.Release(); |
| |
| return true; |
| } |
| |
| #endif // disabled DynamicPatchManager |
| |
| } // namespace vtable_patch |