diff --git a/mozglue/misc/interceptor/MMPolicies.h b/mozglue/misc/interceptor/MMPolicies.h index aaa809951834..30bc31701b09 100644 --- a/mozglue/misc/interceptor/MMPolicies.h +++ b/mozglue/misc/interceptor/MMPolicies.h @@ -8,28 +8,14 @@ #define mozilla_interceptor_MMPolicies_h #include "mozilla/Assertions.h" -#include "mozilla/TypedEnumBits.h" #include "mozilla/Types.h" #include "mozilla/WindowsMapRemoteView.h" #include -// _CRT_RAND_S is not defined everywhere, but we need it. -#if !defined(_CRT_RAND_S) -extern "C" errno_t rand_s(unsigned int* randomValue); -#endif // !defined(_CRT_RAND_S) - namespace mozilla { namespace interceptor { -enum class ReservationFlags : uint32_t -{ - eDefault = 0, - eForceFirst2GB = 1, -}; - -MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ReservationFlags) - class MMPolicyBase { public: @@ -69,100 +55,6 @@ public: return kPageSize; } - -#if defined(_M_X64) - - /** - * This function locates a virtual memory region of |aDesiredBytesLen| that - * resides in the lowest 2GB of address space. We do this by scanning the - * virtual memory space for a block of unallocated memory that is sufficiently - * large. We must stay below the 2GB mark because a 10-byte patch uses movsxd - * (ie, sign extension) to expand the pointer to 64-bits, so bit 31 of the - * found region must be 0. - */ - static PVOID FindLowRegion(HANDLE aProcess, const size_t aDesiredBytesLen) - { - const DWORD granularity = GetAllocGranularity(); - - MOZ_ASSERT(aDesiredBytesLen / granularity > 0); - if (!aDesiredBytesLen) { - return nullptr; - } - - // Generate a randomized base address that falls within the interval - // [1MiB, 2GiB - aDesiredBytesLen] - unsigned int rnd = 0; - rand_s(&rnd); - - // Reduce rnd to a value that falls within the acceptable range - const uint64_t kMinAddress = 0x0000000000100000ULL; - const uint64_t kMaxAddress = 0x0000000080000000ULL; - uint64_t maxOffset = (kMaxAddress - kMinAddress - aDesiredBytesLen) / - granularity; - uint64_t offset = (uint64_t(rnd) % maxOffset) * granularity; - - // Start searching at this address - char* address = reinterpret_cast(kMinAddress) + offset; - // The max address needs to incorporate the desired length - char* const kMaxPtr = reinterpret_cast(kMaxAddress) - - aDesiredBytesLen; - - MOZ_DIAGNOSTIC_ASSERT(address <= kMaxPtr); - - MEMORY_BASIC_INFORMATION mbi = {}; - SIZE_T len = sizeof(mbi); - - // Scan the range for a free chunk that is at least as large as - // aDesiredBytesLen - while (address <= kMaxPtr && - ::VirtualQueryEx(aProcess, address, &mbi, len)) { - if (mbi.State == MEM_FREE && mbi.RegionSize >= aDesiredBytesLen) { - return mbi.BaseAddress; - } - - address = reinterpret_cast(mbi.BaseAddress) + mbi.RegionSize; - } - - return nullptr; - } - -#endif // defined(_M_X64) - - template - static PVOID Reserve(HANDLE aProcess, const uint32_t aSize, - const ReserveFnT& aReserveFn, - const ReservationFlags aFlags) - { -#if defined(_M_X64) - if (aFlags & ReservationFlags::eForceFirst2GB) { - size_t curAttempt = 0; - const size_t kMaxAttempts = 8; - - // We loop here because |FindLowRegion| may return a base address that - // is reserved elsewhere before we have had a chance to reserve it - // ourselves. - while (curAttempt < kMaxAttempts) { - PVOID base = FindLowRegion(aProcess, aSize); - if (!base) { - return nullptr; - } - - PVOID result = aReserveFn(aProcess, base, aSize); - if (result) { - return result; - } - - ++curAttempt; - } - - // If we run out of attempts, we fall through to the default case where - // the system chooses any base address it wants. In that case, the hook - // will be set on a best-effort basis. - } -#endif // defined(_M_X64) - - return aReserveFn(aProcess, nullptr, aSize); - } }; class MMPolicyInProcess : public MMPolicyBase @@ -275,14 +167,6 @@ public: return PAGE_EXECUTE_READWRITE; } -#if defined(_M_X64) - bool IsTrampolineSpaceInLowest2GB() const - { - return (mBase + mReservationSize) <= - reinterpret_cast(0x0000000080000000ULL); - } -#endif // defined(_M_X64) - protected: uint8_t* GetLocalView() const { @@ -298,8 +182,7 @@ protected: /** * @return the effective number of bytes reserved, or 0 on failure */ - uint32_t Reserve(const uint32_t aSize, - const ReservationFlags aFlags) + uint32_t Reserve(const uint32_t aSize) { if (!aSize) { return 0; @@ -311,19 +194,11 @@ protected: } mReservationSize = ComputeAllocationSize(aSize); - - auto reserveFn = [](HANDLE aProcess, PVOID aBase, uint32_t aSize) -> PVOID { - return ::VirtualAlloc(aBase, aSize, MEM_RESERVE, PAGE_NOACCESS); - }; - - mBase = static_cast( - MMPolicyBase::Reserve(::GetCurrentProcess(), mReservationSize, reserveFn, - aFlags)); - + mBase = static_cast(::VirtualAlloc(nullptr, mReservationSize, + MEM_RESERVE, PAGE_NOACCESS)); if (!mBase) { return 0; } - return mReservationSize; } @@ -513,13 +388,6 @@ public: return PAGE_READWRITE; } -#if defined(_M_X64) - bool IsTrampolineSpaceInLowest2GB() const - { - return (GetRemoteView() + mReservationSize) <= 0x0000000080000000ULL; - } -#endif // defined(_M_X64) - protected: uint8_t* GetLocalView() const { @@ -534,8 +402,7 @@ protected: /** * @return the effective number of bytes reserved, or 0 on failure */ - uint32_t Reserve(const uint32_t aSize, - const ReservationFlags aFlags) + uint32_t Reserve(const uint32_t aSize) { if (!aSize || !mProcess) { return 0; @@ -561,14 +428,8 @@ protected: return 0; } - auto reserveFn = [mapping = mMapping](HANDLE aProcess, PVOID aBase, - uint32_t aSize) -> PVOID { - return mozilla::MapRemoteViewOfFile(mapping, aProcess, 0ULL, aBase, 0, 0, - PAGE_EXECUTE_READ); - }; - - mRemoteView = MMPolicyBase::Reserve(mProcess, mReservationSize, reserveFn, - aFlags); + mRemoteView = MapRemoteViewOfFile(mMapping, mProcess, 0ULL, + nullptr, 0, 0, PAGE_EXECUTE_READ); if (!mRemoteView) { return 0; } diff --git a/mozglue/misc/interceptor/PatcherDetour.h b/mozglue/misc/interceptor/PatcherDetour.h index c2e45dd55000..95d05c4bd217 100644 --- a/mozglue/misc/interceptor/PatcherDetour.h +++ b/mozglue/misc/interceptor/PatcherDetour.h @@ -11,7 +11,6 @@ #include "mozilla/interceptor/Trampoline.h" #include "mozilla/ScopeExit.h" -#include "mozilla/TypedEnumBits.h" #define COPY_CODES(NBYTES) do { \ tramp.CopyFrom(origBytes.GetAddress(), NBYTES); \ @@ -21,26 +20,15 @@ namespace mozilla { namespace interceptor { -enum class DetourFlags : uint32_t -{ - eDefault = 0, - eEnable10BytePatch = 1, // Allow 10-byte patches when conditions allow - eTestOnlyForce10BytePatch = 3, // Force 10-byte patches at all times (testing only) -}; - -MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DetourFlags) - template class WindowsDllDetourPatcher final : public WindowsDllPatcherBase { typedef typename VMPolicy::MMPolicyT MMPolicyT; - DetourFlags mFlags; public: template explicit WindowsDllDetourPatcher(Args... aArgs) : WindowsDllPatcherBase(std::forward(aArgs)...) - , mFlags(DetourFlags::eDefault) { } @@ -128,19 +116,25 @@ public: if (!origBytes) { continue; } - - origBytes.Commit(); #elif defined(_M_X64) - if (opcode1 == 0x49) { - if (!Clear13BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) { - continue; - } - } else if (opcode1 == 0xB8) { - if (!Clear10BytePatch(origBytes)) { - continue; - } - } else { - MOZ_ASSERT_UNREACHABLE("Unrecognized patch!"); + // Ensure the MOV R11 from CreateTrampoline is where we expect it to be. + MOZ_ASSERT(opcode1 == 0x49); + if (opcode1 != 0x49) { + continue; + } + + Maybe maybeOpcode2 = origBytes.ReadByte(); + if (!maybeOpcode2) { + continue; + } + + uint8_t opcode2 = maybeOpcode2.value(); + if (opcode2 != 0xBB) { + continue; + } + + origBytes.WritePointer(tramp.GetCurrentRemoteAddress()); + if (!origBytes) { continue; } #elif defined(_M_ARM64) @@ -148,85 +142,19 @@ public: #else #error "Unknown processor type" #endif + + origBytes.Commit(); } this->mVMPolicy.Clear(); } - bool Clear13BytePatch(WritableTargetFunction& aOrigBytes, - const uintptr_t aResetToAddress) - { - Maybe maybeOpcode2 = aOrigBytes.ReadByte(); - if (!maybeOpcode2) { - return false; - } - - uint8_t opcode2 = maybeOpcode2.value(); - if (opcode2 != 0xBB) { - return false; - } - - aOrigBytes.WritePointer(aResetToAddress); - if (!aOrigBytes) { - return false; - } - - return aOrigBytes.Commit(); - } - - bool Clear10BytePatch(WritableTargetFunction& aOrigBytes) - { - Maybe maybePtr32 = aOrigBytes.ReadLong(); - if (!maybePtr32) { - return false; - } - - uint32_t ptr32 = maybePtr32.value(); - // We expect the high bit to be clear - if (ptr32 & 0x80000000) { - return false; - } - - uintptr_t trampPtr = ptr32; - - // trampPtr points to an intermediate trampoline that contains a 13-byte - // patch. We back up by sizeof(uintptr_t) so that we can access the pointer - // to the stub trampoline. - WritableTargetFunction writableIntermediate(this->mVMPolicy, - trampPtr - sizeof(uintptr_t), 13 + sizeof(uintptr_t)); - if (!writableIntermediate) { - return false; - } - - Maybe stubTramp = writableIntermediate.ReadEncodedPtr(); - if (!stubTramp || !stubTramp.value()) { - return false; - } - - Maybe maybeOpcode1 = writableIntermediate.ReadByte(); - if (!maybeOpcode1) { - return false; - } - - // We expect this opcode to be the beginning of our normal mov r11, ptr - // patch sequence. - uint8_t opcode1 = maybeOpcode1.value(); - if (opcode1 != 0x49) { - return false; - } - - // Now we can just delegate the rest to our normal 13-byte patch clearing. - return Clear13BytePatch(writableIntermediate, stubTramp.value()); - } - - void Init(DetourFlags aFlags = DetourFlags::eDefault, int aNumHooks = 0) + void Init(int aNumHooks = 0) { if (Initialized()) { return; } - mFlags = aFlags; - if (aNumHooks == 0) { // Win32 allocates VM addresses at a 64KiB granularity, so by default we // might as well utilize that entire 64KiB reservation instead of @@ -234,12 +162,7 @@ public: aNumHooks = this->mVMPolicy.GetAllocGranularity() / kHookSize; } - ReservationFlags resFlags = ReservationFlags::eDefault; - if (aFlags & DetourFlags::eEnable10BytePatch) { - resFlags |= ReservationFlags::eForceFirst2GB; - } - - this->mVMPolicy.Reserve(aNumHooks, resFlags); + this->mVMPolicy.Reserve(aNumHooks); } bool Initialized() const @@ -622,15 +545,8 @@ protected: } #elif defined(_M_X64) bool foundJmp = false; - // |use10BytePatch| should always default to |false| in production. It is - // not set to true unless we detect that a 10-byte patch is necessary. - // OTOH, for testing purposes, if we want to force a 10-byte patch, we - // always initialize |use10BytePatch| to |true|. - bool use10BytePatch = (mFlags & DetourFlags::eTestOnlyForce10BytePatch) == - DetourFlags::eTestOnlyForce10BytePatch; - const uint32_t bytesRequired = use10BytePatch ? 10 : 13; - while (origBytes.GetOffset() < bytesRequired) { + while (origBytes.GetOffset() < 13) { // If we found JMP 32bit offset, we require that the next bytes must // be NOP or INT3. There is no reason to copy them. // TODO: This used to trigger for Je as well. Now that I allow @@ -644,15 +560,6 @@ protected: ++origBytes; continue; } - - // If our trampoline space is located in the lowest 2GB, we can do a ten - // byte patch instead of a thirteen byte patch. - if (this->mVMPolicy.IsTrampolineSpaceInLowest2GB() && - origBytes.GetOffset() >= 10) { - use10BytePatch = true; - break; - } - MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP"); return; } @@ -1043,18 +950,6 @@ protected: } else if ((origBytes[1] & (kMaskMod|kMaskReg)) == BuildModRmByte(kModReg, 2, 0)) { // CALL reg (ff nn) COPY_CODES(2); - } else if (((origBytes[1] & kMaskReg) >> kRegFieldShift) == 4) { - // JMP r/m - int len = CountModRmSib(origBytes + 1); - if (len < 0) { - // RIP-relative not yet supported - MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence"); - return; - } - - COPY_CODES(len + 1); - - foundJmp = true; } else { MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence"); return; @@ -1110,8 +1005,8 @@ protected: #endif // The trampoline is now complete. - void* trampPtr = tramp.EndExecutableCode(); - if (!trampPtr) { + *aOutTramp = tramp.EndExecutableCode(); + if (!(*aOutTramp)) { return; } @@ -1125,69 +1020,18 @@ protected: target.WriteByte(0xe9); //jmp target.WriteDisp32(aDest); // hook displacement #elif defined(_M_X64) - if (use10BytePatch) { - // Okay, now we can write the actual tramp. - // Note: Even if the target function is also below 2GB, we still use an - // intermediary trampoline so that we consistently have a 64-bit pointer - // that we can use to reset the trampoline upon interceptor shutdown. - Trampoline callTramp(this->mVMPolicy.GetNextTrampoline()); - if (!callTramp) { - return; - } + // mov r11, address + target.WriteByte(0x49); + target.WriteByte(0xbb); + target.WritePointer(aDest); - // Write a null instance so that Clear() does not consider this tramp to - // be a normal tramp to be torn down. - callTramp.WriteEncodedPointer(nullptr); - // Use the second pointer slot to store a pointer to the primary tramp - callTramp.WriteEncodedPointer(trampPtr); - callTramp.StartExecutableCode(); - - // mov r11, address - callTramp.WriteByte(0x49); - callTramp.WriteByte(0xbb); - callTramp.WritePointer(aDest); - - // jmp r11 - callTramp.WriteByte(0x41); - callTramp.WriteByte(0xff); - callTramp.WriteByte(0xe3); - - void* callTrampStart = callTramp.EndExecutableCode(); - if (!callTrampStart) { - return; - } - - target.WriteByte(0xB8); // MOV EAX, IMM32 - - // Assert that the topmost 33 bits are 0 - MOZ_ASSERT(!(reinterpret_cast(callTrampStart) & (~0x7FFFFFFFULL))); - - target.WriteLong(static_cast(reinterpret_cast(callTrampStart) & 0x7FFFFFFFU)); - target.WriteByte(0x48); // REX.W - target.WriteByte(0x63); // MOVSXD r64, r/m32 - // dest: rax, src: eax - target.WriteByte(BuildModRmByte(kModReg, kRegAx, kRegAx)); - target.WriteByte(0xFF); // JMP /4 - target.WriteByte(BuildModRmByte(kModReg, 4, kRegAx)); // rax - } else { - // mov r11, address - target.WriteByte(0x49); - target.WriteByte(0xbb); - target.WritePointer(aDest); - - // jmp r11 - target.WriteByte(0x41); - target.WriteByte(0xff); - target.WriteByte(0xe3); - } + // jmp r11 + target.WriteByte(0x41); + target.WriteByte(0xff); + target.WriteByte(0xe3); #endif - if (!target.Commit()) { - return; - } - - // Output the trampoline, thus signalling that this call was a success - *aOutTramp = trampPtr; + target.Commit(); } }; diff --git a/mozglue/misc/interceptor/TargetFunction.h b/mozglue/misc/interceptor/TargetFunction.h index 46a16c58d1aa..33bf119eadd5 100644 --- a/mozglue/misc/interceptor/TargetFunction.h +++ b/mozglue/misc/interceptor/TargetFunction.h @@ -41,10 +41,6 @@ CommitAndWriteShortInternal( #endif // defined(_M_IX86) -// Forward declaration -template -class ReadOnlyTargetFunction; - template class MOZ_STACK_CLASS WritableTargetFunction final { @@ -246,48 +242,6 @@ public: return Some(value); } - Maybe ReadEncodedPtr() - { - // Reading is only permitted prior to any writing - MOZ_ASSERT(mOffset == mStartWriteOffset); - if (mOffset > mStartWriteOffset) { - mAccumulatedStatus = false; - return Nothing(); - } - - uintptr_t value; - if (!mMMPolicy.Read(&value, reinterpret_cast(mFunc + mOffset), - sizeof(uintptr_t))) { - mAccumulatedStatus = false; - return Nothing(); - } - - mOffset += sizeof(uintptr_t); - mStartWriteOffset += sizeof(uintptr_t); - return Some(ReadOnlyTargetFunction::DecodePtr(value)); - } - - Maybe ReadLong() - { - // Reading is only permitted prior to any writing - MOZ_ASSERT(mOffset == mStartWriteOffset); - if (mOffset > mStartWriteOffset) { - mAccumulatedStatus = false; - return Nothing(); - } - - uint32_t value; - if (!mMMPolicy.Read(&value, reinterpret_cast(mFunc + mOffset), - sizeof(uint32_t))) { - mAccumulatedStatus = false; - return Nothing(); - } - - mOffset += sizeof(uint32_t); - mStartWriteOffset += sizeof(uint32_t); - return Some(value); - } - void WriteShort(const uint16_t& aValue) { if (!mLocalBytes.append(reinterpret_cast(&aValue), @@ -349,19 +303,6 @@ public: mOffset += sizeof(int32_t); } -#if defined(_M_X64) - void WriteLong(const uint32_t aValue) - { - if (!mLocalBytes.append(reinterpret_cast(&aValue), - sizeof(uint32_t))) { - mAccumulatedStatus = false; - return; - } - - mOffset += sizeof(uint32_t); - } -#endif // defined(_M_X64) - void WritePointer(const uintptr_t aAbsTarget) { if (!mLocalBytes.append(reinterpret_cast(&aAbsTarget), diff --git a/mozglue/misc/interceptor/VMSharingPolicies.h b/mozglue/misc/interceptor/VMSharingPolicies.h index 67e5211ffddf..d676db9d79cf 100644 --- a/mozglue/misc/interceptor/VMSharingPolicies.h +++ b/mozglue/misc/interceptor/VMSharingPolicies.h @@ -24,10 +24,10 @@ public: { } - bool Reserve(uint32_t aCount, const ReservationFlags aFlags) + bool Reserve(uint32_t aCount) { MOZ_ASSERT(aCount); - uint32_t bytesReserved = MMPolicy::Reserve(aCount * kChunkSize, aFlags); + uint32_t bytesReserved = MMPolicy::Reserve(aCount * kChunkSize); return !!bytesReserved; } @@ -127,10 +127,10 @@ public: return sUniqueVM.ShouldUnhookUponDestruction(); } - bool Reserve(uint32_t aCount, const ReservationFlags aFlags) + bool Reserve(uint32_t aCount) { AutoCriticalSection lock(&sCS); - return sUniqueVM.Reserve(aCount, aFlags); + return sUniqueVM.Reserve(aCount); } bool IsPageAccessible(void* aVAddress) const @@ -139,14 +139,6 @@ public: return sUniqueVM.IsPageAccessible(aVAddress); } -#if defined(_M_X64) - bool IsTrampolineSpaceInLowest2GB() const - { - AutoCriticalSection lock(&sCS); - return sUniqueVM.IsTrampolineSpaceInLowest2GB(); - } -#endif // defined(_M_X64) - Trampoline GetNextTrampoline() { AutoCriticalSection lock(&sCS); diff --git a/mozglue/misc/nsWindowsDllInterceptor.h b/mozglue/misc/nsWindowsDllInterceptor.h index 86d24c140614..3f9bb16e0dc3 100644 --- a/mozglue/misc/nsWindowsDllInterceptor.h +++ b/mozglue/misc/nsWindowsDllInterceptor.h @@ -371,18 +371,6 @@ public: mNHooks = aNumHooks; } - /** Force a specific configuration for testing purposes. NOT to be used in - production code! **/ - void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags, - int aNumHooks = 0) - { - Init(aModuleName, aNumHooks); - - if (!mDetourPatcher.Initialized()) { - mDetourPatcher.Init(aFlags, mNHooks); - } - } - void Clear() { if (!mModule) { @@ -467,16 +455,7 @@ private: #endif if (!mDetourPatcher.Initialized()) { - DetourFlags flags = DetourFlags::eDefault; -#if defined(_M_X64) - if (mModule == ::GetModuleHandleW(L"ntdll.dll")) { - // NTDLL hooks should attempt to use a 10-byte patch because some - // injected DLLs do the same and interfere with our stuff. - flags |= DetourFlags::eEnable10BytePatch; - } -#endif // defined(_M_X64) - - mDetourPatcher.Init(flags, mNHooks); + mDetourPatcher.Init(mNHooks); } return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc); diff --git a/mozglue/tests/interceptor/TestDllInterceptor.cpp b/mozglue/tests/interceptor/TestDllInterceptor.cpp index f47c7ec435bb..a56ed4d20d6c 100644 --- a/mozglue/tests/interceptor/TestDllInterceptor.cpp +++ b/mozglue/tests/interceptor/TestDllInterceptor.cpp @@ -32,20 +32,6 @@ NTSTATUS NTAPI NtWriteFileGather(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID, NTSTATUS NTAPI NtQueryFullAttributesFile(POBJECT_ATTRIBUTES, PVOID); NTSTATUS NTAPI LdrLoadDll(PWCHAR filePath, PULONG flags, PUNICODE_STRING moduleFileName, PHANDLE handle); NTSTATUS NTAPI LdrUnloadDll(HMODULE); - -enum SECTION_INHERIT -{ - ViewShare = 1, - ViewUnmap = 2 -}; - -NTSTATUS NTAPI -NtMapViewOfSection(HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, - ULONG_PTR aZeroBits, SIZE_T aCommitSize, - PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize, - SECTION_INHERIT aInheritDisposition, ULONG aAllocationType, - ULONG aProtectionFlags); - // These pointers are disguised as PVOID to avoid pulling in obscure headers PVOID NTAPI LdrResolveDelayLoadedAPI(PVOID, PVOID, PVOID, PVOID, PVOID, ULONG); void CALLBACK ProcessCaretEvents(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD, DWORD); @@ -96,11 +82,11 @@ template bool TestFunction(CallableT aFunc); #define DEFINE_TEST_FUNCTION(calling_convention) \ - template