blob: 30a8fe25f7542df38dc46c0e11336040567a3c7a [file] [log] [blame]
// 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/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<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;
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