summaryrefslogtreecommitdiffstats
path: root/mozglue/misc/NativeNt.h
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 01:47:29 +0000
commit0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d (patch)
treea31f07c9bcca9d56ce61e9a1ffd30ef350d513aa /mozglue/misc/NativeNt.h
parentInitial commit. (diff)
downloadfirefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.tar.xz
firefox-esr-0ebf5bdf043a27fd3dfb7f92e0cb63d88954c44d.zip
Adding upstream version 115.8.0esr.upstream/115.8.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mozglue/misc/NativeNt.h')
-rw-r--r--mozglue/misc/NativeNt.h1729
1 files changed, 1729 insertions, 0 deletions
diff --git a/mozglue/misc/NativeNt.h b/mozglue/misc/NativeNt.h
new file mode 100644
index 0000000000..439ef3d12a
--- /dev/null
+++ b/mozglue/misc/NativeNt.h
@@ -0,0 +1,1729 @@
+/* -*- 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_NativeNt_h
+#define mozilla_NativeNt_h
+
+#include <stdint.h>
+#include <windows.h>
+#include <winnt.h>
+#include <winternl.h>
+
+#include <algorithm>
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Range.h"
+#include "mozilla/Span.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/TargetFunction.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "nsString.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// The declarations within this #if block are intended to be used for initial
+// process initialization ONLY. You probably don't want to be using these in
+// normal Gecko code!
+#if !defined(MOZILLA_INTERNAL_API)
+
+extern "C" {
+
+# if !defined(STATUS_ACCESS_DENIED)
+# define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
+# endif // !defined(STATUS_ACCESS_DENIED)
+
+# if !defined(STATUS_DLL_NOT_FOUND)
+# define STATUS_DLL_NOT_FOUND ((NTSTATUS)0xC0000135L)
+# endif // !defined(STATUS_DLL_NOT_FOUND)
+
+# if !defined(STATUS_UNSUCCESSFUL)
+# define STATUS_UNSUCCESSFUL ((NTSTATUS)0xC0000001L)
+# endif // !defined(STATUS_UNSUCCESSFUL)
+
+# if !defined(STATUS_INFO_LENGTH_MISMATCH)
+# define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
+# endif
+
+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);
+
+NTSTATUS NTAPI NtUnmapViewOfSection(HANDLE aProcess, PVOID aBaseAddress);
+
+enum MEMORY_INFORMATION_CLASS {
+ MemoryBasicInformation = 0,
+ MemorySectionName = 2
+};
+
+// NB: When allocating, space for the buffer must also be included
+typedef struct _MEMORY_SECTION_NAME {
+ UNICODE_STRING mSectionFileName;
+} MEMORY_SECTION_NAME, *PMEMORY_SECTION_NAME;
+
+NTSTATUS NTAPI NtQueryVirtualMemory(HANDLE aProcess, PVOID aBaseAddress,
+ MEMORY_INFORMATION_CLASS aMemInfoClass,
+ PVOID aMemInfo, SIZE_T aMemInfoLen,
+ PSIZE_T aReturnLen);
+
+LONG NTAPI RtlCompareUnicodeString(PCUNICODE_STRING aStr1,
+ PCUNICODE_STRING aStr2,
+ BOOLEAN aCaseInsensitive);
+
+BOOLEAN NTAPI RtlEqualUnicodeString(PCUNICODE_STRING aStr1,
+ PCUNICODE_STRING aStr2,
+ BOOLEAN aCaseInsensitive);
+
+NTSTATUS NTAPI RtlGetVersion(PRTL_OSVERSIONINFOW aOutVersionInformation);
+
+VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
+VOID NTAPI RtlAcquireSRWLockShared(PSRWLOCK aLock);
+
+VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
+VOID NTAPI RtlReleaseSRWLockShared(PSRWLOCK aLock);
+
+ULONG NTAPI RtlNtStatusToDosError(NTSTATUS aStatus);
+VOID NTAPI RtlSetLastWin32Error(DWORD aError);
+DWORD NTAPI RtlGetLastWin32Error();
+
+VOID NTAPI RtlRunOnceInitialize(PRTL_RUN_ONCE aRunOnce);
+
+NTSTATUS NTAPI NtReadVirtualMemory(HANDLE aProcessHandle, PVOID aBaseAddress,
+ PVOID aBuffer, SIZE_T aNumBytesToRead,
+ PSIZE_T aNumBytesRead);
+
+NTSTATUS NTAPI LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
+ PUNICODE_STRING aDllName, PHANDLE aOutHandle);
+
+typedef ULONG(NTAPI* PRTL_RUN_ONCE_INIT_FN)(PRTL_RUN_ONCE, PVOID, PVOID*);
+NTSTATUS NTAPI RtlRunOnceExecuteOnce(PRTL_RUN_ONCE aRunOnce,
+ PRTL_RUN_ONCE_INIT_FN aInitFn,
+ PVOID aContext, PVOID* aParameter);
+
+} // extern "C"
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+extern "C" {
+PVOID NTAPI RtlAllocateHeap(PVOID aHeapHandle, ULONG aFlags, SIZE_T aSize);
+
+PVOID NTAPI RtlReAllocateHeap(PVOID aHeapHandle, ULONG aFlags, LPVOID aMem,
+ SIZE_T aNewSize);
+
+BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
+
+BOOLEAN NTAPI RtlQueryPerformanceCounter(LARGE_INTEGER* aPerfCount);
+
+#define RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE 1
+#define RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING 2
+NTSTATUS NTAPI RtlDuplicateUnicodeString(ULONG aFlags, PCUNICODE_STRING aSrc,
+ PUNICODE_STRING aDest);
+
+VOID NTAPI RtlFreeUnicodeString(PUNICODE_STRING aUnicodeString);
+} // extern "C"
+
+namespace mozilla {
+namespace nt {
+
+/**
+ * This class encapsulates a UNICODE_STRING that owns its own buffer. The
+ * buffer is always NULL terminated, thus allowing us to cast to a wide C-string
+ * without requiring any mutation.
+ *
+ * We only allow creation of this owned buffer from outside XUL.
+ */
+class AllocatedUnicodeString final {
+ public:
+ AllocatedUnicodeString() : mUnicodeString() {}
+
+#if defined(MOZILLA_INTERNAL_API)
+ AllocatedUnicodeString(const AllocatedUnicodeString& aOther) = delete;
+
+ AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) =
+ delete;
+#else
+ explicit AllocatedUnicodeString(PCUNICODE_STRING aSrc) {
+ if (!aSrc) {
+ mUnicodeString = {};
+ return;
+ }
+
+ Duplicate(aSrc);
+ }
+
+ explicit AllocatedUnicodeString(const char* aSrc) {
+ if (!aSrc) {
+ mUnicodeString = {};
+ return;
+ }
+
+ Duplicate(aSrc);
+ }
+
+ AllocatedUnicodeString(const AllocatedUnicodeString& aOther) {
+ Duplicate(&aOther.mUnicodeString);
+ }
+
+ AllocatedUnicodeString& operator=(const AllocatedUnicodeString& aOther) {
+ Clear();
+ Duplicate(&aOther.mUnicodeString);
+ return *this;
+ }
+
+ AllocatedUnicodeString& operator=(PCUNICODE_STRING aSrc) {
+ Clear();
+ Duplicate(aSrc);
+ return *this;
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ AllocatedUnicodeString(AllocatedUnicodeString&& aOther)
+ : mUnicodeString(aOther.mUnicodeString) {
+ aOther.mUnicodeString = {};
+ }
+
+ AllocatedUnicodeString& operator=(AllocatedUnicodeString&& aOther) {
+ Clear();
+ mUnicodeString = aOther.mUnicodeString;
+ aOther.mUnicodeString = {};
+ return *this;
+ }
+
+ ~AllocatedUnicodeString() { Clear(); }
+
+ bool IsEmpty() const {
+ return !mUnicodeString.Buffer || !mUnicodeString.Length;
+ }
+
+ operator PCUNICODE_STRING() const { return &mUnicodeString; }
+
+ operator const WCHAR*() const { return mUnicodeString.Buffer; }
+
+ USHORT CharLen() const { return mUnicodeString.Length / sizeof(WCHAR); }
+
+#if defined(MOZILLA_INTERNAL_API)
+ nsDependentString AsString() const {
+ if (!mUnicodeString.Buffer) {
+ return nsDependentString();
+ }
+
+ // We can use nsDependentString here as we guaranteed null termination
+ // when we allocated the string.
+ return nsDependentString(mUnicodeString.Buffer, CharLen());
+ }
+#endif // defined(MOZILLA_INTERNAL_API)
+
+ private:
+#if !defined(MOZILLA_INTERNAL_API)
+ void Duplicate(PCUNICODE_STRING aSrc) {
+ MOZ_ASSERT(aSrc);
+
+ // We duplicate with null termination so that this string may be used
+ // as a wide C-string without any further manipulation.
+ NTSTATUS ntStatus = ::RtlDuplicateUnicodeString(
+ RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE, aSrc, &mUnicodeString);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+ if (!NT_SUCCESS(ntStatus)) {
+ // Make sure that mUnicodeString does not contain bogus data
+ // (since not all callers zero it out before invoking)
+ mUnicodeString = {};
+ }
+ }
+
+ void Duplicate(const char* aSrc) {
+ MOZ_ASSERT(aSrc);
+
+ ANSI_STRING ansiStr;
+ RtlInitAnsiString(&ansiStr, aSrc);
+ NTSTATUS ntStatus =
+ ::RtlAnsiStringToUnicodeString(&mUnicodeString, &ansiStr, TRUE);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+ if (!NT_SUCCESS(ntStatus)) {
+ mUnicodeString = {};
+ }
+ }
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+ void Clear() {
+ if (!mUnicodeString.Buffer) {
+ return;
+ }
+
+ ::RtlFreeUnicodeString(&mUnicodeString);
+ mUnicodeString = {};
+ }
+
+ UNICODE_STRING mUnicodeString;
+};
+
+#if !defined(MOZILLA_INTERNAL_API)
+
+struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
+ MemorySectionNameBuf() {
+ mSectionFileName.Length = 0;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ mSectionFileName.Buffer = mBuf;
+ }
+
+ MemorySectionNameBuf(const MemorySectionNameBuf& aOther) { *this = aOther; }
+
+ MemorySectionNameBuf(MemorySectionNameBuf&& aOther) {
+ *this = std::move(aOther);
+ }
+
+ // We cannot use default copy here because mSectionFileName.Buffer needs to
+ // be updated to point to |this->mBuf|, not |aOther.mBuf|.
+ MemorySectionNameBuf& operator=(const MemorySectionNameBuf& aOther) {
+ mSectionFileName.Length = aOther.mSectionFileName.Length;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength);
+ mSectionFileName.Buffer = mBuf;
+ memcpy(mBuf, aOther.mBuf, aOther.mSectionFileName.Length);
+ return *this;
+ }
+
+ MemorySectionNameBuf& operator=(MemorySectionNameBuf&& aOther) {
+ mSectionFileName.Length = aOther.mSectionFileName.Length;
+ aOther.mSectionFileName.Length = 0;
+ mSectionFileName.MaximumLength = sizeof(mBuf);
+ MOZ_ASSERT(mSectionFileName.Length <= mSectionFileName.MaximumLength);
+ aOther.mSectionFileName.MaximumLength = sizeof(aOther.mBuf);
+ mSectionFileName.Buffer = mBuf;
+ memmove(mBuf, aOther.mBuf, mSectionFileName.Length);
+ return *this;
+ }
+
+ // Native NT paths, so we can't assume MAX_PATH. Use a larger buffer.
+ WCHAR mBuf[2 * MAX_PATH];
+
+ bool IsEmpty() const {
+ return !mSectionFileName.Buffer || !mSectionFileName.Length;
+ }
+
+ operator PCUNICODE_STRING() const { return &mSectionFileName; }
+};
+
+class MemorySectionNameOnHeap {
+ UniquePtr<uint8_t[]> mBuffer;
+
+ MemorySectionNameOnHeap() = default;
+ explicit MemorySectionNameOnHeap(size_t aBufferLen)
+ : mBuffer(MakeUnique<uint8_t[]>(aBufferLen)) {}
+
+ public:
+ static MemorySectionNameOnHeap GetBackingFilePath(HANDLE aProcess,
+ void* aSectionAddr) {
+ SIZE_T bufferLen = MAX_PATH * 2;
+ do {
+ MemorySectionNameOnHeap sectionName(bufferLen);
+
+ SIZE_T requiredBytes;
+ NTSTATUS ntStatus = ::NtQueryVirtualMemory(
+ aProcess, aSectionAddr, MemorySectionName, sectionName.mBuffer.get(),
+ bufferLen, &requiredBytes);
+ if (NT_SUCCESS(ntStatus)) {
+ return sectionName;
+ }
+
+ if (ntStatus != STATUS_INFO_LENGTH_MISMATCH ||
+ bufferLen >= requiredBytes) {
+ break;
+ }
+
+ bufferLen = requiredBytes;
+ } while (1);
+
+ return MemorySectionNameOnHeap();
+ }
+
+ // Allow move & Disallow copy
+ MemorySectionNameOnHeap(MemorySectionNameOnHeap&&) = default;
+ MemorySectionNameOnHeap& operator=(MemorySectionNameOnHeap&&) = default;
+ MemorySectionNameOnHeap(const MemorySectionNameOnHeap&) = delete;
+ MemorySectionNameOnHeap& operator=(const MemorySectionNameOnHeap&) = delete;
+
+ PCUNICODE_STRING AsUnicodeString() const {
+ return reinterpret_cast<PCUNICODE_STRING>(mBuffer.get());
+ }
+};
+
+inline bool FindCharInUnicodeString(const UNICODE_STRING& aStr, WCHAR aChar,
+ uint16_t& aPos, uint16_t aStartIndex = 0) {
+ const uint16_t aMaxIndex = aStr.Length / sizeof(WCHAR);
+
+ for (uint16_t curIndex = aStartIndex; curIndex < aMaxIndex; ++curIndex) {
+ if (aStr.Buffer[curIndex] == aChar) {
+ aPos = curIndex;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline bool IsHexDigit(WCHAR aChar) {
+ return (aChar >= L'0' && aChar <= L'9') || (aChar >= L'A' && aChar <= L'F') ||
+ (aChar >= L'a' && aChar <= L'f');
+}
+
+inline bool MatchUnicodeString(const UNICODE_STRING& aStr,
+ bool (*aPredicate)(WCHAR)) {
+ WCHAR* cur = aStr.Buffer;
+ WCHAR* end = &aStr.Buffer[aStr.Length / sizeof(WCHAR)];
+ while (cur < end) {
+ if (!aPredicate(*cur)) {
+ return false;
+ }
+
+ ++cur;
+ }
+
+ return true;
+}
+
+inline bool Contains12DigitHexString(const UNICODE_STRING& aLeafName) {
+ // Quick check: If the string is too short, don't bother
+ // (We need at least 12 hex digits, one char for '.', and 3 for extension)
+ const USHORT kMinLen = (12 + 1 + 3) * sizeof(wchar_t);
+ if (aLeafName.Length < kMinLen) {
+ return false;
+ }
+
+ uint16_t start, end;
+ if (!FindCharInUnicodeString(aLeafName, L'.', start)) {
+ return false;
+ }
+
+ ++start;
+ if (!FindCharInUnicodeString(aLeafName, L'.', end, start)) {
+ return false;
+ }
+
+ if (end - start != 12) {
+ return false;
+ }
+
+ UNICODE_STRING test;
+ test.Buffer = &aLeafName.Buffer[start];
+ test.Length = (end - start) * sizeof(WCHAR);
+ test.MaximumLength = test.Length;
+
+ return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline bool IsFileNameAtLeast16HexDigits(const UNICODE_STRING& aLeafName) {
+ // Quick check: If the string is too short, don't bother
+ // (We need 16 hex digits, one char for '.', and 3 for extension)
+ const USHORT kMinLen = (16 + 1 + 3) * sizeof(wchar_t);
+ if (aLeafName.Length < kMinLen) {
+ return false;
+ }
+
+ uint16_t dotIndex;
+ if (!FindCharInUnicodeString(aLeafName, L'.', dotIndex)) {
+ return false;
+ }
+
+ if (dotIndex < 16) {
+ return false;
+ }
+
+ UNICODE_STRING test;
+ test.Buffer = aLeafName.Buffer;
+ test.Length = dotIndex * sizeof(WCHAR);
+ test.MaximumLength = aLeafName.MaximumLength;
+
+ return MatchUnicodeString(test, &IsHexDigit);
+}
+
+inline void GetLeafName(PUNICODE_STRING aDestString,
+ PCUNICODE_STRING aSrcString) {
+ WCHAR* buf = aSrcString->Buffer;
+ WCHAR* end = &aSrcString->Buffer[(aSrcString->Length / sizeof(WCHAR)) - 1];
+ WCHAR* cur = end;
+ while (cur >= buf) {
+ if (*cur == L'\\') {
+ break;
+ }
+
+ --cur;
+ }
+
+ // At this point, either cur points to the final backslash, or it points to
+ // buf - 1. Either way, we're interested in cur + 1 as the desired buffer.
+ aDestString->Buffer = cur + 1;
+ aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR);
+ aDestString->MaximumLength = aDestString->Length;
+}
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+#if defined(MOZILLA_INTERNAL_API)
+
+inline const nsDependentSubstring GetLeafName(const nsAString& aString) {
+ auto it = aString.EndReading();
+ size_t pos = aString.Length();
+ while (it > aString.BeginReading()) {
+ if (*(it - 1) == u'\\') {
+ return Substring(aString, pos);
+ }
+
+ MOZ_ASSERT(pos > 0);
+ --pos;
+ --it;
+ }
+
+ return Substring(aString, 0); // No backslash in the string
+}
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+inline char EnsureLowerCaseASCII(char aChar) {
+ if (aChar >= 'A' && aChar <= 'Z') {
+ aChar -= 'A' - 'a';
+ }
+
+ return aChar;
+}
+
+inline int StricmpASCII(const char* aLeft, const char* aRight) {
+ char curLeft, curRight;
+
+ do {
+ curLeft = EnsureLowerCaseASCII(*(aLeft++));
+ curRight = EnsureLowerCaseASCII(*(aRight++));
+ } while (curLeft && curLeft == curRight);
+
+ return curLeft - curRight;
+}
+
+inline int StrcmpASCII(const char* aLeft, const char* aRight) {
+ char curLeft, curRight;
+
+ do {
+ curLeft = *(aLeft++);
+ curRight = *(aRight++);
+ } while (curLeft && curLeft == curRight);
+
+ return curLeft - curRight;
+}
+
+inline size_t StrlenASCII(const char* aStr) {
+ size_t len = 0;
+
+ while (*(aStr++)) {
+ ++len;
+ }
+
+ return len;
+}
+
+struct CodeViewRecord70 {
+ uint32_t signature;
+ GUID pdbSignature;
+ uint32_t pdbAge;
+ // A UTF-8 string, according to
+ // https://github.com/Microsoft/microsoft-pdb/blob/082c5290e5aff028ae84e43affa8be717aa7af73/PDB/dbi/locator.cpp#L785
+ char pdbFileName[1];
+};
+
+class MOZ_RAII PEHeaders final {
+ /**
+ * This structure is documented on MSDN as VS_VERSIONINFO, but is not present
+ * in SDK headers because it cannot be specified as a C struct. The following
+ * structure contains the fixed-length fields at the beginning of
+ * VS_VERSIONINFO.
+ */
+ struct VS_VERSIONINFO_HEADER {
+ WORD wLength;
+ WORD wValueLength;
+ WORD wType;
+ WCHAR szKey[16]; // ArrayLength(L"VS_VERSION_INFO")
+ // Additional data goes here, aligned on a 4-byte boundary
+ };
+
+ public:
+ // The lowest two bits of an HMODULE are used as flags. Stripping those bits
+ // from the HMODULE yields the base address of the binary's memory mapping.
+ // (See LoadLibraryEx docs on MSDN)
+ template <typename T>
+ static T HModuleToBaseAddr(HMODULE aModule) {
+ return reinterpret_cast<T>(reinterpret_cast<uintptr_t>(aModule) &
+ ~uintptr_t(3));
+ }
+
+ explicit PEHeaders(void* aBaseAddress)
+ : PEHeaders(reinterpret_cast<PIMAGE_DOS_HEADER>(aBaseAddress)) {}
+
+ explicit PEHeaders(HMODULE aModule)
+ : PEHeaders(HModuleToBaseAddr<PIMAGE_DOS_HEADER>(aModule)) {}
+
+ explicit PEHeaders(PIMAGE_DOS_HEADER aMzHeader)
+ : mMzHeader(aMzHeader),
+ mPeHeader(nullptr),
+ mImageLimit(nullptr),
+ mIsImportDirectoryTampered(false) {
+ if (!mMzHeader || mMzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return;
+ }
+
+ mPeHeader = RVAToPtrUnchecked<PIMAGE_NT_HEADERS>(mMzHeader->e_lfanew);
+ if (!mPeHeader || mPeHeader->Signature != IMAGE_NT_SIGNATURE) {
+ return;
+ }
+
+ if (mPeHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
+ return;
+ }
+
+ DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
+ // This is a coarse-grained check to ensure that the image size is
+ // reasonable. It we aren't big enough to contain headers, we have a
+ // problem!
+ if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
+ return;
+ }
+
+ mImageLimit = RVAToPtrUnchecked<void*>(imageSize - 1UL);
+
+ PIMAGE_DATA_DIRECTORY importDirEntry =
+ GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_IMPORT);
+ if (!importDirEntry) {
+ return;
+ }
+
+ mIsImportDirectoryTampered = (importDirEntry->VirtualAddress >= imageSize);
+ }
+
+ explicit operator bool() const { return !!mImageLimit; }
+
+ /**
+ * This overload computes absolute virtual addresses relative to the base
+ * address of the binary.
+ */
+ template <typename T, typename R>
+ T RVAToPtr(R aRva) const {
+ return RVAToPtr<T>(mMzHeader, aRva);
+ }
+
+ /**
+ * This overload computes a result by adding aRva to aBase, but also ensures
+ * that the resulting pointer falls within the bounds of this binary's memory
+ * mapping.
+ */
+ template <typename T, typename R>
+ T RVAToPtr(void* aBase, R aRva) const {
+ if (!mImageLimit) {
+ return nullptr;
+ }
+
+ char* absAddress = reinterpret_cast<char*>(aBase) + aRva;
+ if (absAddress < reinterpret_cast<char*>(mMzHeader) ||
+ absAddress > reinterpret_cast<char*>(mImageLimit)) {
+ return nullptr;
+ }
+
+ return reinterpret_cast<T>(absAddress);
+ }
+
+ Maybe<Range<const uint8_t>> GetBounds() const {
+ if (!mImageLimit) {
+ return Nothing();
+ }
+
+ auto base = reinterpret_cast<const uint8_t*>(mMzHeader);
+ DWORD imageSize = mPeHeader->OptionalHeader.SizeOfImage;
+ return Some(Range(base, imageSize));
+ }
+
+ DWORD GetFileCharacteristics() const {
+ return mPeHeader ? mPeHeader->FileHeader.Characteristics : 0;
+ }
+
+ bool IsWithinImage(const void* aAddress) const {
+ uintptr_t addr = reinterpret_cast<uintptr_t>(aAddress);
+ uintptr_t imageBase = reinterpret_cast<uintptr_t>(mMzHeader);
+ uintptr_t imageLimit = reinterpret_cast<uintptr_t>(mImageLimit);
+ return addr >= imageBase && addr <= imageLimit;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR GetImportDirectory() const {
+ // If the import directory is already tampered, we skip bounds check
+ // because it could be located outside the mapped image.
+ return mIsImportDirectoryTampered
+ ? GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR,
+ BoundsCheckPolicy::Skip>(
+ IMAGE_DIRECTORY_ENTRY_IMPORT)
+ : GetImageDirectoryEntry<PIMAGE_IMPORT_DESCRIPTOR>(
+ IMAGE_DIRECTORY_ENTRY_IMPORT);
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY GetResourceTable() const {
+ return GetImageDirectoryEntry<PIMAGE_RESOURCE_DIRECTORY>(
+ IMAGE_DIRECTORY_ENTRY_RESOURCE);
+ }
+
+ PIMAGE_DATA_DIRECTORY GetImageDirectoryEntryPtr(
+ const uint32_t aDirectoryIndex, uint32_t* aOutRva = nullptr) const {
+ if (aOutRva) {
+ *aOutRva = 0;
+ }
+
+ IMAGE_OPTIONAL_HEADER& optionalHeader = mPeHeader->OptionalHeader;
+
+ const uint32_t maxIndex = std::min(optionalHeader.NumberOfRvaAndSizes,
+ DWORD(IMAGE_NUMBEROF_DIRECTORY_ENTRIES));
+ if (aDirectoryIndex >= maxIndex) {
+ return nullptr;
+ }
+
+ PIMAGE_DATA_DIRECTORY dirEntry =
+ &optionalHeader.DataDirectory[aDirectoryIndex];
+ if (aOutRva) {
+ *aOutRva = reinterpret_cast<char*>(dirEntry) -
+ reinterpret_cast<char*>(mMzHeader);
+ MOZ_ASSERT(*aOutRva);
+ }
+
+ return dirEntry;
+ }
+
+ bool GetVersionInfo(uint64_t& aOutVersion) const {
+ // RT_VERSION == 16
+ // Version resources require an id of 1
+ auto root = FindResourceLeaf<VS_VERSIONINFO_HEADER*>(16, 1);
+ if (!root) {
+ return false;
+ }
+
+ VS_FIXEDFILEINFO* fixedInfo = GetFixedFileInfo(root);
+ if (!fixedInfo) {
+ return false;
+ }
+
+ aOutVersion = ((static_cast<uint64_t>(fixedInfo->dwFileVersionMS) << 32) |
+ static_cast<uint64_t>(fixedInfo->dwFileVersionLS));
+ return true;
+ }
+
+ bool GetTimeStamp(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->FileHeader.TimeDateStamp;
+ return true;
+ }
+
+ bool GetImageSize(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->OptionalHeader.SizeOfImage;
+ return true;
+ }
+
+ bool GetCheckSum(DWORD& aResult) const {
+ if (!(*this)) {
+ return false;
+ }
+
+ aResult = mPeHeader->OptionalHeader.CheckSum;
+ return true;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR
+ GetImportDescriptor(const char* aModuleNameASCII) const {
+ for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+ IsValid(curImpDesc); ++curImpDesc) {
+ auto curName = mIsImportDirectoryTampered
+ ? RVAToPtrUnchecked<const char*>(curImpDesc->Name)
+ : RVAToPtr<const char*>(curImpDesc->Name);
+ if (!curName) {
+ return nullptr;
+ }
+
+ if (StricmpASCII(aModuleNameASCII, curName)) {
+ continue;
+ }
+
+ // curImpDesc now points to the IAT for the module we're interested in
+ return curImpDesc;
+ }
+
+ return nullptr;
+ }
+
+ template <typename CallbackT>
+ void EnumImportChunks(const CallbackT& aCallback) const {
+ for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
+ IsValid(curImpDesc); ++curImpDesc) {
+ auto curName = mIsImportDirectoryTampered
+ ? RVAToPtrUnchecked<const char*>(curImpDesc->Name)
+ : RVAToPtr<const char*>(curImpDesc->Name);
+ if (!curName) {
+ continue;
+ }
+
+ aCallback(curName);
+ }
+ }
+
+ /**
+ * If |aBoundaries| is given, this method checks whether each IAT entry is
+ * within the given range, and if any entry is out of the range, we return
+ * Nothing().
+ */
+ Maybe<Span<IMAGE_THUNK_DATA>> GetIATThunksForModule(
+ const char* aModuleNameASCII,
+ const Range<const uint8_t>* aBoundaries = nullptr) const {
+ PIMAGE_IMPORT_DESCRIPTOR impDesc = GetImportDescriptor(aModuleNameASCII);
+ if (!impDesc) {
+ return Nothing();
+ }
+
+ auto firstIatThunk =
+ this->template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
+ if (!firstIatThunk) {
+ return Nothing();
+ }
+
+ // Find the length by iterating through the table until we find a null entry
+ PIMAGE_THUNK_DATA curIatThunk = firstIatThunk;
+ while (IsValid(curIatThunk)) {
+ if (aBoundaries) {
+ auto iatEntry =
+ reinterpret_cast<const uint8_t*>(curIatThunk->u1.Function);
+ if (iatEntry < aBoundaries->begin().get() ||
+ iatEntry >= aBoundaries->end().get()) {
+ return Nothing();
+ }
+ }
+
+ ++curIatThunk;
+ }
+
+ return Some(Span(firstIatThunk, curIatThunk));
+ }
+
+ /**
+ * Resources are stored in a three-level tree. To locate a particular entry,
+ * you must supply a resource type, the resource id, and then the language id.
+ * If aLangId == 0, we just resolve the first entry regardless of language.
+ */
+ template <typename T>
+ T FindResourceLeaf(WORD aType, WORD aResId, WORD aLangId = 0) const {
+ PIMAGE_RESOURCE_DIRECTORY topLevel = GetResourceTable();
+ if (!topLevel) {
+ return nullptr;
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY typeEntry =
+ FindResourceEntry(topLevel, aType);
+ if (!typeEntry || !typeEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto idDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(
+ topLevel, typeEntry->OffsetToDirectory);
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY idEntry = FindResourceEntry(idDir, aResId);
+ if (!idEntry || !idEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto langDir = RVAToPtr<PIMAGE_RESOURCE_DIRECTORY>(
+ topLevel, idEntry->OffsetToDirectory);
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY langEntry;
+ if (aLangId) {
+ langEntry = FindResourceEntry(langDir, aLangId);
+ } else {
+ langEntry = FindFirstResourceEntry(langDir);
+ }
+
+ if (!langEntry || langEntry->DataIsDirectory) {
+ return nullptr;
+ }
+
+ auto dataEntry =
+ RVAToPtr<PIMAGE_RESOURCE_DATA_ENTRY>(topLevel, langEntry->OffsetToData);
+ return dataEntry ? RVAToPtr<T>(dataEntry->OffsetToData) : nullptr;
+ }
+
+ template <size_t N>
+ Maybe<Span<const uint8_t>> FindSection(const char (&aSecName)[N],
+ DWORD aCharacteristicsMask) const {
+ static_assert((N - 1) <= IMAGE_SIZEOF_SHORT_NAME,
+ "Section names must be at most 8 characters excluding null "
+ "terminator");
+
+ if (!(*this)) {
+ return Nothing();
+ }
+
+ Span<IMAGE_SECTION_HEADER> sectionTable = GetSectionTable();
+ for (auto&& sectionHeader : sectionTable) {
+ if (strncmp(reinterpret_cast<const char*>(sectionHeader.Name), aSecName,
+ IMAGE_SIZEOF_SHORT_NAME)) {
+ continue;
+ }
+
+ if (!(sectionHeader.Characteristics & aCharacteristicsMask)) {
+ // We found the section but it does not have the expected
+ // characteristics
+ return Nothing();
+ }
+
+ DWORD rva = sectionHeader.VirtualAddress;
+ if (!rva) {
+ return Nothing();
+ }
+
+ DWORD size = sectionHeader.Misc.VirtualSize;
+ if (!size) {
+ return Nothing();
+ }
+
+ auto base = RVAToPtr<const uint8_t*>(rva);
+ return Some(Span(base, size));
+ }
+
+ return Nothing();
+ }
+
+ // There may be other code sections in the binary besides .text
+ Maybe<Span<const uint8_t>> GetTextSectionInfo() const {
+ return FindSection(".text", IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE |
+ IMAGE_SCN_MEM_READ);
+ }
+
+ static bool IsValid(PIMAGE_IMPORT_DESCRIPTOR aImpDesc) {
+ return aImpDesc && aImpDesc->OriginalFirstThunk != 0;
+ }
+
+ static bool IsValid(PIMAGE_THUNK_DATA aImgThunk) {
+ return aImgThunk && aImgThunk->u1.Ordinal != 0;
+ }
+
+ bool IsImportDirectoryTampered() const { return mIsImportDirectoryTampered; }
+
+ FARPROC GetEntryPoint() const {
+ // Use the unchecked version because the entrypoint may be tampered.
+ return RVAToPtrUnchecked<FARPROC>(
+ mPeHeader->OptionalHeader.AddressOfEntryPoint);
+ }
+
+ const CodeViewRecord70* GetPdbInfo() const {
+ PIMAGE_DEBUG_DIRECTORY debugDirectory =
+ GetImageDirectoryEntry<PIMAGE_DEBUG_DIRECTORY>(
+ IMAGE_DIRECTORY_ENTRY_DEBUG);
+ if (!debugDirectory) {
+ return nullptr;
+ }
+
+ const CodeViewRecord70* debugInfo =
+ RVAToPtr<CodeViewRecord70*>(debugDirectory->AddressOfRawData);
+ return (debugInfo && debugInfo->signature == 'SDSR') ? debugInfo : nullptr;
+ }
+
+ private:
+ enum class BoundsCheckPolicy { Default, Skip };
+
+ template <typename T, BoundsCheckPolicy Policy = BoundsCheckPolicy::Default>
+ T GetImageDirectoryEntry(const uint32_t aDirectoryIndex) const {
+ PIMAGE_DATA_DIRECTORY dirEntry = GetImageDirectoryEntryPtr(aDirectoryIndex);
+ if (!dirEntry) {
+ return nullptr;
+ }
+
+ return Policy == BoundsCheckPolicy::Skip
+ ? RVAToPtrUnchecked<T>(dirEntry->VirtualAddress)
+ : RVAToPtr<T>(dirEntry->VirtualAddress);
+ }
+
+ // This private variant does not have bounds checks, because we need to be
+ // able to resolve the bounds themselves.
+ template <typename T, typename R>
+ T RVAToPtrUnchecked(R aRva) const {
+ return reinterpret_cast<T>(reinterpret_cast<char*>(mMzHeader) + aRva);
+ }
+
+ Span<IMAGE_SECTION_HEADER> GetSectionTable() const {
+ MOZ_ASSERT(*this);
+ auto base = RVAToPtr<PIMAGE_SECTION_HEADER>(
+ &mPeHeader->OptionalHeader, mPeHeader->FileHeader.SizeOfOptionalHeader);
+ // The Windows loader has an internal limit of 96 sections (per PE spec)
+ auto numSections =
+ std::min(mPeHeader->FileHeader.NumberOfSections, WORD(96));
+ return Span{base, numSections};
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY
+ FindResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel, WORD aId) const {
+ if (!aCurLevel) {
+ return nullptr;
+ }
+
+ // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+ // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. Since this function
+ // searches by ID, we need to skip past any named entries before iterating.
+ auto dirEnt =
+ reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1) +
+ aCurLevel->NumberOfNamedEntries;
+ if (!(IsWithinImage(dirEnt) &&
+ IsWithinImage(&dirEnt[aCurLevel->NumberOfIdEntries - 1].Id))) {
+ return nullptr;
+ }
+
+ for (WORD i = 0; i < aCurLevel->NumberOfIdEntries; ++i) {
+ if (dirEnt[i].Id == aId) {
+ return &dirEnt[i];
+ }
+ }
+
+ return nullptr;
+ }
+
+ PIMAGE_RESOURCE_DIRECTORY_ENTRY
+ FindFirstResourceEntry(PIMAGE_RESOURCE_DIRECTORY aCurLevel) const {
+ // Immediately after the IMAGE_RESOURCE_DIRECTORY structure is an array
+ // of IMAGE_RESOURCE_DIRECTORY_ENTRY structures. We just return the first
+ // entry, regardless of whether it is indexed by name or by id.
+ auto dirEnt =
+ reinterpret_cast<PIMAGE_RESOURCE_DIRECTORY_ENTRY>(aCurLevel + 1);
+ WORD numEntries =
+ aCurLevel->NumberOfNamedEntries + aCurLevel->NumberOfIdEntries;
+ if (!numEntries) {
+ return nullptr;
+ }
+
+ return dirEnt;
+ }
+
+ VS_FIXEDFILEINFO* GetFixedFileInfo(VS_VERSIONINFO_HEADER* aVerInfo) const {
+ WORD length = aVerInfo->wLength;
+ if (length < sizeof(VS_VERSIONINFO_HEADER)) {
+ return nullptr;
+ }
+
+ const wchar_t kVersionInfoKey[] = L"VS_VERSION_INFO";
+ if (::RtlCompareMemory(aVerInfo->szKey, kVersionInfoKey,
+ ArrayLength(kVersionInfoKey)) !=
+ ArrayLength(kVersionInfoKey)) {
+ return nullptr;
+ }
+
+ if (aVerInfo->wValueLength != sizeof(VS_FIXEDFILEINFO)) {
+ // Fixed file info does not exist
+ return nullptr;
+ }
+
+ WORD offset = sizeof(VS_VERSIONINFO_HEADER);
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(aVerInfo);
+ // Align up to 4-byte boundary
+#pragma warning(suppress : 4146)
+ offset += (-(base + offset) & 3);
+
+ if (offset >= length) {
+ return nullptr;
+ }
+
+ auto result = reinterpret_cast<VS_FIXEDFILEINFO*>(base + offset);
+ if (result->dwSignature != 0xFEEF04BD) {
+ return nullptr;
+ }
+
+ return result;
+ }
+
+ private:
+ PIMAGE_DOS_HEADER mMzHeader;
+ PIMAGE_NT_HEADERS mPeHeader;
+ void* mImageLimit;
+ bool mIsImportDirectoryTampered;
+};
+
+// This class represents an export section of a local/remote process.
+template <typename MMPolicy>
+class MOZ_RAII PEExportSection {
+ const MMPolicy& mMMPolicy;
+ uintptr_t mImageBase;
+ DWORD mOrdinalBase;
+ DWORD mRvaDirStart;
+ DWORD mRvaDirEnd;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportAddressTable;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, DWORD> mExportNameTable;
+ mozilla::interceptor::TargetObjectArray<MMPolicy, WORD> mExportOrdinalTable;
+
+ explicit PEExportSection(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy),
+ mImageBase(0),
+ mOrdinalBase(0),
+ mRvaDirStart(0),
+ mRvaDirEnd(0),
+ mExportAddressTable(mMMPolicy),
+ mExportNameTable(mMMPolicy),
+ mExportOrdinalTable(mMMPolicy) {}
+
+ PEExportSection(const MMPolicy& aMMPolicy, uintptr_t aImageBase,
+ DWORD aRvaDirStart, DWORD aRvaDirEnd,
+ const IMAGE_EXPORT_DIRECTORY& exportDir)
+ : mMMPolicy(aMMPolicy),
+ mImageBase(aImageBase),
+ mOrdinalBase(exportDir.Base),
+ mRvaDirStart(aRvaDirStart),
+ mRvaDirEnd(aRvaDirEnd),
+ mExportAddressTable(mMMPolicy,
+ mImageBase + exportDir.AddressOfFunctions,
+ exportDir.NumberOfFunctions),
+ mExportNameTable(mMMPolicy, mImageBase + exportDir.AddressOfNames,
+ exportDir.NumberOfNames),
+ mExportOrdinalTable(mMMPolicy,
+ mImageBase + exportDir.AddressOfNameOrdinals,
+ exportDir.NumberOfNames) {}
+
+ static const PEExportSection Get(uintptr_t aImageBase,
+ const MMPolicy& aMMPolicy) {
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_DOS_HEADER> mzHeader(
+ aMMPolicy, aImageBase);
+ if (!mzHeader || mzHeader->e_magic != IMAGE_DOS_SIGNATURE) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_NT_HEADERS> peHeader(
+ aMMPolicy, aImageBase + mzHeader->e_lfanew);
+ if (!peHeader || peHeader->Signature != IMAGE_NT_SIGNATURE) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ if (peHeader->OptionalHeader.Magic != IMAGE_NT_OPTIONAL_HDR_MAGIC) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ const IMAGE_OPTIONAL_HEADER& optionalHeader = peHeader->OptionalHeader;
+
+ DWORD imageSize = optionalHeader.SizeOfImage;
+ // This is a coarse-grained check to ensure that the image size is
+ // reasonable. It we aren't big enough to contain headers, we have a
+ // problem!
+ if (imageSize < sizeof(IMAGE_DOS_HEADER) + sizeof(IMAGE_NT_HEADERS)) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ if (optionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ const IMAGE_DATA_DIRECTORY& exportDirectoryEntry =
+ optionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
+ if (!exportDirectoryEntry.VirtualAddress || !exportDirectoryEntry.Size) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ mozilla::interceptor::TargetObject<MMPolicy, IMAGE_EXPORT_DIRECTORY>
+ exportDirectory(aMMPolicy,
+ aImageBase + exportDirectoryEntry.VirtualAddress);
+ if (!exportDirectory || !exportDirectory->NumberOfFunctions) {
+ return PEExportSection(aMMPolicy);
+ }
+
+ return PEExportSection(
+ aMMPolicy, aImageBase, exportDirectoryEntry.VirtualAddress,
+ exportDirectoryEntry.VirtualAddress + exportDirectoryEntry.Size,
+ *exportDirectory.GetLocalBase());
+ }
+
+ FARPROC GetProcAddressByOrdinal(WORD aOrdinal) const {
+ if (aOrdinal < mOrdinalBase) {
+ return nullptr;
+ }
+
+ auto rvaToFunction = mExportAddressTable[aOrdinal - mOrdinalBase];
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+ return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction);
+ }
+
+ public:
+ static const PEExportSection Get(HMODULE aModule, const MMPolicy& aMMPolicy) {
+ return Get(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule), aMMPolicy);
+ }
+
+ explicit operator bool() const {
+ // Because PEExportSection doesn't use MMPolicy::Reserve(), a boolified
+ // mMMPolicy is expected to be false. We don't check mMMPolicy here.
+ return mImageBase && mRvaDirStart && mRvaDirEnd && mExportAddressTable &&
+ mExportNameTable && mExportOrdinalTable;
+ }
+
+ template <typename T>
+ T RVAToPtr(uint32_t aRva) const {
+ return reinterpret_cast<T>(mImageBase + aRva);
+ }
+
+ PIMAGE_EXPORT_DIRECTORY GetExportDirectory() const {
+ if (!*this) {
+ return nullptr;
+ }
+
+ return RVAToPtr<PIMAGE_EXPORT_DIRECTORY>(mRvaDirStart);
+ }
+
+ /**
+ * This functions searches the export table for a given string as
+ * GetProcAddress does, but this returns a matched entry of the Export
+ * Address Table i.e. a pointer to an RVA of a matched function instead
+ * of a function address. If the entry is forwarded, this function
+ * returns nullptr.
+ */
+ const DWORD* FindExportAddressTableEntry(
+ const char* aFunctionNameASCII) const {
+ if (!*this || !aFunctionNameASCII) {
+ return nullptr;
+ }
+
+ struct NameTableComparator {
+ NameTableComparator(const PEExportSection<MMPolicy>& aExportSection,
+ const char* aTarget)
+ : mExportSection(aExportSection),
+ mTargetName(aTarget),
+ mTargetNamelength(StrlenASCII(aTarget)) {}
+
+ int operator()(DWORD aRVAToString) const {
+ mozilla::interceptor::TargetObjectArray<MMPolicy, char> itemString(
+ mExportSection.mMMPolicy, mExportSection.mImageBase + aRVAToString,
+ mTargetNamelength + 1);
+ return StrcmpASCII(mTargetName, itemString[0]);
+ }
+
+ const PEExportSection<MMPolicy>& mExportSection;
+ const char* mTargetName;
+ size_t mTargetNamelength;
+ };
+
+ const NameTableComparator comp(*this, aFunctionNameASCII);
+
+ size_t match;
+ if (!mExportNameTable.BinarySearchIf(comp, &match)) {
+ return nullptr;
+ }
+
+ const WORD* index = mExportOrdinalTable[match];
+ if (!index) {
+ return nullptr;
+ }
+
+ const DWORD* rvaToFunction = mExportAddressTable[*index];
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+
+ if (*rvaToFunction >= mRvaDirStart && *rvaToFunction < mRvaDirEnd) {
+ // If an entry points to an address within the export section, the
+ // field is a forwarder RVA. We return nullptr because the entry is
+ // not a function address but a null-terminated string used for export
+ // forwarding.
+ return nullptr;
+ }
+
+ return rvaToFunction;
+ }
+
+ /**
+ * This functions behaves the same as the native ::GetProcAddress except
+ * the following cases:
+ * - Returns nullptr if a target entry is forwarded to another dll.
+ */
+ FARPROC GetProcAddress(const char* aFunctionNameASCII) const {
+ uintptr_t maybeOdrinal = reinterpret_cast<uintptr_t>(aFunctionNameASCII);
+ // When the high-order word of |aFunctionNameASCII| is zero, it's not
+ // a string but an ordinal value.
+ if (maybeOdrinal < 0x10000) {
+ return GetProcAddressByOrdinal(static_cast<WORD>(maybeOdrinal));
+ }
+
+ auto rvaToFunction = FindExportAddressTableEntry(aFunctionNameASCII);
+ if (!rvaToFunction) {
+ return nullptr;
+ }
+ return reinterpret_cast<FARPROC>(mImageBase + *rvaToFunction);
+ }
+};
+
+inline HANDLE RtlGetProcessHeap() {
+ PTEB teb = ::NtCurrentTeb();
+ PPEB peb = teb->ProcessEnvironmentBlock;
+ return peb->Reserved4[1];
+}
+
+inline PVOID RtlGetThreadLocalStoragePointer() {
+ return ::NtCurrentTeb()->Reserved1[11];
+}
+
+inline void RtlSetThreadLocalStoragePointerForTestingOnly(PVOID aNewValue) {
+ ::NtCurrentTeb()->Reserved1[11] = aNewValue;
+}
+
+inline DWORD RtlGetCurrentThreadId() {
+ PTEB teb = ::NtCurrentTeb();
+ CLIENT_ID* cid = reinterpret_cast<CLIENT_ID*>(&teb->Reserved1[8]);
+ return static_cast<DWORD>(reinterpret_cast<uintptr_t>(cid->UniqueThread) &
+ 0xFFFFFFFFUL);
+}
+
+const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+
+inline LauncherResult<DWORD> GetParentProcessId() {
+ struct PROCESS_BASIC_INFORMATION {
+ NTSTATUS ExitStatus;
+ PPEB PebBaseAddress;
+ ULONG_PTR AffinityMask;
+ LONG BasePriority;
+ ULONG_PTR UniqueProcessId;
+ ULONG_PTR InheritedFromUniqueProcessId;
+ };
+
+ ULONG returnLength;
+ PROCESS_BASIC_INFORMATION pbi = {};
+ NTSTATUS status =
+ ::NtQueryInformationProcess(kCurrentProcess, ProcessBasicInformation,
+ &pbi, sizeof(pbi), &returnLength);
+ if (!NT_SUCCESS(status)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ }
+
+ return static_cast<DWORD>(pbi.InheritedFromUniqueProcessId & 0xFFFFFFFF);
+}
+
+inline SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen) {
+#if defined(MOZILLA_INTERNAL_API)
+ return ::VirtualQueryEx(aProcess, aAddress, aMemInfo, aMemInfoLen);
+#else
+ SIZE_T returnedLength;
+ NTSTATUS status = ::NtQueryVirtualMemory(
+ aProcess, const_cast<PVOID>(aAddress), MemoryBasicInformation, aMemInfo,
+ aMemInfoLen, &returnedLength);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ returnedLength = 0;
+ }
+ return returnedLength;
+#endif // defined(MOZILLA_INTERNAL_API)
+}
+
+inline SIZE_T WINAPI VirtualQuery(LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen) {
+ return nt::VirtualQueryEx(kCurrentProcess, aAddress, aMemInfo, aMemInfoLen);
+}
+
+struct DataDirectoryEntry : public _IMAGE_DATA_DIRECTORY {
+ DataDirectoryEntry() : _IMAGE_DATA_DIRECTORY() {}
+
+ MOZ_IMPLICIT DataDirectoryEntry(const _IMAGE_DATA_DIRECTORY& aOther)
+ : _IMAGE_DATA_DIRECTORY(aOther) {}
+
+ DataDirectoryEntry(const DataDirectoryEntry& aOther) = default;
+
+ bool operator==(const DataDirectoryEntry& aOther) const {
+ return VirtualAddress == aOther.VirtualAddress && Size == aOther.Size;
+ }
+
+ bool operator!=(const DataDirectoryEntry& aOther) const {
+ return !(*this == aOther);
+ }
+};
+
+inline LauncherResult<void*> GetProcessPebPtr(HANDLE aProcess) {
+ ULONG returnLength;
+ PROCESS_BASIC_INFORMATION pbi;
+ NTSTATUS status = ::NtQueryInformationProcess(
+ aProcess, ProcessBasicInformation, &pbi, sizeof(pbi), &returnLength);
+ if (!NT_SUCCESS(status)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(status);
+ }
+
+ return pbi.PebBaseAddress;
+}
+
+/**
+ * This function relies on a specific offset into the mostly-undocumented PEB
+ * structure. The risk is reduced thanks to the fact that the Chromium sandbox
+ * relies on the location of this field. It is unlikely to change at this point.
+ * To further reduce the risk, we also check for the magic 'MZ' signature that
+ * should indicate the beginning of a PE image.
+ */
+inline LauncherResult<HMODULE> GetProcessExeModule(HANDLE aProcess) {
+ LauncherResult<void*> ppeb = GetProcessPebPtr(aProcess);
+ if (ppeb.isErr()) {
+ return ppeb.propagateErr();
+ }
+
+ PEB peb;
+ SIZE_T bytesRead;
+
+#if defined(MOZILLA_INTERNAL_API)
+ if (!::ReadProcessMemory(aProcess, ppeb.unwrap(), &peb, sizeof(peb),
+ &bytesRead) ||
+ bytesRead != sizeof(peb)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+#else
+ NTSTATUS ntStatus = ::NtReadVirtualMemory(aProcess, ppeb.unwrap(), &peb,
+ sizeof(peb), &bytesRead);
+ if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(peb)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
+ }
+#endif
+
+ // peb.ImageBaseAddress
+ void* baseAddress = peb.Reserved3[1];
+
+ char mzMagic[2];
+#if defined(MOZILLA_INTERNAL_API)
+ if (!::ReadProcessMemory(aProcess, baseAddress, mzMagic, sizeof(mzMagic),
+ &bytesRead) ||
+ bytesRead != sizeof(mzMagic)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+#else
+ ntStatus = ::NtReadVirtualMemory(aProcess, baseAddress, mzMagic,
+ sizeof(mzMagic), &bytesRead);
+ if (!NT_SUCCESS(ntStatus) || bytesRead != sizeof(mzMagic)) {
+ return LAUNCHER_ERROR_FROM_NTSTATUS(ntStatus);
+ }
+#endif
+
+ MOZ_ASSERT(mzMagic[0] == 'M' && mzMagic[1] == 'Z');
+ if (mzMagic[0] != 'M' || mzMagic[1] != 'Z') {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ return static_cast<HMODULE>(baseAddress);
+}
+
+#if defined(_MSC_VER)
+extern "C" IMAGE_DOS_HEADER __ImageBase;
+#endif
+
+// This class manages data transfer from the local process's executable
+// to another process's executable via WriteProcessMemory.
+// Bug 1662560 told us the same executable may be mapped onto a different
+// address in a different process. This means when we transfer data within
+// the mapped executable such as a global variable or IAT from the current
+// process to another process, we need to shift its address by the difference
+// between two executable's mapped imagebase.
+class CrossExecTransferManager final {
+ HANDLE mRemoteProcess;
+ uint8_t* mLocalImagebase;
+ PEHeaders mLocalExec;
+ uint8_t* mRemoteImagebase;
+
+ static HMODULE GetLocalExecModule() {
+#if defined(_MSC_VER)
+ return reinterpret_cast<HMODULE>(&__ImageBase);
+#else
+ return ::GetModuleHandleW(nullptr);
+#endif
+ }
+
+ LauncherVoidResult EnsureRemoteImagebase() {
+ if (!mRemoteImagebase) {
+ LauncherResult<HMODULE> remoteImageBaseResult =
+ GetProcessExeModule(mRemoteProcess);
+ if (remoteImageBaseResult.isErr()) {
+ return remoteImageBaseResult.propagateErr();
+ }
+
+ mRemoteImagebase =
+ reinterpret_cast<uint8_t*>(remoteImageBaseResult.unwrap());
+ }
+ return Ok();
+ }
+
+ template <typename T>
+ T* LocalExecToRemoteExec(T* aLocalAddress) const {
+ MOZ_ASSERT(mRemoteImagebase);
+ MOZ_ASSERT(mLocalExec.IsWithinImage(aLocalAddress));
+
+ if (!mRemoteImagebase || !mLocalExec.IsWithinImage(aLocalAddress)) {
+ return aLocalAddress;
+ }
+
+ uintptr_t offset = reinterpret_cast<uintptr_t>(aLocalAddress) -
+ reinterpret_cast<uintptr_t>(mLocalImagebase);
+ return reinterpret_cast<T*>(mRemoteImagebase + offset);
+ }
+
+ public:
+ explicit CrossExecTransferManager(HANDLE aRemoteProcess)
+ : mRemoteProcess(aRemoteProcess),
+ mLocalImagebase(
+ PEHeaders::HModuleToBaseAddr<uint8_t*>(GetLocalExecModule())),
+ mLocalExec(mLocalImagebase),
+ mRemoteImagebase(nullptr) {}
+
+ CrossExecTransferManager(HANDLE aRemoteProcess, HMODULE aLocalImagebase)
+ : mRemoteProcess(aRemoteProcess),
+ mLocalImagebase(
+ PEHeaders::HModuleToBaseAddr<uint8_t*>(aLocalImagebase)),
+ mLocalExec(mLocalImagebase),
+ mRemoteImagebase(nullptr) {}
+
+ explicit operator bool() const { return !!mLocalExec; }
+ HANDLE RemoteProcess() const { return mRemoteProcess; }
+ const PEHeaders& LocalPEHeaders() const { return mLocalExec; }
+
+ AutoVirtualProtect Protect(void* aLocalAddress, size_t aLength,
+ DWORD aProtFlags) {
+ // If EnsureRemoteImagebase() fails, a subsequent operaion will fail.
+ Unused << EnsureRemoteImagebase();
+ return AutoVirtualProtect(LocalExecToRemoteExec(aLocalAddress), aLength,
+ aProtFlags, mRemoteProcess);
+ }
+
+ LauncherVoidResult Transfer(LPVOID aDestinationAddress,
+ LPCVOID aBufferToWrite, SIZE_T aBufferSize) {
+ LauncherVoidResult result = EnsureRemoteImagebase();
+ if (result.isErr()) {
+ return result.propagateErr();
+ }
+
+ if (!::WriteProcessMemory(mRemoteProcess,
+ LocalExecToRemoteExec(aDestinationAddress),
+ aBufferToWrite, aBufferSize, nullptr)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return Ok();
+ }
+};
+
+#if !defined(MOZILLA_INTERNAL_API)
+
+inline LauncherResult<HMODULE> GetModuleHandleFromLeafName(
+ const UNICODE_STRING& aTarget) {
+ auto maybePeb = nt::GetProcessPebPtr(kCurrentProcess);
+ if (maybePeb.isErr()) {
+ return maybePeb.propagateErr();
+ }
+
+ const PPEB peb = reinterpret_cast<PPEB>(maybePeb.unwrap());
+ if (!peb->Ldr) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ auto firstItem = &peb->Ldr->InMemoryOrderModuleList;
+ for (auto p = firstItem->Flink; p != firstItem; p = p->Flink) {
+ const auto currentTableEntry =
+ CONTAINING_RECORD(p, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
+
+ UNICODE_STRING leafName;
+ nt::GetLeafName(&leafName, &currentTableEntry->FullDllName);
+
+ if (::RtlCompareUnicodeString(&leafName, &aTarget, TRUE) == 0) {
+ return reinterpret_cast<HMODULE>(currentTableEntry->DllBase);
+ }
+ }
+
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_MOD_NOT_FOUND);
+}
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS SRWLock final {
+ public:
+ constexpr SRWLock() : mLock(SRWLOCK_INIT) {}
+
+ void LockShared() { ::RtlAcquireSRWLockShared(&mLock); }
+
+ void LockExclusive() { ::RtlAcquireSRWLockExclusive(&mLock); }
+
+ void UnlockShared() { ::RtlReleaseSRWLockShared(&mLock); }
+
+ void UnlockExclusive() { ::RtlReleaseSRWLockExclusive(&mLock); }
+
+ SRWLock(const SRWLock&) = delete;
+ SRWLock(SRWLock&&) = delete;
+ SRWLock& operator=(const SRWLock&) = delete;
+ SRWLock& operator=(SRWLock&&) = delete;
+
+ SRWLOCK* operator&() { return &mLock; }
+
+ private:
+ SRWLOCK mLock;
+};
+
+class MOZ_RAII AutoExclusiveLock final {
+ public:
+ explicit AutoExclusiveLock(SRWLock& aLock) : mLock(aLock) {
+ aLock.LockExclusive();
+ }
+
+ ~AutoExclusiveLock() { mLock.UnlockExclusive(); }
+
+ AutoExclusiveLock(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock(AutoExclusiveLock&&) = delete;
+ AutoExclusiveLock& operator=(const AutoExclusiveLock&) = delete;
+ AutoExclusiveLock& operator=(AutoExclusiveLock&&) = delete;
+
+ private:
+ SRWLock& mLock;
+};
+
+class MOZ_RAII AutoSharedLock final {
+ public:
+ explicit AutoSharedLock(SRWLock& aLock) : mLock(aLock) { aLock.LockShared(); }
+
+ ~AutoSharedLock() { mLock.UnlockShared(); }
+
+ AutoSharedLock(const AutoSharedLock&) = delete;
+ AutoSharedLock(AutoSharedLock&&) = delete;
+ AutoSharedLock& operator=(const AutoSharedLock&) = delete;
+ AutoSharedLock& operator=(AutoSharedLock&&) = delete;
+
+ private:
+ SRWLock& mLock;
+};
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+class RtlAllocPolicy {
+ public:
+ template <typename T>
+ T* maybe_pod_malloc(size_t aNumElems) {
+ if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(
+ ::RtlAllocateHeap(RtlGetProcessHeap(), 0, aNumElems * sizeof(T)));
+ }
+
+ template <typename T>
+ T* maybe_pod_calloc(size_t aNumElems) {
+ if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(::RtlAllocateHeap(
+ RtlGetProcessHeap(), HEAP_ZERO_MEMORY, aNumElems * sizeof(T)));
+ }
+
+ template <typename T>
+ T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
+ if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
+ return nullptr;
+ }
+
+ return static_cast<T*>(::RtlReAllocateHeap(RtlGetProcessHeap(), 0, aPtr,
+ aNewSize * sizeof(T)));
+ }
+
+ template <typename T>
+ T* pod_malloc(size_t aNumElems) {
+ return maybe_pod_malloc<T>(aNumElems);
+ }
+
+ template <typename T>
+ T* pod_calloc(size_t aNumElems) {
+ return maybe_pod_calloc<T>(aNumElems);
+ }
+
+ template <typename T>
+ T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize) {
+ return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
+ }
+
+ template <typename T>
+ void free_(T* aPtr, size_t aNumElems = 0) {
+ ::RtlFreeHeap(RtlGetProcessHeap(), 0, aPtr);
+ }
+
+ void reportAllocOverflow() const {}
+
+ [[nodiscard]] bool checkSimulatedOOM() const { return true; }
+};
+
+class AutoMappedView final {
+ void* mView;
+
+ void Unmap() {
+ if (!mView) {
+ return;
+ }
+
+#if defined(MOZILLA_INTERNAL_API)
+ ::UnmapViewOfFile(mView);
+#else
+ NTSTATUS status = ::NtUnmapViewOfSection(nt::kCurrentProcess, mView);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ }
+#endif
+ mView = nullptr;
+ }
+
+ public:
+ explicit AutoMappedView(void* aView) : mView(aView) {}
+
+ AutoMappedView(HANDLE aSection, ULONG aProtectionFlags) : mView(nullptr) {
+#if defined(MOZILLA_INTERNAL_API)
+ mView = ::MapViewOfFile(aSection, aProtectionFlags, 0, 0, 0);
+#else
+ SIZE_T viewSize = 0;
+ NTSTATUS status = ::NtMapViewOfSection(aSection, nt::kCurrentProcess,
+ &mView, 0, 0, nullptr, &viewSize,
+ ViewUnmap, 0, aProtectionFlags);
+ if (!NT_SUCCESS(status)) {
+ ::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
+ }
+#endif
+ }
+ ~AutoMappedView() { Unmap(); }
+
+ // Allow move & Disallow copy
+ AutoMappedView(AutoMappedView&& aOther) : mView(aOther.mView) {
+ aOther.mView = nullptr;
+ }
+ AutoMappedView& operator=(AutoMappedView&& aOther) {
+ if (this != &aOther) {
+ Unmap();
+ mView = aOther.mView;
+ aOther.mView = nullptr;
+ }
+ return *this;
+ }
+ AutoMappedView(const AutoMappedView&) = delete;
+ AutoMappedView& operator=(const AutoMappedView&) = delete;
+
+ explicit operator bool() const { return !!mView; }
+ template <typename T>
+ T* as() {
+ return reinterpret_cast<T*>(mView);
+ }
+
+ void* release() {
+ void* p = mView;
+ mView = nullptr;
+ return p;
+ }
+};
+
+} // namespace nt
+} // namespace mozilla
+
+#endif // mozilla_NativeNt_h