Bug 1432653: Refactor the DLL interceptor and parameterize its memory operations; r=handyman

MozReview-Commit-ID: EYxVsQ1kicy

--HG--
rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/interceptor/PatcherBase.h
rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/interceptor/PatcherDetour.h
rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/interceptor/PatcherNopSpace.h
rename : xpcom/build/nsWindowsDllInterceptor.h => mozglue/misc/nsWindowsDllInterceptor.h
rename : toolkit/xre/test/win/TestDllInterceptor.cpp => mozglue/tests/interceptor/TestDllInterceptor.cpp
extra : amend_source : 84a7590b40a649f7321eb05feca4f9256ecc5d22
This commit is contained in:
Aaron Klotz 2018-04-09 13:37:52 -06:00
Родитель 5936ccc549
Коммит 5317435ec0
17 изменённых файлов: 2876 добавлений и 1508 удалений

Просмотреть файл

@ -58,7 +58,7 @@ FunctionHook::HookFunctions(int aQuirks)
// This cache is created when a DLL is registered with a FunctionHook.
// It is cleared on a call to ClearDllInterceptorCache(). It
// must be freed before exit to avoid leaks.
typedef nsClassHashtable<nsCStringHashKey, WindowsDllInterceptor> DllInterceptors;
typedef nsClassHashtable<nsStringHashKey, WindowsDllInterceptor> DllInterceptors;
DllInterceptors* sDllInterceptorCache = nullptr;
WindowsDllInterceptor*
@ -68,9 +68,13 @@ FunctionHook::GetDllInterceptorFor(const char* aModuleName)
sDllInterceptorCache = new DllInterceptors();
}
MOZ_ASSERT(NS_IsAscii(aModuleName), "Non-ASCII module names are not supported");
NS_ConvertASCIItoUTF16 moduleName(aModuleName);
WindowsDllInterceptor* ret =
sDllInterceptorCache->LookupOrAdd(nsCString(aModuleName), aModuleName);
sDllInterceptorCache->LookupOrAdd(moduleName);
MOZ_ASSERT(ret);
ret->Init(moduleName.get());
return ret;
}

Просмотреть файл

@ -0,0 +1,224 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_interceptor_MMPolicies_h
#define mozilla_interceptor_MMPolicies_h
#include "mozilla/Assertions.h"
#include "mozilla/Types.h"
#include <windows.h>
namespace mozilla {
namespace interceptor {
class MMPolicyBase
{
public:
static DWORD ComputeAllocationSize(const uint32_t aRequestedSize)
{
MOZ_ASSERT(aRequestedSize);
DWORD result = aRequestedSize;
const uint32_t granularity = GetAllocGranularity();
uint32_t mod = aRequestedSize % granularity;
if (mod) {
result += (granularity - mod);
}
return result;
}
static DWORD GetAllocGranularity()
{
static const DWORD kAllocGranularity = []() -> DWORD {
SYSTEM_INFO sysInfo;
::GetSystemInfo(&sysInfo);
return sysInfo.dwAllocationGranularity;
}();
return kAllocGranularity;
}
static DWORD GetPageSize()
{
static const DWORD kPageSize = []() -> DWORD {
SYSTEM_INFO sysInfo;
::GetSystemInfo(&sysInfo);
return sysInfo.dwPageSize;
}();
return kPageSize;
}
};
class MMPolicyInProcess : public MMPolicyBase
{
public:
typedef MMPolicyInProcess MMPolicyT;
explicit MMPolicyInProcess()
: mBase(nullptr)
, mReservationSize(0)
, mCommitOffset(0)
{
}
MMPolicyInProcess(const MMPolicyInProcess&) = delete;
MMPolicyInProcess& operator=(const MMPolicyInProcess&) = delete;
MMPolicyInProcess(MMPolicyInProcess&& aOther)
: mBase(nullptr)
, mReservationSize(0)
, mCommitOffset(0)
{
*this = Move(aOther);
}
MMPolicyInProcess& operator=(MMPolicyInProcess&& aOther)
{
mBase = aOther.mBase;
aOther.mBase = nullptr;
mCommitOffset = aOther.mCommitOffset;
aOther.mCommitOffset = 0;
mReservationSize = aOther.mReservationSize;
aOther.mReservationSize = 0;
return *this;
}
// We always leak mBase
~MMPolicyInProcess() = default;
explicit operator bool() const
{
return !!mBase;
}
/**
* Should we unhook everything upon destruction?
*/
bool ShouldUnhookUponDestruction() const
{
return true;
}
bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const
{
::memcpy(aToPtr, aFromPtr, aLen);
return true;
}
bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const
{
::memcpy(aToPtr, aFromPtr, aLen);
return true;
}
bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
uint32_t* aPrevProtFlags) const
{
MOZ_ASSERT(aPrevProtFlags);
BOOL ok = ::VirtualProtect(aVAddress, aSize, aProtFlags,
reinterpret_cast<PDWORD>(aPrevProtFlags));
MOZ_ASSERT(ok);
return !!ok;
}
/**
* @return true if the page that hosts aVAddress is accessible.
*/
bool IsPageAccessible(void* aVAddress) const
{
MEMORY_BASIC_INFORMATION mbi;
SIZE_T result = ::VirtualQuery(aVAddress, &mbi, sizeof(mbi));
return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
mbi.Protect != PAGE_NOACCESS;
}
bool FlushInstructionCache() const
{
return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
}
protected:
uint8_t* GetLocalView() const
{
return mBase;
}
uintptr_t GetRemoteView() const
{
// Same as local view for in-process
return reinterpret_cast<uintptr_t>(mBase);
}
/**
* @return the effective number of bytes reserved, or 0 on failure
*/
uint32_t Reserve(const uint32_t aSize)
{
if (!aSize) {
return 0;
}
if (mBase) {
MOZ_ASSERT(mReservationSize >= aSize);
return mReservationSize;
}
mReservationSize = ComputeAllocationSize(aSize);
mBase = static_cast<uint8_t*>(::VirtualAlloc(nullptr, mReservationSize,
MEM_RESERVE, PAGE_NOACCESS));
if (!mBase) {
return 0;
}
return mReservationSize;
}
bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
const uint32_t aRequestedLength)
{
if (!(*this)) {
return false;
}
uint32_t limit = aRequestedOffset + aRequestedLength - 1;
if (limit < mCommitOffset) {
// No commit required
return true;
}
MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
if (mCommitOffset >= mReservationSize) {
return false;
}
PVOID local = ::VirtualAlloc(mBase + mCommitOffset, GetPageSize(),
MEM_COMMIT, PAGE_EXECUTE_READ);
if (!local) {
return false;
}
mCommitOffset += GetPageSize();
return true;
}
private:
uint8_t* mBase;
uint32_t mReservationSize;
uint32_t mCommitOffset;
};
} // namespace interceptor
} // namespace mozilla
#endif // mozilla_interceptor_MMPolicies_h

Просмотреть файл

@ -0,0 +1,84 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_interceptor_PatcherBase_h
#define mozilla_interceptor_PatcherBase_h
#include "mozilla/interceptor/TargetFunction.h"
namespace mozilla {
namespace interceptor {
template <typename VMPolicy>
class WindowsDllPatcherBase
{
protected:
typedef typename VMPolicy::MMPolicyT MMPolicyT;
template <typename... Args>
explicit WindowsDllPatcherBase(Args... aArgs)
: mVMPolicy(mozilla::Forward<Args>(aArgs)...)
{
}
ReadOnlyTargetFunction<MMPolicyT>
ResolveRedirectedAddress(const void* aOriginalFunction)
{
ReadOnlyTargetFunction<MMPolicyT> origFn(mVMPolicy, aOriginalFunction);
// If function entry is jmp rel8 stub to the internal implementation, we
// resolve redirected address from the jump target.
if (origFn[0] == 0xeb) {
int8_t offset = (int8_t)(origFn[1]);
if (offset <= 0) {
// Bail out for negative offset: probably already patched by some
// third-party code.
return Move(origFn);
}
for (int8_t i = 0; i < offset; i++) {
if (origFn[2 + i] != 0x90) {
// Bail out on insufficient nop space.
return Move(origFn);
}
}
origFn += 2 + offset;
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, origFn.GetAddress());
}
#if defined(_M_IX86)
// If function entry is jmp [disp32] such as used by kernel32,
// we resolve redirected address from import table.
if (origFn[0] == 0xff && origFn[1] == 0x25) {
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy,
reinterpret_cast<const void*>((origFn + 2).template ChasePointer<uintptr_t*>()));
}
#elif defined(_M_X64)
// If function entry is jmp [disp32] such as used by kernel32,
// we resolve redirected address from import table.
if (origFn[0] == 0x48 && origFn[1] == 0xff && origFn[2] == 0x25) {
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy,
reinterpret_cast<const void*>((origFn + 3).ChasePointerFromDisp()));
}
if (origFn[0] == 0xe9) {
// require for TestDllInterceptor with --disable-optimize
uintptr_t abstarget = (origFn + 1).ReadDisp32AsAbsolute();
return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, abstarget);
}
#endif
return Move(origFn);
}
protected:
VMPolicy mVMPolicy;
};
} // namespace interceptor
} // namespace mozilla
#endif // mozilla_interceptor_PatcherBase_h

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -0,0 +1,217 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_interceptor_PatcherNopSpace_h
#define mozilla_interceptor_PatcherNopSpace_h
#if defined(_M_IX86)
#include "mozilla/interceptor/PatcherBase.h"
namespace mozilla {
namespace interceptor {
template <typename VMPolicy>
class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy>
{
// For remembering the addresses of functions we've patched.
mozilla::Vector<void*> mPatchedFns;
public:
template <typename... Args>
explicit WindowsDllNopSpacePatcher(Args... aArgs)
: WindowsDllPatcherBase<VMPolicy>(mozilla::Forward<Args>(aArgs)...)
{}
~WindowsDllNopSpacePatcher()
{
Clear();
}
WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) = delete;
WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
void Clear()
{
// Restore the mov edi, edi to the beginning of each function we patched.
for (auto&& ptr : mPatchedFns) {
WritableTargetFunction<MMPolicyT> fn(mVMPolicy,
reinterpret_cast<uintptr_t>(ptr),
sizeof(uint16_t));
if (!fn) {
continue;
}
// mov edi, edi
fn.WriteShort(0xff8b);
fn.Commit();
}
mPatchedFns.clear();
}
/**
* NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
* in our address space. There is a bug in Detours 2.x that causes it to
* patch at the wrong address when attempting to detour code that is already
* NOP space patched. This function is an effort to detect the presence of
* this NVIDIA code in our address space and disable NOP space patching if it
* is. We also check AppInit_DLLs since this is the mechanism that the Optimus
* drivers use to inject into our process.
*/
static bool IsCompatible()
{
// These DLLs are known to have bad interactions with this style of patching
const wchar_t* kIncompatibleDLLs[] = {
L"detoured.dll",
L"_etoured.dll",
L"nvd3d9wrap.dll",
L"nvdxgiwrap.dll"
};
// See if the infringing DLLs are already loaded
for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
if (GetModuleHandleW(kIncompatibleDLLs[i])) {
return false;
}
}
if (GetModuleHandleW(L"user32.dll")) {
// user32 is loaded but the infringing DLLs are not, assume we're safe to
// proceed.
return true;
}
// If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
// won't be loaded once user32 is initialized.
HKEY hkey = NULL;
if (!RegOpenKeyExW(HKEY_LOCAL_MACHINE,
L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows",
0, KEY_QUERY_VALUE, &hkey)) {
nsAutoRegKey key(hkey);
DWORD numBytes = 0;
const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
// Query for required buffer size
LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
nullptr, nullptr, &numBytes);
mozilla::UniquePtr<wchar_t[]> data;
if (!status) {
// Allocate the buffer and query for the actual data
data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t));
status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr,
nullptr, (LPBYTE)data.get(), &numBytes);
}
if (!status) {
// For each token, split up the filename components and then check the
// name of the file.
const wchar_t kDelimiters[] = L", ";
wchar_t* tokenContext = nullptr;
wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
while (token) {
wchar_t fname[_MAX_FNAME] = {0};
if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0,
fname, mozilla::ArrayLength(fname),
nullptr, 0)) {
// nvinit.dll is responsible for bootstrapping the DLL injection, so
// that is the library that we check for here
const wchar_t kNvInitName[] = L"nvinit";
if (!_wcsnicmp(fname, kNvInitName,
mozilla::ArrayLength(kNvInitName))) {
return false;
}
}
token = wcstok_s(nullptr, kDelimiters, &tokenContext);
}
}
}
return true;
}
bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc)
{
if (!IsCompatible()) {
#if defined(MOZILLA_INTERNAL_API)
NS_WARNING("NOP space patching is unavailable for compatibility reasons");
#endif
return false;
}
MOZ_ASSERT(aTargetFn);
if (!aTargetFn) {
return false;
}
ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
ResolveRedirectedAddress(aTargetFn));
if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
return false;
}
mPatchedFns.append(reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
return true;
}
bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
intptr_t aHookDest, void** aOrigFunc)
{
// Ensure we can read and write starting at fn - 5 (for the long jmp we're
// going to write) and ending at fn + 2 (for the short jmp up to the long
// jmp). These bytes may span two pages with different protection.
WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5));
if (!writableFn) {
return false;
}
// Check that the 5 bytes before the function are NOP's or INT 3's,
const uint8_t nopOrBp[] = { 0x90, 0xCC };
if (!writableFn.VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) {
return false;
}
// ... and that the first 2 bytes of the function are mov(edi, edi).
// There are two ways to encode the same thing:
//
// 0x89 0xff == mov r/m, r
// 0x8b 0xff == mov r, r/m
//
// where "r" is register and "r/m" is register or memory.
// Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
// (These look backwards because little-endian)
const uint16_t possibleEncodings[] = { 0xFF8B, 0xFF89 };
if (!writableFn.VerifyValuesAreOneOf<uint16_t, 1>(possibleEncodings, 5)) {
return false;
}
// Write a long jump into the space above the function.
writableFn.WriteByte(0xe9); // jmp
if (!writableFn) {
return false;
}
writableFn.WriteDisp32(aHookDest); // target
if (!writableFn) {
return false;
}
// Set aOrigFunc here, because after this point, aHookDest might be called,
// and aHookDest might use the aOrigFunc pointer.
*aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() +
sizeof(uint16_t));
// Short jump up into our long jump.
writableFn.WriteShort(0xF9EB); // jmp $-5
return writableFn.Commit();
}
};
} // namespace interceptor
} // namespace mozilla
#endif // defined(_M_IX86)
#endif // mozilla_interceptor_PatcherNopSpace_h

Просмотреть файл

@ -0,0 +1,552 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_interceptor_TargetFunction_h
#define mozilla_interceptor_TargetFunction_h
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Types.h"
#include <memory>
namespace mozilla {
namespace interceptor {
template <typename MMPolicy>
class MOZ_STACK_CLASS WritableTargetFunction final
{
public:
/**
* Used to initialize an invalid WritableTargetFunction, thus signalling an error.
*/
explicit WritableTargetFunction(const MMPolicy& aMMPolicy)
: mMMPolicy(aMMPolicy)
, mFunc(0)
, mNumBytes(0)
, mOffset(0)
, mStartWriteOffset(0)
, mPrevProt(0)
, mAccumulatedStatus(false)
{
}
WritableTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc,
size_t aNumBytes)
: mMMPolicy(aMMPolicy)
, mFunc(aFunc)
, mNumBytes(aNumBytes)
, mOffset(0)
, mStartWriteOffset(0)
, mPrevProt(0)
, mAccumulatedStatus(true)
{
aMMPolicy.Protect(reinterpret_cast<void*>(aFunc), aNumBytes,
PAGE_EXECUTE_READWRITE, &mPrevProt);
}
WritableTargetFunction(WritableTargetFunction&& aOther)
: mMMPolicy(aOther.mMMPolicy)
, mFunc(aOther.mFunc)
, mNumBytes(aOther.mNumBytes)
, mOffset(aOther.mOffset)
, mStartWriteOffset(aOther.mStartWriteOffset)
, mPrevProt(aOther.mPrevProt)
, mLocalBytes(Move(aOther.mLocalBytes))
, mAccumulatedStatus(aOther.mAccumulatedStatus)
{
aOther.mPrevProt = 0;
aOther.mAccumulatedStatus = false;
}
~WritableTargetFunction()
{
if (!mPrevProt) {
return;
}
MOZ_ASSERT(mLocalBytes.empty(), "Did you forget to call Commit?");
DebugOnly<bool> ok = mMMPolicy.Protect(reinterpret_cast<void*>(mFunc),
mNumBytes, mPrevProt, &mPrevProt);
MOZ_ASSERT(ok);
}
WritableTargetFunction(const WritableTargetFunction&) = delete;
WritableTargetFunction& operator=(const WritableTargetFunction&) = delete;
WritableTargetFunction& operator=(WritableTargetFunction&&) = delete;
/**
* @return true if data was successfully committed.
*/
bool Commit()
{
if (!(*this)) {
return false;
}
if (mLocalBytes.empty()) {
// Nothing to commit, treat like success
return true;
}
bool ok = mMMPolicy.Write(reinterpret_cast<void*>(mFunc + mStartWriteOffset),
mLocalBytes.begin(), mLocalBytes.length());
if (!ok) {
return false;
}
mMMPolicy.FlushInstructionCache();
mLocalBytes.clear();
return true;
}
explicit operator bool() const
{
return mPrevProt && mAccumulatedStatus;
}
void WriteByte(const uint8_t& aValue)
{
if (!mLocalBytes.append(aValue)) {
mAccumulatedStatus = false;
return;
}
mOffset += sizeof(uint8_t);
}
Maybe<uint8_t> ReadByte()
{
// Reading is only permitted prior to any writing
MOZ_ASSERT(mOffset == mStartWriteOffset);
if (mOffset > mStartWriteOffset) {
mAccumulatedStatus = false;
return Nothing();
}
uint8_t value;
if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
sizeof(uint8_t))) {
mAccumulatedStatus = false;
return Nothing();
}
mOffset += sizeof(uint8_t);
mStartWriteOffset += sizeof(uint8_t);
return Some(value);
}
void WriteShort(const uint16_t& aValue)
{
if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aValue),
sizeof(uint16_t))) {
mAccumulatedStatus = false;
return;
}
mOffset += sizeof(uint16_t);
}
void WriteDisp32(const uintptr_t aAbsTarget)
{
intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
static_cast<intptr_t>(mFunc + mOffset + sizeof(int32_t));
CheckedInt<int32_t> checkedDisp(diff);
MOZ_ASSERT(checkedDisp.isValid());
if (!checkedDisp.isValid()) {
mAccumulatedStatus = false;
return;
}
int32_t disp = checkedDisp.value();
if (!mLocalBytes.append(reinterpret_cast<uint8_t*>(&disp), sizeof(int32_t))) {
mAccumulatedStatus = false;
return;
}
mOffset += sizeof(int32_t);
}
void WritePointer(const uintptr_t aAbsTarget)
{
if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aAbsTarget),
sizeof(uintptr_t))) {
mAccumulatedStatus = false;
return;
}
mOffset += sizeof(uintptr_t);
}
/**
* @param aValues N-sized array of type T that specifies the set of values
* that are permissible in the first M bytes of the target
* function at aOffset.
* @return true if M values of type T in the function are members of the
* set specified by aValues.
*/
template <typename T, size_t M, size_t N>
bool VerifyValuesAreOneOf(const T (&aValues)[N], const uint8_t aOffset = 0)
{
T buf[M];
if (!mMMPolicy.Read(buf, reinterpret_cast<const void*>(mFunc + mOffset + aOffset),
M * sizeof(T))) {
return false;
}
for (auto&& fnValue : buf) {
bool match = false;
for (auto&& testValue : aValues) {
match |= (fnValue == testValue);
}
if (!match) {
return false;
}
}
return true;
}
uintptr_t GetCurrentAddress() const
{
return mFunc + mOffset;
}
private:
const MMPolicy& mMMPolicy;
const uintptr_t mFunc;
const size_t mNumBytes;
uint32_t mOffset;
uint32_t mStartWriteOffset;
uint32_t mPrevProt;
#if defined(_M_IX86)
static const size_t kInlineStorage = 16;
#elif defined(_M_X64)
static const size_t kInlineStorage = 32;
#endif
Vector<uint8_t, kInlineStorage> mLocalBytes;
bool mAccumulatedStatus;
};
template <typename MMPolicy>
class ReadOnlyTargetBytes;
template <>
class ReadOnlyTargetBytes<MMPolicyInProcess>
{
public:
ReadOnlyTargetBytes(const MMPolicyInProcess& aMMPolicy, const void* aBase)
: mMMPolicy(aMMPolicy)
, mBase(reinterpret_cast<const uint8_t*>(aBase))
{
}
ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
: mMMPolicy(aOther.mMMPolicy)
, mBase(aOther.mBase)
{
}
ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
const uint32_t aOffsetFromOther = 0)
: mMMPolicy(aOther.mMMPolicy)
, mBase(aOther.mBase + aOffsetFromOther)
{
}
void EnsureLimit(uint32_t aDesiredLimit)
{
// In the out-proc case we use this function to read the target function's
// bytes in the other process into a local buffer. We don't need that for
// the in-process case because we already have direct access to our target
// function's bytes.
}
bool IsValidAtOffset(const int8_t aOffset) const
{
if (!aOffset) {
return true;
}
uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
uintptr_t adjusted = base + aOffset;
uint32_t pageSize = mMMPolicy.GetPageSize();
// If |adjusted| is within the same page as |mBase|, we're still valid
if ((base / pageSize) == (adjusted / pageSize)) {
return true;
}
// Otherwise, let's query |adjusted|
return mMMPolicy.IsPageAccessible(reinterpret_cast<void*>(adjusted));
}
const uint8_t* Get() const
{
return mBase;
}
const MMPolicyInProcess& GetMMPolicy() const
{
return mMMPolicy;
}
ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
private:
const MMPolicyInProcess& mMMPolicy;
uint8_t const * const mBase;
};
template <typename MMPolicy>
class MOZ_STACK_CLASS ReadOnlyTargetFunction final
{
template <typename TargetMMPolicy>
class TargetBytesPtr
{
public:
typedef TargetBytesPtr<TargetMMPolicy> Type;
static Type Make(const TargetMMPolicy& aMMPolicy, const void* aFunc)
{
return Move(TargetBytesPtr(aMMPolicy, aFunc));
}
static Type CopyFromOffset(const TargetBytesPtr& aOther,
const uint32_t aOffsetFromOther)
{
return Move(TargetBytesPtr(aOther, aOffsetFromOther));
}
ReadOnlyTargetBytes<TargetMMPolicy>* operator->()
{
return &mTargetBytes;
}
TargetBytesPtr(TargetBytesPtr&& aOther)
: mTargetBytes(Move(aOther.mTargetBytes))
{
}
TargetBytesPtr(const TargetBytesPtr& aOther)
: mTargetBytes(aOther.mTargetBytes)
{
}
TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
private:
TargetBytesPtr(const TargetMMPolicy& aMMPolicy, const void* aFunc)
: mTargetBytes(aMMPolicy, aFunc)
{
}
TargetBytesPtr(const TargetBytesPtr& aOther,
const uint32_t aOffsetFromOther)
: mTargetBytes(aOther.mTargetBytes, aOffsetFromOther)
{
}
ReadOnlyTargetBytes<TargetMMPolicy> mTargetBytes;
};
public:
ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
: mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc))
, mOffset(0)
{
}
ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc)
: mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy,
reinterpret_cast<const void*>(aFunc)))
, mOffset(0)
{
}
ReadOnlyTargetFunction(ReadOnlyTargetFunction&& aOther)
: mTargetBytes(Move(aOther.mTargetBytes))
, mOffset(aOther.mOffset)
{
}
ReadOnlyTargetFunction& operator=(const ReadOnlyTargetFunction&) = delete;
ReadOnlyTargetFunction& operator=(ReadOnlyTargetFunction&&) = delete;
~ReadOnlyTargetFunction() = default;
ReadOnlyTargetFunction operator+(const uint32_t aOffset) const
{
return ReadOnlyTargetFunction(*this, mOffset + aOffset);
}
uintptr_t GetBaseAddress() const
{
return reinterpret_cast<uintptr_t>(mTargetBytes->Get());
}
uintptr_t GetAddress() const
{
return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset);
}
uintptr_t AsEncodedPtr() const
{
return EncodePtr(const_cast<uint8_t*>(mTargetBytes->Get() + mOffset));
}
static uintptr_t EncodePtr(void* aPtr)
{
return reinterpret_cast<uintptr_t>(::EncodePointer(aPtr));
}
static uintptr_t DecodePtr(uintptr_t aEncodedPtr)
{
return reinterpret_cast<uintptr_t>(
::DecodePointer(reinterpret_cast<PVOID>(aEncodedPtr)));
}
uint8_t const & operator*() const
{
mTargetBytes->EnsureLimit(mOffset);
return *(mTargetBytes->Get() + mOffset);
}
uint8_t const & operator[](uint32_t aIndex) const
{
mTargetBytes->EnsureLimit(mOffset + aIndex);
return *(mTargetBytes->Get() + mOffset + aIndex);
}
ReadOnlyTargetFunction& operator++()
{
++mOffset;
return *this;
}
ReadOnlyTargetFunction& operator+=(uint32_t aDelta)
{
mOffset += aDelta;
return *this;
}
uint32_t GetOffset() const
{
return mOffset;
}
uintptr_t ReadDisp32AsAbsolute()
{
mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
int32_t disp = *reinterpret_cast<const int32_t*>(mTargetBytes->Get() + mOffset);
uintptr_t result = reinterpret_cast<uintptr_t>(
mTargetBytes->Get() + mOffset + sizeof(int32_t) + disp);
mOffset += sizeof(int32_t);
return result;
}
uintptr_t OffsetToAbsolute(const uint8_t aOffset) const
{
return reinterpret_cast<uintptr_t>(mTargetBytes->Get() + mOffset + aOffset);
}
/**
* This method promotes the code referenced by this object to be writable.
*
* @param aLen The length of the function's code to make writable. If set
* to zero, this object's current offset is used as the length.
* @param aOffset The result's base address will be offset from this
* object's base address by |aOffset| bytes. This value may be
* negative.
*/
WritableTargetFunction<MMPolicy> Promote(const uint32_t aLen = 0,
const int8_t aOffset = 0) const
{
const uint32_t effectiveLength = aLen ? aLen : mOffset;
MOZ_RELEASE_ASSERT(effectiveLength, "Cannot Promote a zero-length function");
if (!mTargetBytes->IsValidAtOffset(aOffset)) {
return WritableTargetFunction<MMPolicy>(mTargetBytes->GetMMPolicy());
}
WritableTargetFunction<MMPolicy> result(
mTargetBytes->GetMMPolicy(),
reinterpret_cast<uintptr_t>(mTargetBytes->Get() + aOffset),
effectiveLength);
return Move(result);
}
private:
template <typename T>
struct ChasePointerHelper
{
template <typename MMPolicy>
static T Result(const MMPolicy&, T aValue)
{
return aValue;
}
};
template <typename T>
struct ChasePointerHelper<T*>
{
template <typename MMPolicy>
static auto Result(const MMPolicy& aPolicy, T* aValue)
{
ReadOnlyTargetFunction<MMPolicy> ptr(aPolicy, aValue);
return ptr.ChasePointer<T>();
}
};
public:
// Keep chasing pointers until T is not a pointer type anymore
template <typename T>
auto ChasePointer()
{
mTargetBytes->EnsureLimit(mOffset + sizeof(T));
const typename RemoveCV<T>::Type result = *reinterpret_cast<const RemoveCV<T>::Type*>(mTargetBytes->Get() + mOffset);
return ChasePointerHelper<typename RemoveCV<T>::Type>::Result(mTargetBytes->GetMMPolicy(), result);
}
uintptr_t ChasePointerFromDisp()
{
uintptr_t ptrFromDisp = ReadDisp32AsAbsolute();
ReadOnlyTargetFunction<MMPolicy> ptr(mTargetBytes->GetMMPolicy(),
reinterpret_cast<const void*>(ptrFromDisp));
return ptr.template ChasePointer<uintptr_t>();
}
private:
ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther)
: mTargetBytes(aOther.mTargetBytes)
, mOffset(aOther.mOffset)
{
}
ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther,
const uint32_t aOffsetFromOther)
: mTargetBytes(TargetBytesPtr<MMPolicy>::CopyFromOffset(aOther.mTargetBytes,
aOffsetFromOther))
, mOffset(0)
{
}
private:
mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
uint32_t mOffset;
};
} // namespace interceptor
} // namespace mozilla
#endif // mozilla_interceptor_TargetFunction_h

Просмотреть файл

@ -0,0 +1,369 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_interceptor_Trampoline_h
#define mozilla_interceptor_Trampoline_h
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/Maybe.h"
#include "mozilla/Types.h"
namespace mozilla {
namespace interceptor {
template <typename MMPolicy>
class MOZ_STACK_CLASS Trampoline final
{
public:
Trampoline(const MMPolicy* aMMPolicy, uint8_t* const aLocalBase,
const uintptr_t aRemoteBase, const uint32_t aChunkSize)
: mMMPolicy(aMMPolicy)
, mPrevLocalProt(0)
, mLocalBase(aLocalBase)
, mRemoteBase(aRemoteBase)
, mOffset(0)
, mExeOffset(0)
, mMaxOffset(aChunkSize)
, mAccumulatedStatus(true)
{
::VirtualProtect(aLocalBase, aChunkSize, PAGE_EXECUTE_READWRITE,
&mPrevLocalProt);
}
Trampoline(Trampoline&& aOther)
: mMMPolicy(aOther.mMMPolicy)
, mPrevLocalProt(aOther.mPrevLocalProt)
, mLocalBase(aOther.mLocalBase)
, mRemoteBase(aOther.mRemoteBase)
, mOffset(aOther.mOffset)
, mExeOffset(aOther.mExeOffset)
, mMaxOffset(aOther.mMaxOffset)
, mAccumulatedStatus(aOther.mAccumulatedStatus)
{
aOther.mPrevLocalProt = 0;
aOther.mAccumulatedStatus = false;
}
MOZ_IMPLICIT Trampoline(decltype(nullptr))
: mMMPolicy(nullptr)
, mPrevLocalProt(0)
, mLocalBase(nullptr)
, mRemoteBase(0)
, mOffset(0)
, mExeOffset(0)
, mMaxOffset(0)
, mAccumulatedStatus(false)
{
}
Trampoline(const Trampoline&) = delete;
Trampoline& operator=(const Trampoline&) = delete;
Trampoline&& operator=(Trampoline&&) = delete;
~Trampoline()
{
if (!mLocalBase || !mPrevLocalProt) {
return;
}
::VirtualProtect(mLocalBase, mMaxOffset, mPrevLocalProt, &mPrevLocalProt);
}
explicit operator bool() const
{
return mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus;
}
void WriteByte(uint8_t aValue)
{
if (mOffset >= mMaxOffset) {
mAccumulatedStatus = false;
return;
}
*(mLocalBase + mOffset) = aValue;
++mOffset;
}
void WriteInteger(int32_t aValue)
{
if (mOffset + sizeof(int32_t) > mMaxOffset) {
mAccumulatedStatus = false;
return;
}
*reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue;
mOffset += sizeof(int32_t);
}
void WritePointer(uintptr_t aValue)
{
if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
mAccumulatedStatus = false;
return;
}
*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue;
mOffset += sizeof(uintptr_t);
}
void WriteEncodedPointer(void* aValue)
{
uintptr_t encoded = ReadOnlyTargetFunction<MMPolicy>::EncodePtr(aValue);
WritePointer(encoded);
}
Maybe<uintptr_t> ReadPointer()
{
if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
mAccumulatedStatus = false;
return Nothing();
}
auto result = Some(*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset));
mOffset += sizeof(uintptr_t);
return Move(result);
}
Maybe<uintptr_t> ReadEncodedPointer()
{
Maybe<uintptr_t> encoded(ReadPointer());
if (!encoded) {
return encoded;
}
return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value()));
}
void WriteDisp32(uintptr_t aAbsTarget)
{
if (mOffset + sizeof(int32_t) > mMaxOffset) {
mAccumulatedStatus = false;
return;
}
// This needs to be computed from the remote location
intptr_t remoteTrampPosition = static_cast<intptr_t>(mRemoteBase + mOffset);
intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
(remoteTrampPosition + sizeof(int32_t));
CheckedInt<int32_t> checkedDisp(diff);
MOZ_ASSERT(checkedDisp.isValid());
if (!checkedDisp.isValid()) {
mAccumulatedStatus = false;
return;
}
int32_t disp = checkedDisp.value();
*reinterpret_cast<int32_t*>(mLocalBase + mOffset) = disp;
mOffset += sizeof(int32_t);
}
#if defined(_M_IX86)
// 32-bit only
void AdjustDisp32AtOffset(uint32_t aOffset, uintptr_t aAbsTarget)
{
uint32_t effectiveOffset = mExeOffset + aOffset;
if (effectiveOffset + sizeof(int32_t) > mMaxOffset) {
mAccumulatedStatus = false;
return;
}
intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
static_cast<intptr_t>(mRemoteBase + mExeOffset);
*reinterpret_cast<int32_t*>(mLocalBase + effectiveOffset) += diff;
}
#endif // defined(_M_IX86)
void CopyFrom(uintptr_t aOrigBytes, uint32_t aNumBytes)
{
if (!mMMPolicy || mOffset + aNumBytes > mMaxOffset) {
mAccumulatedStatus = false;
return;
}
if (!mMMPolicy->Read(mLocalBase + mOffset,
reinterpret_cast<void*>(aOrigBytes), aNumBytes)) {
mAccumulatedStatus = false;
return;
}
mOffset += aNumBytes;
}
void Rewind()
{
mOffset = 0;
}
uintptr_t GetCurrentRemoteAddress() const
{
return mRemoteBase + mOffset;
}
void StartExecutableCode()
{
MOZ_ASSERT(!mExeOffset);
mExeOffset = mOffset;
}
void* EndExecutableCode() const
{
if (!mAccumulatedStatus) {
return nullptr;
}
// This must always return the start address the executable code
// *in the target process*
return reinterpret_cast<void*>(mRemoteBase + mExeOffset);
}
Trampoline<MMPolicy>& operator--()
{
MOZ_ASSERT(mOffset);
--mOffset;
return *this;
}
private:
const MMPolicy* mMMPolicy;
DWORD mPrevLocalProt;
uint8_t* const mLocalBase;
const uintptr_t mRemoteBase;
uint32_t mOffset;
uint32_t mExeOffset;
const uint32_t mMaxOffset;
bool mAccumulatedStatus;
};
template <typename MMPolicy>
class MOZ_STACK_CLASS TrampolineCollection final
{
public:
class MOZ_STACK_CLASS TrampolineIterator final
{
public:
Trampoline<MMPolicy> operator*()
{
uint32_t offset = mCurTramp * mCollection.mTrampSize;
return Trampoline<MMPolicy>(nullptr,
mCollection.mLocalBase + offset,
mCollection.mRemoteBase + offset,
mCollection.mTrampSize);
}
TrampolineIterator& operator++()
{
++mCurTramp;
return *this;
}
bool operator!=(const TrampolineIterator& aOther) const
{
return mCurTramp != aOther.mCurTramp;
}
private:
explicit TrampolineIterator(
const TrampolineCollection<MMPolicy>& aCollection,
const uint32_t aCurTramp = 0)
: mCollection(aCollection)
, mCurTramp(aCurTramp)
{
}
const TrampolineCollection<MMPolicy>& mCollection;
uint32_t mCurTramp;
friend class TrampolineCollection<MMPolicy>;
};
explicit TrampolineCollection(const MMPolicy& aMMPolicy)
: mMMPolicy(aMMPolicy)
, mLocalBase(0)
, mRemoteBase(0)
, mTrampSize(0)
, mNumTramps(0)
, mPrevProt(0)
{
}
TrampolineCollection(const MMPolicy& aMMPolicy, uint8_t* const aLocalBase,
const uintptr_t aRemoteBase, const uint32_t aTrampSize,
const uint32_t aNumTramps)
: mMMPolicy(aMMPolicy)
, mLocalBase(aLocalBase)
, mRemoteBase(aRemoteBase)
, mTrampSize(aTrampSize)
, mNumTramps(aNumTramps)
, mPrevProt(0)
{
if (!aNumTramps) {
return;
}
DebugOnly<BOOL> ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
PAGE_EXECUTE_READWRITE, &mPrevProt);
MOZ_ASSERT(ok);
}
~TrampolineCollection()
{
if (!mPrevProt) {
return;
}
mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize,
mPrevProt, &mPrevProt);
}
TrampolineIterator begin() const
{
if (!mPrevProt) {
return end();
}
return TrampolineIterator(*this);
}
TrampolineIterator end() const
{
return TrampolineIterator(*this, mNumTramps);
}
TrampolineCollection(const TrampolineCollection&) = delete;
TrampolineCollection& operator=(const TrampolineCollection&) = delete;
TrampolineCollection& operator=(TrampolineCollection&&) = delete;
TrampolineCollection(TrampolineCollection&& aOther)
: mMMPolicy(aOther.mMMPolicy)
, mLocalBase(aOther.mLocalBase)
, mRemoteBase(aOther.mRemoteBase)
, mTrampSize(aOther.mTrampSize)
, mNumTramps(aOther.mNumTramps)
, mPrevProt(aOther.mPrevProt)
{
aOther.mPrevProt = 0;
}
private:
const MMPolicy& mMMPolicy;
uint8_t* const mLocalBase;
const uintptr_t mRemoteBase;
const uint32_t mTrampSize;
const uint32_t mNumTramps;
uint32_t mPrevProt;
friend class TrampolineIterator;
};
} // namespace interceptor
} // namespace mozilla
#endif // mozilla_interceptor_Trampoline_h

Просмотреть файл

@ -0,0 +1,90 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#ifndef mozilla_interceptor_VMSharingPolicies_h
#define mozilla_interceptor_VMSharingPolicies_h
#include "mozilla/Assertions.h"
#include "mozilla/Types.h"
namespace mozilla {
namespace interceptor {
template <typename MMPolicy, uint32_t kChunkSize>
class VMSharingPolicyUnique : public MMPolicy
{
public:
template <typename... Args>
explicit VMSharingPolicyUnique(Args... aArgs)
: MMPolicy(mozilla::Forward<Args>(aArgs)...)
, mNextChunkIndex(0)
{
}
bool Reserve(uint32_t aCount)
{
MOZ_ASSERT(aCount);
uint32_t bytesReserved = MMPolicy::Reserve(aCount * kChunkSize);
return !!bytesReserved;
}
Trampoline<MMPolicy> GetNextTrampoline()
{
uint32_t offset = mNextChunkIndex * kChunkSize;
if (!MaybeCommitNextPage(offset, kChunkSize)) {
return nullptr;
}
Trampoline<MMPolicy> result(this, GetLocalView() + offset,
GetRemoteView() + offset, kChunkSize);
if (!!result) {
++mNextChunkIndex;
}
return Move(result);
}
TrampolineCollection<MMPolicy> Items() const
{
return TrampolineCollection<MMPolicy>(*this, GetLocalView(), GetRemoteView(),
kChunkSize, mNextChunkIndex);
}
void Clear()
{
mNextChunkIndex = 0;
}
~VMSharingPolicyUnique() = default;
VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
: MMPolicy(Move(aOther))
, mNextChunkIndex(aOther.mNextChunkIndex)
{
aOther.mNextChunkIndex = 0;
}
VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther)
{
static_cast<MMPolicy&>(*this) = Move(aOther);
mNextChunkIndex = aOther.mNextChunkIndex;
aOther.mNextChunkIndex = 0;
return *this;
}
private:
uint32_t mNextChunkIndex;
};
} // namespace interceptor
} // namespace mozilla
#endif // mozilla_interceptor_VMSharingPolicies_h

Просмотреть файл

@ -33,6 +33,18 @@ OS_LIBS += CONFIG['REALTIME_LIBS']
DEFINES['IMPL_MFBT'] = True
if CONFIG['OS_ARCH'] == 'WINNT':
EXPORTS += [
'nsWindowsDllInterceptor.h',
]
EXPORTS.mozilla.interceptor += [
'interceptor/MMPolicies.h',
'interceptor/PatcherBase.h',
'interceptor/PatcherDetour.h',
'interceptor/PatcherNopSpace.h',
'interceptor/TargetFunction.h',
'interceptor/Trampoline.h',
'interceptor/VMSharingPolicies.h',
]
SOURCES += [
'TimeStamp_windows.cpp',
]

Просмотреть файл

@ -0,0 +1,241 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
#define NS_WINDOWS_DLL_INTERCEPTOR_H_
#include "mozilla/Assertions.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/NotNull.h"
#include "mozilla/TypeTraits.h"
#include "mozilla/Types.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "nsWindowsHelpers.h"
#include <wchar.h>
#include <windows.h>
#include <winternl.h>
#include "mozilla/interceptor/MMPolicies.h"
#include "mozilla/interceptor/PatcherDetour.h"
#include "mozilla/interceptor/PatcherNopSpace.h"
#include "mozilla/interceptor/VMSharingPolicies.h"
/*
* Simple function interception.
*
* We have two separate mechanisms for intercepting a function: We can use the
* built-in nop space, if it exists, or we can create a detour.
*
* Using the built-in nop space works as follows: On x86-32, DLL functions
* begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
* NOP instructions.
*
* When we detect a function with this prelude, we do the following:
*
* 1. Write a long jump to our interceptor function into the five bytes of NOPs
* before the function.
*
* 2. Write a short jump -5 into the two-byte nop at the beginning of the function.
*
* This mechanism is nice because it's thread-safe. It's even safe to do if
* another thread is currently running the function we're modifying!
*
* When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
* but not the long jump, so re-intercepting the same function won't work,
* because its prelude won't match.
*
*
* Unfortunately nop space patching doesn't work on functions which don't have
* this magic prelude (and in particular, x86-64 never has the prelude). So
* when we can't use the built-in nop space, we fall back to using a detour,
* which works as follows:
*
* 1. Save first N bytes of OrigFunction to trampoline, where N is a
* number of bytes >= 5 that are instruction aligned.
*
* 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
* function.
*
* 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
* continue original program flow.
*
* 4. Hook function needs to call the trampoline during its execution,
* to invoke the original function (so address of trampoline is
* returned).
*
* When the WindowsDllDetourPatcher object is destructed, OrigFunction is
* patched again to jump directly to the trampoline instead of going through
* the hook function. As such, re-intercepting the same function won't work, as
* jump instructions are not supported.
*
* Note that this is not thread-safe. Sad day.
*
*/
namespace mozilla {
namespace interceptor {
enum
{
kDefaultTrampolineSize = 128
};
template <typename VMPolicy =
mozilla::interceptor::VMSharingPolicyUnique<
mozilla::interceptor::MMPolicyInProcess, kDefaultTrampolineSize>>
class WindowsDllInterceptor final
{
interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
#if defined(_M_IX86)
interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT> mNopSpacePatcher;
#endif // defined(_M_IX86)
HMODULE mModule;
int mNHooks;
public:
template <typename... Args>
explicit WindowsDllInterceptor(Args... aArgs)
: mDetourPatcher(mozilla::Forward<Args>(aArgs)...)
#if defined(_M_IX86)
, mNopSpacePatcher(mozilla::Forward<Args>(aArgs)...)
#endif // defined(_M_IX86)
, mModule(nullptr)
, mNHooks(0)
{
}
WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
~WindowsDllInterceptor()
{
Clear();
}
template <size_t N>
void Init(const char (&aModuleName)[N], int aNumHooks = 0)
{
wchar_t moduleName[N];
for (size_t i = 0; i < N; ++i) {
MOZ_ASSERT(!(aModuleName[i] & 0x80),
"Use wide-character overload for non-ASCII module names");
moduleName[i] = aModuleName[i];
}
Init(moduleName, aNumHooks);
}
void Init(const wchar_t* aModuleName, int aNumHooks = 0)
{
if (mModule) {
return;
}
mModule = ::LoadLibraryW(aModuleName);
mNHooks = aNumHooks;
}
void Clear()
{
if (!mModule) {
return;
}
#if defined(_M_IX86)
mNopSpacePatcher.Clear();
#endif // defined(_M_IX86)
mDetourPatcher.Clear();
// NB: We intentionally leak mModule
}
/**
* Hook/detour the method aName from the DLL we set in Init so that it calls
* aHookDest instead. Returns the original method pointer in aOrigFunc
* and returns true if successful.
*
* IMPORTANT: If you use this method, please add your case to the
* TestDllInterceptor in order to detect future failures. Even if this
* succeeds now, updates to the hooked DLL could cause it to fail in
* the future.
*/
bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc)
{
// Use a nop space patch if possible, otherwise fall back to a detour.
// This should be the preferred method for adding hooks.
if (!mModule) {
return false;
}
FARPROC proc = ::GetProcAddress(mModule, aName);
if (!proc) {
return false;
}
#if defined(_M_IX86)
if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
return true;
}
#endif // defined(_M_IX86)
return AddDetour(proc, aHookDest, aOrigFunc);
}
/**
* Detour the method aName from the DLL we set in Init so that it calls
* aHookDest instead. Returns the original method pointer in aOrigFunc
* and returns true if successful.
*
* IMPORTANT: If you use this method, please add your case to the
* TestDllInterceptor in order to detect future failures. Even if this
* succeeds now, updates to the detoured DLL could cause it to fail in
* the future.
*/
bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc)
{
// Generally, code should not call this method directly. Use AddHook unless
// there is a specific need to avoid nop space patches.
if (!mModule) {
return false;
}
FARPROC proc = ::GetProcAddress(mModule, aName);
if (!proc) {
return false;
}
return AddDetour(proc, aHookDest, aOrigFunc);
}
private:
bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc)
{
MOZ_ASSERT(mModule && aProc);
if (!mDetourPatcher.Initialized()) {
mDetourPatcher.Init(mNHooks);
}
return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
}
};
} // namespace interceptor
using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
} // namespace mozilla
#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */

Просмотреть файл

@ -61,7 +61,8 @@ bool CheckHook(HookTestFunc aHookTestFunc, void* aOrigFunc,
return false;
}
bool TestHook(HookTestFunc funcTester, const char *dll, const char *func)
template <size_t N>
bool TestHook(HookTestFunc funcTester, const char (&dll)[N], const char *func)
{
void *orig_func;
bool successful = false;
@ -80,7 +81,8 @@ bool TestHook(HookTestFunc funcTester, const char *dll, const char *func)
}
}
bool TestDetour(const char *dll, const char *func)
template <size_t N>
bool TestDetour(const char (&dll)[N], const char *func)
{
void *orig_func;
bool successful = false;
@ -99,7 +101,8 @@ bool TestDetour(const char *dll, const char *func)
}
}
bool MaybeTestHook(const bool cond, HookTestFunc funcTester, const char* dll, const char* func)
template <size_t N>
bool MaybeTestHook(const bool cond, HookTestFunc funcTester, const char (&dll)[N], const char* func)
{
if (!cond) {
printf("TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from %s\n", func, dll);
@ -595,6 +598,9 @@ bool TestFreeCredentialsHandle(void* aFunc)
int main()
{
LARGE_INTEGER start;
QueryPerformanceCounter(&start);
// We disable this part of the test because the code coverage instrumentation
// injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
// understand.
@ -733,6 +739,19 @@ int main()
TestDetour("kernel32.dll", "BaseThreadInitThunk") &&
TestDetour("ntdll.dll", "LdrLoadDll")) {
printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
LARGE_INTEGER end, freq;
QueryPerformanceCounter(&end);
QueryPerformanceFrequency(&freq);
LARGE_INTEGER result;
result.QuadPart = end.QuadPart - start.QuadPart;
result.QuadPart *= 1000000;
result.QuadPart /= freq.QuadPart;
printf("Elapsed time: %lld microseconds\n", result.QuadPart);
return 0;
}

Просмотреть файл

@ -0,0 +1,21 @@
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
CppUnitTests([
'TestDllInterceptor',
])
DEFINES['NS_NO_XPCOM'] = True
DisableStlWrapping()
OS_LIBS += [
'ole32',
]
USE_LIBS += [
'mfbt',
]

Просмотреть файл

@ -13,3 +13,8 @@ GeckoCppUnitTests([
CppUnitTests([
'TestPrintf',
])
if CONFIG['OS_ARCH'] == 'WINNT':
TEST_DIRS += [
'interceptor',
]

Просмотреть файл

@ -8,10 +8,6 @@ SimplePrograms([
'TestXREMakeCommandLineWin',
])
CppUnitTests([
'TestDllInterceptor',
])
DEFINES['NS_NO_XPCOM'] = True
LOCAL_INCLUDES += [
@ -24,7 +20,6 @@ USE_STATIC_LIBS = True
OS_LIBS += [
'comctl32',
'ole32',
'shell32',
'ws2_32',
]

Просмотреть файл

@ -485,7 +485,7 @@ ClearPoisonIOInterposer()
if (sIOPoisoned) {
// Destroy the DLL interceptor
sIOPoisoned = false;
sNtDllInterceptor = WindowsDllInterceptor();
sNtDllInterceptor.Clear();
}
}

Просмотреть файл

@ -26,8 +26,9 @@ EXPORTS.mozilla += [
]
if CONFIG['OS_ARCH'] == 'WINNT':
EXPORTS += ['nsWindowsDllInterceptor.h']
EXPORTS.mozilla += ['perfprobe.h']
EXPORTS.mozilla += [
'perfprobe.h',
]
SOURCES += ['perfprobe.cpp']
if CONFIG['CC_TYPE'] != 'gcc':
SOURCES += [

Разница между файлами не показана из-за своего большого размера Загрузить разницу