773 lines
22 KiB
C++
773 lines
22 KiB
C++
/* -*- 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/CheckedInt.h"
|
|
#include "mozilla/Maybe.h"
|
|
#include "mozilla/Types.h"
|
|
#include "mozilla/WindowsProcessMitigations.h"
|
|
#include "mozilla/WindowsUnwindInfo.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),
|
|
#ifdef _M_X64
|
|
mCopyCodesEndOffset(0),
|
|
mExeEndOffset(0),
|
|
#endif // _M_X64
|
|
mMaxOffset(aChunkSize),
|
|
mAccumulatedStatus(true) {
|
|
if (!::VirtualProtect(aLocalBase, aChunkSize,
|
|
MMPolicy::GetTrampWriteProtFlags(),
|
|
&mPrevLocalProt)) {
|
|
mPrevLocalProt = 0;
|
|
}
|
|
}
|
|
|
|
Trampoline(Trampoline&& aOther)
|
|
: mMMPolicy(aOther.mMMPolicy),
|
|
mPrevLocalProt(aOther.mPrevLocalProt),
|
|
mLocalBase(aOther.mLocalBase),
|
|
mRemoteBase(aOther.mRemoteBase),
|
|
mOffset(aOther.mOffset),
|
|
mExeOffset(aOther.mExeOffset),
|
|
#ifdef _M_X64
|
|
mCopyCodesEndOffset(aOther.mCopyCodesEndOffset),
|
|
mExeEndOffset(aOther.mExeEndOffset),
|
|
#endif // _M_X64
|
|
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),
|
|
#ifdef _M_X64
|
|
mCopyCodesEndOffset(0),
|
|
mExeEndOffset(0),
|
|
#endif // _M_X64
|
|
mMaxOffset(0),
|
|
mAccumulatedStatus(false) {
|
|
}
|
|
|
|
Trampoline(const Trampoline&) = delete;
|
|
Trampoline& operator=(const Trampoline&) = delete;
|
|
|
|
Trampoline& operator=(Trampoline&& aOther) {
|
|
Clear();
|
|
|
|
mMMPolicy = aOther.mMMPolicy;
|
|
mPrevLocalProt = aOther.mPrevLocalProt;
|
|
mLocalBase = aOther.mLocalBase;
|
|
mRemoteBase = aOther.mRemoteBase;
|
|
mOffset = aOther.mOffset;
|
|
mExeOffset = aOther.mExeOffset;
|
|
#ifdef _M_X64
|
|
mCopyCodesEndOffset = aOther.mCopyCodesEndOffset;
|
|
mExeEndOffset = aOther.mExeEndOffset;
|
|
#endif // _M_X64
|
|
mMaxOffset = aOther.mMaxOffset;
|
|
mAccumulatedStatus = aOther.mAccumulatedStatus;
|
|
|
|
aOther.mPrevLocalProt = 0;
|
|
aOther.mAccumulatedStatus = false;
|
|
|
|
return *this;
|
|
}
|
|
|
|
~Trampoline() { Clear(); }
|
|
|
|
explicit operator bool() const {
|
|
return IsNull() ||
|
|
(mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus);
|
|
}
|
|
|
|
bool IsNull() const { return !mMMPolicy; }
|
|
|
|
#if defined(_M_ARM64)
|
|
|
|
void WriteInstruction(uint32_t aInstruction) {
|
|
const uint32_t kDelta = sizeof(uint32_t);
|
|
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += kDelta;
|
|
return;
|
|
}
|
|
|
|
if (mOffset + kDelta > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
*reinterpret_cast<uint32_t*>(mLocalBase + mOffset) = aInstruction;
|
|
mOffset += kDelta;
|
|
}
|
|
|
|
void WriteLoadLiteral(const uintptr_t aAddress, const uint8_t aReg) {
|
|
const uint32_t kDelta = sizeof(uint32_t) + sizeof(uintptr_t);
|
|
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += kDelta;
|
|
return;
|
|
}
|
|
|
|
// We grow the literal pool from the *end* of the tramp,
|
|
// so we need to ensure that there is enough room for both an instruction
|
|
// and a pointer
|
|
if (mOffset + kDelta > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
mMaxOffset -= sizeof(uintptr_t);
|
|
*reinterpret_cast<uintptr_t*>(mLocalBase + mMaxOffset) = aAddress;
|
|
|
|
CheckedInt<intptr_t> pc(GetCurrentRemoteAddress());
|
|
if (!pc.isValid()) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
CheckedInt<intptr_t> literal(reinterpret_cast<uintptr_t>(mLocalBase) +
|
|
mMaxOffset);
|
|
if (!literal.isValid()) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
CheckedInt<intptr_t> ptrOffset = (literal - pc);
|
|
if (!ptrOffset.isValid()) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
// ptrOffset must be properly aligned
|
|
MOZ_ASSERT((ptrOffset.value() % 4) == 0);
|
|
ptrOffset /= 4;
|
|
|
|
CheckedInt<int32_t> offset(ptrOffset.value());
|
|
if (!offset.isValid()) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
// Ensure that offset falls within the range of a signed 19-bit value
|
|
if (offset.value() < -0x40000 || offset.value() > 0x3FFFF) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
const int32_t kimm19Mask = 0x7FFFF;
|
|
int32_t masked = offset.value() & kimm19Mask;
|
|
|
|
MOZ_ASSERT(aReg < 32);
|
|
uint32_t loadInstr = 0x58000000 | (masked << 5) | aReg;
|
|
WriteInstruction(loadInstr);
|
|
}
|
|
|
|
#else
|
|
|
|
void WriteByte(uint8_t aValue) {
|
|
const uint32_t kDelta = sizeof(uint8_t);
|
|
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += kDelta;
|
|
return;
|
|
}
|
|
|
|
if (mOffset >= mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
*(mLocalBase + mOffset) = aValue;
|
|
++mOffset;
|
|
}
|
|
|
|
void WriteInteger(int32_t aValue) {
|
|
const uint32_t kDelta = sizeof(int32_t);
|
|
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += kDelta;
|
|
return;
|
|
}
|
|
|
|
if (mOffset + kDelta > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
*reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue;
|
|
mOffset += kDelta;
|
|
}
|
|
|
|
void WriteDisp32(uintptr_t aAbsTarget) {
|
|
const uint32_t kDelta = sizeof(int32_t);
|
|
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += kDelta;
|
|
return;
|
|
}
|
|
|
|
if (mOffset + kDelta > 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 + kDelta);
|
|
|
|
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 += kDelta;
|
|
}
|
|
|
|
void WriteBytes(void* aAddr, size_t aSize) {
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += aSize;
|
|
return;
|
|
}
|
|
|
|
if (mOffset + aSize > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
std::memcpy(reinterpret_cast<void*>(mLocalBase + mOffset), aAddr, aSize);
|
|
mOffset += aSize;
|
|
}
|
|
|
|
#endif
|
|
|
|
void WritePointer(uintptr_t aValue) {
|
|
const uint32_t kDelta = sizeof(uintptr_t);
|
|
|
|
if (!mMMPolicy) {
|
|
// Null tramp, just track offset
|
|
mOffset += kDelta;
|
|
return;
|
|
}
|
|
|
|
if (mOffset + kDelta > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue;
|
|
mOffset += kDelta;
|
|
}
|
|
|
|
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 std::move(result);
|
|
}
|
|
|
|
Maybe<uintptr_t> ReadEncodedPointer() {
|
|
Maybe<uintptr_t> encoded(ReadPointer());
|
|
if (!encoded) {
|
|
return encoded;
|
|
}
|
|
|
|
return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value()));
|
|
}
|
|
|
|
#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) {
|
|
// Null tramp, just track offset
|
|
mOffset += aNumBytes;
|
|
return;
|
|
}
|
|
|
|
if (!mMMPolicy || mOffset + aNumBytes > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
if (!mMMPolicy->Read(mLocalBase + mOffset,
|
|
reinterpret_cast<void*>(aOrigBytes), aNumBytes)) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
|
|
mOffset += aNumBytes;
|
|
}
|
|
|
|
void CopyCodes(uintptr_t aOrigBytes, uint32_t aNumBytes) {
|
|
#ifdef _M_X64
|
|
if (mOffset == mCopyCodesEndOffset) {
|
|
mCopyCodesEndOffset += aNumBytes;
|
|
}
|
|
#endif // _M_X64
|
|
CopyFrom(aOrigBytes, aNumBytes);
|
|
}
|
|
|
|
void Rewind() { mOffset = 0; }
|
|
|
|
uintptr_t GetCurrentRemoteAddress() const { return mRemoteBase + mOffset; }
|
|
|
|
void StartExecutableCode() {
|
|
MOZ_ASSERT(!mExeOffset);
|
|
mExeOffset = mOffset;
|
|
#ifdef _M_X64
|
|
mCopyCodesEndOffset = mOffset;
|
|
#endif // _M_X64
|
|
}
|
|
|
|
void* EndExecutableCode() {
|
|
if (!mAccumulatedStatus || !mMMPolicy) {
|
|
return nullptr;
|
|
}
|
|
|
|
#ifdef _M_X64
|
|
mExeEndOffset = mOffset;
|
|
#endif // _M_X64
|
|
|
|
// This must always return the start address the executable code
|
|
// *in the target process*
|
|
return reinterpret_cast<void*>(mRemoteBase + mExeOffset);
|
|
}
|
|
|
|
uint32_t GetCurrentExecutableCodeLen() const { return mOffset - mExeOffset; }
|
|
|
|
#ifdef _M_X64
|
|
|
|
void Align(uint32_t aAlignment) {
|
|
// aAlignment should be a power of 2
|
|
MOZ_ASSERT(!(aAlignment & (aAlignment - 1)));
|
|
|
|
uint32_t alignedOffset = (mOffset + aAlignment - 1) & ~(aAlignment - 1);
|
|
if (alignedOffset > mMaxOffset) {
|
|
mAccumulatedStatus = false;
|
|
return;
|
|
}
|
|
mOffset = alignedOffset;
|
|
}
|
|
|
|
// We assume that all instructions that are part of the prologue are left
|
|
// intact by detouring code, i.e. that they are copied using CopyCodes. This
|
|
// is not true for calls and jumps for example, but calls and jumps cannot be
|
|
// part of the prologue. This assumption allows us to copy unwind information
|
|
// as-is, because unwind information only refers to instructions within the
|
|
// prologue.
|
|
bool AddUnwindInfo(uintptr_t aOrigFuncAddr, uintptr_t aOrigFuncStopOffset) {
|
|
if constexpr (!MMPolicy::kSupportsUnwindInfo) {
|
|
return false;
|
|
}
|
|
|
|
if (!mMMPolicy) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t origFuncOffsetFromBeginAddr = 0;
|
|
uint32_t origFuncOffsetToEndAddr = 0;
|
|
uintptr_t origImageBase = 0;
|
|
auto unwindInfoData =
|
|
mMMPolicy->LookupUnwindInfo(aOrigFuncAddr, &origFuncOffsetFromBeginAddr,
|
|
&origFuncOffsetToEndAddr, &origImageBase);
|
|
if (!unwindInfoData) {
|
|
// If the original function does not have unwind info, there is nothing
|
|
// more to do.
|
|
return true;
|
|
}
|
|
|
|
// We do not support hooking at a location that isn't the beginning of a
|
|
// function.
|
|
MOZ_ASSERT(origFuncOffsetFromBeginAddr == 0);
|
|
if (origFuncOffsetFromBeginAddr != 0) {
|
|
return false;
|
|
}
|
|
|
|
IterableUnwindInfo unwindInfoIt(unwindInfoData.get());
|
|
auto& unwindInfo = unwindInfoIt.Info();
|
|
|
|
// The prologue should contain only instructions that we detour using
|
|
// CopyCodes. If not, there is most likely a mismatch between the unwind
|
|
// information and the actual code we are detouring, so we stop here. This
|
|
// is a best-effort safeguard intended to detect situations where e.g.
|
|
// third-party injected code would have altered the function we are
|
|
// detouring.
|
|
if (mCopyCodesEndOffset < aOrigFuncStopOffset &&
|
|
unwindInfo.size_of_prolog > mCopyCodesEndOffset) {
|
|
return false;
|
|
}
|
|
|
|
// According to the documentation, the array is sorted by descending order
|
|
// of offset in the prologue. Let's double check this assumption if in
|
|
// debug. This also checks that the full unwind information isn't
|
|
// ill-formed, thanks to all the MOZ_ASSERT in iteration code.
|
|
# ifdef DEBUG
|
|
uint8_t previousOffset = 0xFF;
|
|
for (const auto& unwindCode : unwindInfoIt) {
|
|
MOZ_ASSERT(unwindCode.offset_in_prolog <= previousOffset);
|
|
previousOffset = unwindCode.offset_in_prolog;
|
|
}
|
|
# endif // DEBUG
|
|
|
|
// We skip entries that are not part of the code we have detoured.
|
|
// This code relies on the array being sorted by descending order of offset
|
|
// in the prolog.
|
|
uint8_t firstRelevantCode = 0;
|
|
uint8_t countOfCodes = 0;
|
|
auto it = unwindInfoIt.begin();
|
|
for (; it != unwindInfoIt.end(); ++it) {
|
|
const auto& unwindCode = *it;
|
|
if (unwindCode.offset_in_prolog <= aOrigFuncStopOffset) {
|
|
// Found a relevant entry
|
|
firstRelevantCode = it.Index();
|
|
countOfCodes = unwindInfo.count_of_codes - firstRelevantCode;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Check that we encountered no ill-formed unwind codes.
|
|
if (!it.IsValid() && !it.IsAtEnd()) {
|
|
return false;
|
|
}
|
|
|
|
// We do not support chained unwind info. We should add support for chained
|
|
// unwind info if we ever reach this assert. Since we hook functions at
|
|
// their start address, this should not happen.
|
|
if (unwindInfo.flags & UNW_FLAG_CHAININFO) {
|
|
MOZ_ASSERT(
|
|
false,
|
|
"Tried to detour at a location with chained unwind information");
|
|
return false;
|
|
}
|
|
|
|
// We do not support exception handler info either. This could be a problem
|
|
// if we detour code that does not belong to the prologue and contains a
|
|
// call instruction, as this handler would then not be found if unwinding
|
|
// from callees. The following assert checks that this does not happen.
|
|
//
|
|
// Our current assumption is that all the functions we hook either have no
|
|
// associated exception handlers, or it is __GSHandlerCheck. This handler
|
|
// is the most commonly found, for example it is present in LdrLoadDll,
|
|
// SendMessageTimeoutW, GetWindowInfo. It is added to functions that use
|
|
// stack buffers, in order to mitigate stack buffer overflows. We explain
|
|
// below why it is not a problem that we do not preserve __GSHandlerCheck
|
|
// information when we detour code.
|
|
//
|
|
// Preserving exception handler information would raise two challenges:
|
|
//
|
|
// (1) if the exception handler was not written in a generic way, it may
|
|
// behave differently when called for our detoured code compared to
|
|
// what it would do if called from the original location of the code;
|
|
// (2) the exception handler can be followed by handler-specific data,
|
|
// which we cannot copy because we do not know its size.
|
|
//
|
|
// __GSHandlerCheck checks that the stack cookie value wasn't overwritten
|
|
// before continuing to unwind and call further handlers. That is a
|
|
// security feature that we want to preserve. However, since these
|
|
// functions allocate stack space and write the stack cookie as part of
|
|
// their prologue, the 13 bytes that we detour are necessarily part of
|
|
// their prologue, which must contain at least the following instructions:
|
|
//
|
|
// 48 81 ec XX XX XX XX sub rsp, 0xXXXXXXXX
|
|
// 48 8b 05 XX XX XX XX mov rax, qword ptr [rip+__security_cookie]
|
|
// 48 33 c4 xor rax, rsp
|
|
// 48 89 84 24 XX XX XX XX mov qword ptr [RSP + 0xXXXXXXXX],RAX
|
|
//
|
|
// As a consequence, code associated with __GSHandlerCheck will necessarily
|
|
// satisfy (aOrigFuncStopOffset <= unwindInfo.size_of_prolog), and it is OK
|
|
// to not preserve handler info in that case.
|
|
# ifdef DEBUG
|
|
if (aOrigFuncStopOffset > unwindInfo.size_of_prolog) {
|
|
MOZ_ASSERT(!(unwindInfo.flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER)));
|
|
}
|
|
# endif // DEBUG
|
|
|
|
// The unwind info must be DWORD-aligned
|
|
Align(sizeof(uint32_t));
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
uintptr_t unwindInfoOffset = mOffset;
|
|
|
|
unwindInfo.flags &=
|
|
~(UNW_FLAG_CHAININFO | UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER);
|
|
unwindInfo.count_of_codes = countOfCodes;
|
|
if (aOrigFuncStopOffset < unwindInfo.size_of_prolog) {
|
|
unwindInfo.size_of_prolog = aOrigFuncStopOffset;
|
|
}
|
|
|
|
WriteBytes(reinterpret_cast<void*>(&unwindInfo),
|
|
offsetof(UnwindInfo, unwind_code));
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
|
|
WriteBytes(
|
|
reinterpret_cast<void*>(&unwindInfo.unwind_code[firstRelevantCode]),
|
|
countOfCodes * sizeof(UnwindCode));
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
|
|
// The function table must be DWORD-aligned
|
|
Align(sizeof(uint32_t));
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
uintptr_t functionTableOffset = mOffset;
|
|
|
|
WriteInteger(mExeOffset);
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
|
|
WriteInteger(mExeEndOffset);
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
|
|
WriteInteger(unwindInfoOffset);
|
|
if (!mAccumulatedStatus) {
|
|
return false;
|
|
}
|
|
|
|
return mMMPolicy->AddFunctionTable(mRemoteBase + functionTableOffset, 1,
|
|
mRemoteBase);
|
|
}
|
|
|
|
#endif // _M_X64
|
|
|
|
Trampoline<MMPolicy>& operator--() {
|
|
MOZ_ASSERT(mOffset);
|
|
--mOffset;
|
|
return *this;
|
|
}
|
|
|
|
private:
|
|
void Clear() {
|
|
if (!mLocalBase || !mPrevLocalProt) {
|
|
return;
|
|
}
|
|
|
|
DebugOnly<bool> ok = !!::VirtualProtect(mLocalBase, mMaxOffset,
|
|
mPrevLocalProt, &mPrevLocalProt);
|
|
MOZ_ASSERT(ok);
|
|
|
|
mLocalBase = nullptr;
|
|
mRemoteBase = 0;
|
|
mPrevLocalProt = 0;
|
|
mAccumulatedStatus = false;
|
|
}
|
|
|
|
private:
|
|
const MMPolicy* mMMPolicy;
|
|
DWORD mPrevLocalProt;
|
|
uint8_t* mLocalBase;
|
|
uintptr_t mRemoteBase;
|
|
uint32_t mOffset;
|
|
uint32_t mExeOffset;
|
|
#ifdef _M_X64
|
|
uint32_t mCopyCodesEndOffset;
|
|
uint32_t mExeEndOffset;
|
|
#endif // _M_X64
|
|
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>(
|
|
&mCollection.mMMPolicy, 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),
|
|
mCS(nullptr) {}
|
|
|
|
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),
|
|
mCS(nullptr) {
|
|
if (!aNumTramps) {
|
|
return;
|
|
}
|
|
|
|
BOOL ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
|
|
PAGE_EXECUTE_READWRITE, &mPrevProt);
|
|
if (!ok) {
|
|
// When destroying a sandboxed process that uses
|
|
// MITIGATION_DYNAMIC_CODE_DISABLE, we won't be allowed to write to our
|
|
// executable memory so we just do nothing. If we fail to get access
|
|
// to memory for any other reason, we still don't want to crash but we
|
|
// do assert.
|
|
MOZ_ASSERT(IsDynamicCodeDisabled());
|
|
mNumTramps = 0;
|
|
mPrevProt = 0;
|
|
}
|
|
}
|
|
|
|
~TrampolineCollection() {
|
|
if (!mPrevProt) {
|
|
return;
|
|
}
|
|
|
|
mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize, mPrevProt,
|
|
&mPrevProt);
|
|
|
|
if (mCS) {
|
|
::LeaveCriticalSection(mCS);
|
|
}
|
|
}
|
|
|
|
void Lock(CRITICAL_SECTION& aCS) {
|
|
if (!mPrevProt || mCS) {
|
|
return;
|
|
}
|
|
|
|
mCS = &aCS;
|
|
::EnterCriticalSection(&aCS);
|
|
}
|
|
|
|
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),
|
|
mCS(aOther.mCS) {
|
|
aOther.mPrevProt = 0;
|
|
aOther.mCS = nullptr;
|
|
}
|
|
|
|
private:
|
|
const MMPolicy& mMMPolicy;
|
|
uint8_t* const mLocalBase;
|
|
const uintptr_t mRemoteBase;
|
|
const uint32_t mTrampSize;
|
|
uint32_t mNumTramps;
|
|
uint32_t mPrevProt;
|
|
CRITICAL_SECTION* mCS;
|
|
|
|
friend class TrampolineIterator;
|
|
};
|
|
|
|
} // namespace interceptor
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_interceptor_Trampoline_h
|