#include "chrome_frame/vtable_patch_manager.h"
#include <unknwn.h>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/message_loop.h"
#include "base/threading/thread.h"
#include "base/win/scoped_handle.h"
#include "gtest/gtest.h"
#include "gmock/gmock.h"
namespace {
using testing::_;
using testing::Return;
class MockClassFactory : public IClassFactory {
public:
MOCK_METHOD2_WITH_CALLTYPE(__stdcall, QueryInterface,
HRESULT(REFIID riid, void **object));
MOCK_METHOD0_WITH_CALLTYPE(__stdcall, AddRef, ULONG());
MOCK_METHOD0_WITH_CALLTYPE(__stdcall, Release, ULONG());
MOCK_METHOD3_WITH_CALLTYPE(__stdcall, CreateInstance,
HRESULT (IUnknown *outer, REFIID riid, void **object));
MOCK_METHOD1_WITH_CALLTYPE(__stdcall, LockServer, HRESULT(BOOL lock));
};
void* GetVtable(IUnknown* unk) {
return *reinterpret_cast<void**>(unk);
}
extern vtable_patch::MethodPatchInfo IClassFactory_PatchInfo[];
class VtablePatchManagerTest: public testing::Test {
public:
VtablePatchManagerTest() {
EXPECT_TRUE(current_ == NULL);
current_ = this;
}
~VtablePatchManagerTest() {
EXPECT_TRUE(current_ == this);
current_ = NULL;
}
virtual void SetUp() {
void* vtable = GetVtable(&factory_);
MEMORY_BASIC_INFORMATION info;
ASSERT_TRUE(::VirtualQuery(vtable, &info, sizeof(info)));
vtable_protection_ = info.Protect;
memcpy(vtable_backup_, vtable, sizeof(vtable_backup_));
}
virtual void TearDown() {
UnpatchInterfaceMethods(IClassFactory_PatchInfo);
void* vtable = GetVtable(&factory_);
DWORD old_protect = 0;
EXPECT_TRUE(::VirtualProtect(vtable, sizeof(vtable_backup_),
PAGE_EXECUTE_WRITECOPY, &old_protect));
memcpy(vtable, vtable_backup_, sizeof(vtable_backup_));
EXPECT_TRUE(::VirtualProtect(vtable, sizeof(vtable_backup_),
vtable_protection_, &old_protect));
}
typedef HRESULT (__stdcall* LockServerFun)(IClassFactory* self, BOOL lock);
MOCK_METHOD3(LockServerPatch,
HRESULT(LockServerFun old_fun, IClassFactory* self, BOOL lock));
static HRESULT STDMETHODCALLTYPE LockServerPatchCallback(
LockServerFun fun, IClassFactory* self, BOOL lock) {
EXPECT_TRUE(current_ != NULL);
if (current_ != NULL)
return current_->LockServerPatch(fun, self, lock);
else
return E_UNEXPECTED;
}
protected:
static const size_t kFunctionCount = 5;
PROC vtable_backup_[kFunctionCount];
DWORD vtable_protection_;
MockClassFactory factory_;
static VtablePatchManagerTest* current_;
};
VtablePatchManagerTest* VtablePatchManagerTest::current_ = NULL;
BEGIN_VTABLE_PATCHES(IClassFactory)
VTABLE_PATCH_ENTRY(4, &VtablePatchManagerTest::LockServerPatchCallback)
END_VTABLE_PATCHES();
}
TEST_F(VtablePatchManagerTest, ReplacePointer) {
void* const kFunctionOriginal = reinterpret_cast<void*>(0xCAFEBABE);
void* const kFunctionFoo = reinterpret_cast<void*>(0xF0F0F0F0);
void* const kFunctionBar = reinterpret_cast<void*>(0xBABABABA);
using vtable_patch::internal::ReplaceFunctionPointer;
EXPECT_FALSE(ReplaceFunctionPointer(NULL, kFunctionBar, kFunctionFoo));
void* foo_entry = kFunctionOriginal;
EXPECT_FALSE(ReplaceFunctionPointer(&foo_entry, kFunctionBar, kFunctionFoo));
EXPECT_EQ(foo_entry, kFunctionOriginal);
EXPECT_TRUE(ReplaceFunctionPointer(&foo_entry,
kFunctionBar,
kFunctionOriginal));
EXPECT_EQ(foo_entry, kFunctionBar);
}
TEST_F(VtablePatchManagerTest, PatchInterfaceMethods) {
EXPECT_CALL(factory_, LockServer(TRUE))
.WillOnce(Return(E_FAIL));
EXPECT_EQ(E_FAIL, factory_.LockServer(TRUE));
EXPECT_HRESULT_SUCCEEDED(
PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
EXPECT_NE(0, memcmp(GetVtable(&factory_),
vtable_backup_,
sizeof(vtable_backup_)));
EXPECT_CALL(factory_, LockServer(_))
.Times(0);
EXPECT_CALL(*this, LockServerPatch(testing::_, &factory_, TRUE))
.WillOnce(testing::Return(S_FALSE));
EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
}
TEST_F(VtablePatchManagerTest, UnpatchInterfaceMethods) {
EXPECT_HRESULT_SUCCEEDED(
PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
EXPECT_NE(0, memcmp(GetVtable(&factory_),
vtable_backup_,
sizeof(vtable_backup_)));
EXPECT_CALL(factory_, LockServer(testing::_))
.Times(0);
EXPECT_CALL(*this, LockServerPatch(testing::_, &factory_, TRUE))
.WillOnce(testing::Return(S_FALSE));
EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
EXPECT_HRESULT_SUCCEEDED(
UnpatchInterfaceMethods(IClassFactory_PatchInfo));
EXPECT_CALL(factory_, LockServer(FALSE))
.WillOnce(testing::Return(E_FAIL));
EXPECT_EQ(E_FAIL, factory_.LockServer(FALSE));
}
TEST_F(VtablePatchManagerTest, DoublePatch) {
EXPECT_HRESULT_SUCCEEDED(
PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
PROC vtable[kFunctionCount];
memcpy(vtable, GetVtable(&factory_), sizeof(vtable));
EXPECT_HRESULT_SUCCEEDED(
PatchInterfaceMethods(&factory_, IClassFactory_PatchInfo));
EXPECT_EQ(0, memcmp(vtable, GetVtable(&factory_), sizeof(vtable)));
}
namespace vtable_patch {
extern base::Lock patch_lock_;
}
TEST_F(VtablePatchManagerTest, ThreadSafePatching) {
base::Thread background("Background Test Thread");
EXPECT_TRUE(background.Start());
base::win::ScopedHandle event(::CreateEvent(NULL, TRUE, FALSE, NULL));
vtable_patch::patch_lock_.Acquire();
background.message_loop()->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&vtable_patch::PatchInterfaceMethods),
&factory_, &IClassFactory_PatchInfo[0]));
background.message_loop()->PostTask(
FROM_HERE, base::Bind(base::IgnoreResult(::SetEvent), event.Get()));
EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(event.Get(), 50));
EXPECT_CALL(factory_, LockServer(TRUE))
.WillOnce(Return(S_FALSE));
EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
vtable_patch::patch_lock_.Release();
EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE));
EXPECT_CALL(factory_, LockServer(TRUE))
.Times(0);
EXPECT_CALL(*this, LockServerPatch(_, &factory_, TRUE))
.WillOnce(Return(S_FALSE));
EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
::ResetEvent(event.Get());
vtable_patch::patch_lock_.Acquire();
background.message_loop()->PostTask(
FROM_HERE,
base::Bind(base::IgnoreResult(&vtable_patch::UnpatchInterfaceMethods),
&IClassFactory_PatchInfo[0]));
background.message_loop()->PostTask(
FROM_HERE, base::Bind(base::IgnoreResult(::SetEvent), event.Get()));
EXPECT_EQ(WAIT_TIMEOUT, ::WaitForSingleObject(event.Get(), 50));
EXPECT_CALL(factory_, LockServer(TRUE))
.Times(0);
EXPECT_CALL(*this, LockServerPatch(_, &factory_, TRUE))
.WillOnce(Return(S_FALSE));
EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
vtable_patch::patch_lock_.Release();
EXPECT_EQ(WAIT_OBJECT_0, ::WaitForSingleObject(event.Get(), INFINITE));
EXPECT_CALL(factory_, LockServer(TRUE))
.WillOnce(Return(S_FALSE));
EXPECT_EQ(S_FALSE, factory_.LockServer(TRUE));
}