// 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 #include #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/utils.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(entry), reinterpret_cast(curr_proc), reinterpret_cast(new_proc)); return curr_proc == reinterpret_cast(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(*reinterpret_cast(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(it->method_)) << "patching the same method multiple times with different hooks?"; continue; } #endif stub = FunctionStub::Create(reinterpret_cast(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(original_fn)); succeeded = internal::ReplaceFunctionPointer( reinterpret_cast(&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; 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(it->method_)); // Modify the stub to just jump directly to the original function. it->stub_->BypassStub(reinterpret_cast(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(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