summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/dllservices
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre/dllservices')
-rw-r--r--toolkit/xre/dllservices/DynamicBlocklist.h291
-rw-r--r--toolkit/xre/dllservices/DynamicBlocklistWriter.cpp139
-rw-r--r--toolkit/xre/dllservices/ModuleEvaluator.cpp253
-rw-r--r--toolkit/xre/dllservices/ModuleEvaluator.h50
-rw-r--r--toolkit/xre/dllservices/ModuleVersionInfo.cpp111
-rw-r--r--toolkit/xre/dllservices/ModuleVersionInfo.h71
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesData.cpp446
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesData.h650
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesProcessor.cpp1040
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesProcessor.h176
-rw-r--r--toolkit/xre/dllservices/WinDllServices.cpp142
-rw-r--r--toolkit/xre/dllservices/WinDllServices.h57
-rw-r--r--toolkit/xre/dllservices/moz.build45
-rw-r--r--toolkit/xre/dllservices/mozglue/Authenticode.cpp439
-rw-r--r--toolkit/xre/dllservices/mozglue/Authenticode.h32
-rw-r--r--toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp60
-rw-r--r--toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h21
-rw-r--r--toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h125
-rw-r--r--toolkit/xre/dllservices/mozglue/LoaderObserver.cpp149
-rw-r--r--toolkit/xre/dllservices/mozglue/LoaderObserver.h45
-rw-r--r--toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp104
-rw-r--r--toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h44
-rw-r--r--toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h174
-rw-r--r--toolkit/xre/dllservices/mozglue/NtLoaderAPI.h23
-rw-r--r--toolkit/xre/dllservices/mozglue/SharedSection.h28
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp781
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h79
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h46
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in356
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h88
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllServices.h212
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp91
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h39
-rw-r--r--toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py750
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp89
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/Arm64.h221
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h1031
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h141
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h1728
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h205
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h142
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h1000
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h773
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h285
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/moz.build26
-rw-r--r--toolkit/xre/dllservices/mozglue/moz.build80
-rw-r--r--toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h819
-rw-r--r--toolkit/xre/dllservices/tests/AssemblyPayloads.h241
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptor.cpp1416
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest17
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp159
-rw-r--r--toolkit/xre/dllservices/tests/TestIATPatcher.cpp121
-rw-r--r--toolkit/xre/dllservices/tests/TestMMPolicy.cpp204
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp223
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp12
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build21
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp461
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc38
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/moz.build35
-rw-r--r--toolkit/xre/dllservices/tests/moz.build46
79 files changed, 16999 insertions, 0 deletions
diff --git a/toolkit/xre/dllservices/DynamicBlocklist.h b/toolkit/xre/dllservices/DynamicBlocklist.h
new file mode 100644
index 0000000000..9bdc7f4a51
--- /dev/null
+++ b/toolkit/xre/dllservices/DynamicBlocklist.h
@@ -0,0 +1,291 @@
+/* -*- 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_DynamicBlocklist_h
+#define mozilla_DynamicBlocklist_h
+
+#include <winternl.h>
+
+#include "nsWindowsHelpers.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "mozilla/dom/Promise.h"
+# include "nsIOutputStream.h"
+# include "nsTHashSet.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include "mozilla/WindowsDllBlocklistInfo.h"
+
+#if !defined(MOZILLA_INTERNAL_API) && defined(ENABLE_TESTS)
+# include "mozilla/CmdLineAndEnvUtils.h"
+# define BLOCKLIST_INSERT_TEST_ENTRY
+#endif // !defined(MOZILLA_INTERNAL_API) && defined(ENABLE_TESTS)
+
+namespace mozilla {
+using DllBlockInfo = DllBlockInfoT<UNICODE_STRING>;
+
+struct DynamicBlockListBase {
+ static constexpr uint32_t kSignature = 'FFBL'; // Firefox Blocklist
+ static constexpr uint32_t kCurrentVersion = 1;
+
+ struct FileHeader {
+ uint32_t mSignature;
+ uint32_t mFileVersion;
+ uint32_t mPayloadSize;
+ };
+};
+// Define this class in a header so that TestCrossProcessWin
+// can include and test it.
+class DynamicBlockList final : public DynamicBlockListBase {
+ uint32_t mPayloadSize;
+ UniquePtr<uint8_t[]> mPayload;
+
+#ifdef ENABLE_TESTS
+ // These two definitions are needed for the DynamicBlocklistWriter to avoid
+ // writing this test entry out to the blocklist file, so compile these in
+ // even if MOZILLA_INTERNAL_API is defined.
+ public:
+ static constexpr wchar_t kTestDll[] = L"TestDllBlocklist_UserBlocked.dll";
+ // kTestDll is null-terminated, but we don't want that for the blocklist
+ // file
+ static constexpr size_t kTestDllBytes = sizeof(kTestDll) - sizeof(wchar_t);
+
+ private:
+# ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ // Set up a test entry in the blocklist, used for testing purposes.
+ void AssignTestEntry(DllBlockInfo* testEntry, uint32_t nameOffset) {
+ testEntry->mName.Length = kTestDllBytes;
+ testEntry->mName.MaximumLength = kTestDllBytes;
+ testEntry->mName.Buffer = reinterpret_cast<PWSTR>(nameOffset);
+ testEntry->mMaxVersion = DllBlockInfo::ALL_VERSIONS;
+ testEntry->mFlags = DllBlockInfo::FLAGS_DEFAULT;
+ }
+
+ void CreateListWithTestEntry() {
+ mPayloadSize = sizeof(DllBlockInfo) * 2 + kTestDllBytes;
+ mPayload = MakeUnique<uint8_t[]>(mPayloadSize);
+ DllBlockInfo* entry = reinterpret_cast<DllBlockInfo*>(mPayload.get());
+ AssignTestEntry(entry, sizeof(DllBlockInfo) * 2);
+ memcpy(mPayload.get() + sizeof(DllBlockInfo) * 2, kTestDll, kTestDllBytes);
+ ++entry;
+ entry->mName.Length = 0;
+ entry->mName.MaximumLength = 0;
+ }
+# endif // BLOCKLIST_INSERT_TEST_ENTRY
+#endif // ENABLE_TESTS
+
+ bool LoadFile(const wchar_t* aPath) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ const bool shouldInsertBlocklistTestEntry =
+ MOZ_UNLIKELY(mozilla::EnvHasValue("MOZ_DISABLE_NONLOCAL_CONNECTIONS") ||
+ mozilla::EnvHasValue("MOZ_RUN_GTEST"));
+#endif
+
+ nsAutoHandle file(
+ ::CreateFileW(aPath, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
+ if (!file) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ // If the blocklist file doesn't exist, we still want to include a test
+ // entry for testing purposes.
+ CreateListWithTestEntry();
+ return true;
+ }
+#endif
+ return false;
+ }
+
+ DWORD bytesRead = 0;
+ FileHeader header;
+ BOOL ok =
+ ::ReadFile(file.get(), &header, sizeof(header), &bytesRead, nullptr);
+ if (!ok) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ // If we have a problem reading the blocklist file, we still want to
+ // include a test entry for testing purposes.
+ CreateListWithTestEntry();
+ return true;
+ }
+#endif
+ return false;
+ }
+ if (bytesRead != sizeof(header)) {
+ return false;
+ }
+
+ if (header.mSignature != kSignature ||
+ header.mFileVersion != kCurrentVersion) {
+ return false;
+ }
+
+ uint32_t destinationPayloadSize = header.mPayloadSize;
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ // Write an extra entry for testing purposes
+ destinationPayloadSize += sizeof(DllBlockInfo) + kTestDllBytes;
+ }
+#endif
+ UniquePtr<uint8_t[]> payload =
+ MakeUnique<uint8_t[]>(destinationPayloadSize);
+ ok = ::ReadFile(file.get(), payload.get(), header.mPayloadSize, &bytesRead,
+ nullptr);
+ if (!ok || bytesRead != header.mPayloadSize) {
+ return false;
+ }
+
+ uint32_t sizeOfPayloadToIterateOver = header.mPayloadSize;
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ bool haveWrittenTestEntry = false;
+ UNICODE_STRING testUnicodeString;
+ // Cast the const-ness away since we're only going to use
+ // this to compare against strings.
+ testUnicodeString.Buffer = const_cast<PWSTR>(kTestDll);
+ testUnicodeString.Length = kTestDllBytes;
+ testUnicodeString.MaximumLength = kTestDllBytes;
+#endif
+ for (uint32_t offset = 0; offset < sizeOfPayloadToIterateOver;
+ offset += sizeof(DllBlockInfo)) {
+ DllBlockInfo* entry =
+ reinterpret_cast<DllBlockInfo*>(payload.get() + offset);
+ if (!entry->mName.Length) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry && !haveWrittenTestEntry) {
+ // Shift everything forward
+ memmove(payload.get() + offset + sizeof(DllBlockInfo),
+ payload.get() + offset, header.mPayloadSize - offset);
+ AssignTestEntry(entry, destinationPayloadSize - kTestDllBytes);
+ haveWrittenTestEntry = true;
+ }
+#endif
+ break;
+ }
+
+ size_t stringOffset = reinterpret_cast<size_t>(entry->mName.Buffer);
+ if (stringOffset + entry->mName.Length > header.mPayloadSize) {
+ entry->mName.Length = 0;
+ break;
+ }
+ entry->mName.MaximumLength = entry->mName.Length;
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry && !haveWrittenTestEntry) {
+ UNICODE_STRING currentUnicodeString;
+ currentUnicodeString.Buffer =
+ reinterpret_cast<PWSTR>(payload.get() + stringOffset);
+ currentUnicodeString.Length = entry->mName.Length;
+ currentUnicodeString.MaximumLength = entry->mName.Length;
+ if (RtlCompareUnicodeString(&currentUnicodeString, &testUnicodeString,
+ TRUE) > 0) {
+ // Shift everything forward
+ memmove(payload.get() + offset + sizeof(DllBlockInfo),
+ payload.get() + offset, header.mPayloadSize - offset);
+ AssignTestEntry(entry, destinationPayloadSize - kTestDllBytes);
+ haveWrittenTestEntry = true;
+ // Now we have expanded the area of valid memory, so there is more
+ // allowable space to iterate over.
+ sizeOfPayloadToIterateOver = destinationPayloadSize;
+ offset += sizeof(DllBlockInfo);
+ ++entry;
+ }
+ // The start of the string section will be moving ahead (or has already
+ // moved ahead) to make room for the test entry
+ entry->mName.Buffer +=
+ sizeof(DllBlockInfo) / sizeof(entry->mName.Buffer[0]);
+ }
+#endif
+ }
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ memcpy(payload.get() + destinationPayloadSize - kTestDllBytes, kTestDll,
+ kTestDllBytes);
+ }
+#endif
+
+ mPayloadSize = destinationPayloadSize;
+ mPayload = std::move(payload);
+ return true;
+ }
+
+ public:
+ DynamicBlockList() : mPayloadSize(0) {}
+ explicit DynamicBlockList(const wchar_t* aPath) : mPayloadSize(0) {
+ LoadFile(aPath);
+ }
+
+ DynamicBlockList(DynamicBlockList&&) = default;
+ DynamicBlockList& operator=(DynamicBlockList&&) = default;
+ DynamicBlockList(const DynamicBlockList&) = delete;
+ DynamicBlockList& operator=(const DynamicBlockList&) = delete;
+
+ constexpr uint32_t GetPayloadSize() const { return mPayloadSize; }
+
+ // Return the number of bytes copied
+ size_t CopyTo(void* aBuffer, size_t aBufferLength) const {
+ if (mPayloadSize > aBufferLength) {
+ return 0;
+ }
+ memcpy(aBuffer, mPayload.get(), mPayloadSize);
+ return mPayloadSize;
+ }
+};
+
+#if defined(MOZILLA_INTERNAL_API) && defined(MOZ_LAUNCHER_PROCESS)
+
+class DynamicBlocklistWriter final : public DynamicBlockListBase {
+ template <typename T>
+ static nsresult WriteValue(nsIOutputStream* aOutputStream, const T& aValue) {
+ uint32_t written;
+ return aOutputStream->Write(reinterpret_cast<const char*>(&aValue),
+ sizeof(T), &written);
+ }
+
+ template <typename T>
+ static nsresult WriteBuffer(nsIOutputStream* aOutputStream, const T* aBuffer,
+ uint32_t aBufferSize) {
+ uint32_t written;
+ return aOutputStream->Write(reinterpret_cast<const char*>(aBuffer),
+ aBufferSize, &written);
+ }
+
+ RefPtr<dom::Promise> mPromise;
+ Vector<DllBlockInfo> mArray;
+ // All strings are packed in this buffer without null characters
+ UniquePtr<uint8_t[]> mStringBuffer;
+
+ size_t mArraySize;
+ size_t mStringBufferSize;
+
+ nsresult WriteToFile(const nsAString& aName) const;
+
+ public:
+ DynamicBlocklistWriter(
+ RefPtr<dom::Promise> aPromise,
+ const nsTHashSet<nsStringCaseInsensitiveHashKey>& aBlocklist);
+ ~DynamicBlocklistWriter() = default;
+
+ DynamicBlocklistWriter(DynamicBlocklistWriter&&) = default;
+ DynamicBlocklistWriter& operator=(DynamicBlocklistWriter&&) = default;
+ DynamicBlocklistWriter(const DynamicBlocklistWriter&) = delete;
+ DynamicBlocklistWriter& operator=(const DynamicBlocklistWriter&) = delete;
+
+ bool IsReady() const { return mStringBuffer.get(); }
+
+ void Run();
+ void Cancel();
+};
+
+#endif // defined(MOZILLA_INTERNAL_API) && defined(MOZ_LAUNCHER_PROCESS)
+
+} // namespace mozilla
+
+#endif // mozilla_DynamicBlocklist_h
diff --git a/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp b/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp
new file mode 100644
index 0000000000..e967d073c5
--- /dev/null
+++ b/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp
@@ -0,0 +1,139 @@
+/* -*- 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/. */
+
+#include "mozilla/DynamicBlocklist.h"
+#include "mozilla/LauncherRegistryInfo.h"
+
+#include "nsISafeOutputStream.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+#if ENABLE_TESTS
+nsDependentString testEntryString(DynamicBlockList::kTestDll,
+ DynamicBlockList::kTestDllBytes /
+ sizeof(DynamicBlockList::kTestDll[0]));
+#endif
+
+bool ShouldWriteEntry(const nsAString& name) {
+#if ENABLE_TESTS
+ return name != testEntryString;
+#else
+ return true;
+#endif
+}
+
+DynamicBlocklistWriter::DynamicBlocklistWriter(
+ RefPtr<dom::Promise> aPromise,
+ const nsTHashSet<nsStringCaseInsensitiveHashKey>& aBlocklist)
+ : mPromise(aPromise), mArraySize(0), mStringBufferSize(0) {
+ CheckedUint32 payloadSize;
+ bool hasTestEntry = false;
+ for (const nsAString& name : aBlocklist) {
+ if (ShouldWriteEntry(name)) {
+ payloadSize += name.Length() * sizeof(char16_t);
+ if (!payloadSize.isValid()) {
+ return;
+ }
+ } else {
+ hasTestEntry = true;
+ }
+ }
+
+ uint32_t entriesToWrite = aBlocklist.Count();
+ if (hasTestEntry) {
+ entriesToWrite -= 1;
+ }
+
+ mStringBufferSize = payloadSize.value();
+ mArraySize = (entriesToWrite + 1) * sizeof(DllBlockInfo);
+
+ payloadSize += mArraySize;
+ if (!payloadSize.isValid()) {
+ return;
+ }
+
+ mStringBuffer = MakeUnique<uint8_t[]>(mStringBufferSize);
+ Unused << mArray.resize(entriesToWrite + 1); // aBlockEntries + sentinel
+
+ size_t currentStringOffset = 0;
+ size_t i = 0;
+ for (const nsAString& name : aBlocklist) {
+ if (!ShouldWriteEntry(name)) {
+ continue;
+ }
+ const uint32_t nameSize = name.Length() * sizeof(char16_t);
+
+ mArray[i].mMaxVersion = DllBlockInfo::ALL_VERSIONS;
+ mArray[i].mFlags = DllBlockInfo::Flags::FLAGS_DEFAULT;
+
+ // Copy the module's name to the string buffer and store its offset
+ // in mName.Buffer
+ memcpy(mStringBuffer.get() + currentStringOffset, name.BeginReading(),
+ nameSize);
+ mArray[i].mName.Buffer =
+ reinterpret_cast<wchar_t*>(mArraySize + currentStringOffset);
+ // Only keep mName.Length and leave mName.MaximumLength to be zero
+ mArray[i].mName.Length = nameSize;
+
+ currentStringOffset += nameSize;
+ ++i;
+ }
+}
+
+nsresult DynamicBlocklistWriter::WriteToFile(const nsAString& aName) const {
+ nsCOMPtr<nsIFile> file;
+ MOZ_TRY(NS_NewLocalFile(aName, true, getter_AddRefs(file)));
+
+ nsCOMPtr<nsIOutputStream> stream;
+ MOZ_TRY(NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file));
+
+ MOZ_TRY(WriteValue(stream, kSignature));
+ MOZ_TRY(WriteValue(stream, kCurrentVersion));
+ MOZ_TRY(WriteValue(stream,
+ static_cast<uint32_t>(mArraySize + mStringBufferSize)));
+ MOZ_TRY(WriteBuffer(stream, mArray.begin(), mArraySize));
+ MOZ_TRY(WriteBuffer(stream, mStringBuffer.get(), mStringBufferSize));
+
+ nsresult rv;
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return safeStream->Finish();
+}
+
+void DynamicBlocklistWriter::Run() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsresult rv = NS_ERROR_ABORT;
+
+ LauncherRegistryInfo regInfo;
+ LauncherResult<std::wstring> blocklistFile = regInfo.GetBlocklistFileName();
+ if (blocklistFile.isOk()) {
+ const wchar_t* rawBuffer = blocklistFile.inspect().c_str();
+ rv = WriteToFile(nsDependentString(rawBuffer));
+ }
+
+ NS_DispatchToMainThread(
+ // Don't capture mPromise by copy because we're not in the main thread
+ NS_NewRunnableFunction(__func__, [promise = std::move(mPromise), rv]() {
+ if (NS_SUCCEEDED(rv)) {
+ promise->MaybeResolve(JS::NullHandleValue);
+ } else {
+ promise->MaybeReject(rv);
+ }
+ }));
+}
+
+void DynamicBlocklistWriter::Cancel() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mPromise) {
+ mPromise->MaybeReject(NS_ERROR_ABORT);
+ }
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/ModuleEvaluator.cpp b/toolkit/xre/dllservices/ModuleEvaluator.cpp
new file mode 100644
index 0000000000..58814e34a5
--- /dev/null
+++ b/toolkit/xre/dllservices/ModuleEvaluator.cpp
@@ -0,0 +1,253 @@
+/* -*- 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/. */
+
+#include "ModuleEvaluator.h"
+
+#include <algorithm> // For std::find()
+#include <type_traits>
+
+#include <windows.h>
+#include <shlobj.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ModuleVersionInfo.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WinDllServices.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsReadableUtils.h"
+#include "nsWindowsHelpers.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+
+// Fills a Vector with keyboard layout DLLs found in the registry.
+// These are leaf names only, not full paths. Here we will convert them to
+// lowercase before returning, to facilitate case-insensitive searches.
+// On error, this may return partial results.
+static Vector<nsString> GetKeyboardLayoutDlls() {
+ Vector<nsString> result;
+
+ HKEY rawKey;
+ if (::RegOpenKeyExW(HKEY_LOCAL_MACHINE,
+ L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts",
+ 0, KEY_ENUMERATE_SUB_KEYS, &rawKey) != ERROR_SUCCESS) {
+ return result;
+ }
+ nsAutoRegKey key(rawKey);
+
+ DWORD iKey = 0;
+ wchar_t strTemp[MAX_PATH] = {};
+ while (true) {
+ DWORD strTempSize = ArrayLength(strTemp);
+ if (RegEnumKeyExW(rawKey, iKey, strTemp, &strTempSize, nullptr, nullptr,
+ nullptr, nullptr) != ERROR_SUCCESS) {
+ // ERROR_NO_MORE_ITEMS or a real error: bail with what we have.
+ return result;
+ }
+ iKey++;
+
+ strTempSize = sizeof(strTemp);
+ if (::RegGetValueW(rawKey, strTemp, L"Layout File", RRF_RT_REG_SZ, nullptr,
+ strTemp, &strTempSize) == ERROR_SUCCESS &&
+ strTempSize) {
+ nsString ws(strTemp, ((strTempSize + 1) / sizeof(wchar_t)) - 1);
+ ToLowerCase(ws); // To facilitate case-insensitive searches
+ Unused << result.emplaceBack(std::move(ws));
+ }
+ }
+
+ return result;
+}
+
+/* static */
+bool ModuleEvaluator::ResolveKnownFolder(REFKNOWNFOLDERID aFolderId,
+ nsIFile** aOutFile) {
+ if (!aOutFile) {
+ return false;
+ }
+
+ *aOutFile = nullptr;
+
+ // Since we're running off main thread, we can't use NS_GetSpecialDirectory
+ PWSTR rawPath = nullptr;
+ HRESULT hr =
+ ::SHGetKnownFolderPath(aFolderId, KF_FLAG_DEFAULT, nullptr, &rawPath);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ using ShellStringUniquePtr =
+ UniquePtr<std::remove_pointer_t<PWSTR>, CoTaskMemFreeDeleter>;
+
+ ShellStringUniquePtr path(rawPath);
+
+ nsresult rv = NS_NewLocalFile(nsDependentString(path.get()), false, aOutFile);
+ return NS_SUCCEEDED(rv);
+}
+
+ModuleEvaluator::ModuleEvaluator()
+ : mKeyboardLayoutDlls(GetKeyboardLayoutDlls()) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+#if defined(_M_IX86)
+ // We want to resolve to SYSWOW64 when applicable
+ REFKNOWNFOLDERID systemFolderId = FOLDERID_SystemX86;
+#else
+ REFKNOWNFOLDERID systemFolderId = FOLDERID_System;
+#endif // defined(_M_IX86)
+
+ bool resolveOk =
+ ResolveKnownFolder(systemFolderId, getter_AddRefs(mSysDirectory));
+ MOZ_ASSERT(resolveOk);
+ if (!resolveOk) {
+ return;
+ }
+
+ nsCOMPtr<nsIFile> winSxSDir;
+ resolveOk = ResolveKnownFolder(FOLDERID_Windows, getter_AddRefs(winSxSDir));
+ MOZ_ASSERT(resolveOk);
+ if (!resolveOk) {
+ return;
+ }
+
+ nsresult rv = winSxSDir->Append(u"WinSxS"_ns);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ mWinSxSDirectory = std::move(winSxSDir);
+
+ nsCOMPtr<nsIFile> exeFile;
+ rv = XRE_GetBinaryPath(getter_AddRefs(exeFile));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ rv = exeFile->GetParent(getter_AddRefs(mExeDirectory));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ nsAutoString exePath;
+ rv = exeFile->GetPath(exePath);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ ModuleVersionInfo exeVi;
+ if (!exeVi.GetFromImage(exePath)) {
+ return;
+ }
+
+ mExeVersion = Some(ModuleVersion(exeVi.mFileVersion.Version64()));
+}
+
+ModuleEvaluator::operator bool() const {
+ return mExeVersion.isSome() && mExeDirectory && mSysDirectory &&
+ mWinSxSDirectory;
+}
+
+Maybe<ModuleTrustFlags> ModuleEvaluator::GetTrust(
+ const ModuleRecord& aModuleRecord) const {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ // We start by checking authenticode signatures, as the presence of any
+ // signature will produce an immediate pass/fail.
+ if (aModuleRecord.mVendorInfo.isSome() &&
+ aModuleRecord.mVendorInfo.ref().mSource ==
+ VendorInfo::Source::Signature) {
+ const nsString& signedBy = aModuleRecord.mVendorInfo.ref().mVendor;
+
+ if (signedBy.EqualsLiteral("Microsoft Windows")) {
+ return Some(ModuleTrustFlags::MicrosoftWindowsSignature);
+ } else if (signedBy.EqualsLiteral("Microsoft Corporation")) {
+ return Some(ModuleTrustFlags::MicrosoftWindowsSignature);
+ } else if (signedBy.EqualsLiteral("Mozilla Corporation")) {
+ return Some(ModuleTrustFlags::MozillaSignature);
+ } else {
+ // Being signed by somebody who is neither Microsoft nor us is an
+ // automatic and immediate disqualification.
+ return Some(ModuleTrustFlags::None);
+ }
+ }
+
+ const nsCOMPtr<nsIFile>& dllFile = aModuleRecord.mResolvedDosName;
+ MOZ_ASSERT(!!dllFile);
+ if (!dllFile) {
+ return Nothing();
+ }
+
+ nsAutoString dllLeafLower;
+ if (NS_FAILED(dllFile->GetLeafName(dllLeafLower))) {
+ return Nothing();
+ }
+
+ ToLowerCase(dllLeafLower); // To facilitate case-insensitive searching
+
+ // The JIT profiling module doesn't really have any other practical way to
+ // match; hard-code it as being trusted.
+ if (dllLeafLower.EqualsLiteral("jitpi.dll")) {
+ return Some(ModuleTrustFlags::JitPI);
+ }
+
+ ModuleTrustFlags result = ModuleTrustFlags::None;
+
+ nsresult rv;
+ bool contained;
+
+ // Is the DLL in the system directory?
+ rv = mSysDirectory->Contains(dllFile, &contained);
+ if (NS_SUCCEEDED(rv) && contained) {
+ result |= ModuleTrustFlags::SystemDirectory;
+ }
+
+ // Is the DLL in the WinSxS directory? Some Microsoft DLLs (e.g. comctl32) are
+ // loaded from here and don't have digital signatures. So while this is not a
+ // guarantee of trustworthiness, but is at least as valid as system32.
+ rv = mWinSxSDirectory->Contains(dllFile, &contained);
+ if (NS_SUCCEEDED(rv) && contained) {
+ result |= ModuleTrustFlags::WinSxSDirectory;
+ }
+
+ // Is it a keyboard layout DLL?
+ if (std::find(mKeyboardLayoutDlls.begin(), mKeyboardLayoutDlls.end(),
+ dllLeafLower) != mKeyboardLayoutDlls.end()) {
+ result |= ModuleTrustFlags::KeyboardLayout;
+ // This doesn't guarantee trustworthiness by itself. Keyboard layouts also
+ // must be in the system directory.
+ }
+
+ if (aModuleRecord.mVendorInfo.isSome() &&
+ aModuleRecord.mVendorInfo.ref().mSource ==
+ VendorInfo::Source::VersionInfo) {
+ const nsString& companyName = aModuleRecord.mVendorInfo.ref().mVendor;
+
+ if (companyName.EqualsLiteral("Microsoft Corporation")) {
+ result |= ModuleTrustFlags::MicrosoftVersion;
+ }
+ }
+
+ rv = mExeDirectory->Contains(dllFile, &contained);
+ if (NS_SUCCEEDED(rv) && contained) {
+ result |= ModuleTrustFlags::FirefoxDirectory;
+
+ // If the DLL is in the Firefox directory, does it also share the Firefox
+ // version info?
+ if (mExeVersion.isSome() && aModuleRecord.mVersion.isSome() &&
+ mExeVersion.value() == aModuleRecord.mVersion.value()) {
+ result |= ModuleTrustFlags::FirefoxDirectoryAndVersion;
+ }
+ }
+
+ return Some(result);
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/ModuleEvaluator.h b/toolkit/xre/dllservices/ModuleEvaluator.h
new file mode 100644
index 0000000000..565b5d4da4
--- /dev/null
+++ b/toolkit/xre/dllservices/ModuleEvaluator.h
@@ -0,0 +1,50 @@
+/* -*- 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_ModuleEvaluator_h
+#define mozilla_ModuleEvaluator_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UntrustedModulesData.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsString.h"
+
+#include <shtypes.h>
+
+namespace mozilla {
+
+class ModuleRecord;
+
+/**
+ * This class performs trustworthiness evaluation for incoming DLLs.
+ */
+class MOZ_RAII ModuleEvaluator final {
+ public:
+ ModuleEvaluator();
+
+ explicit operator bool() const;
+
+ Maybe<ModuleTrustFlags> GetTrust(const ModuleRecord& aModuleRecord) const;
+
+ private:
+ static bool ResolveKnownFolder(REFKNOWNFOLDERID aFolderId,
+ nsIFile** aOutFile);
+
+ private:
+ Maybe<ModuleVersion> mExeVersion; // Version number of the running EXE image
+ nsCOMPtr<nsIFile> mExeDirectory;
+ nsCOMPtr<nsIFile> mSysDirectory;
+ nsCOMPtr<nsIFile> mWinSxSDirectory;
+ Vector<nsString> mKeyboardLayoutDlls;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ModuleEvaluator_h
diff --git a/toolkit/xre/dllservices/ModuleVersionInfo.cpp b/toolkit/xre/dllservices/ModuleVersionInfo.cpp
new file mode 100644
index 0000000000..c8c2956a74
--- /dev/null
+++ b/toolkit/xre/dllservices/ModuleVersionInfo.cpp
@@ -0,0 +1,111 @@
+/* -*- 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/. */
+
+#include "ModuleVersionInfo.h"
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+/**
+ * Gets a string value from a version info block with the specified translation
+ * and field name.
+ *
+ * @param aBlock [in] The binary version resource block
+ * @param aTranslation [in] The translation ID as obtained in the version
+ * translation list.
+ * @param aFieldName [in] Null-terminated name of the desired field
+ * @param aResult [out] Receives the string value, if successful.
+ * @return true if successful. aResult is unchanged upon failure.
+ */
+static bool QueryStringValue(const void* aBlock, DWORD aTranslation,
+ const wchar_t* aFieldName, nsAString& aResult) {
+ nsAutoString path;
+ path.AppendPrintf("\\StringFileInfo\\%02lX%02lX%02lX%02lX\\%S",
+ (aTranslation & 0x0000ff00) >> 8,
+ (aTranslation & 0x000000ff),
+ (aTranslation & 0xff000000) >> 24,
+ (aTranslation & 0x00ff0000) >> 16, aFieldName);
+
+ wchar_t* lpBuffer = nullptr;
+ UINT len = 0;
+ if (!::VerQueryValueW(aBlock, path.get(), (PVOID*)&lpBuffer, &len)) {
+ return false;
+ }
+ aResult.Assign(lpBuffer, (size_t)len - 1);
+ return true;
+}
+
+/**
+ * Searches through translations in the version resource for the requested
+ * field. English(US) is preferred, otherwise we take the first translation
+ * that succeeds.
+ *
+ * @param aBlock [in] The binary version resource block
+ * @param aTranslations [in] The list of translation IDs available
+ * @param aNumTrans [in] Number of items in aTranslations
+ * @param aFieldName [in] Null-terminated name of the desired field
+ * @param aResult [in] Receives the string value, if successful.
+ * @return true if successful. aResult is unchanged upon failure.
+ */
+static bool QueryStringValue(const void* aBlock, const DWORD* aTranslations,
+ size_t aNumTrans, const wchar_t* aFieldName,
+ nsAString& aResult) {
+ static const DWORD kPreferredTranslation =
+ 0x04b00409; // English (US), Unicode
+ if (QueryStringValue(aBlock, kPreferredTranslation, aFieldName, aResult)) {
+ return true;
+ }
+ for (size_t i = 0; i < aNumTrans; ++i) {
+ if (QueryStringValue(aBlock, aTranslations[i], aFieldName, aResult)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool ModuleVersionInfo::GetFromImage(const nsAString& aPath) {
+ nsString path(aPath);
+ DWORD infoSize = GetFileVersionInfoSizeW(path.get(), nullptr);
+ if (!infoSize) {
+ return false;
+ }
+
+ auto verInfo = MakeUnique<BYTE[]>(infoSize);
+ if (!::GetFileVersionInfoW(path.get(), 0, infoSize, verInfo.get())) {
+ return false;
+ }
+
+ VS_FIXEDFILEINFO* vInfo = nullptr;
+ UINT vInfoLen = 0;
+ if (::VerQueryValueW(verInfo.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen)) {
+ mFileVersion =
+ VersionNumber(vInfo->dwFileVersionMS, vInfo->dwFileVersionLS);
+ mProductVersion =
+ VersionNumber(vInfo->dwProductVersionMS, vInfo->dwProductVersionLS);
+ }
+
+ // Note that regardless the character set indicated, strings are always
+ // returned as Unicode by the Windows APIs.
+ DWORD* pTrans = nullptr;
+ UINT cbTrans = 0;
+ if (::VerQueryValueW(verInfo.get(), L"\\VarFileInfo\\Translation",
+ (PVOID*)&pTrans, &cbTrans)) {
+ size_t numTrans = cbTrans / sizeof(DWORD);
+ QueryStringValue(verInfo.get(), pTrans, numTrans, L"CompanyName",
+ mCompanyName);
+ QueryStringValue(verInfo.get(), pTrans, numTrans, L"ProductName",
+ mProductName);
+ QueryStringValue(verInfo.get(), pTrans, numTrans, L"LegalCopyright",
+ mLegalCopyright);
+ QueryStringValue(verInfo.get(), pTrans, numTrans, L"FileDescription",
+ mFileDescription);
+ }
+
+ return true;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/ModuleVersionInfo.h b/toolkit/xre/dllservices/ModuleVersionInfo.h
new file mode 100644
index 0000000000..2b46817417
--- /dev/null
+++ b/toolkit/xre/dllservices/ModuleVersionInfo.h
@@ -0,0 +1,71 @@
+/* -*- 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_ModuleVersionInfo_h
+#define mozilla_ModuleVersionInfo_h
+
+#include <windows.h>
+#include "nsString.h"
+
+namespace mozilla {
+
+// Obtains basic version info from a module image's version info resource.
+class ModuleVersionInfo {
+ public:
+ // We favor English(US) for these fields, otherwise we take the first
+ // translation provided in the version resource.
+ nsString mCompanyName;
+ nsString mProductName;
+ nsString mLegalCopyright;
+ nsString mFileDescription;
+
+ // Represents an A.B.C.D style version number, internally stored as a uint64_t
+ class VersionNumber {
+ uint64_t mVersion64 = 0;
+
+ public:
+ VersionNumber() = default;
+
+ VersionNumber(DWORD aMostSig, DWORD aLeastSig)
+ : mVersion64((uint64_t)aMostSig << 32 | aLeastSig) {}
+
+ uint16_t A() const {
+ return (uint16_t)((mVersion64 & 0xffff000000000000) >> 48);
+ }
+
+ uint16_t B() const {
+ return (uint16_t)((mVersion64 & 0x0000ffff00000000) >> 32);
+ }
+
+ uint16_t C() const {
+ return (uint16_t)((mVersion64 & 0x00000000ffff0000) >> 16);
+ }
+
+ uint16_t D() const { return (uint16_t)(mVersion64 & 0x000000000000ffff); }
+
+ uint64_t Version64() const { return mVersion64; }
+
+ bool operator==(const VersionNumber& aOther) const {
+ return mVersion64 == aOther.mVersion64;
+ }
+
+ nsCString ToString() const {
+ nsCString ret;
+ ret.AppendPrintf("%d.%d.%d.%d", (int)A(), (int)B(), (int)C(), (int)D());
+ return ret;
+ }
+ };
+
+ VersionNumber mFileVersion;
+ VersionNumber mProductVersion;
+
+ // Returns false if it has no version resource or has no fixed version info.
+ bool GetFromImage(const nsAString& aPath);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ModuleVersionInfo_h
diff --git a/toolkit/xre/dllservices/UntrustedModulesData.cpp b/toolkit/xre/dllservices/UntrustedModulesData.cpp
new file mode 100644
index 0000000000..8b99eb8573
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesData.cpp
@@ -0,0 +1,446 @@
+/* -*- 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/. */
+
+#include "UntrustedModulesData.h"
+
+#include <windows.h>
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/FileUtilsWin.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WinDllServices.h"
+#include "ModuleEvaluator.h"
+#include "nsCOMPtr.h"
+#include "nsDebug.h"
+#include "nsXULAppAPI.h"
+#include "WinUtils.h"
+
+// Some utility functions
+
+static LONGLONG GetQPCFreq() {
+ static const LONGLONG sFreq = []() -> LONGLONG {
+ LARGE_INTEGER freq;
+ ::QueryPerformanceFrequency(&freq);
+ return freq.QuadPart;
+ }();
+
+ return sFreq;
+}
+
+template <typename ReturnT>
+static ReturnT QPCToTimeUnits(const LONGLONG aTimeStamp,
+ const LONGLONG aUnitsPerSec) {
+ return ReturnT(aTimeStamp * aUnitsPerSec) / ReturnT(GetQPCFreq());
+}
+
+template <typename ReturnT>
+static ReturnT QPCToMilliseconds(const LONGLONG aTimeStamp) {
+ const LONGLONG kMillisecondsPerSec = 1000;
+ return QPCToTimeUnits<ReturnT>(aTimeStamp, kMillisecondsPerSec);
+}
+
+template <typename ReturnT>
+static ReturnT QPCToMicroseconds(const LONGLONG aTimeStamp) {
+ const LONGLONG kMicrosecondsPerSec = 1000000;
+ return QPCToTimeUnits<ReturnT>(aTimeStamp, kMicrosecondsPerSec);
+}
+
+static LONGLONG TimeUnitsToQPC(const LONGLONG aTimeStamp,
+ const LONGLONG aUnitsPerSec) {
+ MOZ_ASSERT(aUnitsPerSec != 0);
+
+ LONGLONG result = aTimeStamp;
+ result *= GetQPCFreq();
+ result /= aUnitsPerSec;
+ return result;
+}
+
+namespace mozilla {
+
+static Maybe<double> QPCLoadDurationToMilliseconds(
+ const ModuleLoadInfo& aNtInfo) {
+ if (aNtInfo.IsBare()) {
+ return Nothing();
+ }
+
+ return Some(QPCToMilliseconds<double>(aNtInfo.mLoadTimeInfo.QuadPart));
+}
+
+ModuleRecord::ModuleRecord() : mTrustFlags(ModuleTrustFlags::None) {}
+
+ModuleRecord::ModuleRecord(const nsAString& aResolvedNtPath)
+ : mResolvedNtName(aResolvedNtPath), mTrustFlags(ModuleTrustFlags::None) {
+ if (aResolvedNtPath.IsEmpty()) {
+ return;
+ }
+
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ nsAutoString resolvedDosPath;
+ if (!NtPathToDosPath(aResolvedNtPath, resolvedDosPath)) {
+#if defined(DEBUG)
+ nsAutoCString msg;
+ msg.AppendLiteral("NtPathToDosPath failed for path \"");
+ msg.Append(NS_ConvertUTF16toUTF8(aResolvedNtPath));
+ msg.AppendLiteral("\"");
+ NS_WARNING(msg.get());
+#endif // defined(DEBUG)
+ return;
+ }
+
+ nsresult rv =
+ NS_NewLocalFile(resolvedDosPath, false, getter_AddRefs(mResolvedDosName));
+ if (NS_FAILED(rv) || !mResolvedDosName) {
+ return;
+ }
+
+ GetVersionAndVendorInfo(resolvedDosPath);
+
+ // Now sanitize the resolved DLL name. If we cannot sanitize this then this
+ // record must not be considered valid.
+ nsAutoString strSanitizedPath(resolvedDosPath);
+ if (!widget::WinUtils::PreparePathForTelemetry(strSanitizedPath)) {
+ return;
+ }
+
+ mSanitizedDllName = strSanitizedPath;
+}
+
+void ModuleRecord::GetVersionAndVendorInfo(const nsAString& aPath) {
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+
+ // WinVerifyTrust is too slow and of limited utility for our purposes, so
+ // we pass SkipTrustVerification here to avoid it.
+ UniquePtr<wchar_t[]> signedBy(
+ dllSvc->GetBinaryOrgName(PromiseFlatString(aPath).get(),
+ AuthenticodeFlags::SkipTrustVerification));
+ if (signedBy) {
+ mVendorInfo = Some(VendorInfo(VendorInfo::Source::Signature,
+ nsDependentString(signedBy.get())));
+ }
+
+ ModuleVersionInfo verInfo;
+ if (!verInfo.GetFromImage(aPath)) {
+ return;
+ }
+
+ if (verInfo.mFileVersion.Version64()) {
+ mVersion = Some(ModuleVersion(verInfo.mFileVersion.Version64()));
+ }
+
+ if (!mVendorInfo && !verInfo.mCompanyName.IsEmpty()) {
+ mVendorInfo =
+ Some(VendorInfo(VendorInfo::Source::VersionInfo, verInfo.mCompanyName));
+ }
+}
+
+bool ModuleRecord::IsXUL() const {
+ if (!mResolvedDosName) {
+ return false;
+ }
+
+ nsAutoString leafName;
+ nsresult rv = mResolvedDosName->GetLeafName(leafName);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ return leafName.EqualsIgnoreCase("xul.dll");
+}
+
+int32_t ModuleRecord::GetScoreThreshold() const {
+#ifdef ENABLE_TESTS
+ // Check whether we are running as an xpcshell test.
+ if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) {
+ nsAutoString dllLeaf;
+ if (NS_SUCCEEDED(mResolvedDosName->GetLeafName(dllLeaf))) {
+ // During xpcshell tests, this DLL is hard-coded to pass through all
+ // criteria checks and still result in "untrusted" status, so it shows up
+ // in the untrusted modules ping for the test to examine.
+ // Setting the threshold very high ensures the test will cover all
+ // criteria.
+ if (dllLeaf.EqualsIgnoreCase("modules-test.dll")) {
+ return 99999;
+ }
+ }
+ }
+#endif
+
+ return 100;
+}
+
+bool ModuleRecord::IsTrusted() const {
+ if (mTrustFlags == ModuleTrustFlags::None) {
+ return false;
+ }
+
+ // These flags are immediate passes
+ if (mTrustFlags &
+ (ModuleTrustFlags::MicrosoftWindowsSignature |
+ ModuleTrustFlags::MozillaSignature | ModuleTrustFlags::JitPI)) {
+ return true;
+ }
+
+ // The remaining flags, when set, each count for 50 points toward a
+ // trustworthiness score.
+ int32_t score = static_cast<int32_t>(
+ CountPopulation32(static_cast<uint32_t>(mTrustFlags))) *
+ 50;
+ return score >= GetScoreThreshold();
+}
+
+ProcessedModuleLoadEvent::ProcessedModuleLoadEvent()
+ : mProcessUptimeMS(0ULL),
+ mThreadId(0UL),
+ mBaseAddress(0U),
+ mIsDependent(false),
+ mLoadStatus(0) {}
+
+ProcessedModuleLoadEvent::ProcessedModuleLoadEvent(
+ glue::EnhancedModuleLoadInfo&& aModLoadInfo,
+ RefPtr<ModuleRecord>&& aModuleRecord)
+ : mProcessUptimeMS(QPCTimeStampToProcessUptimeMilliseconds(
+ aModLoadInfo.mNtLoadInfo.mBeginTimestamp)),
+ mLoadDurationMS(QPCLoadDurationToMilliseconds(aModLoadInfo.mNtLoadInfo)),
+ mThreadId(aModLoadInfo.mNtLoadInfo.mThreadId),
+ mThreadName(std::move(aModLoadInfo.mThreadName)),
+ mBaseAddress(
+ reinterpret_cast<uintptr_t>(aModLoadInfo.mNtLoadInfo.mBaseAddr)),
+ mModule(std::move(aModuleRecord)),
+ mIsDependent(aModLoadInfo.mNtLoadInfo.mIsDependent),
+ mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) {
+ if (!mModule || !(*mModule)) {
+ return;
+ }
+
+ mRequestedDllName = aModLoadInfo.mNtLoadInfo.mRequestedDllName.AsString();
+
+ // If we're in the main process, sanitize the requested DLL name here.
+ // If not, we cannot use PreparePathForTelemetry because it may try to
+ // delayload shlwapi.dll and could fail if the process is sandboxed.
+ // We leave mRequestedDllName unsanitized here and sanitize it when
+ // transferring it to the main process.
+ // (See ParamTraits<mozilla::UntrustedModulesData>::ReadEvent)
+ if (XRE_IsParentProcess()) {
+ SanitizeRequestedDllName();
+ }
+}
+
+void ProcessedModuleLoadEvent::SanitizeRequestedDllName() {
+ if (!mRequestedDllName.IsEmpty() &&
+ !widget::WinUtils::PreparePathForTelemetry(mRequestedDllName)) {
+ // If we cannot sanitize a path, we simply do not provide that field to
+ // Telemetry.
+ mRequestedDllName.Truncate();
+ }
+}
+
+/* static */
+Maybe<LONGLONG>
+ProcessedModuleLoadEvent::ComputeQPCTimeStampForProcessCreation() {
+ // This is similar to the algorithm used by TimeStamp::ProcessCreation:
+
+ // 1. Get the process creation timestamp as FILETIME;
+ FILETIME creationTime, exitTime, kernelTime, userTime;
+ if (!::GetProcessTimes(::GetCurrentProcess(), &creationTime, &exitTime,
+ &kernelTime, &userTime)) {
+ return Nothing();
+ }
+
+ // 2. Get current timestamps as both QPC and FILETIME;
+ LARGE_INTEGER nowQPC;
+ ::QueryPerformanceCounter(&nowQPC);
+
+ static const StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(LPFILETIME)>
+ pGetSystemTimePreciseAsFileTime(L"kernel32.dll",
+ "GetSystemTimePreciseAsFileTime");
+
+ FILETIME nowFile;
+ if (pGetSystemTimePreciseAsFileTime) {
+ pGetSystemTimePreciseAsFileTime(&nowFile);
+ } else {
+ ::GetSystemTimeAsFileTime(&nowFile);
+ }
+
+ // 3. Take the difference between the FILETIMEs from (1) and (2),
+ // respectively, yielding the elapsed process uptime in microseconds.
+ ULARGE_INTEGER ulCreation = {
+ {creationTime.dwLowDateTime, creationTime.dwHighDateTime}};
+ ULARGE_INTEGER ulNow = {{nowFile.dwLowDateTime, nowFile.dwHighDateTime}};
+
+ ULONGLONG timeSinceCreationMicroSec =
+ (ulNow.QuadPart - ulCreation.QuadPart) / 10ULL;
+
+ // 4. Convert the QPC timestamp from (1) to microseconds.
+ LONGLONG nowQPCMicroSec = QPCToMicroseconds<LONGLONG>(nowQPC.QuadPart);
+
+ // 5. Convert the elapsed uptime to an absolute timestamp by subtracting
+ // from (4), which yields the absolute timestamp for process creation.
+ // We convert back to QPC units before returning.
+ const LONGLONG kMicrosecondsPerSec = 1000000;
+ return Some(TimeUnitsToQPC(nowQPCMicroSec - timeSinceCreationMicroSec,
+ kMicrosecondsPerSec));
+}
+
+/* static */
+uint64_t ProcessedModuleLoadEvent::QPCTimeStampToProcessUptimeMilliseconds(
+ const LARGE_INTEGER& aTimeStamp) {
+ static const Maybe<LONGLONG> sProcessCreationTimeStamp =
+ ComputeQPCTimeStampForProcessCreation();
+
+ if (!sProcessCreationTimeStamp) {
+ return 0ULL;
+ }
+
+ LONGLONG diff = aTimeStamp.QuadPart - sProcessCreationTimeStamp.value();
+ return QPCToMilliseconds<uint64_t>(diff);
+}
+
+bool ProcessedModuleLoadEvent::IsXULLoad() const {
+ if (!mModule) {
+ return false;
+ }
+
+ return mModule->IsXUL();
+}
+
+bool ProcessedModuleLoadEvent::IsTrusted() const {
+ if (!mModule) {
+ return false;
+ }
+
+ return mModule->IsTrusted();
+}
+
+void UntrustedModulesData::AddNewLoads(
+ const ModulesMap& aModules, UntrustedModuleLoadingEvents&& aEvents,
+ Vector<Telemetry::ProcessedStack>&& aStacks) {
+ MOZ_ASSERT(aEvents.length() == aStacks.length());
+ for (const auto& entry : aModules) {
+ if (entry.GetData()->IsTrusted()) {
+ // Filter out trusted module records
+ continue;
+ }
+
+ Unused << mModules.LookupOrInsert(entry.GetKey(), entry.GetData());
+ }
+
+ MOZ_ASSERT(mEvents.length() <= kMaxEvents);
+
+ mNumEvents += aStacks.length();
+ mEvents.extendBack(std::move(aEvents));
+ for (auto&& stack : aStacks) {
+ mStacks.AddStack(stack);
+ }
+}
+
+void UntrustedModulesData::MergeModules(UntrustedModulesData& aNewData) {
+ for (auto item : aNewData.mEvents) {
+ mModules.WithEntryHandle(item->mEvent.mModule->mResolvedNtName,
+ [&](auto&& addPtr) {
+ if (addPtr) {
+ // Even though the path of a ModuleRecord
+ // matches, the object of ModuleRecord can be
+ // different. Make sure the event's mModule
+ // points to an object in mModules.
+ item->mEvent.mModule = addPtr.Data();
+ } else {
+ addPtr.Insert(item->mEvent.mModule);
+ }
+ });
+ }
+}
+
+void UntrustedModulesData::Merge(UntrustedModulesData&& aNewData) {
+ // Don't merge loading events of a different process
+ MOZ_ASSERT((mProcessType == aNewData.mProcessType) &&
+ (mPid == aNewData.mPid));
+
+ UntrustedModulesData newData(std::move(aNewData));
+
+ if (!mNumEvents) {
+ mNumEvents = newData.mNumEvents;
+ mModules = std::move(newData.mModules);
+ mEvents = std::move(newData.mEvents);
+ mStacks = std::move(newData.mStacks);
+ return;
+ }
+
+ MergeModules(newData);
+ mNumEvents += newData.mNumEvents;
+ mEvents.extendBack(std::move(newData.mEvents));
+ mStacks.AddStacks(newData.mStacks);
+}
+
+void UntrustedModulesData::Truncate() {
+ mStacks.Clear();
+
+ if (mNumEvents <= kMaxEvents) {
+ return;
+ }
+
+ UntrustedModuleLoadingEvents events;
+ events.splice(0, mEvents, mNumEvents - kMaxEvents, kMaxEvents);
+ std::swap(events, mEvents);
+ mNumEvents = kMaxEvents;
+}
+
+void UntrustedModulesData::MergeWithoutStacks(UntrustedModulesData&& aNewData) {
+ // Don't merge loading events of a different process
+ MOZ_ASSERT((mProcessType == aNewData.mProcessType) &&
+ (mPid == aNewData.mPid));
+ MOZ_ASSERT(!mStacks.GetStackCount());
+
+ UntrustedModulesData newData(std::move(aNewData));
+
+ if (mNumEvents > 0) {
+ MergeModules(newData);
+ } else {
+ mModules = std::move(newData.mModules);
+ }
+
+ mNumEvents += newData.mNumEvents;
+ mEvents.extendBack(std::move(newData.mEvents));
+
+ Truncate();
+}
+
+void UntrustedModulesData::Swap(UntrustedModulesData& aOther) {
+ GeckoProcessType tmpProcessType = mProcessType;
+ mProcessType = aOther.mProcessType;
+ aOther.mProcessType = tmpProcessType;
+
+ DWORD tmpPid = mPid;
+ mPid = aOther.mPid;
+ aOther.mPid = tmpPid;
+
+ TimeDuration tmpElapsed = mElapsed;
+ mElapsed = aOther.mElapsed;
+ aOther.mElapsed = tmpElapsed;
+
+ mModules.SwapElements(aOther.mModules);
+ std::swap(mNumEvents, aOther.mNumEvents);
+ std::swap(mEvents, aOther.mEvents);
+ mStacks.Swap(aOther.mStacks);
+
+ Maybe<double> tmpXULLoadDurationMS = mXULLoadDurationMS;
+ mXULLoadDurationMS = aOther.mXULLoadDurationMS;
+ aOther.mXULLoadDurationMS = tmpXULLoadDurationMS;
+
+ uint32_t tmpSanitizationFailures = mSanitizationFailures;
+ mSanitizationFailures = aOther.mSanitizationFailures;
+ aOther.mSanitizationFailures = tmpSanitizationFailures;
+
+ uint32_t tmpTrustTestFailures = mTrustTestFailures;
+ mTrustTestFailures = aOther.mTrustTestFailures;
+ aOther.mTrustTestFailures = tmpTrustTestFailures;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/UntrustedModulesData.h b/toolkit/xre/dllservices/UntrustedModulesData.h
new file mode 100644
index 0000000000..1bd6b91a36
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesData.h
@@ -0,0 +1,650 @@
+/* -*- 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_UntrustedModulesData_h
+#define mozilla_UntrustedModulesData_h
+
+#if defined(XP_WIN)
+
+# include "ipc/IPCMessageUtils.h"
+# include "mozilla/CombinedStacks.h"
+# include "mozilla/DebugOnly.h"
+# include "mozilla/LinkedList.h"
+# include "mozilla/Maybe.h"
+# include "mozilla/RefPtr.h"
+# include "mozilla/TypedEnumBits.h"
+# include "mozilla/Unused.h"
+# include "mozilla/Variant.h"
+# include "mozilla/Vector.h"
+# include "mozilla/WinHeaderOnlyUtils.h"
+# include "nsCOMPtr.h"
+# include "nsHashKeys.h"
+# include "nsIFile.h"
+# include "nsISupportsImpl.h"
+# include "nsRefPtrHashtable.h"
+# include "nsString.h"
+# include "nsXULAppAPI.h"
+
+namespace mozilla {
+namespace glue {
+struct EnhancedModuleLoadInfo;
+} // namespace glue
+
+enum class ModuleTrustFlags : uint32_t {
+ None = 0,
+ MozillaSignature = 1,
+ MicrosoftWindowsSignature = 2,
+ MicrosoftVersion = 4,
+ FirefoxDirectory = 8,
+ FirefoxDirectoryAndVersion = 0x10,
+ SystemDirectory = 0x20,
+ KeyboardLayout = 0x40,
+ JitPI = 0x80,
+ WinSxSDirectory = 0x100,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ModuleTrustFlags);
+
+class VendorInfo final {
+ public:
+ enum class Source : uint32_t {
+ None,
+ Signature,
+ VersionInfo,
+ };
+
+ VendorInfo() : mSource(Source::None) {}
+ VendorInfo(const Source aSource, const nsAString& aVendor)
+ : mSource(aSource), mVendor(aVendor) {
+ MOZ_ASSERT(aSource != Source::None && !aVendor.IsEmpty());
+ }
+
+ Source mSource;
+ nsString mVendor;
+};
+
+class ModulesMap;
+
+class ModuleRecord final {
+ public:
+ explicit ModuleRecord(const nsAString& aResolvedNtPath);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ModuleRecord)
+
+ nsString mResolvedNtName;
+ nsCOMPtr<nsIFile> mResolvedDosName;
+ nsString mSanitizedDllName;
+ Maybe<ModuleVersion> mVersion;
+ Maybe<VendorInfo> mVendorInfo;
+ ModuleTrustFlags mTrustFlags;
+
+ explicit operator bool() const { return !mSanitizedDllName.IsEmpty(); }
+ bool IsXUL() const;
+ bool IsTrusted() const;
+
+ ModuleRecord(const ModuleRecord&) = delete;
+ ModuleRecord(ModuleRecord&&) = delete;
+
+ ModuleRecord& operator=(const ModuleRecord&) = delete;
+ ModuleRecord& operator=(ModuleRecord&&) = delete;
+
+ private:
+ ModuleRecord();
+ ~ModuleRecord() = default;
+ void GetVersionAndVendorInfo(const nsAString& aPath);
+ int32_t GetScoreThreshold() const;
+
+ friend struct ::IPC::ParamTraits<ModulesMap>;
+};
+
+/**
+ * This type holds module path data using one of two internal representations.
+ * It may be created from either a nsTHashtable or a Vector, and may be
+ * serialized from either representation into a common format over the wire.
+ * Deserialization always uses the Vector representation.
+ */
+struct ModulePaths final {
+ using SetType = nsTHashtable<nsStringCaseInsensitiveHashKey>;
+ using VecType = Vector<nsString>;
+
+ Variant<SetType, VecType> mModuleNtPaths;
+
+ template <typename T>
+ explicit ModulePaths(T&& aPaths)
+ : mModuleNtPaths(AsVariant(std::forward<T>(aPaths))) {}
+
+ ModulePaths() : mModuleNtPaths(VecType()) {}
+
+ ModulePaths(const ModulePaths& aOther) = delete;
+ ModulePaths(ModulePaths&& aOther) = default;
+ ModulePaths& operator=(const ModulePaths&) = delete;
+ ModulePaths& operator=(ModulePaths&&) = default;
+};
+
+class ProcessedModuleLoadEvent final {
+ public:
+ ProcessedModuleLoadEvent();
+ ProcessedModuleLoadEvent(glue::EnhancedModuleLoadInfo&& aModLoadInfo,
+ RefPtr<ModuleRecord>&& aModuleRecord);
+
+ explicit operator bool() const { return mModule && *mModule; }
+ bool IsXULLoad() const;
+ bool IsTrusted() const;
+
+ uint64_t mProcessUptimeMS;
+ Maybe<double> mLoadDurationMS;
+ DWORD mThreadId;
+ nsCString mThreadName;
+ nsString mRequestedDllName;
+ // We intentionally store mBaseAddress as part of the event and not the
+ // module, as relocation may cause it to change between loads. If so, we want
+ // to know about it.
+ uintptr_t mBaseAddress;
+ RefPtr<ModuleRecord> mModule;
+ bool mIsDependent;
+ uint32_t mLoadStatus; // corresponding to enum ModuleLoadInfo::Status
+
+ ProcessedModuleLoadEvent(const ProcessedModuleLoadEvent&) = delete;
+ ProcessedModuleLoadEvent& operator=(const ProcessedModuleLoadEvent&) = delete;
+
+ ProcessedModuleLoadEvent(ProcessedModuleLoadEvent&&) = default;
+ ProcessedModuleLoadEvent& operator=(ProcessedModuleLoadEvent&&) = default;
+
+ void SanitizeRequestedDllName();
+
+ private:
+ static Maybe<LONGLONG> ComputeQPCTimeStampForProcessCreation();
+ static uint64_t QPCTimeStampToProcessUptimeMilliseconds(
+ const LARGE_INTEGER& aTimeStamp);
+};
+
+// Declaring ModulesMap this way makes it much easier to forward declare than
+// if we had used |using| or |typedef|.
+class ModulesMap final
+ : public nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, ModuleRecord> {
+ public:
+ ModulesMap()
+ : nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, ModuleRecord>() {}
+};
+
+struct ProcessedModuleLoadEventContainer final
+ : public LinkedListElement<ProcessedModuleLoadEventContainer> {
+ ProcessedModuleLoadEvent mEvent;
+ ProcessedModuleLoadEventContainer() = default;
+ explicit ProcessedModuleLoadEventContainer(ProcessedModuleLoadEvent&& aEvent)
+ : mEvent(std::move(aEvent)) {}
+
+ ProcessedModuleLoadEventContainer(ProcessedModuleLoadEventContainer&&) =
+ default;
+ ProcessedModuleLoadEventContainer& operator=(
+ ProcessedModuleLoadEventContainer&&) = default;
+ ProcessedModuleLoadEventContainer(const ProcessedModuleLoadEventContainer&) =
+ delete;
+ ProcessedModuleLoadEventContainer& operator=(
+ const ProcessedModuleLoadEventContainer&) = delete;
+};
+using UntrustedModuleLoadingEvents =
+ AutoCleanLinkedList<ProcessedModuleLoadEventContainer>;
+
+class UntrustedModulesData final {
+ // Merge aNewData.mEvents into this->mModules and also
+ // make module entries in aNewData point to items in this->mModules.
+ void MergeModules(UntrustedModulesData& aNewData);
+
+ public:
+ // Ensure mEvents will never retain more than kMaxEvents events.
+ // This constant matches the maximum in Telemetry::CombinedStacks.
+ static constexpr size_t kMaxEvents = 50;
+
+ UntrustedModulesData()
+ : mProcessType(XRE_GetProcessType()),
+ mPid(::GetCurrentProcessId()),
+ mNumEvents(0),
+ mSanitizationFailures(0),
+ mTrustTestFailures(0) {}
+
+ UntrustedModulesData(UntrustedModulesData&&) = default;
+ UntrustedModulesData& operator=(UntrustedModulesData&&) = default;
+
+ UntrustedModulesData(const UntrustedModulesData&) = delete;
+ UntrustedModulesData& operator=(const UntrustedModulesData&) = delete;
+
+ explicit operator bool() const {
+ return !mEvents.isEmpty() || mSanitizationFailures || mTrustTestFailures ||
+ mXULLoadDurationMS.isSome();
+ }
+
+ void AddNewLoads(const ModulesMap& aModulesMap,
+ UntrustedModuleLoadingEvents&& aEvents,
+ Vector<Telemetry::ProcessedStack>&& aStacks);
+ void Merge(UntrustedModulesData&& aNewData);
+ void MergeWithoutStacks(UntrustedModulesData&& aNewData);
+ void Swap(UntrustedModulesData& aOther);
+
+ // Drop callstack data and old loading events.
+ void Truncate();
+
+ GeckoProcessType mProcessType;
+ DWORD mPid;
+ TimeDuration mElapsed;
+ ModulesMap mModules;
+ uint32_t mNumEvents;
+ UntrustedModuleLoadingEvents mEvents;
+ Telemetry::CombinedStacks mStacks;
+ Maybe<double> mXULLoadDurationMS;
+ uint32_t mSanitizationFailures;
+ uint32_t mTrustTestFailures;
+};
+
+class ModulesMapResult final {
+ public:
+ ModulesMapResult() : mTrustTestFailures(0) {}
+
+ ModulesMapResult(const ModulesMapResult& aOther) = delete;
+ ModulesMapResult(ModulesMapResult&& aOther) = default;
+ ModulesMapResult& operator=(const ModulesMapResult& aOther) = delete;
+ ModulesMapResult& operator=(ModulesMapResult&& aOther) = default;
+
+ ModulesMap mModules;
+ uint32_t mTrustTestFailures;
+};
+
+} // namespace mozilla
+
+namespace IPC {
+
+template <>
+struct ParamTraits<mozilla::ModuleVersion> {
+ typedef mozilla::ModuleVersion paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt64(aParam.AsInteger());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint64_t ver;
+ if (!aReader->ReadUInt64(&ver)) {
+ return false;
+ }
+
+ *aResult = ver;
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::VendorInfo> {
+ typedef mozilla::VendorInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt32(static_cast<uint32_t>(aParam.mSource));
+ WriteParam(aWriter, aParam.mVendor);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t source;
+ if (!aReader->ReadUInt32(&source)) {
+ return false;
+ }
+
+ aResult->mSource = static_cast<mozilla::VendorInfo::Source>(source);
+
+ if (!ReadParam(aReader, &aResult->mVendor)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModuleRecord> {
+ typedef mozilla::ModuleRecord paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mResolvedNtName);
+
+ nsAutoString resolvedDosName;
+ if (aParam.mResolvedDosName) {
+ mozilla::DebugOnly<nsresult> rv =
+ aParam.mResolvedDosName->GetPath(resolvedDosName);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ WriteParam(aWriter, resolvedDosName);
+ WriteParam(aWriter, aParam.mSanitizedDllName);
+ WriteParam(aWriter, aParam.mVersion);
+ WriteParam(aWriter, aParam.mVendorInfo);
+ aWriter->WriteUInt32(static_cast<uint32_t>(aParam.mTrustFlags));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mResolvedNtName)) {
+ return false;
+ }
+
+ nsAutoString resolvedDosName;
+ if (!ReadParam(aReader, &resolvedDosName)) {
+ return false;
+ }
+
+ if (resolvedDosName.IsEmpty()) {
+ aResult->mResolvedDosName = nullptr;
+ } else if (NS_FAILED(NS_NewLocalFile(
+ resolvedDosName, false,
+ getter_AddRefs(aResult->mResolvedDosName)))) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mSanitizedDllName)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mVersion)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mVendorInfo)) {
+ return false;
+ }
+
+ uint32_t trustFlags;
+ if (!aReader->ReadUInt32(&trustFlags)) {
+ return false;
+ }
+
+ aResult->mTrustFlags = static_cast<mozilla::ModuleTrustFlags>(trustFlags);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModulesMap> {
+ typedef mozilla::ModulesMap paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt32(aParam.Count());
+
+ for (const auto& entry : aParam) {
+ MOZ_RELEASE_ASSERT(entry.GetData());
+ WriteParam(aWriter, entry.GetKey());
+ WriteParam(aWriter, *(entry.GetData()));
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t count;
+ if (!ReadParam(aReader, &count)) {
+ return false;
+ }
+
+ for (uint32_t current = 0; current < count; ++current) {
+ nsAutoString key;
+ if (!ReadParam(aReader, &key) || key.IsEmpty()) {
+ return false;
+ }
+
+ RefPtr<mozilla::ModuleRecord> rec(new mozilla::ModuleRecord());
+ if (!ReadParam(aReader, rec.get())) {
+ return false;
+ }
+
+ aResult->InsertOrUpdate(key, std::move(rec));
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModulePaths> {
+ typedef mozilla::ModulePaths paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aParam.mModuleNtPaths.match(
+ [aWriter](const paramType::SetType& aSet) { WriteSet(aWriter, aSet); },
+ [aWriter](const paramType::VecType& aVec) {
+ WriteVector(aWriter, aVec);
+ });
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t len;
+ if (!aReader->ReadUInt32(&len)) {
+ return false;
+ }
+
+ // As noted in the comments for ModulePaths, we only deserialize using the
+ // Vector representation.
+ auto& vec = aResult->mModuleNtPaths.as<paramType::VecType>();
+ if (!vec.reserve(len)) {
+ return false;
+ }
+
+ for (uint32_t idx = 0; idx < len; ++idx) {
+ nsString str;
+ if (!ReadParam(aReader, &str)) {
+ return false;
+ }
+
+ if (!vec.emplaceBack(std::move(str))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private:
+ // NB: This function must write out the set in the same format as WriteVector
+ static void WriteSet(MessageWriter* aWriter, const paramType::SetType& aSet) {
+ aWriter->WriteUInt32(aSet.Count());
+ for (const auto& key : aSet.Keys()) {
+ WriteParam(aWriter, key);
+ }
+ }
+
+ // NB: This function must write out the vector in the same format as WriteSet
+ static void WriteVector(MessageWriter* aWriter,
+ const paramType::VecType& aVec) {
+ aWriter->WriteUInt32(aVec.length());
+ for (auto const& item : aVec) {
+ WriteParam(aWriter, item);
+ }
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::UntrustedModulesData> {
+ typedef mozilla::UntrustedModulesData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt32(aParam.mProcessType);
+ aWriter->WriteULong(aParam.mPid);
+ WriteParam(aWriter, aParam.mElapsed);
+ WriteParam(aWriter, aParam.mModules);
+
+ aWriter->WriteUInt32(aParam.mNumEvents);
+ for (auto event : aParam.mEvents) {
+ WriteEvent(aWriter, event->mEvent);
+ }
+
+ WriteParam(aWriter, aParam.mStacks);
+ WriteParam(aWriter, aParam.mXULLoadDurationMS);
+ aWriter->WriteUInt32(aParam.mSanitizationFailures);
+ aWriter->WriteUInt32(aParam.mTrustTestFailures);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t processType;
+ if (!aReader->ReadUInt32(&processType)) {
+ return false;
+ }
+
+ aResult->mProcessType = static_cast<GeckoProcessType>(processType);
+
+ if (!aReader->ReadULong(&aResult->mPid)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mElapsed)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mModules)) {
+ return false;
+ }
+
+ // We read mEvents manually so that we can use ReadEvent defined below.
+ if (!ReadParam(aReader, &aResult->mNumEvents)) {
+ return false;
+ }
+
+ for (uint32_t curEventIdx = 0; curEventIdx < aResult->mNumEvents;
+ ++curEventIdx) {
+ auto newEvent =
+ mozilla::MakeUnique<mozilla::ProcessedModuleLoadEventContainer>();
+ if (!ReadEvent(aReader, &newEvent->mEvent, aResult->mModules)) {
+ return false;
+ }
+ aResult->mEvents.insertBack(newEvent.release());
+ }
+
+ if (!ReadParam(aReader, &aResult->mStacks)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mXULLoadDurationMS)) {
+ return false;
+ }
+
+ if (!aReader->ReadUInt32(&aResult->mSanitizationFailures)) {
+ return false;
+ }
+
+ if (!aReader->ReadUInt32(&aResult->mTrustTestFailures)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private:
+ // Because ProcessedModuleLoadEvent depends on a hash table from
+ // UntrustedModulesData, we do its serialization as part of this
+ // specialization.
+ static void WriteEvent(MessageWriter* aWriter,
+ const mozilla::ProcessedModuleLoadEvent& aParam) {
+ aWriter->WriteUInt64(aParam.mProcessUptimeMS);
+ WriteParam(aWriter, aParam.mLoadDurationMS);
+ aWriter->WriteULong(aParam.mThreadId);
+ WriteParam(aWriter, aParam.mThreadName);
+ WriteParam(aWriter, aParam.mRequestedDllName);
+ WriteParam(aWriter, aParam.mBaseAddress);
+ WriteParam(aWriter, aParam.mIsDependent);
+ WriteParam(aWriter, aParam.mLoadStatus);
+
+ // We don't write the ModuleRecord directly; we write its key into the
+ // UntrustedModulesData::mModules hash table.
+ MOZ_ASSERT(aParam.mModule && !aParam.mModule->mResolvedNtName.IsEmpty());
+ WriteParam(aWriter, aParam.mModule->mResolvedNtName);
+ }
+
+ // Because ProcessedModuleLoadEvent depends on a hash table from
+ // UntrustedModulesData, we do its deserialization as part of this
+ // specialization.
+ static bool ReadEvent(MessageReader* aReader,
+ mozilla::ProcessedModuleLoadEvent* aResult,
+ const mozilla::ModulesMap& aModulesMap) {
+ if (!aReader->ReadUInt64(&aResult->mProcessUptimeMS)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mLoadDurationMS)) {
+ return false;
+ }
+
+ if (!aReader->ReadULong(&aResult->mThreadId)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mThreadName)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mRequestedDllName)) {
+ return false;
+ }
+
+ // When ProcessedModuleLoadEvent was constructed in a child process, we left
+ // mRequestedDllName unsanitized, so now is a good time to sanitize it.
+ aResult->SanitizeRequestedDllName();
+
+ if (!ReadParam(aReader, &aResult->mBaseAddress)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mIsDependent)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mLoadStatus)) {
+ return false;
+ }
+
+ nsAutoString resolvedNtName;
+ if (!ReadParam(aReader, &resolvedNtName)) {
+ return false;
+ }
+
+ // NB: While bad data integrity might for some reason result in a null
+ // mModule, we do not fail the deserialization; this is a data error,
+ // rather than an IPC error. The error is detected and dealt with in
+ // telemetry.
+ aResult->mModule = aModulesMap.Get(resolvedNtName);
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModulesMapResult> {
+ typedef mozilla::ModulesMapResult paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mModules);
+ aWriter->WriteUInt32(aParam.mTrustTestFailures);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mModules)) {
+ return false;
+ }
+
+ if (!aReader->ReadUInt32(&aResult->mTrustTestFailures)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+} // namespace IPC
+
+#else // defined(XP_WIN)
+
+namespace mozilla {
+
+// For compiling IPDL on non-Windows platforms
+using UntrustedModulesData = uint32_t;
+using ModulePaths = uint32_t;
+using ModulesMapResult = uint32_t;
+
+} // namespace mozilla
+
+#endif // defined(XP_WIN)
+
+#endif // mozilla_UntrustedModulesData_h
diff --git a/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp b/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp
new file mode 100644
index 0000000000..0bab977e41
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp
@@ -0,0 +1,1040 @@
+/* -*- 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/. */
+
+#include "UntrustedModulesProcessor.h"
+
+#include <windows.h>
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/RDDChild.h"
+#include "mozilla/RDDParent.h"
+#include "mozilla/RDDProcessManager.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+#include "ModuleEvaluator.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIObserverService.h"
+#include "nsTHashtable.h"
+#include "nsThreadUtils.h"
+#include "nsXULAppAPI.h"
+#include "private/prpriv.h" // For PR_GetThreadID
+
+static DWORD ToWin32ThreadId(nsIThread* aThread) {
+ if (!aThread) {
+ return 0UL;
+ }
+
+ PRThread* prThread;
+ nsresult rv = aThread->GetPRThread(&prThread);
+ if (NS_FAILED(rv)) {
+ // Possible when a LazyInitThread's underlying nsThread is not present
+ return 0UL;
+ }
+
+ return DWORD(::PR_GetThreadID(prThread));
+}
+
+namespace mozilla {
+
+class MOZ_RAII BackgroundPriorityRegion final {
+ public:
+ BackgroundPriorityRegion()
+ : mIsBackground(
+ ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_IDLE)) {}
+
+ ~BackgroundPriorityRegion() {
+ if (!mIsBackground) {
+ return;
+ }
+
+ Clear(::GetCurrentThread());
+ }
+
+ static void Clear(nsIThread* aThread) {
+ DWORD tid = ToWin32ThreadId(aThread);
+ if (!tid) {
+ return;
+ }
+
+ nsAutoHandle thread(
+ ::OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, tid));
+ if (!thread) {
+ return;
+ }
+
+ Clear(thread);
+ }
+
+ BackgroundPriorityRegion(const BackgroundPriorityRegion&) = delete;
+ BackgroundPriorityRegion(BackgroundPriorityRegion&&) = delete;
+ BackgroundPriorityRegion& operator=(const BackgroundPriorityRegion&) = delete;
+ BackgroundPriorityRegion& operator=(BackgroundPriorityRegion&&) = delete;
+
+ private:
+ static void Clear(HANDLE aThread) {
+ DebugOnly<BOOL> ok = ::SetThreadPriority(aThread, THREAD_PRIORITY_NORMAL);
+ MOZ_ASSERT(ok);
+ }
+
+ private:
+ const BOOL mIsBackground;
+};
+
+/* static */
+bool UntrustedModulesProcessor::IsSupportedProcessType() {
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Default:
+ case GeckoProcessType_Content:
+ case GeckoProcessType_Socket:
+ return Telemetry::CanRecordReleaseData();
+ case GeckoProcessType_RDD:
+ // For RDD process, we check the telemetry settings in RDDChild::Init()
+ // running in the browser process because CanRecordReleaseData() always
+ // returns false here.
+ return true;
+ default:
+ return false;
+ }
+}
+
+/* static */
+RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create(
+ bool aIsReadyForBackgroundProcessing) {
+ if (!IsSupportedProcessType()) {
+ return nullptr;
+ }
+
+ RefPtr<UntrustedModulesProcessor> result(
+ new UntrustedModulesProcessor(aIsReadyForBackgroundProcessing));
+ return result.forget();
+}
+
+NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver)
+
+static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes
+
+UntrustedModulesProcessor::UntrustedModulesProcessor(
+ bool aIsReadyForBackgroundProcessing)
+ : mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules"_ns,
+ LazyIdleThread::ManualShutdown)),
+ mUnprocessedMutex(
+ "mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
+ mModuleCacheMutex(
+ "mozilla::UntrustedModulesProcessor::mModuleCacheMutex"),
+ mStatus(aIsReadyForBackgroundProcessing ? Status::Allowed
+ : Status::StartingUp) {
+ AddObservers();
+}
+
+void UntrustedModulesProcessor::AddObservers() {
+ nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
+ obsServ->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false);
+ obsServ->AddObserver(this, "xpcom-shutdown-threads", false);
+ obsServ->AddObserver(this, "unblock-untrusted-modules-thread", false);
+ if (XRE_IsContentProcess()) {
+ obsServ->AddObserver(this, "content-child-will-shutdown", false);
+ }
+}
+
+bool UntrustedModulesProcessor::IsReadyForBackgroundProcessing() const {
+ return mStatus == Status::Allowed;
+}
+
+void UntrustedModulesProcessor::Disable() {
+ // Ensure that mThread cannot run at low priority anymore
+ BackgroundPriorityRegion::Clear(mThread);
+
+ // No more background processing allowed beyond this point
+ if (mStatus.exchange(Status::ShuttingDown) != Status::Allowed) {
+ return;
+ }
+
+ MutexAutoLock lock(mUnprocessedMutex);
+ CancelScheduledProcessing(lock);
+}
+
+NS_IMETHODIMP UntrustedModulesProcessor::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) ||
+ !strcmp(aTopic, "content-child-will-shutdown")) {
+ Disable();
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "xpcom-shutdown-threads")) {
+ Disable();
+ mThread->Shutdown();
+
+ RemoveObservers();
+
+ mThread = nullptr;
+ return NS_OK;
+ }
+
+ if (!strcmp(aTopic, "unblock-untrusted-modules-thread")) {
+ nsCOMPtr<nsIObserverService> obs(services::GetObserverService());
+ obs->RemoveObserver(this, "unblock-untrusted-modules-thread");
+
+ mStatus.compareExchange(Status::StartingUp, Status::Allowed);
+
+ if (!IsReadyForBackgroundProcessing()) {
+ // If we're shutting down, stop here.
+ return NS_OK;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Propagate notification to child processes
+ nsTArray<dom::ContentParent*> contentProcesses;
+ dom::ContentParent::GetAll(contentProcesses);
+ for (auto* proc : contentProcesses) {
+ Unused << proc->SendUnblockUntrustedModulesThread();
+ }
+ if (auto* proc = net::SocketProcessParent::GetSingleton()) {
+ Unused << proc->SendUnblockUntrustedModulesThread();
+ }
+ if (auto* rddMgr = RDDProcessManager::Get()) {
+ if (auto* proc = rddMgr->GetRDDChild()) {
+ Unused << proc->SendUnblockUntrustedModulesThread();
+ }
+ }
+ }
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Not reachable");
+
+ return NS_OK;
+}
+
+void UntrustedModulesProcessor::RemoveObservers() {
+ nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
+ obsServ->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+ obsServ->RemoveObserver(this, "xpcom-shutdown-threads");
+ obsServ->RemoveObserver(this, "unblock-untrusted-modules-thread");
+ if (XRE_IsContentProcess()) {
+ obsServ->RemoveObserver(this, "content-child-will-shutdown");
+ }
+}
+
+void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing(
+ const MutexAutoLock& aProofOfLock) {
+ // In case something tried to load a DLL during shutdown
+ if (!mThread) {
+ return;
+ }
+
+#if defined(ENABLE_TESTS)
+ // Don't bother scheduling background processing in short-lived xpcshell
+ // processes; it makes the test suites take too long.
+ if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) {
+ return;
+ }
+#endif // defined(ENABLE_TESTS)
+
+ if (mIdleRunnable) {
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ // Schedule a runnable to trigger background processing once the main thread
+ // has gone idle. We do it this way to ensure that we don't start doing a
+ // bunch of processing during periods of heavy main thread activity.
+ nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod(
+ "UntrustedModulesProcessor::DispatchBackgroundProcessing", this,
+ &UntrustedModulesProcessor::DispatchBackgroundProcessing));
+
+ if (NS_FAILED(NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable),
+ EventQueuePriority::Idle))) {
+ return;
+ }
+
+ mIdleRunnable = std::move(idleRunnable);
+}
+
+void UntrustedModulesProcessor::CancelScheduledProcessing(
+ const MutexAutoLock& aProofOfLock) {
+ if (!mIdleRunnable) {
+ return;
+ }
+
+ nsCOMPtr<nsICancelableRunnable> cancelable(do_QueryInterface(mIdleRunnable));
+ if (cancelable) {
+ // Stop the pending idle runnable from doing anything
+ cancelable->Cancel();
+ }
+
+ mIdleRunnable = nullptr;
+}
+
+void UntrustedModulesProcessor::DispatchBackgroundProcessing() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod(
+ "UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this,
+ &UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue));
+
+ mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+}
+
+void UntrustedModulesProcessor::Enqueue(
+ glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
+ if (mStatus == Status::ShuttingDown) {
+ return;
+ }
+
+ DWORD bgThreadId = ToWin32ThreadId(mThread);
+ if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) {
+ // Exclude loads that were caused by our own background thread
+ return;
+ }
+
+ MutexAutoLock lock(mUnprocessedMutex);
+
+ mUnprocessedModuleLoads.insertBack(
+ new UnprocessedModuleLoadInfoContainer(std::move(aModLoadInfo)));
+
+ ScheduleNonEmptyQueueProcessing(lock);
+}
+
+void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
+ if (mStatus == Status::ShuttingDown) {
+ return;
+ }
+
+ // We do not need to attempt to exclude our background thread in this case
+ // because |aEvents| was accumulated prior to |mThread|'s existence.
+
+ MutexAutoLock lock(mUnprocessedMutex);
+
+ for (auto& event : aEvents) {
+ mUnprocessedModuleLoads.insertBack(
+ new UnprocessedModuleLoadInfoContainer(std::move(event)));
+ }
+
+ ScheduleNonEmptyQueueProcessing(lock);
+}
+
+void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() {
+#if defined(DEBUG)
+ PRThread* curThread;
+ PRThread* lazyIdleThread;
+
+ MOZ_ASSERT(NS_SUCCEEDED(NS_GetCurrentThread()->GetPRThread(&curThread)) &&
+ NS_SUCCEEDED(mThread->GetPRThread(&lazyIdleThread)) &&
+ curThread == lazyIdleThread);
+#endif // defined(DEBUG)
+}
+
+RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // Clear any background priority in case background processing is running.
+ BackgroundPriorityRegion::Clear(mThread);
+
+ RefPtr<UntrustedModulesProcessor> self(this);
+ return InvokeAsync(
+ mThread->SerialEventTarget(), __func__,
+ [self = std::move(self)]() { return self->GetProcessedDataInternal(); });
+}
+
+RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrust(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
+ MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread());
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ RefPtr<UntrustedModulesProcessor> self(this);
+ auto run = [self = std::move(self), modPaths = std::move(aModPaths),
+ runNormal = aRunAtNormalPriority]() mutable {
+ return self->GetModulesTrustInternal(std::move(modPaths), runNormal);
+ };
+
+ if (aRunAtNormalPriority) {
+ // Clear any background priority in case background processing is running.
+ BackgroundPriorityRegion::Clear(mThread);
+
+ return InvokeAsync(mThread->SerialEventTarget(), __func__, std::move(run));
+ }
+
+ RefPtr<ModulesTrustPromise::Private> p(
+ new ModulesTrustPromise::Private(__func__));
+ nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget());
+ const char* source = __func__;
+
+ auto runWrap = [evtTarget = std::move(evtTarget), p, source,
+ run = std::move(run)]() mutable -> void {
+ InvokeAsync(evtTarget, source, std::move(run))->ChainTo(p.forget(), source);
+ };
+
+ nsCOMPtr<nsIRunnable> idleRunnable(
+ NS_NewRunnableFunction(source, std::move(runWrap)));
+
+ nsresult rv = NS_DispatchToMainThreadQueue(idleRunnable.forget(),
+ EventQueuePriority::Idle);
+ if (NS_FAILED(rv)) {
+ p->Reject(rv, source);
+ }
+
+ return p;
+}
+
+RefPtr<UntrustedModulesPromise>
+UntrustedModulesProcessor::GetProcessedDataInternal() {
+ AssertRunningOnLazyIdleThread();
+ if (!XRE_IsParentProcess()) {
+ return GetProcessedDataInternalChildProcess();
+ }
+
+ ProcessModuleLoadQueue();
+
+ return GetAllProcessedData(__func__);
+}
+
+RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetAllProcessedData(
+ const char* aSource) {
+ AssertRunningOnLazyIdleThread();
+
+ UntrustedModulesData result;
+
+ if (!mProcessedModuleLoads) {
+ return UntrustedModulesPromise::CreateAndResolve(Nothing(), aSource);
+ }
+
+ result.Swap(mProcessedModuleLoads);
+
+ result.mElapsed = TimeStamp::Now() - TimeStamp::ProcessCreation();
+
+ return UntrustedModulesPromise::CreateAndResolve(
+ Some(UntrustedModulesData(std::move(result))), aSource);
+}
+
+RefPtr<UntrustedModulesPromise>
+UntrustedModulesProcessor::GetProcessedDataInternalChildProcess() {
+ AssertRunningOnLazyIdleThread();
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ RefPtr<GetModulesTrustPromise> whenProcessed(
+ ProcessModuleLoadQueueChildProcess(Priority::Default));
+
+ RefPtr<UntrustedModulesProcessor> self(this);
+ RefPtr<UntrustedModulesPromise::Private> p(
+ new UntrustedModulesPromise::Private(__func__));
+ nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget());
+
+ const char* source = __func__;
+ auto completionRoutine = [evtTarget = std::move(evtTarget), p,
+ self = std::move(self), source,
+ whenProcessed = std::move(whenProcessed)]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!self->IsReadyForBackgroundProcessing()) {
+ // We can't do any more work, just reject all the things
+ whenProcessed->Then(
+ GetMainThreadSerialEventTarget(), source,
+ [p, source](Maybe<ModulesMapResultWithLoads>&& aResult) {
+ p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, source);
+ },
+ [p, source](nsresult aRv) { p->Reject(aRv, source); });
+ return;
+ }
+
+ whenProcessed->Then(
+ evtTarget, source,
+ [p, self = std::move(self),
+ source](Maybe<ModulesMapResultWithLoads>&& aResult) mutable {
+ if (aResult.isSome()) {
+ self->CompleteProcessing(std::move(aResult.ref()));
+ }
+ self->GetAllProcessedData(source)->ChainTo(p.forget(), source);
+ },
+ [p, source](nsresult aRv) { p->Reject(aRv, source); });
+ };
+
+ // We always send |completionRoutine| on a trip through the main thread
+ // due to some subtlety with |mThread| being a LazyIdleThread: we can only
+ // Dispatch or Then to |mThread| from its creating thread, which is the
+ // main thread. Hopefully we can get rid of this in the future and just
+ // invoke whenProcessed->Then() directly.
+ nsresult rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, std::move(completionRoutine)));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (NS_FAILED(rv)) {
+ p->Reject(rv, __func__);
+ }
+
+ return p;
+}
+
+void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue() {
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ BackgroundPriorityRegion bgRgn;
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ ProcessModuleLoadQueue();
+}
+
+RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord(
+ const ModuleEvaluator& aModEval, const nsAString& aResolvedNtPath) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MutexAutoLock lock(mModuleCacheMutex);
+ return mGlobalModuleCache.WithEntryHandle(
+ aResolvedNtPath, [&](auto&& addPtr) -> RefPtr<ModuleRecord> {
+ if (addPtr) {
+ return addPtr.Data();
+ }
+
+ RefPtr<ModuleRecord> newMod(new ModuleRecord(aResolvedNtPath));
+ if (!(*newMod)) {
+ return nullptr;
+ }
+
+ Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod);
+ if (maybeTrust.isNothing()) {
+ return nullptr;
+ }
+
+ newMod->mTrustFlags = maybeTrust.value();
+
+ return addPtr.Insert(std::move(newMod));
+ });
+}
+
+RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord(
+ const ModulesMap& aModules,
+ const glue::EnhancedModuleLoadInfo& aModuleLoadInfo) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ return aModules.Get(aModuleLoadInfo.mNtLoadInfo.mSectionName.AsString());
+}
+
+void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueueChildProcess() {
+ RefPtr<GetModulesTrustPromise> whenProcessed(
+ ProcessModuleLoadQueueChildProcess(Priority::Background));
+
+ RefPtr<UntrustedModulesProcessor> self(this);
+ nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget());
+
+ const char* source = __func__;
+ auto completionRoutine = [evtTarget = std::move(evtTarget),
+ self = std::move(self), source,
+ whenProcessed = std::move(whenProcessed)]() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!self->IsReadyForBackgroundProcessing()) {
+ // We can't do any more work, just no-op
+ whenProcessed->Then(
+ GetMainThreadSerialEventTarget(), source,
+ [](Maybe<ModulesMapResultWithLoads>&& aResult) {},
+ [](nsresult aRv) {});
+ return;
+ }
+
+ whenProcessed->Then(
+ evtTarget, source,
+ [self = std::move(self)](Maybe<ModulesMapResultWithLoads>&& aResult) {
+ if (aResult.isNothing() || !self->IsReadyForBackgroundProcessing()) {
+ // Nothing to do
+ return;
+ }
+
+ BackgroundPriorityRegion bgRgn;
+ self->CompleteProcessing(std::move(aResult.ref()));
+ },
+ [](nsresult aRv) {});
+ };
+
+ // We always send |completionRoutine| on a trip through the main thread
+ // due to some subtlety with |mThread| being a LazyIdleThread: we can only
+ // Dispatch or Then to |mThread| from its creating thread, which is the
+ // main thread. Hopefully we can get rid of this in the future and just
+ // invoke whenProcessed->Then() directly.
+ DebugOnly<nsresult> rv = NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, std::move(completionRoutine)));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+UnprocessedModuleLoads UntrustedModulesProcessor::ExtractLoadingEventsToProcess(
+ size_t aMaxLength) {
+ UnprocessedModuleLoads loadsToProcess;
+
+ MutexAutoLock lock(mUnprocessedMutex);
+ CancelScheduledProcessing(lock);
+
+ loadsToProcess.splice(0, mUnprocessedModuleLoads, 0, aMaxLength);
+ return loadsToProcess;
+}
+
+// This function contains multiple IsReadyForBackgroundProcessing() checks so
+// that we can quickly bail out at the first sign of shutdown. This may be
+// important when the current thread is running under background priority.
+void UntrustedModulesProcessor::ProcessModuleLoadQueue() {
+ AssertRunningOnLazyIdleThread();
+ if (!XRE_IsParentProcess()) {
+ BackgroundProcessModuleLoadQueueChildProcess();
+ return;
+ }
+
+ UnprocessedModuleLoads loadsToProcess =
+ ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
+ if (!IsReadyForBackgroundProcessing() || loadsToProcess.isEmpty()) {
+ return;
+ }
+
+ ModuleEvaluator modEval;
+ MOZ_ASSERT(!!modEval);
+ if (!modEval) {
+ return;
+ }
+
+ Telemetry::BatchProcessedStackGenerator stackProcessor;
+ Maybe<double> maybeXulLoadDuration;
+ Vector<Telemetry::ProcessedStack> processedStacks;
+ UntrustedModuleLoadingEvents processedEvents;
+ uint32_t sanitizationFailures = 0;
+ uint32_t trustTestFailures = 0;
+
+ for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
+ glue::EnhancedModuleLoadInfo& entry = container->mInfo;
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ RefPtr<ModuleRecord> module(GetOrAddModuleRecord(
+ modEval, entry.mNtLoadInfo.mSectionName.AsString()));
+ if (!module) {
+ // We failed to obtain trust information about the module.
+ // Don't include test failures in the ping to avoid flooding it.
+ ++trustTestFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
+ std::move(entry.mNtLoadInfo.mBacktrace);
+ ProcessedModuleLoadEvent event(std::move(entry), std::move(module));
+
+ if (!event) {
+ // We don't have a sanitized DLL path, so we cannot include this event
+ // for privacy reasons.
+ ++sanitizationFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (event.IsTrusted()) {
+ if (event.IsXULLoad()) {
+ maybeXulLoadDuration = event.mLoadDurationMS;
+ }
+
+ // Trusted modules are not included in the ping
+ continue;
+ }
+
+ mProcessedModuleLoads.mModules.LookupOrInsert(
+ event.mModule->mResolvedNtName, event.mModule);
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Telemetry::ProcessedStack processedStack =
+ stackProcessor.GetStackAndModules(backtrace);
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Unused << processedStacks.emplaceBack(std::move(processedStack));
+ processedEvents.insertBack(
+ new ProcessedModuleLoadEventContainer(std::move(event)));
+ }
+
+ if (processedStacks.empty() && processedEvents.isEmpty() &&
+ !sanitizationFailures && !trustTestFailures) {
+ // Nothing to save
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ // Modules have been added to mProcessedModuleLoads.mModules
+ // in the loop above. Passing an empty ModulesMap to AddNewLoads.
+ mProcessedModuleLoads.AddNewLoads(ModulesMap{}, std::move(processedEvents),
+ std::move(processedStacks));
+ if (maybeXulLoadDuration) {
+ MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS);
+ mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration;
+ }
+
+ mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures;
+ mProcessedModuleLoads.mTrustTestFailures += trustTestFailures;
+}
+
+template <typename ActorT>
+static RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust(
+ ActorT* aActor, ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
+ MOZ_ASSERT(NS_IsMainThread());
+ return aActor->SendGetModulesTrust(std::move(aModPaths),
+ aRunAtNormalPriority);
+}
+
+RefPtr<GetModulesTrustIpcPromise>
+UntrustedModulesProcessor::SendGetModulesTrust(ModulePaths&& aModules,
+ Priority aPriority) {
+ MOZ_ASSERT(NS_IsMainThread());
+ bool runNormal = aPriority == Priority::Default;
+
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Content: {
+ return ::mozilla::SendGetModulesTrust(dom::ContentChild::GetSingleton(),
+ std::move(aModules), runNormal);
+ }
+ case GeckoProcessType_RDD: {
+ return ::mozilla::SendGetModulesTrust(RDDParent::GetSingleton(),
+ std::move(aModules), runNormal);
+ }
+ case GeckoProcessType_Socket: {
+ return ::mozilla::SendGetModulesTrust(
+ net::SocketProcessChild::GetSingleton(), std::move(aModules),
+ runNormal);
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("Unsupported process type");
+ return GetModulesTrustIpcPromise::CreateAndReject(
+ ipc::ResponseRejectReason::SendError, __func__);
+ }
+ }
+}
+
+/**
+ * This method works very similarly to ProcessModuleLoadQueue, with the
+ * exception that a sandboxed child process does not have sufficient rights to
+ * be able to evaluate a module's trustworthiness. Instead, we accumulate the
+ * resolved paths for all of the modules in this batch and send them to the
+ * parent to determine trustworthiness.
+ *
+ * The parent process returns a list of untrusted modules and invokes
+ * CompleteProcessing to handle the remainder of the process.
+ *
+ * By doing it this way, we minimize the amount of data that needs to be sent
+ * over IPC and avoid the need to process every load's metadata only
+ * to throw most of it away (since most modules will be trusted).
+ */
+RefPtr<UntrustedModulesProcessor::GetModulesTrustPromise>
+UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess(
+ UntrustedModulesProcessor::Priority aPriority) {
+ AssertRunningOnLazyIdleThread();
+ MOZ_ASSERT(!XRE_IsParentProcess());
+
+ UnprocessedModuleLoads loadsToProcess =
+ ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
+ if (loadsToProcess.isEmpty()) {
+ // Nothing to process
+ return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return GetModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet;
+
+ // Build a set of modules to be processed by the parent
+ for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
+ glue::EnhancedModuleLoadInfo& entry = container->mInfo;
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return GetModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString());
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return GetModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ MOZ_ASSERT(!moduleNtPathSet.IsEmpty());
+ if (moduleNtPathSet.IsEmpty()) {
+ // Nothing to process
+ return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
+ }
+
+ ModulePaths moduleNtPaths(std::move(moduleNtPathSet));
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return GetModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ RefPtr<UntrustedModulesProcessor> self(this);
+
+ auto invoker = [self = std::move(self),
+ moduleNtPaths = std::move(moduleNtPaths),
+ priority = aPriority]() mutable {
+ return self->SendGetModulesTrust(std::move(moduleNtPaths), priority);
+ };
+
+ RefPtr<GetModulesTrustPromise::Private> p(
+ new GetModulesTrustPromise::Private(__func__));
+
+ if (!IsReadyForBackgroundProcessing()) {
+ p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ return p;
+ }
+
+ // Send the IPC request via the main thread
+ InvokeAsync(GetMainThreadSerialEventTarget(), __func__, std::move(invoker))
+ ->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [p, loads = std::move(loadsToProcess)](
+ Maybe<ModulesMapResult>&& aResult) mutable {
+ ModulesMapResultWithLoads result(std::move(aResult),
+ std::move(loads));
+ p->Resolve(Some(ModulesMapResultWithLoads(std::move(result))),
+ __func__);
+ },
+ [p](ipc::ResponseRejectReason aReason) {
+ p->Reject(NS_ERROR_FAILURE, __func__);
+ });
+
+ return p;
+}
+
+void UntrustedModulesProcessor::CompleteProcessing(
+ UntrustedModulesProcessor::ModulesMapResultWithLoads&& aModulesAndLoads) {
+ MOZ_ASSERT(!XRE_IsParentProcess());
+ AssertRunningOnLazyIdleThread();
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (aModulesAndLoads.mModMapResult.isNothing()) {
+ // No untrusted modules in this batch, nothing to save.
+ return;
+ }
+
+ // This map only contains information about modules deemed to be untrusted,
+ // plus xul.dll. Any module referenced by load requests that is *not* in the
+ // map is deemed to be trusted.
+ ModulesMap& modules = aModulesAndLoads.mModMapResult.ref().mModules;
+ const uint32_t& trustTestFailures =
+ aModulesAndLoads.mModMapResult.ref().mTrustTestFailures;
+ UnprocessedModuleLoads& loads = aModulesAndLoads.mLoads;
+
+ if (modules.IsEmpty() && !trustTestFailures) {
+ // No data, nothing to save.
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Telemetry::BatchProcessedStackGenerator stackProcessor;
+
+ Maybe<double> maybeXulLoadDuration;
+ Vector<Telemetry::ProcessedStack> processedStacks;
+ UntrustedModuleLoadingEvents processedEvents;
+ uint32_t sanitizationFailures = 0;
+
+ if (!modules.IsEmpty()) {
+ for (UnprocessedModuleLoadInfoContainer* container : loads) {
+ glue::EnhancedModuleLoadInfo& item = container->mInfo;
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ RefPtr<ModuleRecord> module(GetModuleRecord(modules, item));
+ if (!module) {
+ // If module is null then |item| is trusted
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
+ std::move(item.mNtLoadInfo.mBacktrace);
+ ProcessedModuleLoadEvent event(std::move(item), std::move(module));
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (!event) {
+ // We don't have a sanitized DLL path, so we cannot include this event
+ // for privacy reasons.
+ ++sanitizationFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (event.IsXULLoad()) {
+ maybeXulLoadDuration = event.mLoadDurationMS;
+ // We saved the XUL load duration, but it is still trusted, so we
+ // continue.
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Telemetry::ProcessedStack processedStack =
+ stackProcessor.GetStackAndModules(backtrace);
+
+ Unused << processedStacks.emplaceBack(std::move(processedStack));
+ processedEvents.insertBack(
+ new ProcessedModuleLoadEventContainer(std::move(event)));
+ }
+ }
+
+ if (processedStacks.empty() && processedEvents.isEmpty() &&
+ !sanitizationFailures && !trustTestFailures) {
+ // Nothing to save
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents),
+ std::move(processedStacks));
+ if (maybeXulLoadDuration) {
+ MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS);
+ mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration;
+ }
+
+ mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures;
+ mProcessedModuleLoads.mTrustTestFailures += trustTestFailures;
+}
+
+// The thread priority of this job should match the priority that the child
+// process is running with, as specified by |aRunAtNormalPriority|.
+RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ AssertRunningOnLazyIdleThread();
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ if (aRunAtNormalPriority) {
+ return GetModulesTrustInternal(std::move(aModPaths));
+ }
+
+ BackgroundPriorityRegion bgRgn;
+ return GetModulesTrustInternal(std::move(aModPaths));
+}
+
+// For each module in |aModPaths|, evaluate its trustworthiness and only send
+// ModuleRecords for untrusted modules back to the child process. We also save
+// XUL's ModuleRecord so that the child process may report XUL's load time.
+RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal(
+ ModulePaths&& aModPaths) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ AssertRunningOnLazyIdleThread();
+
+ ModulesMapResult result;
+
+ ModulesMap& modMap = result.mModules;
+ uint32_t& trustTestFailures = result.mTrustTestFailures;
+
+ ModuleEvaluator modEval;
+ MOZ_ASSERT(!!modEval);
+ if (!modEval) {
+ return ModulesTrustPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+ }
+
+ for (auto& resolvedNtPath :
+ aModPaths.mModuleNtPaths.as<ModulePaths::VecType>()) {
+ if (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ MOZ_ASSERT(!resolvedNtPath.IsEmpty());
+ if (resolvedNtPath.IsEmpty()) {
+ continue;
+ }
+
+ RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modEval, resolvedNtPath));
+ if (!module) {
+ // We failed to obtain trust information.
+ ++trustTestFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ if (module->IsTrusted() && !module->IsXUL()) {
+ // If the module is trusted we exclude it from results, unless it's XUL.
+ // (We save XUL so that the child process may report XUL's load time)
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ modMap.InsertOrUpdate(resolvedNtPath, std::move(module));
+ }
+
+ return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__);
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/UntrustedModulesProcessor.h b/toolkit/xre/dllservices/UntrustedModulesProcessor.h
new file mode 100644
index 0000000000..1da5a25b2c
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesProcessor.h
@@ -0,0 +1,176 @@
+/* -*- 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_UntrustedModulesProcessor_h
+#define mozilla_UntrustedModulesProcessor_h
+
+#include "mozilla/Atomics.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/LazyIdleThread.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UntrustedModulesData.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIObserver.h"
+#include "nsIRunnable.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+class ModuleEvaluator;
+
+using UntrustedModulesPromise =
+ MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
+
+using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>;
+
+using GetModulesTrustIpcPromise =
+ MozPromise<Maybe<ModulesMapResult>, ipc::ResponseRejectReason, true>;
+
+struct UnprocessedModuleLoadInfoContainer final
+ : public LinkedListElement<UnprocessedModuleLoadInfoContainer> {
+ glue::EnhancedModuleLoadInfo mInfo;
+
+ template <typename T>
+ explicit UnprocessedModuleLoadInfoContainer(T&& aInfo)
+ : mInfo(std::move(aInfo)) {}
+
+ UnprocessedModuleLoadInfoContainer(
+ const UnprocessedModuleLoadInfoContainer&) = delete;
+ UnprocessedModuleLoadInfoContainer& operator=(
+ const UnprocessedModuleLoadInfoContainer&) = delete;
+};
+using UnprocessedModuleLoads =
+ AutoCleanLinkedList<UnprocessedModuleLoadInfoContainer>;
+
+class UntrustedModulesProcessor final : public nsIObserver {
+ public:
+ static RefPtr<UntrustedModulesProcessor> Create(
+ bool aIsReadyForBackgroundProcessing);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // Called to check if the parent process is ready when a child process
+ // is spanwed
+ bool IsReadyForBackgroundProcessing() const;
+
+ // Called by DLL Services to explicitly begin shutting down
+ void Disable();
+
+ // Called by DLL Services to submit module load data to the processor
+ void Enqueue(glue::EnhancedModuleLoadInfo&& aModLoadInfo);
+ void Enqueue(ModuleLoadInfoVec&& aEvents);
+
+ // Called by telemetry to retrieve the processed data
+ RefPtr<UntrustedModulesPromise> GetProcessedData();
+
+ // Called by IPC actors in the parent process to evaluate module trust
+ // on behalf of child processes
+ RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths,
+ bool aRunAtNormalPriority);
+
+ UntrustedModulesProcessor(const UntrustedModulesProcessor&) = delete;
+ UntrustedModulesProcessor(UntrustedModulesProcessor&&) = delete;
+ UntrustedModulesProcessor& operator=(const UntrustedModulesProcessor&) =
+ delete;
+ UntrustedModulesProcessor& operator=(UntrustedModulesProcessor&&) = delete;
+
+ private:
+ ~UntrustedModulesProcessor() = default;
+ explicit UntrustedModulesProcessor(bool aIsReadyForBackgroundProcessing);
+
+ static bool IsSupportedProcessType();
+
+ void AddObservers();
+ void RemoveObservers();
+
+ void ScheduleNonEmptyQueueProcessing(const MutexAutoLock& aProofOfLock);
+ void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock);
+ void DispatchBackgroundProcessing();
+
+ void BackgroundProcessModuleLoadQueue();
+ void ProcessModuleLoadQueue();
+
+ // Extract the loading events from mUnprocessedModuleLoads to process and
+ // move to mProcessedModuleLoads. It's guaranteed that the total length of
+ // mProcessedModuleLoads will not exceed |aMaxLength|.
+ UnprocessedModuleLoads ExtractLoadingEventsToProcess(size_t aMaxLength);
+
+ class ModulesMapResultWithLoads final {
+ public:
+ ModulesMapResultWithLoads(Maybe<ModulesMapResult>&& aModMapResult,
+ UnprocessedModuleLoads&& aLoads)
+ : mModMapResult(std::move(aModMapResult)), mLoads(std::move(aLoads)) {}
+ Maybe<ModulesMapResult> mModMapResult;
+ UnprocessedModuleLoads mLoads;
+ };
+
+ using GetModulesTrustPromise =
+ MozPromise<Maybe<ModulesMapResultWithLoads>, nsresult, true>;
+
+ enum class Priority { Default, Background };
+
+ RefPtr<GetModulesTrustPromise> ProcessModuleLoadQueueChildProcess(
+ Priority aPriority);
+ void BackgroundProcessModuleLoadQueueChildProcess();
+
+ void AssertRunningOnLazyIdleThread();
+
+ RefPtr<UntrustedModulesPromise> GetProcessedDataInternal();
+ RefPtr<UntrustedModulesPromise> GetProcessedDataInternalChildProcess();
+
+ RefPtr<ModulesTrustPromise> GetModulesTrustInternal(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority);
+ RefPtr<ModulesTrustPromise> GetModulesTrustInternal(ModulePaths&& aModPaths);
+
+ // This function is only called by the parent process
+ RefPtr<ModuleRecord> GetOrAddModuleRecord(const ModuleEvaluator& aModEval,
+ const nsAString& aResolvedNtPath);
+
+ // Only called by child processes
+ RefPtr<ModuleRecord> GetModuleRecord(
+ const ModulesMap& aModules,
+ const glue::EnhancedModuleLoadInfo& aModuleLoadInfo);
+
+ RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust(ModulePaths&& aModules,
+ Priority aPriority);
+
+ void CompleteProcessing(ModulesMapResultWithLoads&& aModulesAndLoads);
+ RefPtr<UntrustedModulesPromise> GetAllProcessedData(const char* aSource);
+
+ private:
+ RefPtr<LazyIdleThread> mThread;
+
+ Mutex mUnprocessedMutex MOZ_UNANNOTATED;
+ Mutex mModuleCacheMutex MOZ_UNANNOTATED;
+
+ // The members in this group are protected by mUnprocessedMutex
+ UnprocessedModuleLoads mUnprocessedModuleLoads;
+ nsCOMPtr<nsIRunnable> mIdleRunnable;
+
+ // This member must only be touched on mThread
+ UntrustedModulesData mProcessedModuleLoads;
+
+ enum class Status { StartingUp, Allowed, ShuttingDown };
+
+ // This member may be touched by any thread
+ Atomic<Status> mStatus;
+
+ // Cache all module records, including ones trusted and ones loaded in
+ // child processes, in the browser process to avoid evaluating the same
+ // module multiple times
+ ModulesMap mGlobalModuleCache;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_UntrustedModulesProcessor_h
diff --git a/toolkit/xre/dllservices/WinDllServices.cpp b/toolkit/xre/dllservices/WinDllServices.cpp
new file mode 100644
index 0000000000..b98a15252b
--- /dev/null
+++ b/toolkit/xre/dllservices/WinDllServices.cpp
@@ -0,0 +1,142 @@
+/* -*- 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/. */
+
+#include "mozilla/WinDllServices.h"
+
+#include <windows.h>
+#include <psapi.h>
+
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticLocalPtr.h"
+#include "mozilla/UntrustedModulesProcessor.h"
+#include "nsCOMPtr.h"
+#include "nsIObserverService.h"
+#include "nsString.h"
+#include "nsXULAppAPI.h"
+
+namespace mozilla {
+
+const char* DllServices::kTopicDllLoadedMainThread = "dll-loaded-main-thread";
+const char* DllServices::kTopicDllLoadedNonMainThread =
+ "dll-loaded-non-main-thread";
+
+/* static */
+DllServices* DllServices::Get() {
+ static StaticLocalRefPtr<DllServices> sInstance(
+ []() -> already_AddRefed<DllServices> {
+ RefPtr<DllServices> dllSvc(new DllServices());
+ // Full DLL services require XPCOM, which GMP doesn't have
+ if (XRE_IsGMPluginProcess()) {
+ dllSvc->EnableBasic();
+ } else {
+ dllSvc->EnableFull();
+ }
+
+ auto setClearOnShutdown = [ptr = &sInstance]() -> void {
+ ClearOnShutdown(ptr);
+ };
+
+ if (NS_IsMainThread()) {
+ setClearOnShutdown();
+ return dllSvc.forget();
+ }
+
+ SchedulerGroup::Dispatch(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("mozilla::DllServices::Get",
+ std::move(setClearOnShutdown)));
+
+ return dllSvc.forget();
+ }());
+
+ return sInstance;
+}
+
+DllServices::~DllServices() { DisableFull(); }
+
+void DllServices::StartUntrustedModulesProcessor(bool aIsStartingUp) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mUntrustedModulesProcessor);
+ mUntrustedModulesProcessor = UntrustedModulesProcessor::Create(aIsStartingUp);
+}
+
+bool DllServices::IsReadyForBackgroundProcessing() const {
+ return mUntrustedModulesProcessor &&
+ mUntrustedModulesProcessor->IsReadyForBackgroundProcessing();
+}
+
+RefPtr<UntrustedModulesPromise> DllServices::GetUntrustedModulesData() {
+ if (!mUntrustedModulesProcessor) {
+ return UntrustedModulesPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
+ __func__);
+ }
+
+ return mUntrustedModulesProcessor->GetProcessedData();
+}
+
+void DllServices::DisableFull() {
+ if (XRE_IsGMPluginProcess()) {
+ return;
+ }
+
+ if (mUntrustedModulesProcessor) {
+ mUntrustedModulesProcessor->Disable();
+ }
+
+ glue::DllServices::DisableFull();
+}
+
+RefPtr<ModulesTrustPromise> DllServices::GetModulesTrust(
+ ModulePaths&& aModPaths, bool aRunAtNormalPriority) {
+ if (!mUntrustedModulesProcessor) {
+ return ModulesTrustPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED,
+ __func__);
+ }
+
+ return mUntrustedModulesProcessor->GetModulesTrust(std::move(aModPaths),
+ aRunAtNormalPriority);
+}
+
+void DllServices::NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ const char* topic;
+
+ if (aModLoadInfo.mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) {
+ topic = kTopicDllLoadedMainThread;
+ } else {
+ topic = kTopicDllLoadedNonMainThread;
+ }
+
+ // We save the path to a nsAutoString because once we have submitted
+ // aModLoadInfo for processing there is no guarantee that the original
+ // buffer will continue to be valid.
+ nsAutoString dllFilePath(aModLoadInfo.GetSectionName());
+
+ if (mUntrustedModulesProcessor) {
+ mUntrustedModulesProcessor->Enqueue(std::move(aModLoadInfo));
+ }
+
+ nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService());
+ if (!obsServ) {
+ return;
+ }
+
+ obsServ->NotifyObservers(nullptr, topic, dllFilePath.get());
+}
+
+void DllServices::NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (!mUntrustedModulesProcessor) {
+ return;
+ }
+
+ mUntrustedModulesProcessor->Enqueue(std::move(aEvents));
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/WinDllServices.h b/toolkit/xre/dllservices/WinDllServices.h
new file mode 100644
index 0000000000..a5d4f9b363
--- /dev/null
+++ b/toolkit/xre/dllservices/WinDllServices.h
@@ -0,0 +1,57 @@
+/* -*- 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_WinDllServices_h
+#define mozilla_WinDllServices_h
+
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+class UntrustedModulesData;
+class UntrustedModulesProcessor;
+
+using UntrustedModulesPromise =
+ MozPromise<Maybe<UntrustedModulesData>, nsresult, true>;
+
+struct ModulePaths;
+class ModulesMapResult;
+
+using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>;
+
+class DllServices final : public glue::DllServices {
+ public:
+ static DllServices* Get();
+
+ virtual void DisableFull() override;
+
+ static const char* kTopicDllLoadedMainThread;
+ static const char* kTopicDllLoadedNonMainThread;
+
+ void StartUntrustedModulesProcessor(bool aIsReadyForBackgroundProcessing);
+ bool IsReadyForBackgroundProcessing() const;
+
+ RefPtr<UntrustedModulesPromise> GetUntrustedModulesData();
+
+ RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths,
+ bool aRunAtNormalPriority);
+
+ private:
+ DllServices() = default;
+ ~DllServices();
+
+ void NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) override;
+ void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) override;
+
+ RefPtr<UntrustedModulesProcessor> mUntrustedModulesProcessor;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_WinDllServices_h
diff --git a/toolkit/xre/dllservices/moz.build b/toolkit/xre/dllservices/moz.build
new file mode 100644
index 0000000000..bb23f5fb53
--- /dev/null
+++ b/toolkit/xre/dllservices/moz.build
@@ -0,0 +1,45 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DLL Services")
+
+Library("dllservices")
+
+FINAL_LIBRARY = "xul"
+
+EXPORTS.mozilla += [
+ "DynamicBlocklist.h",
+ "ModuleVersionInfo.h",
+ "UntrustedModulesData.h",
+ "UntrustedModulesProcessor.h",
+ "WinDllServices.h",
+]
+
+DIRS += [
+ "mozglue",
+]
+
+UNIFIED_SOURCES += [
+ "ModuleEvaluator.cpp",
+ "ModuleVersionInfo.cpp",
+ "UntrustedModulesData.cpp",
+ "UntrustedModulesProcessor.cpp",
+ "WinDllServices.cpp",
+]
+
+if CONFIG["MOZ_LAUNCHER_PROCESS"]:
+ UNIFIED_SOURCES += [
+ "DynamicBlocklistWriter.cpp",
+ ]
+
+TEST_DIRS += [
+ "tests",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/toolkit/xre/dllservices/mozglue/Authenticode.cpp b/toolkit/xre/dllservices/mozglue/Authenticode.cpp
new file mode 100644
index 0000000000..ee702b2ac6
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/Authenticode.cpp
@@ -0,0 +1,439 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// We need Windows 8 functions and structures to be able to verify SHA-256.
+#if defined(_WIN32_WINNT)
+# undef _WIN32_WINNT
+# define _WIN32_WINNT _WIN32_WINNT_WIN8
+#endif // defined(_WIN32_WINNT)
+#if defined(NTDDI_VERSION)
+# undef NTDDI_VERSION
+# define NTDDI_VERSION NTDDI_WIN8
+#endif // defined(NTDDI_VERSION)
+
+#include "Authenticode.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowsHelpers.h"
+
+#include <windows.h>
+#include <softpub.h>
+#include <wincrypt.h>
+#include <wintrust.h>
+#include <mscat.h>
+
+#include <string.h>
+
+namespace {
+
+struct CertStoreDeleter {
+ typedef HCERTSTORE pointer;
+ void operator()(pointer aStore) { ::CertCloseStore(aStore, 0); }
+};
+
+struct CryptMsgDeleter {
+ typedef HCRYPTMSG pointer;
+ void operator()(pointer aMsg) { ::CryptMsgClose(aMsg); }
+};
+
+struct CertContextDeleter {
+ void operator()(PCCERT_CONTEXT aCertContext) {
+ ::CertFreeCertificateContext(aCertContext);
+ }
+};
+
+struct CATAdminContextDeleter {
+ typedef HCATADMIN pointer;
+ void operator()(pointer aCtx) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminReleaseContext)>
+ pCryptCATAdminReleaseContext(L"wintrust.dll",
+ "CryptCATAdminReleaseContext");
+
+ MOZ_ASSERT(!!pCryptCATAdminReleaseContext);
+ if (!pCryptCATAdminReleaseContext) {
+ return;
+ }
+
+ pCryptCATAdminReleaseContext(aCtx, 0);
+ }
+};
+
+typedef mozilla::UniquePtr<HCERTSTORE, CertStoreDeleter> CertStoreUniquePtr;
+typedef mozilla::UniquePtr<HCRYPTMSG, CryptMsgDeleter> CryptMsgUniquePtr;
+typedef mozilla::UniquePtr<const CERT_CONTEXT, CertContextDeleter>
+ CertContextUniquePtr;
+typedef mozilla::UniquePtr<HCATADMIN, CATAdminContextDeleter>
+ CATAdminContextUniquePtr;
+
+static const DWORD kEncodingTypes = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
+
+class SignedBinary final {
+ public:
+ SignedBinary(const wchar_t* aFilePath, mozilla::AuthenticodeFlags aFlags);
+
+ explicit operator bool() const { return mCertStore && mCryptMsg && mCertCtx; }
+
+ mozilla::UniquePtr<wchar_t[]> GetOrgName();
+
+ SignedBinary(const SignedBinary&) = delete;
+ SignedBinary(SignedBinary&&) = delete;
+ SignedBinary& operator=(const SignedBinary&) = delete;
+ SignedBinary& operator=(SignedBinary&&) = delete;
+
+ private:
+ bool VerifySignature(const wchar_t* aFilePath);
+ bool QueryObject(const wchar_t* aFilePath);
+ static bool VerifySignatureInternal(WINTRUST_DATA& aTrustData);
+
+ private:
+ enum class TrustSource { eNone, eEmbedded, eCatalog };
+
+ private:
+ const mozilla::AuthenticodeFlags mFlags;
+ TrustSource mTrustSource;
+ CertStoreUniquePtr mCertStore;
+ CryptMsgUniquePtr mCryptMsg;
+ CertContextUniquePtr mCertCtx;
+};
+
+SignedBinary::SignedBinary(const wchar_t* aFilePath,
+ mozilla::AuthenticodeFlags aFlags)
+ : mFlags(aFlags), mTrustSource(TrustSource::eNone) {
+ if (!VerifySignature(aFilePath)) {
+ return;
+ }
+
+ DWORD certInfoLen = 0;
+ BOOL ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0,
+ nullptr, &certInfoLen);
+ if (!ok) {
+ return;
+ }
+
+ auto certInfoBuf = mozilla::MakeUnique<char[]>(certInfoLen);
+
+ ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0,
+ certInfoBuf.get(), &certInfoLen);
+ if (!ok) {
+ return;
+ }
+
+ auto certInfo = reinterpret_cast<CERT_INFO*>(certInfoBuf.get());
+
+ PCCERT_CONTEXT certCtx =
+ CertFindCertificateInStore(mCertStore.get(), kEncodingTypes, 0,
+ CERT_FIND_SUBJECT_CERT, certInfo, nullptr);
+ if (!certCtx) {
+ return;
+ }
+
+ mCertCtx.reset(certCtx);
+}
+
+bool SignedBinary::QueryObject(const wchar_t* aFilePath) {
+ DWORD encodingType, contentType, formatType;
+ HCERTSTORE rawCertStore;
+ HCRYPTMSG rawCryptMsg;
+ BOOL result = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, aFilePath,
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
+ CERT_QUERY_FORMAT_FLAG_BINARY, 0,
+ &encodingType, &contentType, &formatType,
+ &rawCertStore, &rawCryptMsg, nullptr);
+ if (!result) {
+ return false;
+ }
+
+ mCertStore.reset(rawCertStore);
+ mCryptMsg.reset(rawCryptMsg);
+
+ return true;
+}
+
+/**
+ * @param aTrustData must be a WINTRUST_DATA structure that has been zeroed out
+ * and then populated at least with its |cbStruct|,
+ * |dwUnionChoice|, and appropriate union field. This function
+ * will then populate the remaining fields as appropriate.
+ */
+/* static */
+bool SignedBinary::VerifySignatureInternal(WINTRUST_DATA& aTrustData) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::WinVerifyTrust)>
+ pWinVerifyTrust(L"wintrust.dll", "WinVerifyTrust");
+ if (!pWinVerifyTrust) {
+ return false;
+ }
+
+ aTrustData.dwUIChoice = WTD_UI_NONE;
+ aTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ aTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
+ aTrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+
+ const HWND hwnd = (HWND)INVALID_HANDLE_VALUE;
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ LONG result = pWinVerifyTrust(hwnd, &policyGUID, &aTrustData);
+
+ aTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
+ pWinVerifyTrust(hwnd, &policyGUID, &aTrustData);
+
+ return result == ERROR_SUCCESS;
+}
+
+bool SignedBinary::VerifySignature(const wchar_t* aFilePath) {
+ // First, try the binary itself
+ if (QueryObject(aFilePath)) {
+ mTrustSource = TrustSource::eEmbedded;
+ if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
+ return true;
+ }
+
+ WINTRUST_FILE_INFO fileInfo = {sizeof(fileInfo)};
+ fileInfo.pcwszFilePath = aFilePath;
+
+ WINTRUST_DATA trustData = {sizeof(trustData)};
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.pFile = &fileInfo;
+
+ return VerifySignatureInternal(trustData);
+ }
+
+ // We didn't find anything in the binary, so now try a catalog file.
+
+ // First, we open a catalog admin context.
+ HCATADMIN rawCatAdmin;
+
+ // Windows 7 also exports the CryptCATAdminAcquireContext2 API, but it does
+ // *not* sign its binaries with SHA-256, so we use the old API in that case.
+ if (mozilla::IsWin8OrLater()) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminAcquireContext2)>
+ pCryptCATAdminAcquireContext2(L"wintrust.dll",
+ "CryptCATAdminAcquireContext2");
+ if (!pCryptCATAdminAcquireContext2) {
+ return false;
+ }
+
+ CERT_STRONG_SIGN_PARA policy = {sizeof(policy)};
+ policy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;
+ policy.pszOID = const_cast<char*>(
+ szOID_CERT_STRONG_SIGN_OS_CURRENT); // -Wwritable-strings
+
+ if (!pCryptCATAdminAcquireContext2(&rawCatAdmin, nullptr,
+ BCRYPT_SHA256_ALGORITHM, &policy, 0)) {
+ return false;
+ }
+ } else {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminAcquireContext)>
+ pCryptCATAdminAcquireContext(L"wintrust.dll",
+ "CryptCATAdminAcquireContext");
+
+ if (!pCryptCATAdminAcquireContext ||
+ !pCryptCATAdminAcquireContext(&rawCatAdmin, nullptr, 0)) {
+ return false;
+ }
+ }
+
+ CATAdminContextUniquePtr catAdmin(rawCatAdmin);
+
+ // Now we need to hash the file at aFilePath.
+ // Since we're hashing this file, let's open it with a sequential scan hint.
+ HANDLE rawFile =
+ ::CreateFileW(aFilePath, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
+ if (rawFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ nsAutoHandle file(rawFile);
+ DWORD hashLen = 0;
+ mozilla::UniquePtr<BYTE[]> hashBuf;
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminCalcHashFromFileHandle2)>
+ pCryptCATAdminCalcHashFromFileHandle2(
+ L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle2");
+ if (pCryptCATAdminCalcHashFromFileHandle2) {
+ if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
+ nullptr, 0) &&
+ ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
+
+ if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
+ hashBuf.get(), 0)) {
+ return false;
+ }
+ } else {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminCalcHashFromFileHandle)>
+ pCryptCATAdminCalcHashFromFileHandle(
+ L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle");
+
+ if (!pCryptCATAdminCalcHashFromFileHandle) {
+ return false;
+ }
+
+ if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, nullptr, 0) &&
+ ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
+
+ if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, hashBuf.get(),
+ 0)) {
+ return false;
+ }
+ }
+
+ // Now that we've hashed the file, query the catalog system to see if any
+ // catalogs reference a binary with our hash.
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminEnumCatalogFromHash)>
+ pCryptCATAdminEnumCatalogFromHash(L"wintrust.dll",
+ "CryptCATAdminEnumCatalogFromHash");
+ if (!pCryptCATAdminEnumCatalogFromHash) {
+ return false;
+ }
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminReleaseCatalogContext)>
+ pCryptCATAdminReleaseCatalogContext(L"wintrust.dll",
+ "CryptCATAdminReleaseCatalogContext");
+ if (!pCryptCATAdminReleaseCatalogContext) {
+ return false;
+ }
+
+ HCATINFO catInfoHdl = pCryptCATAdminEnumCatalogFromHash(
+ rawCatAdmin, hashBuf.get(), hashLen, 0, nullptr);
+ if (!catInfoHdl) {
+ return false;
+ }
+
+ // We can't use UniquePtr for this because the deleter function requires two
+ // parameters.
+ auto cleanCatInfoHdl =
+ mozilla::MakeScopeExit([rawCatAdmin, catInfoHdl]() -> void {
+ pCryptCATAdminReleaseCatalogContext(rawCatAdmin, catInfoHdl, 0);
+ });
+
+ // We found a catalog! Now query for the path to the catalog file.
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATCatalogInfoFromContext)>
+ pCryptCATCatalogInfoFromContext(L"wintrust.dll",
+ "CryptCATCatalogInfoFromContext");
+ if (!pCryptCATCatalogInfoFromContext) {
+ return false;
+ }
+
+ CATALOG_INFO_ catInfo = {sizeof(catInfo)};
+ if (!pCryptCATCatalogInfoFromContext(catInfoHdl, &catInfo, 0)) {
+ return false;
+ }
+
+ if (!QueryObject(catInfo.wszCatalogFile)) {
+ return false;
+ }
+
+ mTrustSource = TrustSource::eCatalog;
+
+ if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
+ return true;
+ }
+
+ // WINTRUST_CATALOG_INFO::pcwszMemberTag is commonly set to the string
+ // representation of the file hash, so we build that here.
+
+ DWORD strHashBufLen = (hashLen * 2) + 1;
+ auto strHashBuf = mozilla::MakeUnique<wchar_t[]>(strHashBufLen);
+ if (!::CryptBinaryToStringW(hashBuf.get(), hashLen,
+ CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF,
+ strHashBuf.get(), &strHashBufLen)) {
+ return false;
+ }
+
+ // Ensure that the tag is uppercase for WinVerifyTrust
+ // NB: CryptBinaryToStringW overwrites strHashBufLen with the length excluding
+ // the null terminator, so we need to add it back for this call.
+ if (_wcsupr_s(strHashBuf.get(), strHashBufLen + 1)) {
+ return false;
+ }
+
+ // Now, given the path to the catalog, and the path to the member (ie, the
+ // binary whose hash we are validating), we may now validate. If the
+ // validation is successful, we then QueryObject on the *catalog file*
+ // instead of the binary.
+
+ WINTRUST_CATALOG_INFO wtCatInfo = {sizeof(wtCatInfo)};
+ wtCatInfo.pcwszCatalogFilePath = catInfo.wszCatalogFile;
+ wtCatInfo.pcwszMemberTag = strHashBuf.get();
+ wtCatInfo.pcwszMemberFilePath = aFilePath;
+ wtCatInfo.hMemberFile = rawFile;
+ if (mozilla::IsWin8OrLater()) {
+ wtCatInfo.hCatAdmin = rawCatAdmin;
+ }
+
+ WINTRUST_DATA trustData = {sizeof(trustData)};
+ trustData.dwUnionChoice = WTD_CHOICE_CATALOG;
+ trustData.pCatalog = &wtCatInfo;
+
+ return VerifySignatureInternal(trustData);
+}
+
+mozilla::UniquePtr<wchar_t[]> SignedBinary::GetOrgName() {
+ DWORD charCount = CertGetNameStringW(
+ mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0);
+ if (charCount <= 1) {
+ // Not found
+ return nullptr;
+ }
+
+ auto result = mozilla::MakeUnique<wchar_t[]>(charCount);
+ charCount = CertGetNameStringW(mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ 0, nullptr, result.get(), charCount);
+ MOZ_ASSERT(charCount > 1);
+
+ return result;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+
+class AuthenticodeImpl : public Authenticode {
+ public:
+ virtual UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override;
+};
+
+UniquePtr<wchar_t[]> AuthenticodeImpl::GetBinaryOrgName(
+ const wchar_t* aFilePath, AuthenticodeFlags aFlags) {
+ SignedBinary bin(aFilePath, aFlags);
+ if (!bin) {
+ return nullptr;
+ }
+
+ return bin.GetOrgName();
+}
+
+static AuthenticodeImpl sAuthenticodeImpl;
+
+Authenticode* GetAuthenticode() { return &sAuthenticodeImpl; }
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/Authenticode.h b/toolkit/xre/dllservices/mozglue/Authenticode.h
new file mode 100644
index 0000000000..182512da2c
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/Authenticode.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Authenticode_h
+#define mozilla_Authenticode_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+enum class AuthenticodeFlags : uint32_t {
+ Default = 0,
+ SkipTrustVerification = 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AuthenticodeFlags)
+
+class Authenticode {
+ public:
+ virtual UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) = 0;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Authenticode_h
diff --git a/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp
new file mode 100644
index 0000000000..f3eef13504
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CacheNtDllThunk.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+
+static StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>> sCachedNtDllThunk;
+
+// This static method initializes sCachedNtDllThunk. Because it's called in
+// XREMain::XRE_main, which happens long before WindowsProcessLauncher's ctor
+// accesses sCachedNtDllThunk, there is no race on sCachedNtDllThunk, thus
+// no mutex is needed.
+static void CacheNtDllThunk() {
+ if (sCachedNtDllThunk) {
+ return;
+ }
+
+ do {
+ nt::PEHeaders ourExeImage(::GetModuleHandleW(nullptr));
+ if (!ourExeImage) {
+ break;
+ }
+
+ nt::PEHeaders ntdllImage(::GetModuleHandleW(L"ntdll.dll"));
+ if (!ntdllImage) {
+ break;
+ }
+
+ Maybe<Range<const uint8_t>> ntdllBoundaries = ntdllImage.GetBounds();
+ if (!ntdllBoundaries) {
+ break;
+ }
+
+ Maybe<Span<IMAGE_THUNK_DATA>> maybeNtDllThunks =
+ ourExeImage.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr());
+ if (maybeNtDllThunks.isNothing()) {
+ break;
+ }
+
+ sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>(maybeNtDllThunks.value());
+ return;
+ } while (false);
+
+ // Failed to cache IAT. Initializing the variable with nullptr.
+ sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>();
+}
+
+static Buffer<IMAGE_THUNK_DATA>* GetCachedNtDllThunk() {
+ return sCachedNtDllThunk;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h
new file mode 100644
index 0000000000..403e001afc
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CacheNtDllThunk_h
+#define mozilla_CacheNtDllThunk_h
+
+#include "mozilla/Types.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/NativeNt.h"
+
+namespace mozilla {
+
+MFBT_API void CacheNtDllThunk();
+MFBT_API Buffer<IMAGE_THUNK_DATA>* GetCachedNtDllThunk();
+
+}; // namespace mozilla
+
+#endif // mozilla_CacheNtDllThunk_h
diff --git a/toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h b/toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h
new file mode 100644
index 0000000000..16ee93c987
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h
@@ -0,0 +1,125 @@
+/* -*- 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_LoaderAPIInterfaces_h
+#define mozilla_LoaderAPIInterfaces_h
+
+#include "nscore.h"
+#include "mozilla/glue/SharedSection.h"
+#include "mozilla/ModuleLoadInfo.h"
+
+namespace mozilla {
+namespace nt {
+
+class NS_NO_VTABLE LoaderObserver {
+ public:
+ /**
+ * Notification that a DLL load has begun.
+ *
+ * @param aContext Outparam that allows this observer to store any context
+ * information pertaining to the current load.
+ * @param aRequestedDllName The DLL name requested by whatever invoked the
+ * loader. This name may not match the effective
+ * name of the DLL once the loader has completed
+ * its path search.
+ */
+ virtual void OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) = 0;
+
+ /**
+ * Query the observer to determine whether the DLL named |aLSPLeafName| needs
+ * to be substituted with another module, and substitute the module handle
+ * when necessary.
+ *
+ * @return true when substitution occurs, otherwise false
+ */
+ virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) = 0;
+
+ /**
+ * Notification that a DLL load has ended.
+ *
+ * @param aContext The context that was set by the corresponding call to
+ * OnBeginDllLoad
+ * @param aNtStatus The NTSTATUS returned by LdrLoadDll
+ * @param aModuleLoadInfo Telemetry information that was gathered about the
+ * load.
+ */
+ virtual void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) = 0;
+
+ /**
+ * Called to inform the observer that it is no longer active and, if
+ * necessary, call aNext->OnForward() with any accumulated telemetry
+ * information.
+ */
+ virtual void Forward(LoaderObserver* aNext) = 0;
+
+ /**
+ * Receives a vector of module load telemetry from a previous LoaderObserver.
+ */
+ virtual void OnForward(ModuleLoadInfoVec&& aInfo) = 0;
+};
+
+class NS_NO_VTABLE LoaderAPI {
+ public:
+ /**
+ * Construct a new ModuleLoadInfo structure and notify the LoaderObserver
+ * that a library load is beginning.
+ */
+ virtual ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) = 0;
+
+ /**
+ * Query to determine whether the DLL named |aLSPLeafName| needs to be
+ * substituted with another module, and substitute the module handle when
+ * necessary.
+ *
+ * @return true when substitution occurs, otherwise false
+ */
+ virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) = 0;
+
+ /**
+ * Notification that a DLL load has ended.
+ */
+ virtual void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) = 0;
+
+ /**
+ * Given the address of a mapped section, obtain the name of the file that is
+ * backing it.
+ */
+ virtual AllocatedUnicodeString GetSectionName(void* aSectionAddr) = 0;
+
+ using InitDllBlocklistOOPFnPtr = LauncherVoidResultWithLineInfo (*)(
+ const wchar_t*, HANDLE, const IMAGE_THUNK_DATA*, const bool, const bool);
+ using HandleLauncherErrorFnPtr = void (*)(const LauncherError&, const char*);
+
+ /**
+ * Return a pointer to winlauncher's function.
+ * Used by sandboxBroker::LaunchApp.
+ */
+ virtual InitDllBlocklistOOPFnPtr GetDllBlocklistInitFn() = 0;
+ virtual HandleLauncherErrorFnPtr GetHandleLauncherErrorFn() = 0;
+ virtual SharedSection* GetSharedSection() = 0;
+};
+
+struct WinLauncherServices final {
+ nt::LoaderAPI::InitDllBlocklistOOPFnPtr mInitDllBlocklistOOP;
+ nt::LoaderAPI::HandleLauncherErrorFnPtr mHandleLauncherError;
+ SharedSection* mSharedSection;
+
+ WinLauncherServices()
+ : mInitDllBlocklistOOP(nullptr),
+ mHandleLauncherError(nullptr),
+ mSharedSection(nullptr) {}
+};
+
+} // namespace nt
+} // namespace mozilla
+
+#endif // mozilla_LoaderAPIInterfaces_h
diff --git a/toolkit/xre/dllservices/mozglue/LoaderObserver.cpp b/toolkit/xre/dllservices/mozglue/LoaderObserver.cpp
new file mode 100644
index 0000000000..d2014365c4
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/LoaderObserver.cpp
@@ -0,0 +1,149 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "LoaderObserver.h"
+
+#include "mozilla/AutoProfilerLabel.h"
+#include "mozilla/BaseProfilerMarkers.h"
+#include "mozilla/glue/WindowsUnicode.h"
+#include "mozilla/StackWalk_windows.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+extern glue::Win32SRWLock gDllServicesLock;
+extern glue::detail::DllServicesBase* gDllServices;
+
+namespace glue {
+
+void LoaderObserver::OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) {
+ MOZ_ASSERT(aContext);
+ if (IsProfilerPresent()) {
+ UniquePtr<char[]> utf8RequestedDllName(WideToUTF8(aRequestedDllName));
+ BASE_PROFILER_MARKER_TEXT(
+ "DllLoad", OTHER, MarkerTiming::IntervalStart(),
+ mozilla::ProfilerString8View::WrapNullTerminatedString(
+ utf8RequestedDllName.get()));
+ *aContext = utf8RequestedDllName.release();
+ }
+
+#ifdef _M_AMD64
+ // Prevent the stack walker from suspending this thread when LdrLoadDll
+ // holds the RtlLookupFunctionEntry lock.
+ SuppressStackWalking();
+#endif
+}
+
+bool LoaderObserver::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) {
+ // Currently unsupported
+ return false;
+}
+
+void LoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) {
+#ifdef _M_AMD64
+ DesuppressStackWalking();
+#endif
+
+ if (aContext) {
+ UniquePtr<char[]> utf8RequestedDllName{static_cast<char*>(aContext)};
+ BASE_PROFILER_MARKER_TEXT(
+ "DllLoad", OTHER, MarkerTiming::IntervalEnd(),
+ mozilla::ProfilerString8View::WrapNullTerminatedString(
+ utf8RequestedDllName.get()));
+ }
+
+ // We want to record a denied DLL load regardless of |aNtStatus| because
+ // |aNtStatus| is set to access-denied when DLL load was blocked.
+ if ((!NT_SUCCESS(aNtStatus) && !aModuleLoadInfo.WasDenied()) ||
+ !aModuleLoadInfo.WasMapped()) {
+ return;
+ }
+
+ { // Scope for lock
+ AutoSharedLock lock(gDllServicesLock);
+ if (gDllServices) {
+ gDllServices->DispatchDllLoadNotification(std::move(aModuleLoadInfo));
+ return;
+ }
+ }
+
+ // No dll services, save for later
+ AutoExclusiveLock lock(mLock);
+ if (!mEnabled) {
+ return;
+ }
+
+ if (!mModuleLoads) {
+ mModuleLoads = new ModuleLoadInfoVec();
+ }
+
+ Unused << mModuleLoads->emplaceBack(
+ std::forward<ModuleLoadInfo>(aModuleLoadInfo));
+}
+
+void LoaderObserver::Forward(nt::LoaderObserver* aNext) {
+ MOZ_ASSERT_UNREACHABLE(
+ "This implementation does not forward to any more "
+ "nt::LoaderObserver objects");
+}
+
+void LoaderObserver::Forward(detail::DllServicesBase* aNext) {
+ MOZ_ASSERT(aNext);
+ if (!aNext) {
+ return;
+ }
+
+ ModuleLoadInfoVec* moduleLoads = nullptr;
+
+ { // Scope for lock
+ AutoExclusiveLock lock(mLock);
+ moduleLoads = mModuleLoads;
+ mModuleLoads = nullptr;
+ }
+
+ if (!moduleLoads) {
+ return;
+ }
+
+ aNext->DispatchModuleLoadBacklogNotification(std::move(*moduleLoads));
+ delete moduleLoads;
+}
+
+void LoaderObserver::Disable() {
+ ModuleLoadInfoVec* moduleLoads = nullptr;
+
+ { // Scope for lock
+ AutoExclusiveLock lock(mLock);
+ moduleLoads = mModuleLoads;
+ mModuleLoads = nullptr;
+ mEnabled = false;
+ }
+
+ delete moduleLoads;
+}
+
+void LoaderObserver::OnForward(ModuleLoadInfoVec&& aInfo) {
+ AutoExclusiveLock lock(mLock);
+ if (!mModuleLoads) {
+ mModuleLoads = new ModuleLoadInfoVec();
+ }
+
+ MOZ_ASSERT(mModuleLoads->empty());
+ if (mModuleLoads->empty()) {
+ *mModuleLoads = std::move(aInfo);
+ } else {
+ // This should not happen, but we can handle it
+ for (auto&& item : aInfo) {
+ Unused << mModuleLoads->append(std::move(item));
+ }
+ }
+}
+
+} // namespace glue
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/LoaderObserver.h b/toolkit/xre/dllservices/mozglue/LoaderObserver.h
new file mode 100644
index 0000000000..39b13035a3
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/LoaderObserver.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_LoaderObserver_h
+#define mozilla_glue_LoaderObserver_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/LoaderAPIInterfaces.h"
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/glue/WinUtils.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace glue {
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS LoaderObserver final
+ : public nt::LoaderObserver {
+ public:
+ constexpr LoaderObserver() : mModuleLoads(nullptr), mEnabled(true) {}
+
+ void OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aPreliminaryDllName) final;
+ bool SubstituteForLSP(PCUNICODE_STRING aLspLeafName,
+ PHANDLE aOutHandle) final;
+ void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) final;
+ void Forward(nt::LoaderObserver* aNext) final;
+ void OnForward(ModuleLoadInfoVec&& aInfo) final;
+
+ void Forward(mozilla::glue::detail::DllServicesBase* aSvc);
+ void Disable();
+
+ private:
+ Win32SRWLock mLock;
+ ModuleLoadInfoVec* mModuleLoads;
+ bool mEnabled;
+};
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_LoaderObserver_h
diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp
new file mode 100644
index 0000000000..a49505adbc
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp
@@ -0,0 +1,104 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ModuleLoadFrame.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/UniquePtr.h"
+#include "NtLoaderAPI.h"
+
+#include <string.h>
+
+#include "WindowsFallbackLoaderAPI.h"
+
+static bool IsNullTerminated(PCUNICODE_STRING aStr) {
+ return aStr && (aStr->MaximumLength >= (aStr->Length + sizeof(WCHAR))) &&
+ aStr->Buffer && aStr->Buffer[aStr->Length / sizeof(WCHAR)] == 0;
+}
+
+static mozilla::FallbackLoaderAPI gFallbackLoaderAPI;
+
+namespace mozilla {
+namespace glue {
+
+nt::LoaderAPI* ModuleLoadFrame::sLoaderAPI;
+
+using GetNtLoaderAPIFn = decltype(&mozilla::GetNtLoaderAPI);
+
+/* static */
+void ModuleLoadFrame::StaticInit(nt::LoaderObserver* aNewObserver,
+ nt::WinLauncherServices* aOutWinLauncher) {
+ const auto pGetNtLoaderAPI = reinterpret_cast<GetNtLoaderAPIFn>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "GetNtLoaderAPI"));
+ if (!pGetNtLoaderAPI) {
+ // This case occurs in processes other than firefox.exe that do not contain
+ // the launcher process blocklist.
+ gFallbackLoaderAPI.SetObserver(aNewObserver);
+ sLoaderAPI = &gFallbackLoaderAPI;
+
+ if (aOutWinLauncher) {
+ aOutWinLauncher->mHandleLauncherError = [](const mozilla::LauncherError&,
+ const char*) {};
+ // We intentionally leave mInitDllBlocklistOOP null to make sure calling
+ // mInitDllBlocklistOOP in non-Firefox hits MOZ_RELEASE_ASSERT.
+ }
+ return;
+ }
+
+ sLoaderAPI = pGetNtLoaderAPI(aNewObserver);
+ MOZ_ASSERT(sLoaderAPI);
+
+ if (aOutWinLauncher) {
+ aOutWinLauncher->mInitDllBlocklistOOP = sLoaderAPI->GetDllBlocklistInitFn();
+ aOutWinLauncher->mHandleLauncherError =
+ sLoaderAPI->GetHandleLauncherErrorFn();
+ aOutWinLauncher->mSharedSection = sLoaderAPI->GetSharedSection();
+ }
+}
+
+ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
+ : mAlreadyLoaded(false),
+ mContext(nullptr),
+ mDllLoadStatus(STATUS_UNSUCCESSFUL),
+ mLoadInfo(sLoaderAPI->ConstructAndNotifyBeginDllLoad(&mContext,
+ aRequestedDllName)) {
+ if (!aRequestedDllName) {
+ return;
+ }
+
+ UniquePtr<WCHAR[]> nameBuf;
+ const WCHAR* name = nullptr;
+
+ if (IsNullTerminated(aRequestedDllName)) {
+ name = aRequestedDllName->Buffer;
+ } else {
+ USHORT charLenExclNul = aRequestedDllName->Length / sizeof(WCHAR);
+ USHORT charLenInclNul = charLenExclNul + 1;
+ nameBuf = MakeUnique<WCHAR[]>(charLenInclNul);
+ if (!wcsncpy_s(nameBuf.get(), charLenInclNul, aRequestedDllName->Buffer,
+ charLenExclNul)) {
+ name = nameBuf.get();
+ }
+ }
+
+ mAlreadyLoaded = name && !!::GetModuleHandleW(name);
+}
+
+ModuleLoadFrame::~ModuleLoadFrame() {
+ sLoaderAPI->NotifyEndDllLoad(mContext, mDllLoadStatus, std::move(mLoadInfo));
+}
+
+void ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle) {
+ mDllLoadStatus = aNtStatus;
+ void* baseAddr = mozilla::nt::PEHeaders::HModuleToBaseAddr<void*>(
+ reinterpret_cast<HMODULE>(aHandle));
+ mLoadInfo.mBaseAddr = baseAddr;
+ if (!mAlreadyLoaded) {
+ mLoadInfo.mSectionName = sLoaderAPI->GetSectionName(baseAddr);
+ }
+}
+
+} // namespace glue
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h
new file mode 100644
index 0000000000..d47e42ab0c
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_ModuleLoadFrame_h
+#define mozilla_glue_ModuleLoadFrame_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/LoaderAPIInterfaces.h"
+
+namespace mozilla {
+namespace glue {
+
+class MOZ_RAII ModuleLoadFrame final {
+ public:
+ explicit ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName);
+ ~ModuleLoadFrame();
+
+ void SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle);
+
+ ModuleLoadFrame(const ModuleLoadFrame&) = delete;
+ ModuleLoadFrame(ModuleLoadFrame&&) = delete;
+ ModuleLoadFrame& operator=(const ModuleLoadFrame&) = delete;
+ ModuleLoadFrame& operator=(ModuleLoadFrame&&) = delete;
+
+ static void StaticInit(nt::LoaderObserver* aNewObserver,
+ nt::WinLauncherServices* aOutWinLauncher);
+
+ private:
+ bool mAlreadyLoaded;
+ void* mContext;
+ NTSTATUS mDllLoadStatus;
+ ModuleLoadInfo mLoadInfo;
+
+ private:
+ static nt::LoaderAPI* sLoaderAPI;
+};
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_ModuleLoadFrame_h
diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h
new file mode 100644
index 0000000000..807adaae0a
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h
@@ -0,0 +1,174 @@
+/* -*- 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_ModuleLoadInfo_h
+#define mozilla_ModuleLoadInfo_h
+
+#include "mozilla/NativeNt.h"
+#include "mozilla/Vector.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+struct ModuleLoadInfo final {
+ enum class Status : uint32_t {
+ Loaded = 0,
+ Blocked,
+ Redirected,
+ };
+
+ // We do not provide these methods inside Gecko proper.
+#if !defined(MOZILLA_INTERNAL_API)
+
+ /**
+ * This constructor is for use by the LdrLoadDll hook.
+ */
+ explicit ModuleLoadInfo(PCUNICODE_STRING aRequestedDllName)
+ : mLoadTimeInfo(),
+ mThreadId(nt::RtlGetCurrentThreadId()),
+ mRequestedDllName(aRequestedDllName),
+ mBaseAddr(nullptr),
+ mStatus(Status::Loaded),
+ mIsDependent(false) {
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&mBeginTimestamp);
+# else
+ ::RtlQueryPerformanceCounter(&mBeginTimestamp);
+# endif // defined(IMPL_MFBT)
+ }
+
+ /**
+ * This constructor is used by the NtMapViewOfSection hook IF AND ONLY IF
+ * the LdrLoadDll hook did not already construct a ModuleLoadInfo for the
+ * current DLL load. This may occur while the loader is loading dependencies
+ * of another library.
+ */
+ ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aBaseAddr, Status aLoadStatus, bool aIsDependent)
+ : mLoadTimeInfo(),
+ mThreadId(nt::RtlGetCurrentThreadId()),
+ mSectionName(std::move(aSectionName)),
+ mBaseAddr(aBaseAddr),
+ mStatus(aLoadStatus),
+ mIsDependent(aIsDependent) {
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&mBeginTimestamp);
+# else
+ ::RtlQueryPerformanceCounter(&mBeginTimestamp);
+# endif // defined(IMPL_MFBT)
+ }
+
+ /**
+ * Marks the time that LdrLoadDll began loading this library.
+ */
+ void SetBeginLoadTimeStamp() {
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&mLoadTimeInfo);
+# else
+ ::RtlQueryPerformanceCounter(&mLoadTimeInfo);
+# endif // defined(IMPL_MFBT)
+ }
+
+ /**
+ * Marks the time that LdrLoadDll finished loading this library.
+ */
+ void SetEndLoadTimeStamp() {
+ LARGE_INTEGER endTimeStamp;
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&endTimeStamp);
+# else
+ ::RtlQueryPerformanceCounter(&endTimeStamp);
+# endif // defined(IMPL_MFBT)
+
+ LONGLONG& timeInfo = mLoadTimeInfo.QuadPart;
+ if (!timeInfo) {
+ return;
+ }
+
+ timeInfo = endTimeStamp.QuadPart - timeInfo;
+ }
+
+ /**
+ * Saves the current thread's call stack.
+ */
+ void CaptureBacktrace() {
+ const DWORD kMaxBacktraceSize = 512;
+
+ if (!mBacktrace.resize(kMaxBacktraceSize)) {
+ return;
+ }
+
+ // We don't use a Win32 variant here because Win32's CaptureStackBackTrace
+ // is just a macro that resolve to this function anyway.
+ WORD numCaptured = ::RtlCaptureStackBackTrace(2, kMaxBacktraceSize,
+ mBacktrace.begin(), nullptr);
+ Unused << mBacktrace.resize(numCaptured);
+ // These backtraces might stick around for a while, so let's trim any
+ // excess memory.
+ mBacktrace.shrinkStorageToFit();
+ }
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+ ModuleLoadInfo(ModuleLoadInfo&&) = default;
+ ModuleLoadInfo& operator=(ModuleLoadInfo&&) = default;
+
+ ModuleLoadInfo() = delete;
+ ModuleLoadInfo(const ModuleLoadInfo&) = delete;
+ ModuleLoadInfo& operator=(const ModuleLoadInfo&) = delete;
+
+ /**
+ * A "bare" module load is one that was mapped without the code passing
+ * through a call to ntdll!LdrLoadDll.
+ */
+ bool IsBare() const {
+ // SetBeginLoadTimeStamp() and SetEndLoadTimeStamp() are only called by the
+ // LdrLoadDll hook, so when mLoadTimeInfo == 0, we know that we are bare.
+ return !mLoadTimeInfo.QuadPart;
+ }
+
+ /**
+ * Returns true for DLL loads where LdrLoadDll was called but
+ * NtMapViewOfSection was not. This will happen for DLL requests where the DLL
+ * was already mapped into memory by a previous request.
+ */
+ bool WasMapped() const { return !mSectionName.IsEmpty(); }
+
+ /**
+ * Returns true for DLL load which was denied by our blocklist.
+ */
+ bool WasDenied() const {
+ return mStatus == ModuleLoadInfo::Status::Blocked ||
+ mStatus == ModuleLoadInfo::Status::Redirected;
+ }
+
+ // Timestamp for the creation of this event
+ LARGE_INTEGER mBeginTimestamp;
+ // Duration of the LdrLoadDll call
+ LARGE_INTEGER mLoadTimeInfo;
+ // Thread ID of this DLL load
+ DWORD mThreadId;
+ // The name requested of LdrLoadDll by its caller
+ nt::AllocatedUnicodeString mRequestedDllName;
+ // The name of the DLL that backs section that was mapped by the loader. This
+ // string is the effective name of the DLL that was resolved by the loader's
+ // path search algorithm.
+ nt::AllocatedUnicodeString mSectionName;
+ // The base address of the module's mapped section
+ const void* mBaseAddr;
+ // If the module was successfully loaded, stack trace of the DLL load request
+ Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace;
+ // The status of DLL load
+ Status mStatus;
+ // Whether the module is one of the executables's dependent modules or not
+ bool mIsDependent;
+};
+
+using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>;
+
+} // namespace mozilla
+
+#endif // mozilla_ModuleLoadInfo_h
diff --git a/toolkit/xre/dllservices/mozglue/NtLoaderAPI.h b/toolkit/xre/dllservices/mozglue/NtLoaderAPI.h
new file mode 100644
index 0000000000..628609092b
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/NtLoaderAPI.h
@@ -0,0 +1,23 @@
+/* -*- 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_NtLoaderAPI_h
+#define mozilla_NtLoaderAPI_h
+
+#include "mozilla/LoaderAPIInterfaces.h"
+
+#if !defined(IMPL_MFBT)
+# error "This should only be included from mozglue!"
+#endif // !defined(IMPL_MFBT)
+
+namespace mozilla {
+
+extern "C" MOZ_IMPORT_API nt::LoaderAPI* GetNtLoaderAPI(
+ nt::LoaderObserver* aNewObserver);
+
+} // namespace mozilla
+
+#endif // mozilla_NtLoaderAPI_h
diff --git a/toolkit/xre/dllservices/mozglue/SharedSection.h b/toolkit/xre/dllservices/mozglue/SharedSection.h
new file mode 100644
index 0000000000..162c8e8343
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/SharedSection.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_SharedSection_h
+#define mozilla_glue_SharedSection_h
+
+#include <winternl.h>
+#include "nscore.h"
+#include "mozilla/Span.h"
+#include "mozilla/WindowsDllBlocklistInfo.h"
+
+namespace mozilla::nt {
+
+// This interface provides a way to access winlauncher's shared section
+// through DllServices.
+struct NS_NO_VTABLE SharedSection {
+ virtual Span<const wchar_t> GetDependentModules() = 0;
+ // Returns a span of the whole space for dynamic blocklist entries.
+ // Use IsValidDynamicBlocklistEntry() to determine the end of the list.
+ virtual Span<const DllBlockInfoT<UNICODE_STRING>> GetDynamicBlocklist() = 0;
+};
+
+} // namespace mozilla::nt
+
+#endif // mozilla_glue_SharedSection_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
new file mode 100644
index 0000000000..9f0b50a583
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
@@ -0,0 +1,781 @@
+/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <winternl.h>
+
+#pragma warning(push)
+#pragma warning(disable : 4275 4530) // See msvc-stl-wrapper.template.h
+#include <map>
+#pragma warning(pop)
+
+#include "Authenticode.h"
+#include "BaseProfiler.h"
+#include "nsWindowsDllInterceptor.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/StackWalk_windows.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsWindowsHelpers.h"
+#include "WindowsDllBlocklist.h"
+#include "mozilla/AutoProfilerLabel.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/glue/WinUtils.h"
+
+// Start new implementation
+#include "LoaderObserver.h"
+#include "ModuleLoadFrame.h"
+#include "mozilla/glue/WindowsUnicode.h"
+
+namespace mozilla {
+
+glue::Win32SRWLock gDllServicesLock;
+glue::detail::DllServicesBase* gDllServices;
+
+} // namespace mozilla
+
+using namespace mozilla;
+
+using CrashReporter::Annotation;
+using CrashReporter::AnnotationWriter;
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE const char*
+#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
+
+// define this for very verbose dll load debug spew
+#undef DEBUG_very_verbose
+
+static uint32_t sInitFlags;
+static bool sBlocklistInitAttempted;
+static bool sBlocklistInitFailed;
+static bool sUser32BeforeBlocklist;
+
+typedef MOZ_NORETURN_PTR void(__fastcall* BaseThreadInitThunk_func)(
+ BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
+static WindowsDllInterceptor::FuncHookType<BaseThreadInitThunk_func>
+ stub_BaseThreadInitThunk;
+
+typedef NTSTATUS(NTAPI* LdrLoadDll_func)(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName,
+ PHANDLE handle);
+static WindowsDllInterceptor::FuncHookType<LdrLoadDll_func> stub_LdrLoadDll;
+
+#ifdef _M_AMD64
+typedef decltype(RtlInstallFunctionTableCallback)*
+ RtlInstallFunctionTableCallback_func;
+static WindowsDllInterceptor::FuncHookType<RtlInstallFunctionTableCallback_func>
+ stub_RtlInstallFunctionTableCallback;
+
+extern uint8_t* sMsMpegJitCodeRegionStart;
+extern size_t sMsMpegJitCodeRegionSize;
+
+BOOLEAN WINAPI patched_RtlInstallFunctionTableCallback(
+ DWORD64 TableIdentifier, DWORD64 BaseAddress, DWORD Length,
+ PGET_RUNTIME_FUNCTION_CALLBACK Callback, PVOID Context,
+ PCWSTR OutOfProcessCallbackDll) {
+ // msmpeg2vdec.dll sets up a function table callback for their JIT code that
+ // just terminates the process, because their JIT doesn't have unwind info.
+ // If we see this callback being registered, record the region address, so
+ // that StackWalk.cpp can avoid unwinding addresses in this region.
+ //
+ // To keep things simple I'm not tracking unloads of msmpeg2vdec.dll.
+ // Worst case the stack walker will needlessly avoid a few pages of memory.
+
+ // Tricky: GetModuleHandleExW adds a ref by default; GetModuleHandleW doesn't.
+ HMODULE callbackModule = nullptr;
+ DWORD moduleFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
+
+ // These GetModuleHandle calls enter a critical section on Win7.
+ AutoSuppressStackWalking suppress;
+
+ if (GetModuleHandleExW(moduleFlags, (LPWSTR)Callback, &callbackModule) &&
+ GetModuleHandleW(L"msmpeg2vdec.dll") == callbackModule) {
+ sMsMpegJitCodeRegionStart = (uint8_t*)BaseAddress;
+ sMsMpegJitCodeRegionSize = Length;
+ }
+
+ return stub_RtlInstallFunctionTableCallback(TableIdentifier, BaseAddress,
+ Length, Callback, Context,
+ OutOfProcessCallbackDll);
+}
+#endif
+
+template <class T>
+struct RVAMap {
+ RVAMap(HANDLE map, DWORD offset) {
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+
+ DWORD alignedOffset =
+ (offset / info.dwAllocationGranularity) * info.dwAllocationGranularity;
+
+ MOZ_ASSERT(offset - alignedOffset < info.dwAllocationGranularity, "Wtf");
+
+ mRealView = ::MapViewOfFile(map, FILE_MAP_READ, 0, alignedOffset,
+ sizeof(T) + (offset - alignedOffset));
+
+ mMappedView =
+ mRealView
+ ? reinterpret_cast<T*>((char*)mRealView + (offset - alignedOffset))
+ : nullptr;
+ }
+ ~RVAMap() {
+ if (mRealView) {
+ ::UnmapViewOfFile(mRealView);
+ }
+ }
+ operator const T*() const { return mMappedView; }
+ const T* operator->() const { return mMappedView; }
+
+ private:
+ const T* mMappedView;
+ void* mRealView;
+};
+
+static DWORD GetTimestamp(const wchar_t* path) {
+ DWORD timestamp = 0;
+
+ HANDLE file = ::CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (file != INVALID_HANDLE_VALUE) {
+ HANDLE map =
+ ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
+ if (map) {
+ RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0);
+ if (peHeader) {
+ RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew);
+ if (ntHeader) {
+ timestamp = ntHeader->FileHeader.TimeDateStamp;
+ }
+ }
+ ::CloseHandle(map);
+ }
+ ::CloseHandle(file);
+ }
+
+ return timestamp;
+}
+
+// This lock protects both the reentrancy sentinel and the crash reporter
+// data structures.
+static CRITICAL_SECTION sLock;
+
+/**
+ * Some versions of Windows call LoadLibraryEx to get the version information
+ * for a DLL, which causes our patched LdrLoadDll implementation to re-enter
+ * itself and cause infinite recursion and a stack-exhaustion crash. We protect
+ * against reentrancy by allowing recursive loads of the same DLL.
+ *
+ * Note that we don't use __declspec(thread) because that doesn't work in DLLs
+ * loaded via LoadLibrary and there can be a limited number of TLS slots, so
+ * we roll our own.
+ */
+class ReentrancySentinel {
+ public:
+ explicit ReentrancySentinel(const char* dllName) {
+ DWORD currentThreadId = GetCurrentThreadId();
+ AutoCriticalSection lock(&sLock);
+ mPreviousDllName = (*sThreadMap)[currentThreadId];
+
+ // If there is a DLL currently being loaded and it has the same name
+ // as the current attempt, we're re-entering.
+ mReentered = mPreviousDllName && !stricmp(mPreviousDllName, dllName);
+ (*sThreadMap)[currentThreadId] = dllName;
+ }
+
+ ~ReentrancySentinel() {
+ DWORD currentThreadId = GetCurrentThreadId();
+ AutoCriticalSection lock(&sLock);
+ (*sThreadMap)[currentThreadId] = mPreviousDllName;
+ }
+
+ bool BailOut() const { return mReentered; };
+
+ static void InitializeStatics() {
+ InitializeCriticalSection(&sLock);
+ sThreadMap = new std::map<DWORD, const char*>;
+ }
+
+ private:
+ static std::map<DWORD, const char*>* sThreadMap;
+
+ const char* mPreviousDllName;
+ bool mReentered;
+};
+
+std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap;
+
+using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;
+
+/**
+ * This is a linked list of DLLs that have been blocked. It doesn't use
+ * mozilla::LinkedList because this is an append-only list and doesn't need
+ * to be doubly linked.
+ */
+class DllBlockSet {
+ public:
+ static void Add(const char* name, unsigned long long version);
+
+ // Write the list of blocked DLLs to a WritableBuffer object. This method is
+ // run after a crash occurs and must therefore not use the heap, etc.
+ static void Write(WritableBuffer& buffer);
+
+ private:
+ DllBlockSet(const char* name, unsigned long long version)
+ : mName(name), mVersion(version), mNext(nullptr) {}
+
+ const char* mName; // points into the gWindowsDllBlocklist string
+ unsigned long long mVersion;
+ DllBlockSet* mNext;
+
+ static DllBlockSet* gFirst;
+};
+
+DllBlockSet* DllBlockSet::gFirst;
+
+void DllBlockSet::Add(const char* name, unsigned long long version) {
+ AutoCriticalSection lock(&sLock);
+ for (DllBlockSet* b = gFirst; b; b = b->mNext) {
+ if (0 == strcmp(b->mName, name) && b->mVersion == version) {
+ return;
+ }
+ }
+ // Not already present
+ DllBlockSet* n = new DllBlockSet(name, version);
+ n->mNext = gFirst;
+ gFirst = n;
+}
+
+void DllBlockSet::Write(WritableBuffer& buffer) {
+ // It would be nicer to use AutoCriticalSection here. However, its destructor
+ // might not run if an exception occurs, in which case we would never leave
+ // the critical section. (MSVC warns about this possibility.) So we
+ // enter and leave manually.
+ ::EnterCriticalSection(&sLock);
+
+ // Because this method is called after a crash occurs, and uses heap memory,
+ // protect this entire block with a structured exception handler.
+ MOZ_SEH_TRY {
+ for (DllBlockSet* b = gFirst; b; b = b->mNext) {
+ // write name[,v.v.v.v];
+ buffer.Write(b->mName, strlen(b->mName));
+ if (b->mVersion != DllBlockInfo::ALL_VERSIONS) {
+ buffer.Write(",", 1);
+ uint16_t parts[4];
+ parts[0] = b->mVersion >> 48;
+ parts[1] = (b->mVersion >> 32) & 0xFFFF;
+ parts[2] = (b->mVersion >> 16) & 0xFFFF;
+ parts[3] = b->mVersion & 0xFFFF;
+ for (int p = 0; p < 4; ++p) {
+ char buf[32];
+ _ltoa_s(parts[p], buf, sizeof(buf), 10);
+ buffer.Write(buf, strlen(buf));
+ if (p != 3) {
+ buffer.Write(".", 1);
+ }
+ }
+ }
+ buffer.Write(";", 1);
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {}
+
+ ::LeaveCriticalSection(&sLock);
+}
+
+static UniquePtr<wchar_t[]> getFullPath(PWCHAR filePath, wchar_t* fname) {
+ // In Windows 8, the first parameter seems to be used for more than just the
+ // path name. For example, its numerical value can be 1. Passing a non-valid
+ // pointer to SearchPathW will cause a crash, so we need to check to see if we
+ // are handed a valid pointer, and otherwise just pass nullptr to SearchPathW.
+ PWCHAR sanitizedFilePath = nullptr;
+ if ((uintptr_t(filePath) >= 65536) && ((uintptr_t(filePath) & 1) == 0)) {
+ sanitizedFilePath = filePath;
+ }
+
+ // figure out the length of the string that we need
+ DWORD pathlen =
+ SearchPathW(sanitizedFilePath, fname, L".dll", 0, nullptr, nullptr);
+ if (pathlen == 0) {
+ return nullptr;
+ }
+
+ auto full_fname = MakeUnique<wchar_t[]>(pathlen + 1);
+ if (!full_fname) {
+ // couldn't allocate memory?
+ return nullptr;
+ }
+
+ // now actually grab it
+ SearchPathW(sanitizedFilePath, fname, L".dll", pathlen + 1, full_fname.get(),
+ nullptr);
+ return full_fname;
+}
+
+// No builtin function to find the last character matching a set
+static wchar_t* lastslash(wchar_t* s, int len) {
+ for (wchar_t* c = s + len - 1; c >= s; --c) {
+ if (*c == L'\\' || *c == L'/') {
+ return c;
+ }
+ }
+ return nullptr;
+}
+
+static NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName,
+ PHANDLE handle) {
+ // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename
+ // portion
+#define DLLNAME_MAX 128
+ char dllName[DLLNAME_MAX + 1];
+ wchar_t* dll_part;
+ char* dot;
+
+ int len = moduleFileName->Length / 2;
+ wchar_t* fname = moduleFileName->Buffer;
+ UniquePtr<wchar_t[]> full_fname;
+
+ // The filename isn't guaranteed to be null terminated, but in practice
+ // it always will be; ensure that this is so, and bail if not.
+ // This is done instead of the more robust approach because of bug 527122,
+ // where lots of weird things were happening when we tried to make a copy.
+ if (moduleFileName->MaximumLength < moduleFileName->Length + 2 ||
+ fname[len] != 0) {
+#ifdef DEBUG
+ printf_stderr("LdrLoadDll: non-null terminated string found!\n");
+#endif
+ goto continue_loading;
+ }
+
+ dll_part = lastslash(fname, len);
+ if (dll_part) {
+ dll_part = dll_part + 1;
+ len -= dll_part - fname;
+ } else {
+ dll_part = fname;
+ }
+
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: dll_part '%S' %d\n", dll_part, len);
+#endif
+
+ // if it's too long, then, we assume we won't want to block it,
+ // since DLLNAME_MAX should be at least long enough to hold the longest
+ // entry in our blocklist.
+ if (len > DLLNAME_MAX) {
+#ifdef DEBUG
+ printf_stderr("LdrLoadDll: len too long! %d\n", len);
+#endif
+ goto continue_loading;
+ }
+
+ // copy over to our char byte buffer, lowercasing ASCII as we go
+ for (int i = 0; i < len; i++) {
+ wchar_t c = dll_part[i];
+
+ if (c > 0x7f) {
+ // welp, it's not ascii; if we need to add non-ascii things to
+ // our blocklist, we'll have to remove this limitation.
+ goto continue_loading;
+ }
+
+ // ensure that dll name is all lowercase
+ if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
+
+ dllName[i] = (char)c;
+ }
+
+ dllName[len] = 0;
+
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: dll name '%s'\n", dllName);
+#endif
+
+ if (!(sInitFlags & eDllBlocklistInitFlagWasBootstrapped)) {
+ // Block a suspicious binary that uses various 12-digit hex strings
+ // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138)
+ dot = strchr(dllName, '.');
+ if (dot && (strchr(dot + 1, '.') == dot + 13)) {
+ char* end = nullptr;
+ _strtoui64(dot + 1, &end, 16);
+ if (end == dot + 13) {
+ return STATUS_DLL_NOT_FOUND;
+ }
+ }
+ // Block binaries where the filename is at least 16 hex digits
+ if (dot && ((dot - dllName) >= 16)) {
+ char* current = dllName;
+ while (current < dot && isxdigit(*current)) {
+ current++;
+ }
+ if (current == dot) {
+ return STATUS_DLL_NOT_FOUND;
+ }
+ }
+
+ // then compare to everything on the blocklist
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
+ while (info->mName) {
+ if (strcmp(info->mName, dllName) == 0) break;
+
+ info++;
+ }
+
+ if (info->mName) {
+ bool load_ok = false;
+
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: info->mName: '%s'\n", info->mName);
+#endif
+
+ if (info->mFlags & DllBlockInfo::REDIRECT_TO_NOOP_ENTRYPOINT) {
+ printf_stderr(
+ "LdrLoadDll: "
+ "Ignoring the REDIRECT_TO_NOOP_ENTRYPOINT flag\n");
+ }
+
+ if ((info->mFlags & DllBlockInfo::BLOCK_WIN8_AND_OLDER) &&
+ IsWin8Point1OrLater()) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::BLOCK_WIN7_AND_OLDER) &&
+ IsWin8OrLater()) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::CHILD_PROCESSES_ONLY) &&
+ !(sInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::UTILITY_PROCESSES_ONLY) &&
+ !(sInitFlags & eDllBlocklistInitFlagIsUtilityProcess)) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::SOCKET_PROCESSES_ONLY) &&
+ !(sInitFlags & eDllBlocklistInitFlagIsSocketProcess)) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::BROWSER_PROCESS_ONLY) &&
+ (sInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
+ goto continue_loading;
+ }
+
+ unsigned long long fVersion = DllBlockInfo::ALL_VERSIONS;
+
+ if (info->mMaxVersion != DllBlockInfo::ALL_VERSIONS) {
+ ReentrancySentinel sentinel(dllName);
+ if (sentinel.BailOut()) {
+ goto continue_loading;
+ }
+
+ full_fname = getFullPath(filePath, fname);
+ if (!full_fname) {
+ // uh, we couldn't find the DLL at all, so...
+ printf_stderr(
+ "LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find "
+ "it?)\n",
+ dllName);
+ return STATUS_DLL_NOT_FOUND;
+ }
+
+ if (info->mFlags & DllBlockInfo::USE_TIMESTAMP) {
+ fVersion = GetTimestamp(full_fname.get());
+ if (fVersion > info->mMaxVersion) {
+ load_ok = true;
+ }
+ } else {
+ LauncherResult<ModuleVersion> version =
+ GetModuleVersion(full_fname.get());
+ // If we failed to get the version information, we block.
+ if (version.isOk()) {
+ load_ok = !info->IsVersionBlocked(version.unwrap());
+ }
+ }
+ }
+
+ if (!load_ok) {
+ printf_stderr(
+ "LdrLoadDll: Blocking load of '%s' -- see "
+ "http://www.mozilla.com/en-US/blocklist/\n",
+ dllName);
+ DllBlockSet::Add(info->mName, fVersion);
+ return STATUS_DLL_NOT_FOUND;
+ }
+ }
+ }
+
+continue_loading:
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: continuing load... ('%S')\n",
+ moduleFileName->Buffer);
+#endif
+
+ glue::ModuleLoadFrame loadFrame(moduleFileName);
+
+ NTSTATUS ret;
+ HANDLE myHandle;
+
+ {
+#if defined(_M_AMD64) || defined(_M_ARM64)
+ AutoSuppressStackWalking suppress;
+#endif
+ ret = stub_LdrLoadDll(filePath, flags, moduleFileName, &myHandle);
+ }
+
+ if (handle) {
+ *handle = myHandle;
+ }
+
+ loadFrame.SetLoadStatus(ret, myHandle);
+
+ return ret;
+}
+
+#if defined(NIGHTLY_BUILD)
+// Map of specific thread proc addresses we should block. In particular,
+// LoadLibrary* APIs which indicate DLL injection
+static void* gStartAddressesToBlock[4];
+#endif // defined(NIGHTLY_BUILD)
+
+static bool ShouldBlockThread(void* aStartAddress) {
+ // Allows crashfirefox.exe to continue to work. Also if your threadproc is
+ // null, this crash is intentional.
+ if (aStartAddress == nullptr) {
+ return false;
+ }
+
+#if defined(NIGHTLY_BUILD)
+ for (auto p : gStartAddressesToBlock) {
+ if (p == aStartAddress) {
+ return true;
+ }
+ }
+#endif
+
+ bool shouldBlock = false;
+ MEMORY_BASIC_INFORMATION startAddressInfo = {0};
+ if (VirtualQuery(aStartAddress, &startAddressInfo,
+ sizeof(startAddressInfo))) {
+ shouldBlock |= startAddressInfo.State != MEM_COMMIT;
+ shouldBlock |= startAddressInfo.Protect != PAGE_EXECUTE_READ;
+ }
+
+ return shouldBlock;
+}
+
+// Allows blocked threads to still run normally through BaseThreadInitThunk, in
+// case there's any magic there that we shouldn't skip.
+static DWORD WINAPI NopThreadProc(void* /* aThreadParam */) { return 0; }
+
+static MOZ_NORETURN void __fastcall patched_BaseThreadInitThunk(
+ BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam) {
+ if (ShouldBlockThread(aStartAddress)) {
+ aStartAddress = (void*)NopThreadProc;
+ }
+
+ stub_BaseThreadInitThunk(aIsInitialThread, aStartAddress, aThreadParam);
+}
+
+static WindowsDllInterceptor NtDllIntercept;
+static WindowsDllInterceptor Kernel32Intercept;
+
+static void GetNativeNtBlockSetWriter();
+
+static glue::LoaderObserver gMozglueLoaderObserver;
+static nt::WinLauncherServices gWinLauncher;
+
+MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) {
+ if (sBlocklistInitAttempted) {
+ return;
+ }
+ sBlocklistInitAttempted = true;
+
+ sInitFlags = aInitFlags;
+
+ glue::ModuleLoadFrame::StaticInit(&gMozglueLoaderObserver, &gWinLauncher);
+
+#ifdef _M_AMD64
+ if (!IsWin8OrLater()) {
+ Kernel32Intercept.Init(L"kernel32.dll");
+ // The crash that this hook works around is only seen on Win7.
+ stub_RtlInstallFunctionTableCallback.Set(
+ Kernel32Intercept, "RtlInstallFunctionTableCallback",
+ &patched_RtlInstallFunctionTableCallback);
+ }
+#endif
+
+ // Bug 1361410: WRusr.dll will overwrite our hook and cause a crash.
+ // Workaround: If we detect WRusr.dll, don't hook.
+ if (!GetModuleHandleW(L"WRusr.dll")) {
+ Kernel32Intercept.Init(L"kernel32.dll");
+ if (!stub_BaseThreadInitThunk.SetDetour(Kernel32Intercept,
+ "BaseThreadInitThunk",
+ &patched_BaseThreadInitThunk)) {
+#ifdef DEBUG
+ printf_stderr("BaseThreadInitThunk hook failed\n");
+#endif
+ }
+ }
+
+#if defined(NIGHTLY_BUILD)
+ // Populate a list of thread start addresses to block.
+ HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
+ if (hKernel) {
+ void* pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryA");
+ gStartAddressesToBlock[0] = pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryW");
+ gStartAddressesToBlock[1] = pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExA");
+ gStartAddressesToBlock[2] = pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExW");
+ gStartAddressesToBlock[3] = pProc;
+ }
+#endif
+
+ if (aInitFlags & eDllBlocklistInitFlagWasBootstrapped) {
+ GetNativeNtBlockSetWriter();
+ return;
+ }
+
+ // There are a couple of exceptional cases where we skip user32.dll check.
+ // - If the the process was bootstrapped by the launcher process, AppInit
+ // DLLs will be intercepted by the new DllBlockList. No need to check
+ // here.
+ // - The code to initialize the base profiler loads winmm.dll which
+ // statically links user32.dll on an older Windows. This means if the base
+ // profiler is active before coming here, we cannot fully intercept AppInit
+ // DLLs. Given that the base profiler is used outside the typical use
+ // cases, it's ok not to check user32.dll in this scenario.
+ const bool skipUser32Check =
+ (sInitFlags & eDllBlocklistInitFlagWasBootstrapped) ||
+ (!IsWin10AnniversaryUpdateOrLater() &&
+ baseprofiler::profiler_is_active());
+
+ // In order to be effective against AppInit DLLs, the blocklist must be
+ // initialized before user32.dll is loaded into the process (bug 932100).
+ if (!skipUser32Check && GetModuleHandleW(L"user32.dll")) {
+ sUser32BeforeBlocklist = true;
+#ifdef DEBUG
+ printf_stderr("DLL blocklist was unable to intercept AppInit DLLs.\n");
+#endif
+ }
+
+ NtDllIntercept.Init("ntdll.dll");
+
+ ReentrancySentinel::InitializeStatics();
+
+ // We specifically use a detour, because there are cases where external
+ // code also tries to hook LdrLoadDll, and doesn't know how to relocate our
+ // nop space patches. (Bug 951827)
+ bool ok = stub_LdrLoadDll.SetDetour(NtDllIntercept, "LdrLoadDll",
+ &patched_LdrLoadDll);
+
+ if (!ok) {
+ sBlocklistInitFailed = true;
+#ifdef DEBUG
+ printf_stderr("LdrLoadDll hook failed, no dll blocklisting active\n");
+#endif
+ }
+
+ // If someone injects a thread early that causes user32.dll to load off the
+ // main thread this causes issues, so load it as soon as we've initialized
+ // the block-list. (See bug 1400637)
+ if (!sUser32BeforeBlocklist && !IsWin32kLockedDown()) {
+ ::LoadLibraryW(L"user32.dll");
+ }
+}
+
+#ifdef DEBUG
+MFBT_API void DllBlocklist_Shutdown() {}
+#endif // DEBUG
+
+static void InternalWriteNotes(AnnotationWriter& aWriter) {
+ WritableBuffer buffer;
+ DllBlockSet::Write(buffer);
+
+ aWriter.Write(Annotation::BlockedDllList, buffer.Data(), buffer.Length());
+
+ if (sBlocklistInitFailed) {
+ aWriter.Write(Annotation::BlocklistInitFailed, "1");
+ }
+
+ if (sUser32BeforeBlocklist) {
+ aWriter.Write(Annotation::User32BeforeBlocklist, "1");
+ }
+}
+
+using WriterFn = void (*)(AnnotationWriter&);
+static WriterFn gWriterFn = &InternalWriteNotes;
+
+static void GetNativeNtBlockSetWriter() {
+ auto nativeWriter = reinterpret_cast<WriterFn>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "NativeNtBlockSet_Write"));
+ if (nativeWriter) {
+ gWriterFn = nativeWriter;
+ }
+}
+
+MFBT_API void DllBlocklist_WriteNotes(AnnotationWriter& aWriter) {
+ MOZ_ASSERT(gWriterFn);
+ gWriterFn(aWriter);
+}
+
+MFBT_API bool DllBlocklist_CheckStatus() {
+ if (sBlocklistInitFailed || sUser32BeforeBlocklist) return false;
+ return true;
+}
+
+// ============================================================================
+// This section is for DLL Services
+// ============================================================================
+
+namespace mozilla {
+Authenticode* GetAuthenticode();
+} // namespace mozilla
+
+/**
+ * Please note that DllBlocklist_SetFullDllServices is called with
+ * aSvc = nullptr to de-initialize the resources even though they
+ * have been initialized via DllBlocklist_SetBasicDllServices.
+ */
+MFBT_API void DllBlocklist_SetFullDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc) {
+ glue::AutoExclusiveLock lock(gDllServicesLock);
+ if (aSvc) {
+ aSvc->SetAuthenticodeImpl(GetAuthenticode());
+ aSvc->SetWinLauncherServices(gWinLauncher);
+ gMozglueLoaderObserver.Forward(aSvc);
+ }
+
+ gDllServices = aSvc;
+}
+
+MFBT_API void DllBlocklist_SetBasicDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc) {
+ if (!aSvc) {
+ return;
+ }
+
+ aSvc->SetAuthenticodeImpl(GetAuthenticode());
+ gMozglueLoaderObserver.Disable();
+}
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
new file mode 100644
index 0000000000..d67f49a17e
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_windowsdllblocklist_h
+#define mozilla_windowsdllblocklist_h
+
+#if (defined(_MSC_VER) || defined(__MINGW32__)) && \
+ (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64))
+
+# include <windows.h>
+# include "CrashAnnotations.h"
+# include "mozilla/Attributes.h"
+# include "mozilla/Types.h"
+
+# define HAS_DLL_BLOCKLIST
+
+enum DllBlocklistInitFlags {
+ eDllBlocklistInitFlagDefault = 0,
+ eDllBlocklistInitFlagIsChildProcess = 1,
+ eDllBlocklistInitFlagWasBootstrapped = 2,
+ eDllBlocklistInitFlagIsUtilityProcess = 4,
+ eDllBlocklistInitFlagIsSocketProcess = 8
+};
+
+// Only available from within firefox.exe
+# if !defined(IMPL_MFBT) && !defined(MOZILLA_INTERNAL_API)
+extern uint32_t gBlocklistInitFlags;
+# endif // !defined(IMPL_MFBT) && !defined(MOZILLA_INTERNAL_API)
+
+MFBT_API void DllBlocklist_Initialize(
+ uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
+MFBT_API void DllBlocklist_WriteNotes(CrashReporter::AnnotationWriter& aWriter);
+MFBT_API bool DllBlocklist_CheckStatus();
+
+// This export intends to clean up after DllBlocklist_Initialize().
+// It's disabled in release builds for performance and to limit callers' ability
+// to interfere with dll blocking.
+# ifdef DEBUG
+MFBT_API void DllBlocklist_Shutdown();
+# endif // DEBUG
+
+namespace mozilla {
+namespace glue {
+namespace detail {
+// Forward declaration
+class DllServicesBase;
+
+template <size_t N>
+class WritableBuffer {
+ char mBuffer[N];
+ size_t mLen;
+
+ size_t Available() const { return sizeof(mBuffer) - mLen; }
+
+ public:
+ WritableBuffer() : mBuffer{0}, mLen(0) {}
+
+ void Write(const char* aData, size_t aLen) {
+ size_t writable_len = std::min(aLen, Available());
+ memcpy(mBuffer + mLen, aData, writable_len);
+ mLen += writable_len;
+ }
+
+ size_t Length() const { return mLen; }
+ const char* Data() const { return mBuffer; }
+};
+} // namespace detail
+} // namespace glue
+} // namespace mozilla
+
+MFBT_API void DllBlocklist_SetFullDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc);
+MFBT_API void DllBlocklist_SetBasicDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc);
+
+#endif // defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+#endif // mozilla_windowsdllblocklist_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h
new file mode 100644
index 0000000000..b9f176ac02
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h
@@ -0,0 +1,46 @@
+/* -*- 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_WindowsDllBlocklistCommon_h
+#define mozilla_WindowsDllBlocklistCommon_h
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/WindowsDllBlocklistInfo.h"
+
+#if !defined(DLL_BLOCKLIST_STRING_TYPE)
+# error "You must define DLL_BLOCKLIST_STRING_TYPE"
+#endif // !defined(DLL_BLOCKLIST_STRING_TYPE)
+
+#define DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(name) \
+ using DllBlockInfo = mozilla::DllBlockInfoT<DLL_BLOCKLIST_STRING_TYPE>; \
+ static const DllBlockInfo name[] = {
+#define DLL_BLOCKLIST_DEFINITIONS_BEGIN \
+ DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gWindowsDllBlocklist)
+
+#define DLL_BLOCKLIST_DEFINITIONS_END \
+ {} \
+ } \
+ ;
+
+#define DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY_FOR(name, list) \
+ const DllBlockInfo* name = &list[0]
+
+#define DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(name) \
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY_FOR(name, gWindowsDllBlocklist)
+
+#define DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY_FOR(name, list) \
+ const DllBlockInfo* name = &list[mozilla::ArrayLength(list) - 1]
+
+#define DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(name) \
+ DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY_FOR(name, gWindowsDllBlocklist)
+
+#define DECLARE_DLL_BLOCKLIST_NUM_ENTRIES_FOR(name, list) \
+ const size_t name = mozilla::ArrayLength(list) - 1
+
+#define DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(name) \
+ DECLARE_DLL_BLOCKLIST_NUM_ENTRIES_FOR(name, gWindowsDllBlocklist)
+
+#endif // mozilla_WindowsDllBlocklistCommon_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in
new file mode 100644
index 0000000000..99360930ca
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in
@@ -0,0 +1,356 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# This file exposes five lists:
+# ALL_PROCESSES, BROWSER_PROCESS, CHILD_PROCESSES, UTILITY_PROCESSES,
+# and SOCKET_PROCESSES
+#
+# In addition, each of those lists supports a special variant for test-only
+# entries:
+# ALL_PROCESSES_TESTS, BROWSER_PROCESS_TESTS, CHILD_PROCESSES_TESTS,
+# UTILITY_PROCESSES_TESTS, and SOCKET_PROCESSES_TESTS
+#
+# Choose the list that is applicable to the applicable process type(s) for your
+# DLL block.
+#
+# The currently supported blocklist entry types are:
+# DllBlocklistEntry, A11yBlocklistEntry, LspBlocklistEntry,
+# RedirectToNoOpEntryPoint
+# (See gen_dll_blocklist_defs.py for their documentation.)
+#
+# Example:
+# ALL_PROCESSES += [
+# DllBlocklistEntry("foo.dll", (1, 2, 3, 4)),
+# DllBlocklistEntry("foo.dll", ALL_VERSIONS),
+# DllBlocklistEntry("foo.dll", UNVERSIONED),
+# DllBlocklistEntry("foo.dll", 0x0000123400000000),
+# DllBlocklistEntry("foo.dll", PETimeStamp(0x12345678)),
+# ]
+#
+# The version parameter the "last bad" version, that is, we block anything that
+# is less-than or equal to that version.
+
+ALL_PROCESSES += [
+ # NPFFAddon - Known malware
+ DllBlocklistEntry("npffaddon.dll", ALL_VERSIONS),
+
+ # AVG 8 - Antivirus vendor AVG, old version, plugin already blocklisted
+ DllBlocklistEntry("avgrsstx.dll", (8,5,0,401)),
+
+ # calc.dll - Suspected malware
+ DllBlocklistEntry("calc.dll", (1,0,0,1)),
+
+ # hook.dll - Suspected malware
+ DllBlocklistEntry("hook.dll", ALL_VERSIONS),
+
+ # GoogleDesktopNetwork3.dll - Extremely old, unversioned instances
+ # of this DLL cause crashes
+ DllBlocklistEntry("googledesktopnetwork3.dll", UNVERSIONED),
+
+ # rdolib.dll - Suspected malware
+ DllBlocklistEntry("rdolib.dll", (6,0,88,4)),
+
+ # fgjk4wvb.dll - Suspected malware
+ DllBlocklistEntry("fgjk4wvb.dll", (8,8,8,8)),
+
+ # radhslib.dll - Naomi internet filter - unmaintained since 2006
+ DllBlocklistEntry("radhslib.dll", UNVERSIONED),
+
+ # Music download filter for vkontakte.ru - old instances
+ # of this DLL cause crashes
+ DllBlocklistEntry("vksaver.dll", (2,2,2,0)),
+
+ # Topcrash in Firefox 4.0b1
+ DllBlocklistEntry("rlxf.dll", (1,2,323,1)),
+
+ # psicon.dll - Topcrashes in Thunderbird, and some crashes in Firefox
+ # Adobe photoshop library, now redundant in later installations
+ DllBlocklistEntry("psicon.dll", ALL_VERSIONS),
+
+ # Topcrash in Firefox 4 betas (bug 618899),
+ DllBlocklistEntry("accelerator.dll", (3,2,1,6)),
+
+ # Topcrash with Roboform in Firefox 8 (bug 699134),
+ DllBlocklistEntry("rf-firefox.dll", (7,6,1,0)),
+ DllBlocklistEntry("roboform.dll", (7,6,1,0)),
+
+ # Topcrash with Babylon Toolbar on FF16+ (bug 721264),
+ DllBlocklistEntry("babyfox.dll", ALL_VERSIONS),
+
+ # sprotector.dll crashes, bug 957258
+ DllBlocklistEntry("sprotector.dll", ALL_VERSIONS),
+
+ # Windows Media Foundation FLAC decoder and type sniffer (bug 839031).
+ DllBlocklistEntry("mfflac.dll", ALL_VERSIONS),
+
+ # Older Relevant Knowledge DLLs cause us to crash (bug 904001).
+ DllBlocklistEntry("rlnx.dll", (1, 3, 334, 9)),
+ DllBlocklistEntry("pmnx.dll", (1, 3, 334, 9)),
+ DllBlocklistEntry("opnx.dll", (1, 3, 334, 9)),
+ DllBlocklistEntry("prnx.dll", (1, 3, 334, 9)),
+
+ # Older belgian ID card software causes Firefox to crash or hang on
+ # shutdown, bug 831285 and 918399.
+ DllBlocklistEntry("beid35cardlayer.dll", (3, 5, 6, 6968)),
+
+ # bug 925459, bitguard crashes
+ DllBlocklistEntry("bitguard.dll", ALL_VERSIONS),
+
+ # bug 812683 - crashes in Windows library when Asus Gamer OSD is installed
+ # Software is discontinued/unsupported
+ DllBlocklistEntry("atkdx11disp.dll", ALL_VERSIONS),
+
+ # Topcrash with Conduit SearchProtect, bug 944542
+ DllBlocklistEntry("spvc32.dll", ALL_VERSIONS),
+
+ # Topcrash with V-bates, bug 1002748 and bug 1023239
+ DllBlocklistEntry("libinject.dll", UNVERSIONED),
+ DllBlocklistEntry("libinject2.dll", PETimeStamp(0x537DDC93)),
+ DllBlocklistEntry("libredir2.dll", PETimeStamp(0x5385B7ED)),
+
+ # Crashes with RoboForm2Go written against old SDK, bug 988311/1196859
+ DllBlocklistEntry("rf-firefox-22.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rf-firefox-40.dll", ALL_VERSIONS),
+
+ # Crashes with DesktopTemperature, bug 1046382
+ DllBlocklistEntry("dtwxsvc.dll", PETimeStamp(0x53153234)),
+
+ # Startup crashes with Lenovo Onekey Theater, bug 1123778
+ DllBlocklistEntry("activedetect32.dll", UNVERSIONED),
+ DllBlocklistEntry("activedetect64.dll", UNVERSIONED),
+ DllBlocklistEntry("windowsapihookdll32.dll", UNVERSIONED),
+ DllBlocklistEntry("windowsapihookdll64.dll", UNVERSIONED),
+
+ # Flash crashes with RealNetworks RealDownloader, bug 1132663
+ DllBlocklistEntry("rndlnpshimswf.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rndlmainbrowserrecordplugin.dll", ALL_VERSIONS),
+
+ # Startup crashes with RealNetworks Browser Record Plugin, bug 1170141
+ DllBlocklistEntry("nprpffbrowserrecordext.dll", ALL_VERSIONS),
+ DllBlocklistEntry("nprndlffbrowserrecordext.dll", ALL_VERSIONS),
+
+ # Crashes with CyberLink YouCam, bug 1136968
+ DllBlocklistEntry("ycwebcamerasource.ax", (2, 0, 0, 1611)),
+
+ # Old version of WebcamMax crashes WebRTC, bug 1130061
+ DllBlocklistEntry("vwcsource.ax", (1, 5, 0, 0)),
+
+ # NetOp School, discontinued product, bug 763395
+ DllBlocklistEntry("nlsp.dll", (6, 23, 2012, 19)),
+
+ # Orbit Downloader, bug 1222819
+ DllBlocklistEntry("grabdll.dll", (2, 6, 1, 0)),
+ DllBlocklistEntry("grabkernel.dll", (1, 0, 0, 1)),
+
+ # ESET, bug 1229252
+ DllBlocklistEntry("eoppmonitor.dll", ALL_VERSIONS),
+
+ # SS2OSD, bug 1262348
+ DllBlocklistEntry("ss2osd.dll", ALL_VERSIONS),
+ DllBlocklistEntry("ss2devprops.dll", ALL_VERSIONS),
+
+ # NHASUSSTRIXOSD.DLL, bug 1269244
+ DllBlocklistEntry("nhasusstrixosd.dll", ALL_VERSIONS),
+ DllBlocklistEntry("nhasusstrixdevprops.dll", ALL_VERSIONS),
+
+ # Crashes with PremierOpinion/RelevantKnowledge, bug 1277846
+ DllBlocklistEntry("opls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("opls64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("pmls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("pmls64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("prls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("prls64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rlls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rlls64.dll", ALL_VERSIONS),
+
+ # Vorbis DirectShow filters, bug 1239690.
+ DllBlocklistEntry("vorbis.acm", (0, 0, 3, 6)),
+
+ # AhnLab Internet Security, bug 1311969
+ DllBlocklistEntry("nzbrcom.dll", ALL_VERSIONS),
+
+ # K7TotalSecurity, bug 1339083 and bug 1694489.
+ DllBlocklistEntry("k7pswsen.dll", (15, 2, 2, 102)),
+
+ # smci*.dll - goobzo crashware (bug 1339908),
+ DllBlocklistEntry("smci32.dll", ALL_VERSIONS),
+ DllBlocklistEntry("smci64.dll", ALL_VERSIONS),
+
+ # Crashes with Internet Download Manager, bug 1333486
+ DllBlocklistEntry("idmcchandler7.dll", ALL_VERSIONS),
+ DllBlocklistEntry("idmcchandler7_64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("idmcchandler5.dll", ALL_VERSIONS),
+ DllBlocklistEntry("idmcchandler5_64.dll", ALL_VERSIONS),
+
+ # Nahimic 2 breaks applicaton update (bug 1356637),
+ DllBlocklistEntry("nahimic2devprops.dll", (2, 5, 19, 0xffff)),
+ # Nahimic is causing crashes, bug 1233556
+ DllBlocklistEntry("nahimicmsiosd.dll", UNVERSIONED),
+ # Nahimic is causing crashes, bug 1360029
+ DllBlocklistEntry("nahimicvrdevprops.dll", UNVERSIONED),
+ DllBlocklistEntry("nahimic2osd.dll", (2, 5, 19, 0xffff)),
+ DllBlocklistEntry("nahimicmsidevprops.dll", UNVERSIONED),
+
+ # Bug 1268470 - crashes with Kaspersky Lab on Windows 8
+ DllBlocklistEntry("klsihk64.dll", (14, 0, 456, 0xffff),
+ BLOCK_WIN8_AND_OLDER),
+
+ # Bug 1579758, crashes with OpenSC nightly version 0.19.0.448 and lower
+ DllBlocklistEntry("onepin-opensc-pkcs11.dll", (0, 19, 0, 448)),
+
+ # Avecto Privilege Guard causes crashes, bug 1385542
+ DllBlocklistEntry("pghook.dll", ALL_VERSIONS),
+
+ # Old versions of G DATA BankGuard, bug 1421991
+ DllBlocklistEntry("banksafe64.dll", (1, 2, 15299, 65535)),
+
+ # Old versions of G DATA, bug 1043775
+ DllBlocklistEntry("gdkbfltdll64.dll", (1, 0, 14141, 240)),
+
+ # Dell Backup and Recovery tool causes crashes, bug 1433408
+ DllBlocklistEntry("dbroverlayiconnotbackuped.dll", (1, 8, 0, 9)),
+ DllBlocklistEntry("dbroverlayiconbackuped.dll", (1, 8, 0, 9)),
+
+ # NVIDIA nView Desktop Management causes crashes, bug 1465787
+ DllBlocklistEntry("nviewh64.dll", (6, 14, 10, 14847)),
+
+ # Ivanti Endpoint Security, bug 1553776
+ DllBlocklistEntry("sxwmon.dll", ALL_VERSIONS),
+ DllBlocklistEntry("sxwmon64.dll", ALL_VERSIONS),
+
+ # 360 Safeguard/360 Total Security causes a11y crashes, bug 1536227.
+ DllBlocklistEntry("safemon64.dll", ALL_VERSIONS),
+
+ # Old versions of Digital Guardian, bug 1318858, bug 1603974,
+ # and bug 1672367
+ RedirectToNoOpEntryPoint("dgapi.dll", (7, 5, 0xffff, 0xffff)),
+ RedirectToNoOpEntryPoint("dgapi64.dll", (7, 5, 0xffff, 0xffff)),
+
+ # Old versions of COMODO Internet Security, bug 1608048
+ DllBlocklistEntry("IseGuard32.dll", (1, 6, 13835, 184)),
+ DllBlocklistEntry("IseGuard64.dll", (1, 6, 13835, 184)),
+
+ # Old version of COMODO Firewall, bug 1407712 and bug 1624336
+ DllBlocklistEntry("guard64.dll", (8, 4, 0, 65535)),
+
+ # Old version of Panda Security, bug 1637984 and bug 1705125
+ DllBlocklistEntry("PavLspHook64.dll", (9, 2, 2, 1), BLOCK_WIN7_AND_OLDER),
+ DllBlocklistEntry("PavSHook64.dll", (9, 2, 2, 1), BLOCK_WIN7_AND_OLDER),
+
+ # Webroot SecureAnywhere causes crashes, bug 1700281
+ DllBlocklistEntry("WRDll.x64.dll", (1, 1, 0, 227)),
+ DllBlocklistEntry("WRDll.x86.dll", (1, 1, 0, 227)),
+
+ # Webroot SecureAnywhere causes deadlocks, bug 1752466
+ DllBlocklistEntry("WRusr.dll", (9, 0, 32, 49)),
+
+ # InfoWatch Device Monitor causes crashes, bug 1704276
+ DllBlocklistEntry("iwprn.dll", (6, 9, 11, 360)),
+ DllBlocklistEntry("iwprn_x86.dll", (6, 9, 11, 360)),
+
+ # Forcepoint DLLs causing crashes, bug 1767993
+ DllBlocklistEntry("qipcap.dll", (7, 7, 819, 1)),
+ DllBlocklistEntry("qipcap64.dll", (7, 7, 819, 1)),
+
+ # Cylance, bug 1756190 and bug 1799562
+ DllBlocklistEntry("CylanceMemDef64.dll", ALL_VERSIONS),
+]
+
+ALL_PROCESSES_TESTS += [
+ # DLLs used by TestDllBlocklist* gTests
+ DllBlocklistEntry("testdllblocklist_matchbyname.dll", ALL_VERSIONS),
+ DllBlocklistEntry("testdllblocklist_matchbyversion.dll", (5, 5, 5, 5)),
+ DllBlocklistEntry("testdllblocklist_allowbyversion.dll", (5, 5, 5, 5)),
+ RedirectToNoOpEntryPoint("testdllblocklist_noopentrypoint.dll",
+ (5, 5, 5, 5)),
+]
+
+BROWSER_PROCESS += [
+ # RealPlayer, bug 1418535, bug 1437417
+ # Versions before 18.1.11.0 cause severe performance problems.
+ A11yBlocklistEntry("dtvhooks.dll", (18, 1, 10, 0xffff)),
+ A11yBlocklistEntry("dtvhooks64.dll", (18, 1, 10, 0xffff)),
+
+ # SolidWorks Windows Explorer integration causes crashes, bug 1566109
+ # and bug 1468250
+ DllBlocklistEntry("Database.dll", ALL_VERSIONS),
+
+ # Hancom Office shell extension causes crashes when the file picker is
+ # opened. See bug 1581092.
+ DllBlocklistEntry("hncshellext64.dll", (1, 0, 0 ,3)),
+
+ # Cambridge Silicon Radio, bug 1634538
+ DllBlocklistEntry("BLEtokenCredentialProvider.dll", (2, 1, 63, 0)),
+
+ # FYunZip and PuddingZip, loaded as shell extension, cause crashes
+ # bug 1576728
+ DllBlocklistEntry("oly64.dll", (1, 1, 3, 19920)),
+ DllBlocklistEntry("oly.dll", (1, 1, 3, 19920)),
+ DllBlocklistEntry("pdzipmenu64.dll", (1, 4, 4, 20103)),
+ DllBlocklistEntry("pdzipmenu32.dll", (1, 4, 4, 20103)),
+
+ # McAfee Data Loss Prevention causes crashs with multiple signatures,
+ # bug 1634090, bug 1717676
+ DllBlocklistEntry("fcagff.dll", (11, 6, 300, 2)),
+ DllBlocklistEntry("fcagff64.dll", (11, 6, 300, 2)),
+
+ # AVerMedia's virtual camera module causes crashes (bug 1692908)
+ DllBlocklistEntry("avmvirtualsource.ax", (1, 0, 0, 3)),
+
+ # Avast (bug 1794064)
+ DllBlocklistEntry("aswJsFlt.dll", (18, 0, 1473, 0)),
+
+ # ASUS Web Storage (bug 1714940)
+ DllBlocklistEntry("asuswsshellext64.dll", (1, 1, 0, 27)),
+
+ # ExplorerPatcher (bug 1798707)
+ DllBlocklistEntry("explorerpatcher.amd64.dll", ALL_VERSIONS),
+]
+
+CHILD_PROCESSES += [
+ # Causes crashes in the GPU process with WebRender enabled, bug 1544435
+ DllBlocklistEntry("wbload.dll", ALL_VERSIONS),
+
+ # Causes crashes in the content process with win32k lockdown, bug 1766022
+ DllBlocklistEntry("videocapturer.dll", ALL_VERSIONS),
+ DllBlocklistEntry("videocapturerhk32.dll", ALL_VERSIONS),
+ DllBlocklistEntry("videocapturerhk64.dll", ALL_VERSIONS),
+
+ # Causes crashes in the content process with win32k lockdown, bug 1766029
+ # The crash seems to be in the *_m module which has been unloaded.
+ DllBlocklistEntry("safaweb.dll", (3, 0, 21, 3181)),
+ DllBlocklistEntry("safaweb_m.dll", (3, 0, 21, 3181)),
+ DllBlocklistEntry("safaweb64.dll", (3, 0, 21, 3181)),
+ DllBlocklistEntry("safaweb64_m.dll", (3, 0, 21, 3181)),
+
+ # Causes crashes in the content process with win32k lockdown, bug 1769309
+ DllBlocklistEntry("hmpalert.dll", (3, 8, 8, 889)),
+]
+
+SOCKET_PROCESSES += [
+ # Causes crashes in the socket process, bug 1760668
+ DllBlocklistEntry("ipseng64.dll", (17, 2, 6, 25)),
+ DllBlocklistEntry("ipseng32.dll", (17, 2, 6, 25)),
+
+ # Causes crashes in the socket process, bug 1807038
+ DllBlocklistEntry("kwsui64.dll", (2022, 2, 8, 125)),
+]
+
+SOCKET_PROCESSES_TESTS += [
+ # DLLs used by TestDllBlocklist* gTests
+ DllBlocklistEntry("testdllblocklist_socketprocessonly.dll", ALL_VERSIONS),
+]
+
+UTILITY_PROCESSES += [
+ # Generated dynamic code that we block
+ DllBlocklistEntry("cyinjct.dll", ALL_VERSIONS),
+]
+
+UTILITY_PROCESSES_TESTS += [
+ # DLLs used by TestDllBlocklist* gTests
+ DllBlocklistEntry("testdllblocklist_utilityprocessonly.dll", ALL_VERSIONS),
+]
+
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h
new file mode 100644
index 0000000000..c808e49ca1
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h
@@ -0,0 +1,88 @@
+/* -*- 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_WindowsDllBlocklistInfo_h
+#define mozilla_WindowsDllBlocklistInfo_h
+
+#include <stdint.h>
+
+namespace mozilla {
+
+template <typename StrType>
+struct DllBlockInfoT {
+ // The name of the DLL -- in LOWERCASE! It will be compared to
+ // a lowercase version of the DLL name only.
+ StrType mName;
+
+ // If mMaxVersion is ALL_VERSIONS, we'll block all versions of this
+ // dll. Otherwise, we'll block all versions less than or equal to
+ // the given version, as queried by GetFileVersionInfo and
+ // VS_FIXEDFILEINFO's dwFileVersionMS and dwFileVersionLS fields.
+ //
+ // Note that the version is usually 4 components, which is A.B.C.D
+ // encoded as 0x AAAA BBBB CCCC DDDD ULL (spaces added for clarity),
+ // but it's not required to be of that format.
+ uint64_t mMaxVersion;
+
+ // If the USE_TIMESTAMP flag is set, then we use the timestamp from
+ // the IMAGE_FILE_HEADER in lieu of a version number.
+ //
+ // If the UTILITY_PROCESSES_ONLY or SOCKET_PROCESSES_ONLY flags are
+ // set, the dll will only be blocked for these specific child
+ // processes. Note that these are a subset of CHILD_PROCESSES_ONLY.
+ enum Flags {
+ FLAGS_DEFAULT = 0,
+ BLOCK_WIN7_AND_OLDER = 1 << 0,
+ BLOCK_WIN8_AND_OLDER = 1 << 1,
+ USE_TIMESTAMP = 1 << 2,
+ CHILD_PROCESSES_ONLY = 1 << 3,
+ BROWSER_PROCESS_ONLY = 1 << 4,
+ REDIRECT_TO_NOOP_ENTRYPOINT = 1 << 5,
+ UTILITY_PROCESSES_ONLY = 1 << 6,
+ SOCKET_PROCESSES_ONLY = 1 << 7,
+ } mFlags;
+
+ bool IsVersionBlocked(const uint64_t aOther) const {
+ if (mMaxVersion == ALL_VERSIONS) {
+ return true;
+ }
+
+ return aOther <= mMaxVersion;
+ }
+
+ bool IsValidDynamicBlocklistEntry() const;
+
+ static const uint64_t ALL_VERSIONS = (uint64_t)-1LL;
+
+ // DLLs sometimes ship without a version number, particularly early
+ // releases. Blocking "version <= 0" has the effect of blocking unversioned
+ // DLLs (since the call to get version info fails), but not blocking
+ // any versioned instance.
+ static const uint64_t UNVERSIONED = 0ULL;
+};
+
+} // namespace mozilla
+
+// Convert the 4 (decimal) components of a DLL version number into a
+// single unsigned long long, as needed by the blocklist
+#if defined(_MSC_VER) && !defined(__clang__)
+
+// MSVC does not properly handle the constexpr MAKE_VERSION, so we use a macro
+// instead (ugh).
+# define MAKE_VERSION(a, b, c, d) \
+ ((a##ULL << 48) + (b##ULL << 32) + (c##ULL << 16) + d##ULL)
+
+#else
+
+static inline constexpr uint64_t MAKE_VERSION(uint16_t a, uint16_t b,
+ uint16_t c, uint16_t d) {
+ return static_cast<uint64_t>(a) << 48 | static_cast<uint64_t>(b) << 32 |
+ static_cast<uint64_t>(c) << 16 | static_cast<uint64_t>(d);
+}
+
+#endif
+
+#endif // mozilla_WindowsDllBlocklistInfo_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllServices.h b/toolkit/xre/dllservices/mozglue/WindowsDllServices.h
new file mode 100644
index 0000000000..17e85991e5
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllServices.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_WindowsDllServices_h
+#define mozilla_glue_WindowsDllServices_h
+
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Authenticode.h"
+#include "mozilla/LoaderAPIInterfaces.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/WindowsDllBlocklist.h"
+#include "mozilla/mozalloc.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "MainThreadUtils.h"
+# include "nsISupportsImpl.h"
+# include "nsString.h"
+# include "nsThreadUtils.h"
+# include "prthread.h"
+# include "mozilla/SchedulerGroup.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// For PCUNICODE_STRING
+#include <winternl.h>
+
+namespace mozilla {
+namespace glue {
+namespace detail {
+
+class DllServicesBase : public Authenticode {
+ public:
+ /**
+ * WARNING: This method is called from within an unsafe context that holds
+ * multiple locks inside the Windows loader. The only thing that
+ * this function should be used for is dispatching the event to our
+ * event loop so that it may be handled in a safe context.
+ */
+ virtual void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) = 0;
+
+ /**
+ * This function accepts module load events to be processed later for
+ * the untrusted modules telemetry ping.
+ *
+ * WARNING: This method is run from within the Windows loader and should
+ * only perform trivial, loader-friendly operations.
+ */
+ virtual void DispatchModuleLoadBacklogNotification(
+ ModuleLoadInfoVec&& aEvents) = 0;
+
+ void SetAuthenticodeImpl(Authenticode* aAuthenticode) {
+ mAuthenticode = aAuthenticode;
+ }
+
+ void SetWinLauncherServices(const nt::WinLauncherServices& aWinLauncher) {
+ mWinLauncher = aWinLauncher;
+ }
+
+ template <typename... Args>
+ LauncherVoidResultWithLineInfo InitDllBlocklistOOP(Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(mWinLauncher.mInitDllBlocklistOOP);
+ return mWinLauncher.mInitDllBlocklistOOP(std::forward<Args>(aArgs)...);
+ }
+
+ template <typename... Args>
+ void HandleLauncherError(Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(mWinLauncher.mHandleLauncherError);
+ mWinLauncher.mHandleLauncherError(std::forward<Args>(aArgs)...);
+ }
+
+ nt::SharedSection* GetSharedSection() { return mWinLauncher.mSharedSection; }
+
+ // In debug builds we override GetBinaryOrgName to add a Gecko-specific
+ // assertion. OTOH, we normally do not want people overriding this function,
+ // so we'll make it final in the release case, thus covering all bases.
+#if defined(DEBUG)
+ UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override
+#else
+ UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) final
+#endif // defined(DEBUG)
+ {
+ if (!mAuthenticode) {
+ return nullptr;
+ }
+
+ return mAuthenticode->GetBinaryOrgName(aFilePath, aFlags);
+ }
+
+ virtual void DisableFull() { DllBlocklist_SetFullDllServices(nullptr); }
+
+ DllServicesBase(const DllServicesBase&) = delete;
+ DllServicesBase(DllServicesBase&&) = delete;
+ DllServicesBase& operator=(const DllServicesBase&) = delete;
+ DllServicesBase& operator=(DllServicesBase&&) = delete;
+
+ protected:
+ DllServicesBase() : mAuthenticode(nullptr) {}
+
+ virtual ~DllServicesBase() = default;
+
+ void EnableFull() { DllBlocklist_SetFullDllServices(this); }
+ void EnableBasic() { DllBlocklist_SetBasicDllServices(this); }
+
+ private:
+ Authenticode* mAuthenticode;
+ nt::WinLauncherServices mWinLauncher;
+};
+
+} // namespace detail
+
+#if defined(MOZILLA_INTERNAL_API)
+
+struct EnhancedModuleLoadInfo final {
+ explicit EnhancedModuleLoadInfo(ModuleLoadInfo&& aModLoadInfo)
+ : mNtLoadInfo(std::move(aModLoadInfo)) {
+ // Only populate mThreadName when we're on the same thread as the event
+ if (mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) {
+ mThreadName = PR_GetThreadName(PR_GetCurrentThread());
+ }
+ MOZ_ASSERT(!mNtLoadInfo.mSectionName.IsEmpty());
+ }
+
+ EnhancedModuleLoadInfo(EnhancedModuleLoadInfo&&) = default;
+ EnhancedModuleLoadInfo& operator=(EnhancedModuleLoadInfo&&) = default;
+
+ EnhancedModuleLoadInfo(const EnhancedModuleLoadInfo&) = delete;
+ EnhancedModuleLoadInfo& operator=(const EnhancedModuleLoadInfo&) = delete;
+
+ nsDependentString GetSectionName() const {
+ return mNtLoadInfo.mSectionName.AsString();
+ }
+
+ using BacktraceType = decltype(ModuleLoadInfo::mBacktrace);
+
+ ModuleLoadInfo mNtLoadInfo;
+ nsCString mThreadName;
+};
+
+class DllServices : public detail::DllServicesBase {
+ public:
+ void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) final {
+ nsCOMPtr<nsIRunnable> runnable(
+ NewRunnableMethod<StoreCopyPassByRRef<EnhancedModuleLoadInfo>>(
+ "DllServices::NotifyDllLoad", this, &DllServices::NotifyDllLoad,
+ std::move(aModLoadInfo)));
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
+ }
+
+ void DispatchModuleLoadBacklogNotification(
+ ModuleLoadInfoVec&& aEvents) final {
+ nsCOMPtr<nsIRunnable> runnable(
+ NewRunnableMethod<StoreCopyPassByRRef<ModuleLoadInfoVec>>(
+ "DllServices::NotifyModuleLoadBacklog", this,
+ &DllServices::NotifyModuleLoadBacklog, std::move(aEvents)));
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
+ }
+
+# if defined(DEBUG)
+ UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) final {
+ // This function may perform disk I/O, so we should never call it on the
+ // main thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+ return detail::DllServicesBase::GetBinaryOrgName(aFilePath, aFlags);
+ }
+# endif // defined(DEBUG)
+
+ NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(DllServices)
+
+ protected:
+ DllServices() = default;
+ ~DllServices() = default;
+
+ virtual void NotifyDllLoad(EnhancedModuleLoadInfo&& aModLoadInfo) = 0;
+ virtual void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) = 0;
+};
+
+#else
+
+class BasicDllServices final : public detail::DllServicesBase {
+ public:
+ BasicDllServices() { EnableBasic(); }
+
+ ~BasicDllServices() = default;
+
+ // Not useful in this class, so provide a default implementation
+ virtual void DispatchDllLoadNotification(
+ ModuleLoadInfo&& aModLoadInfo) override {}
+
+ virtual void DispatchModuleLoadBacklogNotification(
+ ModuleLoadInfoVec&& aEvents) override {}
+};
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_WindowsDllServices_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp
new file mode 100644
index 0000000000..bda309d499
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp
@@ -0,0 +1,91 @@
+/* -*- 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/. */
+
+#include "WindowsFallbackLoaderAPI.h"
+
+namespace mozilla {
+
+ModuleLoadInfo FallbackLoaderAPI::ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) {
+ ModuleLoadInfo loadInfo(aRequestedDllName);
+
+ MOZ_ASSERT(mLoaderObserver);
+ if (mLoaderObserver) {
+ mLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName);
+ }
+
+ return loadInfo;
+}
+
+bool FallbackLoaderAPI::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) {
+ MOZ_ASSERT(mLoaderObserver);
+ if (!mLoaderObserver) {
+ return false;
+ }
+
+ return mLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle);
+}
+
+void FallbackLoaderAPI::NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) {
+ aModuleLoadInfo.SetEndLoadTimeStamp();
+
+ if (NT_SUCCESS(aLoadNtStatus)) {
+ aModuleLoadInfo.CaptureBacktrace();
+ }
+
+ MOZ_ASSERT(mLoaderObserver);
+ if (mLoaderObserver) {
+ mLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus,
+ std::move(aModuleLoadInfo));
+ }
+}
+
+nt::AllocatedUnicodeString FallbackLoaderAPI::GetSectionName(
+ void* aSectionAddr) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::NtQueryVirtualMemory)>
+ pNtQueryVirtualMemory(L"ntdll.dll", "NtQueryVirtualMemory");
+ MOZ_ASSERT(pNtQueryVirtualMemory);
+
+ if (!pNtQueryVirtualMemory) {
+ return nt::AllocatedUnicodeString();
+ }
+
+ nt::MemorySectionNameBuf buf;
+ NTSTATUS ntStatus =
+ pNtQueryVirtualMemory(::GetCurrentProcess(), aSectionAddr,
+ MemorySectionName, &buf, sizeof(buf), nullptr);
+ if (!NT_SUCCESS(ntStatus)) {
+ return nt::AllocatedUnicodeString();
+ }
+
+ return nt::AllocatedUnicodeString(&buf.mSectionFileName);
+}
+
+nt::LoaderAPI::InitDllBlocklistOOPFnPtr
+FallbackLoaderAPI::GetDllBlocklistInitFn() {
+ MOZ_ASSERT_UNREACHABLE("This should not be called so soon!");
+ return nullptr;
+}
+
+nt::LoaderAPI::HandleLauncherErrorFnPtr
+FallbackLoaderAPI::GetHandleLauncherErrorFn() {
+ MOZ_ASSERT_UNREACHABLE("This should not be called so soon!");
+ return nullptr;
+}
+
+nt::SharedSection* FallbackLoaderAPI::GetSharedSection() {
+ MOZ_ASSERT_UNREACHABLE("This should not be called so soon!");
+ return nullptr;
+}
+
+void FallbackLoaderAPI::SetObserver(nt::LoaderObserver* aLoaderObserver) {
+ mLoaderObserver = aLoaderObserver;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h
new file mode 100644
index 0000000000..8d6fcb2f0c
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h
@@ -0,0 +1,39 @@
+/* -*- 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_WindowsFallbackLoaderAPI_h
+#define mozilla_WindowsFallbackLoaderAPI_h
+
+#include "mozilla/Attributes.h"
+#include "NtLoaderAPI.h"
+
+namespace mozilla {
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FallbackLoaderAPI final
+ : public nt::LoaderAPI {
+ public:
+ constexpr FallbackLoaderAPI() : mLoaderObserver(nullptr) {}
+
+ ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) final;
+ bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) final;
+ void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) final;
+ nt::AllocatedUnicodeString GetSectionName(void* aSectionAddr) final;
+ nt::LoaderAPI::InitDllBlocklistOOPFnPtr GetDllBlocklistInitFn() final;
+ nt::LoaderAPI::HandleLauncherErrorFnPtr GetHandleLauncherErrorFn() final;
+ nt::SharedSection* GetSharedSection() final;
+
+ void SetObserver(nt::LoaderObserver* aLoaderObserver);
+
+ private:
+ nt::LoaderObserver* mLoaderObserver;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsFallbackLoaderAPI_h
diff --git a/toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py b/toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py
new file mode 100644
index 0000000000..056db4d995
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py
@@ -0,0 +1,750 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+import os
+from copy import deepcopy
+from struct import unpack
+from uuid import UUID
+
+from six import iteritems
+
+H_HEADER = """/* -*- 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 file was auto-generated from {0} by gen_dll_blocklist_data.py. */
+
+#ifndef mozilla_{1}_h
+#define mozilla_{1}_h
+
+"""
+
+H_FOOTER = """#endif // mozilla_{1}_h
+
+"""
+
+H_DEFS_BEGIN_DEFAULT = """#include "mozilla/WindowsDllBlocklistCommon.h"
+
+DLL_BLOCKLIST_DEFINITIONS_BEGIN
+
+"""
+
+H_DEFS_END_DEFAULT = """
+DLL_BLOCKLIST_DEFINITIONS_END
+
+"""
+
+H_BEGIN_LSP = """#include <guiddef.h>
+
+static const GUID gLayerGuids[] = {
+
+"""
+
+H_END_LSP = """
+};
+
+"""
+
+H_BEGIN_A11Y = """#include "mozilla/WindowsDllBlocklistCommon.h"
+
+DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gBlockedInprocDlls)
+
+"""
+
+# These flag names should match the ones defined in WindowsDllBlocklistCommon.h
+FLAGS_DEFAULT = "FLAGS_DEFAULT"
+BLOCK_WIN8_AND_OLDER = "BLOCK_WIN8_AND_OLDER"
+BLOCK_WIN7_AND_OLDER = "BLOCK_WIN7_AND_OLDER"
+USE_TIMESTAMP = "USE_TIMESTAMP"
+CHILD_PROCESSES_ONLY = "CHILD_PROCESSES_ONLY"
+BROWSER_PROCESS_ONLY = "BROWSER_PROCESS_ONLY"
+SUBSTITUTE_LSP_PASSTHROUGH = "SUBSTITUTE_LSP_PASSTHROUGH"
+REDIRECT_TO_NOOP_ENTRYPOINT = "REDIRECT_TO_NOOP_ENTRYPOINT"
+UTILITY_PROCESSES_ONLY = "UTILITY_PROCESSES_ONLY"
+SOCKET_PROCESSES_ONLY = "SOCKET_PROCESSES_ONLY"
+
+# Only these flags are available in the input script
+INPUT_ONLY_FLAGS = {
+ BLOCK_WIN8_AND_OLDER,
+ BLOCK_WIN7_AND_OLDER,
+}
+
+
+def FILTER_ALLOW_ALL(entry):
+ # A11y entries are special, so we always exclude those by default
+ # (so it's not really allowing 'all', but it is simpler to reason about by
+ # pretending that it is allowing all.)
+ return not isinstance(entry, A11yBlocklistEntry)
+
+
+def FILTER_DENY_ALL(entry):
+ return False
+
+
+def FILTER_ALLOW_ONLY_A11Y(entry):
+ return isinstance(entry, A11yBlocklistEntry)
+
+
+def FILTER_ALLOW_ONLY_LSP(entry):
+ return isinstance(entry, LspBlocklistEntry)
+
+
+def FILTER_TESTS_ONLY(entry):
+ return not isinstance(entry, A11yBlocklistEntry) and entry.is_test()
+
+
+def derive_test_key(key):
+ return key + "_TESTS"
+
+
+ALL_DEFINITION_LISTS = (
+ "ALL_PROCESSES",
+ "BROWSER_PROCESS",
+ "CHILD_PROCESSES",
+ "UTILITY_PROCESSES",
+ "SOCKET_PROCESSES",
+)
+
+
+class BlocklistDescriptor(object):
+ """This class encapsulates every file that is output from this script.
+ Each instance has a name, an "input specification", and optional "flag
+ specification" and "output specification" entries.
+ """
+
+ DEFAULT_OUTSPEC = {
+ "mode": "",
+ "filter": FILTER_ALLOW_ALL,
+ "begin": H_DEFS_BEGIN_DEFAULT,
+ "end": H_DEFS_END_DEFAULT,
+ }
+
+ FILE_NAME_TPL = "WindowsDllBlocklist{0}Defs"
+
+ OutputDir = None
+ ExistingFd = None
+ ExistingFdLeafName = None
+
+ def __init__(self, name, inspec, **kwargs):
+ """Positional arguments:
+
+ name -- String containing the name of the output list.
+
+ inspec -- One or more members of ALL_DEFINITION_LISTS. The input used
+ for this blocklist file is the union of all lists specified by this
+ variable.
+
+ Keyword arguments:
+
+ outspec -- an optional list of dicts that specify how the lists output
+ will be written out to a file. Each dict may contain the following keys:
+
+ 'mode' -- a string that specifies a mode that is used when writing
+ out list entries to this particular output. This is passed in as the
+ mode argument to DllBlocklistEntry's write method.
+
+ 'filter' -- a function that, given a blocklist entry, decides
+ whether or not that entry shall be included in this output file.
+
+ 'begin' -- a string that is written to the output file after writing
+ the file's header, but prior to writing out any blocklist entries.
+
+ 'end' -- a string that is written to the output file after writing
+ out any blocklist entries but before the file's footer.
+
+ Any unspecified keys will be assigned default values.
+
+ flagspec -- an optional dict whose keys consist of one or more of the
+ list names from inspec. Each corresponding value is a set of flags that
+ should be applied to each entry from that list. For example, the
+ flagspec:
+
+ {'CHILD_PROCESSES': {CHILD_PROCESSES_ONLY}}
+
+ causes any blocklist entries from the CHILD_PROCESSES list to be output
+ with the CHILD_PROCESSES_ONLY flag set.
+
+ """
+
+ self._name = name
+
+ # inspec's elements must all come from ALL_DEFINITION_LISTS
+ assert not (set(inspec).difference(set(ALL_DEFINITION_LISTS)))
+
+ # Internally to the descriptor, we store input specifications as a dict
+ # that maps each input blocklist name to the set of flags to be applied
+ # to each entry in that blocklist.
+ self._inspec = {blocklist: set() for blocklist in inspec}
+
+ self._outspecs = kwargs.get("outspec", BlocklistDescriptor.DEFAULT_OUTSPEC)
+ if isinstance(self._outspecs, dict):
+ # _outspecs should always be stored as a list of dicts
+ self._outspecs = [self._outspecs]
+
+ flagspecs = kwargs.get("flagspec", dict())
+ # flagspec's keys must all come from ALL_DEFINITION_LISTS
+ assert not (set(flagspecs.keys()).difference(set(self._inspec.keys())))
+
+ # Merge the flags from flagspec into _inspec's sets
+ for blocklist, flagspec in iteritems(flagspecs):
+ spec = self._inspec[blocklist]
+ if not isinstance(spec, set):
+ raise TypeError("Flag spec for list %s must be a set!" % blocklist)
+ spec.update(flagspec)
+
+ @staticmethod
+ def set_output_fd(fd):
+ """The build system has already provided an open file descriptor for
+ one of our outputs. We save that here so that we may use that fd once
+ we're ready to process that fd's file. We also obtain the output dir for
+ use as the base directory for the other output files that we open.
+ """
+ BlocklistDescriptor.ExistingFd = fd
+ (
+ BlocklistDescriptor.OutputDir,
+ BlocklistDescriptor.ExistingFdLeafName,
+ ) = os.path.split(fd.name)
+
+ @staticmethod
+ def ensure_no_dupes(defs):
+ """Ensure that defs does not contain any dupes. We raise a ValueError
+ because this is a bug in the input and requires developer intervention.
+ """
+ seen = set()
+ for e in defs:
+ name = e.get_name()
+ if name not in seen:
+ seen.add(name)
+ else:
+ raise ValueError("Duplicate entry found: %s" % name)
+
+ @staticmethod
+ def get_test_entries(exec_env, blocklist):
+ """Obtains all test entries for the corresponding blocklist, and also
+ ensures that each entry has its test flag set.
+
+ Positional arguments:
+
+ exec_env -- dict containing the globals that were passed to exec to
+ when the input script was run.
+
+ blocklist -- The name of the blocklist to obtain tests from. This
+ should be one of the members of ALL_DEFINITION_LISTS
+ """
+ test_key = derive_test_key(blocklist)
+
+ def set_is_test(elem):
+ elem.set_is_test()
+ return elem
+
+ return map(set_is_test, exec_env[test_key])
+
+ def gen_list(self, exec_env, filter_func):
+ """Generates a sorted list of blocklist entries from the input script,
+ filtered via filter_func.
+
+ Positional arguments:
+
+ exec_env -- dict containing the globals that were passed to exec to
+ when the input script was run. This function expects exec_env to
+ contain lists of blocklist entries, keyed using one of the members of
+ ALL_DEFINITION_LISTS.
+
+ filter_func -- a filter function that evaluates each blocklist entry
+ to determine whether or not it should be included in the results.
+ """
+
+ # This list contains all the entries across all blocklists that we
+ # potentially want to process
+ unified_list = []
+
+ # For each blocklist specified in the _inspec, we query the globals
+ # for their entries, add any flags, and then add them to the
+ # unified_list.
+ for blocklist, listflags in iteritems(self._inspec):
+
+ def add_list_flags(elem):
+ # We deep copy so that flags set for an entry in one blocklist
+ # do not interfere with flags set for an entry in a different
+ # list.
+ result = deepcopy(elem)
+ result.add_flags(listflags)
+ return result
+
+ # We add list flags *before* filtering because the filters might
+ # want to access flags as part of their operation.
+ unified_list.extend(map(add_list_flags, exec_env[blocklist]))
+
+ # We also add any test entries for the lists specified by _inspec
+ unified_list.extend(
+ map(add_list_flags, self.get_test_entries(exec_env, blocklist))
+ )
+
+ # There should be no dupes in the input. If there are, raise an error.
+ self.ensure_no_dupes(unified_list)
+
+ # Now we filter out any unwanted list entries
+ filtered_list = filter(filter_func, unified_list)
+
+ # Sort the list on entry name so that the blocklist code may use
+ # binary search if it so chooses.
+ return sorted(filtered_list, key=lambda e: e.get_name())
+
+ @staticmethod
+ def get_fd(outspec_leaf_name):
+ """If BlocklistDescriptor.ExistingFd corresponds to outspec_leaf_name,
+ then we return that. Otherwise, we construct a new absolute path to
+ outspec_leaf_name and open a new file descriptor for writing.
+ """
+ if (
+ not BlocklistDescriptor.ExistingFd
+ or BlocklistDescriptor.ExistingFdLeafName != outspec_leaf_name
+ ):
+ new_name = os.path.join(BlocklistDescriptor.OutputDir, outspec_leaf_name)
+ return open(new_name, "w")
+
+ fd = BlocklistDescriptor.ExistingFd
+ BlocklistDescriptor.ExistingFd = None
+ return fd
+
+ def write(self, src_file, exec_env):
+ """Write out all output files that are specified by this descriptor.
+
+ Positional arguments:
+
+ src_file -- name of the input file from which the lists were generated.
+
+ exec_env -- dictionary containing the lists that were parsed from the
+ input file when it was executed.
+ """
+
+ for outspec in self._outspecs:
+ # Use DEFAULT_OUTSPEC to supply defaults for any unused outspec keys
+ effective_outspec = BlocklistDescriptor.DEFAULT_OUTSPEC.copy()
+ effective_outspec.update(outspec)
+
+ entries = self.gen_list(exec_env, effective_outspec["filter"])
+ if not entries:
+ continue
+
+ mode = effective_outspec["mode"]
+
+ # Since each output descriptor may generate output across multiple
+ # modes, each list is uniquified via the concatenation of our name
+ # and the mode.
+ listname = self._name + mode
+ leafname_no_ext = BlocklistDescriptor.FILE_NAME_TPL.format(listname)
+ leafname = leafname_no_ext + ".h"
+
+ with self.get_fd(leafname) as output:
+ print(H_HEADER.format(src_file, leafname_no_ext), file=output, end="")
+ print(effective_outspec["begin"], file=output, end="")
+
+ for e in entries:
+ e.write(output, mode)
+
+ print(effective_outspec["end"], file=output, end="")
+ print(H_FOOTER.format(src_file, leafname_no_ext), file=output, end="")
+
+
+A11Y_OUTPUT_SPEC = {
+ "filter": FILTER_ALLOW_ONLY_A11Y,
+ "begin": H_BEGIN_A11Y,
+}
+
+LSP_MODE_GUID = "Guid"
+
+LSP_OUTPUT_SPEC = [
+ {
+ "mode": LSP_MODE_GUID,
+ "filter": FILTER_ALLOW_ONLY_LSP,
+ "begin": H_BEGIN_LSP,
+ "end": H_END_LSP,
+ },
+]
+
+GENERATED_BLOCKLIST_FILES = [
+ BlocklistDescriptor("A11y", ["BROWSER_PROCESS"], outspec=A11Y_OUTPUT_SPEC),
+ BlocklistDescriptor(
+ "Launcher",
+ ALL_DEFINITION_LISTS,
+ flagspec={
+ "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY},
+ "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY},
+ "UTILITY_PROCESSES": {UTILITY_PROCESSES_ONLY},
+ "SOCKET_PROCESSES": {SOCKET_PROCESSES_ONLY},
+ },
+ ),
+ BlocklistDescriptor(
+ "Legacy",
+ ALL_DEFINITION_LISTS,
+ flagspec={
+ "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY},
+ "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY},
+ "UTILITY_PROCESSES": {UTILITY_PROCESSES_ONLY},
+ "SOCKET_PROCESSES": {SOCKET_PROCESSES_ONLY},
+ },
+ ),
+ # Roughed-in for the moment; we'll enable this in bug 1238735
+ # BlocklistDescriptor('LSP', ALL_DEFINITION_LISTS, outspec=LSP_OUTPUT_SPEC),
+ BlocklistDescriptor(
+ "Test", ALL_DEFINITION_LISTS, outspec={"filter": FILTER_TESTS_ONLY}
+ ),
+]
+
+
+class PETimeStamp(object):
+ def __init__(self, ts):
+ max_timestamp = (2 ** 32) - 1
+ if ts < 0 or ts > max_timestamp:
+ raise ValueError("Invalid timestamp value")
+ self._value = ts
+
+ def __str__(self):
+ return "0x%08XU" % self._value
+
+
+class Version(object):
+ """Encapsulates a DLL version."""
+
+ ALL_VERSIONS = 0xFFFFFFFFFFFFFFFF
+ UNVERSIONED = 0
+
+ def __init__(self, *args):
+ """There are multiple ways to construct a Version:
+
+ As a tuple containing four elements (recommended);
+ As four integral arguments;
+ As a PETimeStamp;
+ As a long integer.
+
+ The tuple and list formats require the value of each element to be
+ between 0 and 0xFFFF, inclusive.
+ """
+
+ self._ver = Version.UNVERSIONED
+
+ if len(args) == 1:
+ if isinstance(args[0], tuple):
+ self.validate_iterable(args[0])
+
+ self._ver = "MAKE_VERSION%r" % (args[0],)
+ elif isinstance(args[0], PETimeStamp):
+ self._ver = args[0]
+ else:
+ self._ver = int(args[0])
+ elif len(args) == 4:
+ self.validate_iterable(args)
+
+ self._ver = "MAKE_VERSION%r" % (tuple(args),)
+ else:
+ raise ValueError("Bad arguments to Version constructor")
+
+ def validate_iterable(self, arg):
+ if len(arg) != 4:
+ raise ValueError("Versions must be a 4-tuple")
+
+ for component in arg:
+ if not isinstance(component, int) or component < 0 or component > 0xFFFF:
+ raise ValueError(
+ "Each version component must be a 16-bit " "unsigned integer"
+ )
+
+ def build_long(self, args):
+ self.validate_iterable(args)
+ return (
+ (int(args[0]) << 48)
+ | (int(args[1]) << 32)
+ | (int(args[2]) << 16)
+ | int(args[3])
+ )
+
+ def is_timestamp(self):
+ return isinstance(self._ver, PETimeStamp)
+
+ def __str__(self):
+ if isinstance(self._ver, int):
+ if self._ver == Version.ALL_VERSIONS:
+ return "DllBlockInfo::ALL_VERSIONS"
+
+ if self._ver == Version.UNVERSIONED:
+ return "DllBlockInfo::UNVERSIONED"
+
+ return "0x%016XULL" % self._ver
+
+ return str(self._ver)
+
+
+class DllBlocklistEntry(object):
+ TEST_CONDITION = "defined(ENABLE_TESTS)"
+
+ def __init__(self, name, ver, flags=(), **kwargs):
+ """Positional arguments:
+
+ name -- The leaf name of the DLL.
+
+ ver -- The maximum version to be blocked. NB: The comparison used by the
+ blocklist is <=, so you should specify the last bad version, as opposed
+ to the first good version.
+
+ flags -- iterable containing the flags that should be applicable to
+ this blocklist entry.
+
+ Keyword arguments:
+
+ condition -- a string containing a C++ preprocessor expression. This
+ condition is written as a condition for an #if/#endif block that is
+ generated around the entry during output.
+ """
+
+ self.check_ascii(name)
+ self._name = name.lower()
+ self._ver = Version(ver)
+
+ self._flags = set()
+ self.add_flags(flags)
+ if self._ver.is_timestamp():
+ self._flags.add(USE_TIMESTAMP)
+
+ self._cond = kwargs.get("condition", set())
+ if isinstance(self._cond, str):
+ self._cond = {self._cond}
+
+ @staticmethod
+ def check_ascii(name):
+ try:
+ # Supported in Python 3.7
+ if not name.isascii():
+ raise ValueError('DLL name "%s" must be ASCII!' % name)
+ return
+ except AttributeError:
+ pass
+
+ try:
+ name.encode("ascii")
+ except UnicodeEncodeError:
+ raise ValueError('DLL name "%s" must be ASCII!' % name)
+
+ def get_name(self):
+ return self._name
+
+ def set_condition(self, cond):
+ self._cond.add(cond)
+
+ def get_condition(self):
+ if len(self._cond) == 1:
+ fmt = "{0}"
+ else:
+ fmt = "({0})"
+
+ return " && ".join([fmt.format(c) for c in self._cond])
+
+ def set_is_test(self):
+ self.set_condition(DllBlocklistEntry.TEST_CONDITION)
+
+ def is_test(self):
+ return self._cond and DllBlocklistEntry.TEST_CONDITION in self._cond
+
+ def add_flags(self, new_flags):
+ if isinstance(new_flags, str):
+ self._flags.add(new_flags)
+ else:
+ self._flags.update(new_flags)
+
+ @staticmethod
+ def get_flag_string(flag):
+ return "DllBlockInfo::" + flag
+
+ def get_flags_list(self):
+ return self._flags
+
+ def write(self, output, mode):
+ if self._cond:
+ print("#if %s" % self.get_condition(), file=output)
+
+ flags_str = ""
+
+ flags = self.get_flags_list()
+ if flags:
+ flags_str = ", " + " | ".join(map(self.get_flag_string, flags))
+
+ entry_str = ' DLL_BLOCKLIST_ENTRY("%s", %s%s)' % (
+ self._name,
+ str(self._ver),
+ flags_str,
+ )
+ print(entry_str, file=output)
+
+ if self._cond:
+ print("#endif // %s" % self.get_condition(), file=output)
+
+
+class A11yBlocklistEntry(DllBlocklistEntry):
+ """Represents a blocklist entry for injected a11y DLLs. This class does
+ not need to do anything special compared to a DllBlocklistEntry; we just
+ use this type to distinguish a11y entries from "regular" blocklist entries.
+ """
+
+ def __init__(self, name, ver, flags=(), **kwargs):
+ """These arguments are identical to DllBlocklistEntry.__init__"""
+
+ super(A11yBlocklistEntry, self).__init__(name, ver, flags, **kwargs)
+
+
+class RedirectToNoOpEntryPoint(DllBlocklistEntry):
+ """Represents a blocklist entry to hook the entrypoint into a function
+ just returning TRUE to keep a module alive and harmless.
+ This entry is intended to block a DLL which is injected by IAT patching
+ which is planted by a kernel callback routine for LoadImage because
+ blocking such a DLL makes a process fail to launch.
+ """
+
+ def __init__(self, name, ver, flags=(), **kwargs):
+ """These arguments are identical to DllBlocklistEntry.__init__"""
+
+ super(RedirectToNoOpEntryPoint, self).__init__(name, ver, flags, **kwargs)
+
+ def get_flags_list(self):
+ flags = super(RedirectToNoOpEntryPoint, self).get_flags_list()
+ # RedirectToNoOpEntryPoint items always include the following flag
+ flags.add(REDIRECT_TO_NOOP_ENTRYPOINT)
+ return flags
+
+
+class LspBlocklistEntry(DllBlocklistEntry):
+ """Represents a blocklist entry for a WinSock Layered Service Provider (LSP)."""
+
+ GUID_UNPACK_FMT_LE = "<IHHBBBBBBBB"
+ Guids = dict()
+
+ def __init__(self, name, ver, guids, flags=(), **kwargs):
+ """Positional arguments:
+
+ name -- The leaf name of the DLL.
+
+ ver -- The maximum version to be blocked. NB: The comparison used by the
+ blocklist is <=, so you should specify the last bad version, as opposed
+ to the first good version.
+
+ guids -- Either a string or list of strings containing the GUIDs that
+ uniquely identify the LSP. These GUIDs are generated by the developer of
+ the LSP and are registered with WinSock alongside the LSP. We record
+ this GUID as part of the "Winsock_LSP" annotation in our crash reports.
+
+ flags -- iterable containing the flags that should be applicable to
+ this blocklist entry.
+
+ Keyword arguments:
+
+ condition -- a string containing a C++ preprocessor expression. This
+ condition is written as a condition for an #if/#endif block that is
+ generated around the entry during output.
+ """
+
+ super(LspBlocklistEntry, self).__init__(name, ver, flags, **kwargs)
+ if not guids:
+ raise ValueError("Missing GUID(s)!")
+
+ if isinstance(guids, str):
+ self.insert(UUID(guids), name)
+ else:
+ for guid in guids:
+ self.insert(UUID(guid), name)
+
+ def insert(self, guid, name):
+ # Some explanation here: Multiple DLLs (and thus multiple instances of
+ # LspBlocklistEntry) may share the same GUIDs. To ensure that we do not
+ # have any duplicates, we store each GUID in a class variable, Guids.
+ # We include the original DLL name from the blocklist definitions so
+ # that we may output a comment above each GUID indicating which entries
+ # correspond to which GUID.
+ LspBlocklistEntry.Guids.setdefault(guid, []).append(name)
+
+ def get_flags_list(self):
+ flags = super(LspBlocklistEntry, self).get_flags_list()
+ # LSP entries always include the following flag
+ flags.add(SUBSTITUTE_LSP_PASSTHROUGH)
+ return flags
+
+ @staticmethod
+ def as_c_struct(guid, names):
+ parts = unpack(LspBlocklistEntry.GUID_UNPACK_FMT_LE, guid.bytes_le)
+ str_guid = (
+ " // %r\n // {%s}\n { 0x%08x, 0x%04x, 0x%04x, "
+ "{ 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x }"
+ " }"
+ % (
+ names,
+ str(guid),
+ parts[0],
+ parts[1],
+ parts[2],
+ parts[3],
+ parts[4],
+ parts[5],
+ parts[6],
+ parts[7],
+ parts[8],
+ parts[9],
+ parts[10],
+ )
+ )
+ return str_guid
+
+ def write(self, output, mode):
+ if mode != LSP_MODE_GUID:
+ super(LspBlocklistEntry, self).write(output, mode)
+ return
+
+ # We dump the entire contents of Guids on the first call, and then
+ # clear it. Remaining invocations of this method are no-ops.
+ if LspBlocklistEntry.Guids:
+ result = ",\n".join(
+ [
+ self.as_c_struct(guid, names)
+ for guid, names in iteritems(LspBlocklistEntry.Guids)
+ ]
+ )
+ print(result, file=output)
+ LspBlocklistEntry.Guids.clear()
+
+
+def exec_script_file(script_name, globals):
+ with open(script_name) as script:
+ exec(compile(script.read(), script_name, "exec"), globals)
+
+
+def gen_blocklists(first_fd, defs_filename):
+
+ BlocklistDescriptor.set_output_fd(first_fd)
+
+ # exec_env defines the global variables that will be present in the
+ # execution environment when defs_filename is run by exec.
+ exec_env = {
+ # Add the blocklist entry types
+ "A11yBlocklistEntry": A11yBlocklistEntry,
+ "DllBlocklistEntry": DllBlocklistEntry,
+ "LspBlocklistEntry": LspBlocklistEntry,
+ "RedirectToNoOpEntryPoint": RedirectToNoOpEntryPoint,
+ # Add the special version types
+ "ALL_VERSIONS": Version.ALL_VERSIONS,
+ "UNVERSIONED": Version.UNVERSIONED,
+ "PETimeStamp": PETimeStamp,
+ }
+
+ # Import definition lists into exec_env
+ for defname in ALL_DEFINITION_LISTS:
+ exec_env[defname] = []
+ # For each defname, add a special list for test-only entries
+ exec_env[derive_test_key(defname)] = []
+
+ # Import flags into exec_env
+ exec_env.update({flag: flag for flag in INPUT_ONLY_FLAGS})
+
+ # Now execute the input script with exec_env providing the globals
+ exec_script_file(defs_filename, exec_env)
+
+ # Tell the output descriptors to write out the output files.
+ for desc in GENERATED_BLOCKLIST_FILES:
+ desc.write(defs_filename, exec_env)
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp
new file mode 100644
index 0000000000..81d8e6d09b
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp
@@ -0,0 +1,89 @@
+/* -*- 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/. */
+
+#include "Arm64.h"
+
+#include "mozilla/ResultVariant.h"
+
+namespace mozilla {
+namespace interceptor {
+namespace arm64 {
+
+struct PCRelativeLoadTest {
+ // Bitmask to be ANDed with the instruction to isolate the bits that this
+ // instance is interested in
+ uint32_t mTestMask;
+ // The desired bits that we want to see after masking
+ uint32_t mMatchBits;
+ // If we match, mDecodeFn provide the code to decode the instruction.
+ LoadOrBranch (*mDecodeFn)(const uintptr_t aPC, const uint32_t aInst);
+};
+
+static LoadOrBranch ADRPDecode(const uintptr_t aPC, const uint32_t aInst) {
+ // Keep in mind that on Windows aarch64, uint32_t is little-endian
+ const uint32_t kMaskDataProcImmPcRelativeImmLo = 0x60000000;
+ const uint32_t kMaskDataProcImmPcRelativeImmHi = 0x00FFFFE0;
+
+ uintptr_t base = aPC;
+ intptr_t offset = SignExtend<intptr_t>(
+ ((aInst & kMaskDataProcImmPcRelativeImmHi) >> 3) |
+ ((aInst & kMaskDataProcImmPcRelativeImmLo) >> 29),
+ 21);
+
+ base &= ~0xFFFULL;
+ offset <<= 12;
+
+ uint8_t reg = aInst & 0x1F;
+
+ return LoadOrBranch(base + offset, reg);
+}
+
+MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC,
+ const uint32_t aInst) {
+ int32_t offset = SignExtend<int32_t>(aInst & 0x03FFFFFFU, 26);
+ return LoadOrBranch(aPC + offset);
+}
+
+// Order is important here; more specific encoding tests must be placed before
+// less specific encoding tests.
+static const PCRelativeLoadTest gPCRelTests[] = {
+ {0x9FC00000, 0x10000000, nullptr}, // ADR
+ {0x9FC00000, 0x90000000, &ADRPDecode}, // ADRP
+ {0xFF000000, 0x58000000, nullptr}, // LDR (literal) 64-bit GPR
+ {0x3B000000, 0x18000000, nullptr}, // LDR (literal) (remaining forms)
+ {0x7C000000, 0x14000000, nullptr}, // B (unconditional immediate)
+ {0xFE000000, 0x54000000, nullptr}, // B.Cond
+ {0x7E000000, 0x34000000, nullptr}, // Compare and branch (imm)
+ {0x7E000000, 0x36000000, nullptr}, // Test and branch (imm)
+ {0xFE000000, 0xD6000000, nullptr} // Unconditional branch (reg)
+};
+
+/**
+ * In this function we interate through each entry in |gPCRelTests|, AND
+ * |aInst| with |test.mTestMask| to isolate the bits that we're interested in,
+ * then compare that result against |test.mMatchBits|. If we have a match,
+ * then that particular entry is applicable to |aInst|. If |test.mDecodeFn| is
+ * present, then we call it to decode the instruction. If it is not present,
+ * then we assume that this particular instruction is unsupported.
+ */
+MFBT_API Result<LoadOrBranch, PCRelCheckError> CheckForPCRel(
+ const uintptr_t aPC, const uint32_t aInst) {
+ for (auto&& test : gPCRelTests) {
+ if ((aInst & test.mTestMask) == test.mMatchBits) {
+ if (!test.mDecodeFn) {
+ return Err(PCRelCheckError::NoDecoderAvailable);
+ }
+
+ return test.mDecodeFn(aPC, aInst);
+ }
+ }
+
+ return Err(PCRelCheckError::InstructionNotPCRel);
+}
+
+} // namespace arm64
+} // namespace interceptor
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Arm64.h b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.h
new file mode 100644
index 0000000000..4070fbf99f
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.h
@@ -0,0 +1,221 @@
+/* -*- 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_Arm64_h
+#define mozilla_interceptor_Arm64_h
+
+#include <type_traits>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/Saturate.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+namespace arm64 {
+
+// clang-format off
+enum class IntegerConditionCode : uint8_t {
+ // From the ARMv8 Architectural Reference Manual, Section C1.2.4
+ // Description Condition Flags
+ EQ = 0b0000, // == Z == 1
+ NE = 0b0001, // != Z == 0
+ CS = 0b0010, // carry set C == 1
+ HS = 0b0010, // carry set (alias) C == 1
+ CC = 0b0011, // carry clear C == 0
+ LO = 0b0011, // carry clear (alias) C == 0
+ MI = 0b0100, // < 0 N == 1
+ PL = 0b0101, // >= 0 N == 0
+ VS = 0b0110, // overflow V == 1
+ VC = 0b0111, // no overflow V == 0
+ HI = 0b1000, // unsigned > C == 1 && Z == 0
+ LS = 0b1001, // unsigned <= !(C == 1 && Z == 0)
+ GE = 0b1010, // signed >= N == V
+ LT = 0b1011, // signed < N != V
+ GT = 0b1100, // signed > Z == 0 && N == V
+ LE = 0b1101, // signed <= !(Z == 0 && N == V)
+ AL = 0b1110, // unconditional <Any>
+ NV = 0b1111 // unconditional (but AL is the preferred encoding)
+};
+// clang-format on
+
+struct LoadOrBranch {
+ enum class Type {
+ Load,
+ Branch,
+ };
+
+ // Load constructor
+ LoadOrBranch(const uintptr_t aAbsAddress, const uint8_t aDestReg)
+ : mType(Type::Load), mAbsAddress(aAbsAddress), mDestReg(aDestReg) {
+ MOZ_ASSERT(aDestReg < 32);
+ }
+
+ // Unconditional branch constructor
+ explicit LoadOrBranch(const uintptr_t aAbsAddress)
+ : mType(Type::Branch),
+ mAbsAddress(aAbsAddress),
+ mCond(IntegerConditionCode::AL) {}
+
+ // Conditional branch constructor
+ LoadOrBranch(const uintptr_t aAbsAddress, const IntegerConditionCode aCond)
+ : mType(Type::Branch), mAbsAddress(aAbsAddress), mCond(aCond) {}
+
+ Type mType;
+
+ // The absolute address to be loaded into a register, or branched to
+ uintptr_t mAbsAddress;
+
+ union {
+ // The destination register for the load
+ uint8_t mDestReg;
+
+ // The condition code for the branch
+ IntegerConditionCode mCond;
+ };
+};
+
+enum class PCRelCheckError {
+ InstructionNotPCRel,
+ NoDecoderAvailable,
+};
+
+MFBT_API Result<LoadOrBranch, PCRelCheckError> CheckForPCRel(
+ const uintptr_t aPC, const uint32_t aInst);
+
+/**
+ * Casts |aValue| to a |ResultT| via sign extension.
+ *
+ * This function should be used when extracting signed immediate values from
+ * an instruction.
+ *
+ * @param aValue The value to be sign extended. This value should already be
+ * isolated from the remainder of the instruction's bits and
+ * shifted all the way to the right.
+ * @param aNumValidBits The number of bits in |aValue| that contain the
+ * immediate signed value, including the sign bit.
+ */
+template <typename ResultT>
+inline ResultT SignExtend(const uint32_t aValue, const uint8_t aNumValidBits) {
+ static_assert(std::is_integral_v<ResultT> && std::is_signed_v<ResultT>,
+ "ResultT must be a signed integral type");
+ MOZ_ASSERT(aNumValidBits < 32U && aNumValidBits > 1);
+
+ using UnsignedResultT = std::decay_t<std::make_unsigned_t<ResultT>>;
+
+ const uint8_t kResultWidthBits = sizeof(ResultT) * 8;
+
+ // Shift left unsigned
+ const uint8_t shiftAmt = kResultWidthBits - aNumValidBits;
+ UnsignedResultT shiftedLeft = static_cast<UnsignedResultT>(aValue)
+ << shiftAmt;
+
+ // Now shift right signed
+ auto result = static_cast<ResultT>(shiftedLeft);
+ result >>= shiftAmt;
+
+ return result;
+}
+
+inline static uint32_t BuildUnconditionalBranchToRegister(const uint32_t aReg) {
+ MOZ_ASSERT(aReg < 32);
+ // BR aReg
+ return 0xD61F0000 | (aReg << 5);
+}
+
+MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC,
+ const uint32_t aInst);
+
+/**
+ * If |aTarget| is more than 128MB away from |aPC|, we need to use a veneer.
+ */
+inline static bool IsVeneerRequired(const uintptr_t aPC,
+ const uintptr_t aTarget) {
+ detail::Saturate<intptr_t> saturated(aTarget);
+ saturated -= aPC;
+
+ uintptr_t absDiff = Abs(saturated.value());
+
+ return absDiff >= 0x08000000U;
+}
+
+inline static bool IsUnconditionalBranchImm(const uint32_t aInst) {
+ return (aInst & 0xFC000000U) == 0x14000000U;
+}
+
+inline static Maybe<uint32_t> BuildUnconditionalBranchImm(
+ const uintptr_t aPC, const uintptr_t aTarget) {
+ detail::Saturate<intptr_t> saturated(aTarget);
+ saturated -= aPC;
+
+ CheckedInt<int32_t> offset(saturated.value());
+ if (!offset.isValid()) {
+ return Nothing();
+ }
+
+ // offset should be a multiple of 4
+ MOZ_ASSERT(offset.value() % 4 == 0);
+ if (offset.value() % 4) {
+ return Nothing();
+ }
+
+ offset /= 4;
+ if (!offset.isValid()) {
+ return Nothing();
+ }
+
+ uint32_t signbits = static_cast<uint32_t>(offset.value()) & 0xFE000000;
+ // Ensure that offset is small enough to fit into the 26 bit region.
+ // We check that the sign bits are either all ones or all zeros.
+ MOZ_ASSERT(signbits == 0xFE000000 || !signbits);
+ if (signbits && signbits != 0xFE000000) {
+ return Nothing();
+ }
+
+ uint32_t masked = static_cast<uint32_t>(offset.value()) & 0x03FFFFFF;
+
+ // B imm26
+ return Some(0x14000000U | masked);
+}
+
+/**
+ * Allocate and construct a veneer that provides an absolute 64-bit branch to
+ * the hook function.
+ */
+template <typename TrampPoolT>
+inline static uintptr_t MakeVeneer(TrampPoolT& aTrampPool, void* aPrimaryTramp,
+ const uintptr_t aDestAddress) {
+ auto maybeVeneer = aTrampPool.GetNextTrampoline();
+ if (!maybeVeneer) {
+ return 0;
+ }
+
+ Trampoline<typename TrampPoolT::MMPolicyT> veneer(
+ std::move(maybeVeneer.ref()));
+
+ // Write the same header information that is used for trampolines
+ veneer.WriteEncodedPointer(nullptr);
+ veneer.WriteEncodedPointer(aPrimaryTramp);
+
+ veneer.StartExecutableCode();
+
+ // Register 16 is explicitly intended for veneers in ARM64, so we use that
+ // register without fear of clobbering anything important.
+ veneer.WriteLoadLiteral(aDestAddress, 16);
+ veneer.WriteInstruction(BuildUnconditionalBranchToRegister(16));
+
+ return reinterpret_cast<uintptr_t>(veneer.EndExecutableCode());
+}
+
+} // namespace arm64
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_Arm64_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h b/toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h
new file mode 100644
index 0000000000..0a309a1065
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h
@@ -0,0 +1,1031 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_MMPolicies_h
+#define mozilla_interceptor_MMPolicies_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsMapRemoteView.h"
+#include "mozilla/WindowsUnwindInfo.h"
+
+#include <windows.h>
+
+#if (NTDDI_VERSION < NTDDI_WIN10_RS4) || defined(__MINGW32__)
+PVOID WINAPI VirtualAlloc2(HANDLE Process, PVOID BaseAddress, SIZE_T Size,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+PVOID WINAPI MapViewOfFile3(HANDLE FileMapping, HANDLE Process,
+ PVOID BaseAddress, ULONG64 Offset, SIZE_T ViewSize,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+#endif // (NTDDI_VERSION < NTDDI_WIN10_RS4) || defined(__MINGW32__)
+
+// _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)
+
+// Declaring only the functions we need in NativeNt.h. To include the entire
+// NativeNt.h causes circular dependency.
+namespace mozilla {
+namespace nt {
+SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen);
+
+SIZE_T WINAPI VirtualQuery(LPCVOID aAddress, PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen);
+} // namespace nt
+} // namespace mozilla
+
+namespace mozilla {
+namespace interceptor {
+
+// This class implements memory operations not involving any kernel32's
+// functions, so that derived classes can use them.
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyInProcessPrimitive {
+ protected:
+ bool ProtectInternal(decltype(&::VirtualProtect) aVirtualProtect,
+ void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = aVirtualProtect(aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ if (!ok && aPrevProtFlags) {
+ // VirtualProtect can fail but still set valid protection flags.
+ // Let's clear those upon failure.
+ *aPrevProtFlags = 0;
+ }
+
+ return !!ok;
+ }
+
+ public:
+ bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ ::memcpy(aToPtr, aFromPtr, aLen);
+ return true;
+ }
+
+ bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ ::memcpy(aToPtr, aFromPtr, aLen);
+ return true;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(uintptr_t aVAddress) const {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = nt::VirtualQuery(reinterpret_cast<LPCVOID>(aVAddress), &mbi,
+ sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
+ mbi.Protect != PAGE_NOACCESS;
+ }
+};
+
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyBase {
+ protected:
+ static uintptr_t AlignDown(const uintptr_t aUnaligned,
+ const uintptr_t aAlignTo) {
+ MOZ_ASSERT(IsPowerOfTwo(aAlignTo));
+#pragma warning(suppress : 4146)
+ return aUnaligned & (-aAlignTo);
+ }
+
+ static uintptr_t AlignUp(const uintptr_t aUnaligned,
+ const uintptr_t aAlignTo) {
+ MOZ_ASSERT(IsPowerOfTwo(aAlignTo));
+#pragma warning(suppress : 4146)
+ return aUnaligned + ((-aUnaligned) & (aAlignTo - 1));
+ }
+
+ static PVOID AlignUpToRegion(PVOID aUnaligned, uintptr_t aAlignTo,
+ size_t aLen, size_t aDesiredLen) {
+ uintptr_t unaligned = reinterpret_cast<uintptr_t>(aUnaligned);
+ uintptr_t aligned = AlignUp(unaligned, aAlignTo);
+ MOZ_ASSERT(aligned >= unaligned);
+
+ if (aLen < aligned - unaligned) {
+ return nullptr;
+ }
+
+ aLen -= (aligned - unaligned);
+ return reinterpret_cast<PVOID>((aLen >= aDesiredLen) ? aligned : 0);
+ }
+
+ public:
+#if defined(NIGHTLY_BUILD)
+ Maybe<DetourError> mLastError;
+ const Maybe<DetourError>& GetLastDetourError() const { return mLastError; }
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ mLastError = Some(DetourError(std::forward<Args>(aArgs)...));
+ }
+#else
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {}
+#endif // defined(NIGHTLY_BUILD)
+
+ DWORD ComputeAllocationSize(const uint32_t aRequestedSize) const {
+ MOZ_ASSERT(aRequestedSize);
+ DWORD result = aRequestedSize;
+
+ const uint32_t granularity = GetAllocGranularity();
+
+ uint32_t mod = aRequestedSize % granularity;
+ if (mod) {
+ result += (granularity - mod);
+ }
+
+ return result;
+ }
+
+ DWORD GetAllocGranularity() const {
+ static const DWORD kAllocGranularity = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwAllocationGranularity;
+ }();
+
+ return kAllocGranularity;
+ }
+
+ DWORD GetPageSize() const {
+ static const DWORD kPageSize = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }();
+
+ return kPageSize;
+ }
+
+ uintptr_t GetMaxUserModeAddress() const {
+ static const uintptr_t kMaxUserModeAddr = []() -> uintptr_t {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return reinterpret_cast<uintptr_t>(sysInfo.lpMaximumApplicationAddress);
+ }();
+
+ return kMaxUserModeAddr;
+ }
+
+ static const uint8_t* GetLowerBound(const Span<const uint8_t>& aBounds) {
+ return &(*aBounds.cbegin());
+ }
+
+ static const uint8_t* GetUpperBoundIncl(const Span<const uint8_t>& aBounds) {
+ // We return an upper bound that is inclusive.
+ return &(*(aBounds.cend() - 1));
+ }
+
+ static const uint8_t* GetUpperBoundExcl(const Span<const uint8_t>& aBounds) {
+ // We return an upper bound that is exclusive by adding 1 to the inclusive
+ // upper bound.
+ return GetUpperBoundIncl(aBounds) + 1;
+ }
+
+ /**
+ * It is convenient for us to provide address range information based on a
+ * "pivot" and a distance from that pivot, as branch instructions operate
+ * within a range of the program counter. OTOH, to actually manage the
+ * regions of memory, it is easier to think about them in terms of their
+ * lower and upper bounds. This function converts from the former format to
+ * the latter format.
+ */
+ Maybe<Span<const uint8_t>> SpanFromPivotAndDistance(
+ const uint32_t aSize, const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) const {
+ if (!aPivotAddr || !aMaxDistanceFromPivot) {
+ return Nothing();
+ }
+
+ // We don't allow regions below 1MB so that we're not allocating near any
+ // sensitive areas in our address space.
+ const uintptr_t kMinAllowableAddress = 0x100000;
+
+ const uintptr_t kGranularity(GetAllocGranularity());
+
+ // We subtract the max distance from the pivot to determine our lower bound.
+ CheckedInt<uintptr_t> lowerBound(aPivotAddr);
+ lowerBound -= aMaxDistanceFromPivot;
+ if (lowerBound.isValid()) {
+ // In this case, the subtraction has not underflowed, but we still want
+ // the lower bound to be at least kMinAllowableAddress.
+ lowerBound = std::max(lowerBound.value(), kMinAllowableAddress);
+ } else {
+ // In this case, we underflowed. Forcibly set the lower bound to
+ // kMinAllowableAddress.
+ lowerBound = CheckedInt<uintptr_t>(kMinAllowableAddress);
+ }
+
+ // Align up to the next unit of allocation granularity when necessary.
+ lowerBound = AlignUp(lowerBound.value(), kGranularity);
+ MOZ_ASSERT(lowerBound.isValid());
+ if (!lowerBound.isValid()) {
+ return Nothing();
+ }
+
+ // We must ensure that our region is below the maximum allowable user-mode
+ // address, or our reservation will fail.
+ const uintptr_t kMaxUserModeAddr = GetMaxUserModeAddress();
+
+ // We add the max distance from the pivot to determine our upper bound.
+ CheckedInt<uintptr_t> upperBound(aPivotAddr);
+ upperBound += aMaxDistanceFromPivot;
+ if (upperBound.isValid()) {
+ // In this case, the addition has not overflowed, but we still want
+ // the upper bound to be at most kMaxUserModeAddr.
+ upperBound = std::min(upperBound.value(), kMaxUserModeAddr);
+ } else {
+ // In this case, we overflowed. Forcibly set the upper bound to
+ // kMaxUserModeAddr.
+ upperBound = CheckedInt<uintptr_t>(kMaxUserModeAddr);
+ }
+
+ // Subtract the desired allocation size so that any chunk allocated in the
+ // region will be reachable.
+ upperBound -= aSize;
+ if (!upperBound.isValid()) {
+ return Nothing();
+ }
+
+ // Align down to the next unit of allocation granularity when necessary.
+ upperBound = AlignDown(upperBound.value(), kGranularity);
+ if (!upperBound.isValid()) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(lowerBound.value() < upperBound.value());
+ if (lowerBound.value() >= upperBound.value()) {
+ return Nothing();
+ }
+
+ // Return the result as a Span
+ return Some(Span(reinterpret_cast<const uint8_t*>(lowerBound.value()),
+ upperBound.value() - lowerBound.value()));
+ }
+
+ /**
+ * This function locates a virtual memory region of |aDesiredBytesLen| that
+ * resides in the interval [aRangeMin, aRangeMax). We do this by scanning the
+ * virtual memory space for a block of unallocated memory that is sufficiently
+ * large.
+ */
+ PVOID FindRegion(HANDLE aProcess, const size_t aDesiredBytesLen,
+ const uint8_t* aRangeMin, const uint8_t* aRangeMax) {
+ // Convert the given pointers to uintptr_t because we should not
+ // compare two pointers unless they are from the same array or object.
+ uintptr_t rangeMin = reinterpret_cast<uintptr_t>(aRangeMin);
+ uintptr_t rangeMax = reinterpret_cast<uintptr_t>(aRangeMax);
+
+ const DWORD kGranularity = GetAllocGranularity();
+ if (!aDesiredBytesLen) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_INVALIDLEN);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(rangeMin < rangeMax);
+ if (rangeMin >= rangeMax) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_INVALIDRANGE);
+ return nullptr;
+ }
+
+ // Generate a randomized base address that falls within the interval
+ // [aRangeMin, aRangeMax - aDesiredBytesLen]
+ unsigned int rnd = 0;
+ rand_s(&rnd);
+
+ // Reduce rnd to a value that falls within the acceptable range
+ uintptr_t maxOffset =
+ (rangeMax - rangeMin - aDesiredBytesLen) / kGranularity;
+ // Divide by maxOffset + 1 because maxOffset * kGranularity is acceptable.
+ uintptr_t offset = (uintptr_t(rnd) % (maxOffset + 1)) * kGranularity;
+
+ // Start searching at this address
+ const uintptr_t searchStart = rangeMin + offset;
+ // The max address needs to incorporate the desired length
+ const uintptr_t kMaxPtr = rangeMax - aDesiredBytesLen;
+
+ MOZ_DIAGNOSTIC_ASSERT(searchStart <= 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
+ // Scan [searchStart, kMaxPtr]
+ for (uintptr_t address = searchStart; address <= kMaxPtr;) {
+ if (nt::VirtualQueryEx(aProcess, reinterpret_cast<uint8_t*>(address),
+ &mbi, len) != len) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ if (mbi.State == MEM_FREE) {
+ // |mbi.BaseAddress| is aligned with the page granularity, but may not
+ // be aligned with the allocation granularity. VirtualAlloc does not
+ // accept such a non-aligned address unless the corresponding allocation
+ // region is free. So we get the next boundary's start address.
+ PVOID regionStart = AlignUpToRegion(mbi.BaseAddress, kGranularity,
+ mbi.RegionSize, aDesiredBytesLen);
+ if (regionStart) {
+ return regionStart;
+ }
+ }
+
+ address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
+ }
+
+ // Scan [aRangeMin, searchStart)
+ for (uintptr_t address = rangeMin; address < searchStart;) {
+ if (nt::VirtualQueryEx(aProcess, reinterpret_cast<uint8_t*>(address),
+ &mbi, len) != len) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ if (mbi.State == MEM_FREE) {
+ PVOID regionStart = AlignUpToRegion(mbi.BaseAddress, kGranularity,
+ mbi.RegionSize, aDesiredBytesLen);
+ if (regionStart) {
+ return regionStart;
+ }
+ }
+
+ address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
+ }
+
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_NO_FREE_REGION,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ /**
+ * This function reserves a |aSize| block of virtual memory.
+ *
+ * When |aBounds| is Nothing, it just calls |aReserveFn| and lets Windows
+ * choose the base address.
+ *
+ * Otherwise, it tries to call |aReserveRangeFn| to reserve the memory within
+ * the bounds provided by |aBounds|. It is advantageous to use this function
+ * because the OS's VM manager has better information as to which base
+ * addresses are the best to use.
+ *
+ * If |aReserveRangeFn| retuns Nothing, this means that the platform support
+ * is not available. In that case, we fall back to manually computing a region
+ * to use for reserving the memory by calling |FindRegion|.
+ */
+ template <typename ReserveFnT, typename ReserveRangeFnT>
+ PVOID Reserve(HANDLE aProcess, const uint32_t aSize,
+ const ReserveFnT& aReserveFn,
+ const ReserveRangeFnT& aReserveRangeFn,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aBounds) {
+ // No restrictions, let the OS choose the base address
+ PVOID ret = aReserveFn(aProcess, nullptr, aSize);
+ if (!ret) {
+ SetLastDetourError(MMPOLICY_RESERVE_NOBOUND_RESERVE_ERROR,
+ ::GetLastError());
+ }
+ return ret;
+ }
+
+ const uint8_t* lowerBound = GetLowerBound(aBounds.ref());
+ const uint8_t* upperBoundExcl = GetUpperBoundExcl(aBounds.ref());
+
+ Maybe<PVOID> result =
+ aReserveRangeFn(aProcess, aSize, lowerBound, upperBoundExcl);
+ if (result) {
+ return result.value();
+ }
+
+ // aReserveRangeFn is not available on this machine. We'll do a manual
+ // search.
+
+ size_t curAttempt = 0;
+ const size_t kMaxAttempts = 8;
+
+ // We loop here because |FindRegion| may return a base address that
+ // is reserved elsewhere before we have had a chance to reserve it
+ // ourselves.
+ while (curAttempt < kMaxAttempts) {
+ PVOID base = FindRegion(aProcess, aSize, lowerBound, upperBoundExcl);
+ if (!base) {
+ return nullptr;
+ }
+
+ result = Some(aReserveFn(aProcess, base, aSize));
+ if (result.value()) {
+ return result.value();
+ }
+
+ ++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.
+ PVOID ret = aReserveFn(aProcess, nullptr, aSize);
+ if (!ret) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINAL_RESERVE_ERROR,
+ ::GetLastError());
+ }
+ return ret;
+ }
+};
+
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyInProcess
+ : public MMPolicyInProcessPrimitive,
+ public MMPolicyBase {
+ public:
+ typedef MMPolicyInProcess MMPolicyT;
+
+ constexpr MMPolicyInProcess()
+ : mBase(nullptr), mReservationSize(0), mCommitOffset(0) {}
+
+ MMPolicyInProcess(const MMPolicyInProcess&) = delete;
+ MMPolicyInProcess& operator=(const MMPolicyInProcess&) = delete;
+
+ MMPolicyInProcess(MMPolicyInProcess&& aOther)
+ : mBase(nullptr), mReservationSize(0), mCommitOffset(0) {
+ *this = std::move(aOther);
+ }
+
+ MMPolicyInProcess& operator=(MMPolicyInProcess&& aOther) {
+ mBase = aOther.mBase;
+ aOther.mBase = nullptr;
+
+ mCommitOffset = aOther.mCommitOffset;
+ aOther.mCommitOffset = 0;
+
+ mReservationSize = aOther.mReservationSize;
+ aOther.mReservationSize = 0;
+
+ return *this;
+ }
+
+ explicit operator bool() const { return !!mBase; }
+
+ /**
+ * Should we unhook everything upon destruction?
+ */
+ bool ShouldUnhookUponDestruction() const { return true; }
+
+#if defined(_M_IX86)
+ bool WriteAtomic(void* aDestPtr, const uint16_t aValue) const {
+ *static_cast<uint16_t*>(aDestPtr) = aValue;
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ return ProtectInternal(::VirtualProtect, aVAddress, aSize, aProtFlags,
+ aPrevProtFlags);
+ }
+
+ bool FlushInstructionCache() const {
+ return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
+ }
+
+ static DWORD GetTrampWriteProtFlags() { return PAGE_EXECUTE_READWRITE; }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB() const {
+ return (mBase + mReservationSize) <=
+ reinterpret_cast<uint8_t*>(0x0000000080000000ULL);
+ }
+
+ static constexpr bool kSupportsUnwindInfo = true;
+
+ mozilla::UniquePtr<uint8_t[]> LookupUnwindInfo(
+ uintptr_t aOrigFuncAddr, uint32_t* aOffsetFromBeginAddr,
+ uint32_t* aOffsetToEndAddr, uintptr_t* aOrigImageBase) const {
+ DWORD64 origImageBase = 0;
+ auto origFuncEntry =
+ RtlLookupFunctionEntry(aOrigFuncAddr, &origImageBase, nullptr);
+ if (!origFuncEntry) {
+ return nullptr;
+ }
+
+ if (aOffsetFromBeginAddr) {
+ *aOffsetFromBeginAddr =
+ aOrigFuncAddr - (origImageBase + origFuncEntry->BeginAddress);
+ }
+ if (aOffsetToEndAddr) {
+ *aOffsetToEndAddr =
+ (origImageBase + origFuncEntry->EndAddress) - aOrigFuncAddr;
+ }
+ if (aOrigImageBase) {
+ *aOrigImageBase = origImageBase;
+ }
+ return reinterpret_cast<const UnwindInfo*>(origImageBase +
+ origFuncEntry->UnwindData)
+ ->Copy();
+ }
+
+ bool AddFunctionTable(uintptr_t aFunctionTable, uint32_t aEntryCount,
+ uintptr_t aBaseAddress) const {
+ return bool(
+ RtlAddFunctionTable(reinterpret_cast<PRUNTIME_FUNCTION>(aFunctionTable),
+ aEntryCount, aBaseAddress));
+ }
+#endif // defined(_M_X64)
+
+ protected:
+ uint8_t* GetLocalView() const { return mBase; }
+
+ uintptr_t GetRemoteView() const {
+ // Same as local view for in-process
+ return reinterpret_cast<uintptr_t>(mBase);
+ }
+
+ /**
+ * @return the effective number of bytes reserved, or 0 on failure
+ */
+ uint32_t Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aSize) {
+ return 0;
+ }
+
+ if (mBase) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+
+ auto reserveFn = [](HANDLE aProcess, PVOID aBase, uint32_t aSize) -> PVOID {
+ return ::VirtualAlloc(aBase, aSize, MEM_RESERVE, PAGE_NOACCESS);
+ };
+
+ auto reserveWithinRangeFn =
+ [](HANDLE aProcess, uint32_t aSize, const uint8_t* aRangeMin,
+ const uint8_t* aRangeMaxExcl) -> Maybe<PVOID> {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::VirtualAlloc2)>
+ pVirtualAlloc2(L"kernelbase.dll", "VirtualAlloc2");
+ if (!pVirtualAlloc2) {
+ return Nothing();
+ }
+
+ // NB: MEM_ADDRESS_REQUIREMENTS::HighestEndingAddress is *inclusive*
+ MEM_ADDRESS_REQUIREMENTS memReq = {
+ const_cast<uint8_t*>(aRangeMin),
+ const_cast<uint8_t*>(aRangeMaxExcl - 1)};
+
+ MEM_EXTENDED_PARAMETER memParam = {};
+ memParam.Type = MemExtendedParameterAddressRequirements;
+ memParam.Pointer = &memReq;
+
+ return Some(pVirtualAlloc2(aProcess, nullptr, aSize, MEM_RESERVE,
+ PAGE_NOACCESS, &memParam, 1));
+ };
+
+ mBase = static_cast<uint8_t*>(
+ MMPolicyBase::Reserve(::GetCurrentProcess(), mReservationSize,
+ reserveFn, reserveWithinRangeFn, aBounds));
+
+ if (!mBase) {
+ return 0;
+ }
+
+ return mReservationSize;
+ }
+
+ bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
+ const uint32_t aRequestedLength) {
+ if (!(*this)) {
+ return false;
+ }
+
+ uint32_t limit = aRequestedOffset + aRequestedLength - 1;
+ if (limit < mCommitOffset) {
+ // No commit required
+ return true;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
+ if (mCommitOffset >= mReservationSize) {
+ return false;
+ }
+
+ PVOID local = ::VirtualAlloc(mBase + mCommitOffset, GetPageSize(),
+ MEM_COMMIT, PAGE_EXECUTE_READ);
+ if (!local) {
+ return false;
+ }
+
+ mCommitOffset += GetPageSize();
+ return true;
+ }
+
+ private:
+ uint8_t* mBase;
+ uint32_t mReservationSize;
+ uint32_t mCommitOffset;
+};
+
+// This class manages in-process memory access without using functions
+// imported from kernel32.dll. Instead, it uses functions in its own
+// function table that are provided from outside.
+class MMPolicyInProcessEarlyStage : public MMPolicyInProcessPrimitive {
+ public:
+ struct Kernel32Exports {
+ decltype(&::FlushInstructionCache) mFlushInstructionCache;
+ decltype(&::GetModuleHandleW) mGetModuleHandleW;
+ decltype(&::GetSystemInfo) mGetSystemInfo;
+ decltype(&::VirtualProtect) mVirtualProtect;
+ };
+
+ private:
+ static DWORD GetPageSize(const Kernel32Exports& aK32Exports) {
+ SYSTEM_INFO sysInfo;
+ aK32Exports.mGetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }
+
+ const Kernel32Exports& mK32Exports;
+ const DWORD mPageSize;
+
+ public:
+ explicit MMPolicyInProcessEarlyStage(const Kernel32Exports& aK32Exports)
+ : mK32Exports(aK32Exports), mPageSize(GetPageSize(mK32Exports)) {}
+
+ // The pattern of constructing a local static variable with a lambda,
+ // which can be seen in MMPolicyBase, is compiled into code with the
+ // critical section APIs like EnterCriticalSection imported from kernel32.dll.
+ // Because this class needs to be able to run in a process's early stage
+ // when IAT is not yet resolved, we cannot use that patten, thus simply
+ // caching a value as a local member in the class.
+ DWORD GetPageSize() const { return mPageSize; }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ return ProtectInternal(mK32Exports.mVirtualProtect, aVAddress, aSize,
+ aProtFlags, aPrevProtFlags);
+ }
+
+ bool FlushInstructionCache() const {
+ const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+ return !!mK32Exports.mFlushInstructionCache(kCurrentProcess, nullptr, 0);
+ }
+};
+
+class MMPolicyOutOfProcess : public MMPolicyBase {
+ public:
+ typedef MMPolicyOutOfProcess MMPolicyT;
+
+ explicit MMPolicyOutOfProcess(HANDLE aProcess)
+ : mProcess(nullptr),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ MOZ_ASSERT(aProcess);
+ ::DuplicateHandle(::GetCurrentProcess(), aProcess, ::GetCurrentProcess(),
+ &mProcess, kAccessFlags, FALSE, 0);
+ MOZ_ASSERT(mProcess);
+ }
+
+ explicit MMPolicyOutOfProcess(DWORD aPid)
+ : mProcess(::OpenProcess(kAccessFlags, FALSE, aPid)),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ MOZ_ASSERT(mProcess);
+ }
+
+ ~MMPolicyOutOfProcess() { Destroy(); }
+
+ MMPolicyOutOfProcess(MMPolicyOutOfProcess&& aOther)
+ : mProcess(nullptr),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ *this = std::move(aOther);
+ }
+
+ MMPolicyOutOfProcess(const MMPolicyOutOfProcess& aOther) = delete;
+ MMPolicyOutOfProcess& operator=(const MMPolicyOutOfProcess&) = delete;
+
+ MMPolicyOutOfProcess& operator=(MMPolicyOutOfProcess&& aOther) {
+ Destroy();
+
+ mProcess = aOther.mProcess;
+ aOther.mProcess = nullptr;
+
+ mMapping = aOther.mMapping;
+ aOther.mMapping = nullptr;
+
+ mLocalView = aOther.mLocalView;
+ aOther.mLocalView = nullptr;
+
+ mRemoteView = aOther.mRemoteView;
+ aOther.mRemoteView = nullptr;
+
+ mReservationSize = aOther.mReservationSize;
+ aOther.mReservationSize = 0;
+
+ mCommitOffset = aOther.mCommitOffset;
+ aOther.mCommitOffset = 0;
+
+ return *this;
+ }
+
+ explicit operator bool() const {
+ return mProcess && mMapping && mLocalView && mRemoteView;
+ }
+
+ bool ShouldUnhookUponDestruction() const {
+ // We don't clean up hooks for remote processes; they are expected to
+ // outlive our process.
+ return false;
+ }
+
+ // This function reads as many bytes as |aLen| from the target process and
+ // succeeds only when the entire area to be read is accessible.
+ bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ SIZE_T numBytes = 0;
+ BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr, aLen, &numBytes);
+ return ok && numBytes == aLen;
+ }
+
+ // This function reads as many bytes as possible from the target process up
+ // to |aLen| bytes and returns the number of bytes which was actually read.
+ size_t TryRead(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return 0;
+ }
+
+ uint32_t pageSize = GetPageSize();
+ uintptr_t pageMask = pageSize - 1;
+
+ auto rangeStart = reinterpret_cast<uintptr_t>(aFromPtr);
+ auto rangeEnd = rangeStart + aLen;
+
+ while (rangeStart < rangeEnd) {
+ SIZE_T numBytes = 0;
+ BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr,
+ rangeEnd - rangeStart, &numBytes);
+ if (ok) {
+ return numBytes;
+ }
+
+ // If ReadProcessMemory fails, try to read up to each page boundary from
+ // the end of the requested area one by one.
+ if (rangeEnd & pageMask) {
+ rangeEnd &= ~pageMask;
+ } else {
+ rangeEnd -= pageSize;
+ }
+ }
+
+ return 0;
+ }
+
+ bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ SIZE_T numBytes = 0;
+ BOOL ok = ::WriteProcessMemory(mProcess, aToPtr, aFromPtr, aLen, &numBytes);
+ return ok && numBytes == aLen;
+ }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = ::VirtualProtectEx(mProcess, aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ if (!ok && aPrevProtFlags) {
+ // VirtualProtectEx can fail but still set valid protection flags.
+ // Let's clear those upon failure.
+ *aPrevProtFlags = 0;
+ }
+
+ return !!ok;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(uintptr_t aVAddress) const {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = nt::VirtualQueryEx(
+ mProcess, reinterpret_cast<LPCVOID>(aVAddress), &mbi, sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
+ mbi.Protect != PAGE_NOACCESS;
+ }
+
+ bool FlushInstructionCache() const {
+ return !!::FlushInstructionCache(mProcess, nullptr, 0);
+ }
+
+ static DWORD GetTrampWriteProtFlags() { return PAGE_READWRITE; }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB() const {
+ return (GetRemoteView() + mReservationSize) <= 0x0000000080000000ULL;
+ }
+
+ // TODO: We should also implement unwind info for our out-of-process policy.
+ static constexpr bool kSupportsUnwindInfo = false;
+
+ inline mozilla::UniquePtr<uint8_t[]> LookupUnwindInfo(
+ uintptr_t aOrigFuncAddr, uint32_t* aOffsetFromBeginAddr,
+ uint32_t* aOffsetToEndAddr, uintptr_t* aOrigImageBase) const {
+ return nullptr;
+ }
+
+ inline bool AddFunctionTable(uintptr_t aNewTable, uint32_t aEntryCount,
+ uintptr_t aBaseAddress) const {
+ return false;
+ }
+#endif // defined(_M_X64)
+
+ protected:
+ uint8_t* GetLocalView() const { return mLocalView; }
+
+ uintptr_t GetRemoteView() const {
+ return reinterpret_cast<uintptr_t>(mRemoteView);
+ }
+
+ /**
+ * @return the effective number of bytes reserved, or 0 on failure
+ */
+ uint32_t Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aSize || !mProcess) {
+ SetLastDetourError(MMPOLICY_RESERVE_INVALIDARG);
+ return 0;
+ }
+
+ if (mRemoteView) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ SetLastDetourError(MMPOLICY_RESERVE_ZERO_RESERVATIONSIZE);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+
+ mMapping = ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
+ PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0,
+ mReservationSize, nullptr);
+ if (!mMapping) {
+ SetLastDetourError(MMPOLICY_RESERVE_CREATEFILEMAPPING, ::GetLastError());
+ return 0;
+ }
+
+ mLocalView = static_cast<uint8_t*>(
+ ::MapViewOfFile(mMapping, FILE_MAP_WRITE, 0, 0, 0));
+ if (!mLocalView) {
+ SetLastDetourError(MMPOLICY_RESERVE_MAPVIEWOFFILE, ::GetLastError());
+ 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);
+ };
+
+ auto reserveWithinRangeFn =
+ [mapping = mMapping](HANDLE aProcess, uint32_t aSize,
+ const uint8_t* aRangeMin,
+ const uint8_t* aRangeMaxExcl) -> Maybe<PVOID> {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::MapViewOfFile3)>
+ pMapViewOfFile3(L"kernelbase.dll", "MapViewOfFile3");
+ if (!pMapViewOfFile3) {
+ return Nothing();
+ }
+
+ // NB: MEM_ADDRESS_REQUIREMENTS::HighestEndingAddress is *inclusive*
+ MEM_ADDRESS_REQUIREMENTS memReq = {
+ const_cast<uint8_t*>(aRangeMin),
+ const_cast<uint8_t*>(aRangeMaxExcl - 1)};
+
+ MEM_EXTENDED_PARAMETER memParam = {};
+ memParam.Type = MemExtendedParameterAddressRequirements;
+ memParam.Pointer = &memReq;
+
+ return Some(pMapViewOfFile3(mapping, aProcess, nullptr, 0, aSize, 0,
+ PAGE_EXECUTE_READ, &memParam, 1));
+ };
+
+ mRemoteView = MMPolicyBase::Reserve(mProcess, mReservationSize, reserveFn,
+ reserveWithinRangeFn, aBounds);
+ if (!mRemoteView) {
+ return 0;
+ }
+
+ return mReservationSize;
+ }
+
+ bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
+ const uint32_t aRequestedLength) {
+ if (!(*this)) {
+ return false;
+ }
+
+ uint32_t limit = aRequestedOffset + aRequestedLength - 1;
+ if (limit < mCommitOffset) {
+ // No commit required
+ return true;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
+ if (mCommitOffset >= mReservationSize) {
+ return false;
+ }
+
+ PVOID local = ::VirtualAlloc(mLocalView + mCommitOffset, GetPageSize(),
+ MEM_COMMIT, PAGE_READWRITE);
+ if (!local) {
+ return false;
+ }
+
+ PVOID remote = ::VirtualAllocEx(
+ mProcess, static_cast<uint8_t*>(mRemoteView) + mCommitOffset,
+ GetPageSize(), MEM_COMMIT, PAGE_EXECUTE_READ);
+ if (!remote) {
+ return false;
+ }
+
+ mCommitOffset += GetPageSize();
+ return true;
+ }
+
+ private:
+ void Destroy() {
+ // We always leak the remote view
+ if (mLocalView) {
+ ::UnmapViewOfFile(mLocalView);
+ mLocalView = nullptr;
+ }
+
+ if (mMapping) {
+ ::CloseHandle(mMapping);
+ mMapping = nullptr;
+ }
+
+ if (mProcess) {
+ ::CloseHandle(mProcess);
+ mProcess = nullptr;
+ }
+ }
+
+ private:
+ HANDLE mProcess;
+ HANDLE mMapping;
+ uint8_t* mLocalView;
+ PVOID mRemoteView;
+ uint32_t mReservationSize;
+ uint32_t mCommitOffset;
+
+ static const DWORD kAccessFlags = PROCESS_QUERY_INFORMATION |
+ PROCESS_VM_OPERATION | PROCESS_VM_READ |
+ PROCESS_VM_WRITE;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_MMPolicies_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h b/toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h
new file mode 100644
index 0000000000..e39a38fafd
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h
@@ -0,0 +1,141 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_PatcherBase_h
+#define mozilla_interceptor_PatcherBase_h
+
+#include "mozilla/interceptor/TargetFunction.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename MMPolicy>
+struct GetProcAddressSelector;
+
+template <>
+struct GetProcAddressSelector<MMPolicyOutOfProcess> {
+ FARPROC operator()(HMODULE aModule, const char* aName,
+ const MMPolicyOutOfProcess& aMMPolicy) const {
+ auto exportSection =
+ mozilla::nt::PEExportSection<MMPolicyOutOfProcess>::Get(aModule,
+ aMMPolicy);
+ return exportSection.GetProcAddress(aName);
+ }
+};
+
+template <>
+struct GetProcAddressSelector<MMPolicyInProcess> {
+ FARPROC operator()(HMODULE aModule, const char* aName,
+ const MMPolicyInProcess&) const {
+ // PEExportSection works for MMPolicyInProcess, too, but the native
+ // GetProcAddress is still better because PEExportSection does not
+ // solve a forwarded entry.
+ return ::GetProcAddress(aModule, aName);
+ }
+};
+
+template <typename VMPolicy>
+class WindowsDllPatcherBase {
+ protected:
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
+
+ template <typename... Args>
+ explicit WindowsDllPatcherBase(Args&&... aArgs)
+ : mVMPolicy(std::forward<Args>(aArgs)...) {}
+
+ ReadOnlyTargetFunction<MMPolicyT> ResolveRedirectedAddress(
+ FARPROC aOriginalFunction) {
+ uintptr_t currAddr = reinterpret_cast<uintptr_t>(aOriginalFunction);
+
+#if defined(_M_IX86) || defined(_M_X64)
+ uintptr_t prevAddr = 0;
+ while (prevAddr != currAddr) {
+ ReadOnlyTargetFunction<MMPolicyT> currFunc(mVMPolicy, currAddr);
+ prevAddr = currAddr;
+
+ // If function entry is jmp rel8 stub to the internal implementation, we
+ // resolve redirected address from the jump target.
+ uintptr_t nextAddr = 0;
+ if (currFunc.IsRelativeShortJump(&nextAddr)) {
+ int8_t offset = nextAddr - currFunc.GetAddress() - 2;
+
+# if defined(_M_X64)
+ // We redirect to the target of a short jump backwards if the target
+ // is another jump (only 32-bit displacement is currently supported).
+ // This case is used by GetFileAttributesW in Win7 x64.
+ if ((offset < 0) && (currFunc.IsValidAtOffset(2 + offset))) {
+ ReadOnlyTargetFunction<MMPolicyT> redirectFn(mVMPolicy, nextAddr);
+ if (redirectFn.IsIndirectNearJump(&nextAddr)) {
+ return redirectFn;
+ }
+ }
+# endif
+
+ // We check the downstream has enough nop-space only when the offset is
+ // positive. Otherwise we stop chasing redirects and let the caller
+ // fail to hook.
+ if (offset > 0) {
+ bool isNopSpace = true;
+ for (int8_t i = 0; i < offset; i++) {
+ if (currFunc[2 + i] != 0x90) {
+ isNopSpace = false;
+ break;
+ }
+ }
+
+ if (isNopSpace) {
+ currAddr = nextAddr;
+ }
+ }
+# if defined(_M_X64)
+ } else if (currFunc.IsIndirectNearJump(&nextAddr) ||
+ currFunc.IsRelativeNearJump(&nextAddr)) {
+# else
+ } else if (currFunc.IsIndirectNearJump(&nextAddr)) {
+# endif
+ // If function entry is jmp [disp32] such as used by kernel32, we
+ // resolve redirected address from import table. For x64, we resolve
+ // a relative near jump for TestDllInterceptor with --disable-optimize.
+ currAddr = nextAddr;
+ }
+ }
+#endif // defined(_M_IX86) || defined(_M_X64)
+
+ if (currAddr != reinterpret_cast<uintptr_t>(aOriginalFunction) &&
+ !mVMPolicy.IsPageAccessible(currAddr)) {
+ currAddr = reinterpret_cast<uintptr_t>(aOriginalFunction);
+ }
+ return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, currAddr);
+ }
+
+ public:
+ FARPROC GetProcAddress(HMODULE aModule, const char* aName) const {
+ GetProcAddressSelector<MMPolicyT> selector;
+ return selector(aModule, aName, mVMPolicy);
+ }
+
+ bool IsPageAccessible(uintptr_t aAddress) const {
+ return mVMPolicy.IsPageAccessible(aAddress);
+ }
+
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& GetLastDetourError() const {
+ return mVMPolicy.GetLastDetourError();
+ }
+#endif // defined(NIGHTLY_BUILD)
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ mVMPolicy.SetLastDetourError(std::forward<Args>(aArgs)...);
+ }
+
+ protected:
+ VMPolicy mVMPolicy;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_PatcherBase_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h b/toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h
new file mode 100644
index 0000000000..1e8afcf9bc
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h
@@ -0,0 +1,1728 @@
+/* -*- 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_PatcherDetour_h
+#define mozilla_interceptor_PatcherDetour_h
+
+#if defined(_M_ARM64)
+# include "mozilla/interceptor/Arm64.h"
+#endif // defined(_M_ARM64)
+#include <utility>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "mozilla/interceptor/PatcherBase.h"
+#include "mozilla/interceptor/Trampoline.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+
+#define COPY_CODES(NBYTES) \
+ do { \
+ tramp.CopyCodes(origBytes.GetAddress(), NBYTES); \
+ origBytes += NBYTES; \
+ } while (0)
+
+namespace mozilla {
+namespace interceptor {
+
+enum class DetourFlags : uint32_t {
+ eDefault = 0,
+ eEnable10BytePatch = 1, // Allow 10-byte patches when conditions allow
+ eTestOnlyForceShortPatch =
+ 2, // Force short patches at all times (x86-64 and arm64 testing only)
+ eDontResolveRedirection =
+ 4, // Don't resolve the redirection of JMP (e.g. kernel32 -> kernelbase)
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DetourFlags)
+
+// This class is responsible to do tasks which depend on MMPolicy, decoupled
+// from VMPolicy. We already have WindowsDllPatcherBase, but it needs to
+// depend on VMPolicy to hold an instance of VMPolicy as a member.
+template <typename MMPolicyT>
+class WindowsDllDetourPatcherPrimitive {
+ protected:
+#if defined(_M_ARM64)
+ // LDR x16, .+8
+ static const uint32_t kLdrX16Plus8 = 0x58000050U;
+#endif // defined(_M_ARM64)
+
+ static void ApplyDefaultPatch(WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+#if defined(_M_IX86)
+ target.WriteByte(0xe9); // jmp
+ target.WriteDisp32(aDest); // hook displacement
+#elif defined(_M_X64)
+ // mov r11, address
+ target.WriteByte(0x49);
+ target.WriteByte(0xbb);
+ target.WritePointer(aDest);
+
+ // jmp r11
+ target.WriteByte(0x41);
+ target.WriteByte(0xff);
+ target.WriteByte(0xe3);
+#elif defined(_M_ARM64)
+ // The default patch requires 16 bytes
+ // LDR x16, .+8
+ target.WriteLong(kLdrX16Plus8);
+ // BR x16
+ target.WriteLong(arm64::BuildUnconditionalBranchToRegister(16));
+ target.WritePointer(aDest);
+#else
+# error "Unsupported processor architecture"
+#endif
+ }
+
+ public:
+ constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
+#if defined(_M_IX86)
+ return 5;
+#elif defined(_M_X64)
+ return 13;
+#elif defined(_M_ARM64)
+ return 16;
+#else
+# error "Unsupported processor architecture"
+#endif
+ }
+
+ WindowsDllDetourPatcherPrimitive() = default;
+
+ WindowsDllDetourPatcherPrimitive(const WindowsDllDetourPatcherPrimitive&) =
+ delete;
+ WindowsDllDetourPatcherPrimitive(WindowsDllDetourPatcherPrimitive&&) = delete;
+ WindowsDllDetourPatcherPrimitive& operator=(
+ const WindowsDllDetourPatcherPrimitive&) = delete;
+ WindowsDllDetourPatcherPrimitive& operator=(
+ WindowsDllDetourPatcherPrimitive&&) = delete;
+
+ bool AddIrreversibleHook(const MMPolicyT& aMMPolicy, FARPROC aTargetFn,
+ intptr_t aHookDest) {
+ ReadOnlyTargetFunction<MMPolicyT> targetReadOnly(aMMPolicy, aTargetFn);
+
+ WritableTargetFunction<MMPolicyT> targetWritable(
+ targetReadOnly.Promote(GetWorstCaseRequiredBytesToPatch()));
+ if (!targetWritable) {
+ return false;
+ }
+
+ ApplyDefaultPatch(targetWritable, aHookDest);
+
+ return targetWritable.Commit();
+ }
+};
+
+template <typename VMPolicy>
+class WindowsDllDetourPatcher final
+ : public WindowsDllDetourPatcherPrimitive<typename VMPolicy::MMPolicyT>,
+ public WindowsDllPatcherBase<VMPolicy> {
+ using MMPolicyT = typename VMPolicy::MMPolicyT;
+ using TrampPoolT = typename VMPolicy::PoolType;
+ using PrimitiveT = WindowsDllDetourPatcherPrimitive<MMPolicyT>;
+ Maybe<DetourFlags> mFlags;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllDetourPatcher(Args&&... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
+
+ ~WindowsDllDetourPatcher() { Clear(); }
+
+ WindowsDllDetourPatcher(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher(WindowsDllDetourPatcher&&) = delete;
+ WindowsDllDetourPatcher& operator=(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher& operator=(WindowsDllDetourPatcher&&) = delete;
+
+ void Clear() {
+ if (!this->mVMPolicy.ShouldUnhookUponDestruction()) {
+ return;
+ }
+
+#if defined(_M_IX86)
+ size_t nBytes = 1 + sizeof(intptr_t);
+#elif defined(_M_X64)
+ size_t nBytes = 2 + sizeof(intptr_t);
+#elif defined(_M_ARM64)
+ size_t nBytes = 2 * sizeof(uint32_t) + sizeof(uintptr_t);
+#else
+# error "Unknown processor type"
+#endif
+
+ const auto& tramps = this->mVMPolicy.Items();
+ for (auto&& tramp : tramps) {
+ // First we read the pointer to the interceptor instance.
+ Maybe<uintptr_t> instance = tramp.ReadEncodedPointer();
+ if (!instance) {
+ continue;
+ }
+
+ if (instance.value() != reinterpret_cast<uintptr_t>(this)) {
+ // tramp does not belong to this interceptor instance.
+ continue;
+ }
+
+ auto clearInstance = MakeScopeExit([&tramp]() -> void {
+ // Clear the instance pointer so that no future instances with the same
+ // |this| pointer will attempt to reset its hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+ });
+
+ // Now we read the pointer to the intercepted function.
+ Maybe<uintptr_t> interceptedFn = tramp.ReadEncodedPointer();
+ if (!interceptedFn) {
+ continue;
+ }
+
+ WritableTargetFunction<MMPolicyT> origBytes(
+ this->mVMPolicy, interceptedFn.value(), nBytes);
+ if (!origBytes) {
+ continue;
+ }
+
+#if defined(_M_IX86) || defined(_M_X64)
+
+ Maybe<uint8_t> maybeOpcode1 = origBytes.ReadByte();
+ if (!maybeOpcode1) {
+ continue;
+ }
+
+ uint8_t opcode1 = maybeOpcode1.value();
+
+# if defined(_M_IX86)
+ // Ensure the JMP from CreateTrampoline is where we expect it to be.
+ MOZ_ASSERT(opcode1 == 0xE9);
+ if (opcode1 != 0xE9) {
+ continue;
+ }
+
+ intptr_t startOfTrampInstructions =
+ static_cast<intptr_t>(tramp.GetCurrentRemoteAddress());
+
+ origBytes.WriteDisp32(startOfTrampInstructions);
+ if (!origBytes) {
+ continue;
+ }
+
+ origBytes.Commit();
+# elif defined(_M_X64)
+ // Note: At the moment we clear 13-byte patches by replacing the jump to
+ // the patched function by a jump to the stub code. The original
+ // bytes of the original function are *not* restored. This implies
+ // that the stub code outlives our cleaning, so unwind information
+ // remains useful and must not be removed here.
+ if (opcode1 == 0x49) {
+ if (!Clear13BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else if (opcode1 == 0xB8) {
+ if (!Clear10BytePatch(origBytes)) {
+ continue;
+ }
+ } else if (opcode1 == 0x48) {
+ // The original function was just a different trampoline
+ if (!ClearTrampolinePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ continue;
+ }
+# endif
+
+#elif defined(_M_ARM64)
+
+ // Ensure that we see the instruction that we expect
+ Maybe<uint32_t> inst1 = origBytes.ReadLong();
+ if (!inst1) {
+ continue;
+ }
+
+ if (inst1.value() == this->kLdrX16Plus8) {
+ if (!Clear16BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else if (arm64::IsUnconditionalBranchImm(inst1.value())) {
+ if (!Clear4BytePatch(inst1.value(), origBytes)) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ continue;
+ }
+
+#else
+# error "Unknown processor type"
+#endif
+ }
+
+ this->mVMPolicy.Clear();
+ }
+
+#if defined(_M_X64)
+ bool Clear13BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aResetToAddress) {
+ Maybe<uint8_t> 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 ClearTrampolinePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aPtrToResetToAddress) {
+ // The target of the trampoline we replaced is stored at
+ // aPtrToResetToAddress. We simply put it back where we got it from.
+ Maybe<uint8_t> maybeOpcode2 = aOrigBytes.ReadByte();
+ if (!maybeOpcode2) {
+ return false;
+ }
+
+ uint8_t opcode2 = maybeOpcode2.value();
+ if (opcode2 != 0xB8) {
+ return false;
+ }
+
+ auto oldPtr = *(reinterpret_cast<const uintptr_t*>(aPtrToResetToAddress));
+
+ aOrigBytes.WritePointer(oldPtr);
+ if (!aOrigBytes) {
+ return false;
+ }
+
+ return aOrigBytes.Commit();
+ }
+
+ bool Clear10BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes) {
+ Maybe<uint32_t> 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<MMPolicyT> writableIntermediate(
+ this->mVMPolicy, trampPtr - sizeof(uintptr_t), 13 + sizeof(uintptr_t));
+ if (!writableIntermediate) {
+ return false;
+ }
+
+ Maybe<uintptr_t> stubTramp = writableIntermediate.ReadEncodedPtr();
+ if (!stubTramp || !stubTramp.value()) {
+ return false;
+ }
+
+ Maybe<uint8_t> 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());
+ }
+#endif // defined(_M_X64)
+
+#if defined(_M_ARM64)
+ bool Clear4BytePatch(const uint32_t aBranchImm,
+ WritableTargetFunction<MMPolicyT>& aOrigBytes) {
+ MOZ_ASSERT(arm64::IsUnconditionalBranchImm(aBranchImm));
+
+ arm64::LoadOrBranch decoded = arm64::BUncondImmDecode(
+ aOrigBytes.GetCurrentAddress() - sizeof(uint32_t), aBranchImm);
+
+ uintptr_t trampPtr = decoded.mAbsAddress;
+
+ // trampPtr points to an intermediate trampoline that contains a veneer.
+ // We back up by sizeof(uintptr_t) so that we can access the pointer to the
+ // stub trampoline.
+
+ // We want trampLen to be the size of the veneer, plus one pointer (since
+ // we are backing up trampPtr by one pointer)
+ size_t trampLen = 16 + sizeof(uintptr_t);
+
+ WritableTargetFunction<MMPolicyT> writableIntermediate(
+ this->mVMPolicy, trampPtr - sizeof(uintptr_t), trampLen);
+ if (!writableIntermediate) {
+ return false;
+ }
+
+ Maybe<uintptr_t> stubTramp = writableIntermediate.ReadEncodedPtr();
+ if (!stubTramp || !stubTramp.value()) {
+ return false;
+ }
+
+ Maybe<uint32_t> inst1 = writableIntermediate.ReadLong();
+ if (!inst1 || inst1.value() != this->kLdrX16Plus8) {
+ return false;
+ }
+
+ return Clear16BytePatch(writableIntermediate, stubTramp.value());
+ }
+
+ bool Clear16BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aResetToAddress) {
+ Maybe<uint32_t> inst2 = aOrigBytes.ReadLong();
+ if (!inst2) {
+ return false;
+ }
+
+ if (inst2.value() != arm64::BuildUnconditionalBranchToRegister(16)) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ return false;
+ }
+
+ // Clobber the pointer to our hook function with a pointer to the
+ // start of the trampoline.
+ aOrigBytes.WritePointer(aResetToAddress);
+ aOrigBytes.Commit();
+
+ return true;
+ }
+#endif // defined(_M_ARM64)
+
+ void Init(DetourFlags aFlags = DetourFlags::eDefault) {
+ if (Initialized()) {
+ return;
+ }
+
+#if defined(_M_X64)
+ if (aFlags & DetourFlags::eTestOnlyForceShortPatch) {
+ aFlags |= DetourFlags::eEnable10BytePatch;
+ }
+#endif // defined(_M_X64)
+
+ mFlags = Some(aFlags);
+ }
+
+ bool Initialized() const { return mFlags.isSome(); }
+
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
+ ReadOnlyTargetFunction<MMPolicyT> target(
+ (mFlags.value() & DetourFlags::eDontResolveRedirection)
+ ? ReadOnlyTargetFunction<MMPolicyT>(
+ this->mVMPolicy, reinterpret_cast<uintptr_t>(aTargetFn))
+ : this->ResolveRedirectedAddress(aTargetFn));
+
+ TrampPoolT* trampPool = nullptr;
+
+#if defined(_M_ARM64)
+ // ARM64 uses two passes to build its trampoline. The first pass uses a
+ // null tramp to determine how many bytes are needed. Once that is known,
+ // CreateTrampoline calls itself recursively with a "real" tramp.
+ Trampoline<MMPolicyT> tramp(nullptr);
+#else
+ Maybe<TrampPoolT> maybeTrampPool = DoReserve();
+ MOZ_ASSERT(maybeTrampPool);
+ if (!maybeTrampPool) {
+ return false;
+ }
+
+ trampPool = maybeTrampPool.ptr();
+
+ Maybe<Trampoline<MMPolicyT>> maybeTramp(trampPool->GetNextTrampoline());
+ if (!maybeTramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_NEXT_TRAMPOLINE_ERROR);
+ return false;
+ }
+
+ Trampoline<MMPolicyT> tramp(std::move(maybeTramp.ref()));
+#endif
+
+ CreateTrampoline(target, trampPool, tramp, aHookDest, aOrigFunc);
+ if (!*aOrigFunc) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private:
+ /**
+ * This function returns a maximum distance that can be reached by a single
+ * unconditional jump instruction. This is dependent on the processor ISA.
+ * Note that this distance is *exclusive* when added to the pivot, so the
+ * distance returned by this function is actually
+ * (maximum_absolute_offset + 1).
+ */
+ static uint32_t GetDefaultPivotDistance() {
+#if defined(_M_ARM64)
+ // Immediate unconditional branch allows for +/- 128MB
+ return 0x08000000U;
+#elif defined(_M_IX86) || defined(_M_X64)
+ // For these ISAs, our distance will assume the use of an unconditional jmp
+ // with a 32-bit signed displacement.
+ return 0x80000000U;
+#else
+# error "Not defined for this processor arch"
+#endif
+ }
+
+ /**
+ * If we're reserving trampoline space for a specific module, we base the
+ * pivot off of the median address of the module's .text section. While this
+ * may not be precise, it should be accurate enough for our purposes: To
+ * ensure that the trampoline space is reachable by any executable code in the
+ * module.
+ */
+ Maybe<TrampPoolT> ReserveForModule(HMODULE aModule) {
+ nt::PEHeaders moduleHeaders(aModule);
+ if (!moduleHeaders) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_PE_ERROR);
+ return Nothing();
+ }
+
+ Maybe<Span<const uint8_t>> textSectionInfo =
+ moduleHeaders.GetTextSectionInfo();
+ if (!textSectionInfo) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_TEXT_ERROR);
+ return Nothing();
+ }
+
+ const uint8_t* median = textSectionInfo.value().data() +
+ (textSectionInfo.value().LengthBytes() / 2);
+
+ Maybe<TrampPoolT> maybeTrampPool = this->mVMPolicy.Reserve(
+ reinterpret_cast<uintptr_t>(median), GetDefaultPivotDistance());
+ if (!maybeTrampPool) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_RESERVE_ERROR);
+ }
+ return maybeTrampPool;
+ }
+
+ Maybe<TrampPoolT> DoReserve(HMODULE aModule = nullptr) {
+ if (aModule) {
+ return ReserveForModule(aModule);
+ }
+
+ uintptr_t pivot = 0;
+ uint32_t distance = 0;
+
+#if defined(_M_X64)
+ if (mFlags.value() & DetourFlags::eEnable10BytePatch) {
+ // 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 any
+ // pointers into the reserved region must be 0.
+ pivot = 0x40000000U;
+ distance = 0x40000000U;
+ }
+#endif // defined(_M_X64)
+
+ Maybe<TrampPoolT> maybeTrampPool = this->mVMPolicy.Reserve(pivot, distance);
+#if defined(NIGHTLY_BUILD)
+ if (!maybeTrampPool && this->GetLastDetourError().isNothing()) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_DO_RESERVE_ERROR);
+ }
+#endif // defined(NIGHTLY_BUILD)
+ return maybeTrampPool;
+ }
+
+ protected:
+#if !defined(_M_ARM64)
+
+ const static int kPageSize = 4096;
+
+ // rex bits
+ static const BYTE kMaskHighNibble = 0xF0;
+ static const BYTE kRexOpcode = 0x40;
+ static const BYTE kMaskRexW = 0x08;
+ static const BYTE kMaskRexR = 0x04;
+ static const BYTE kMaskRexX = 0x02;
+ static const BYTE kMaskRexB = 0x01;
+
+ // mod r/m bits
+ static const BYTE kRegFieldShift = 3;
+ static const BYTE kMaskMod = 0xC0;
+ static const BYTE kMaskReg = 0x38;
+ static const BYTE kMaskRm = 0x07;
+ static const BYTE kRmNeedSib = 0x04;
+ static const BYTE kModReg = 0xC0;
+ static const BYTE kModDisp32 = 0x80;
+ static const BYTE kModDisp8 = 0x40;
+ static const BYTE kModNoRegDisp = 0x00;
+ static const BYTE kRmNoRegDispDisp32 = 0x05;
+
+ // sib bits
+ static const BYTE kMaskSibScale = 0xC0;
+ static const BYTE kMaskSibIndex = 0x38;
+ static const BYTE kMaskSibBase = 0x07;
+ static const BYTE kSibBaseEbp = 0x05;
+
+ // Register bit IDs.
+ static const BYTE kRegAx = 0x0;
+ static const BYTE kRegCx = 0x1;
+ static const BYTE kRegDx = 0x2;
+ static const BYTE kRegBx = 0x3;
+ static const BYTE kRegSp = 0x4;
+ static const BYTE kRegBp = 0x5;
+ static const BYTE kRegSi = 0x6;
+ static const BYTE kRegDi = 0x7;
+
+ // Special ModR/M codes. These indicate operands that cannot be simply
+ // memcpy-ed.
+ // Operand is a 64-bit RIP-relative address.
+ static const int kModOperand64 = -2;
+ // Operand is not yet handled by our trampoline.
+ static const int kModUnknown = -1;
+
+ /**
+ * Returns the number of bytes taken by the ModR/M byte, SIB (if present)
+ * and the instruction's operand. In special cases, the special MODRM codes
+ * above are returned.
+ * aModRm points to the ModR/M byte of the instruction.
+ * On return, aSubOpcode (if present) is filled with the subopcode/register
+ * code found in the ModR/M byte.
+ */
+ int CountModRmSib(const ReadOnlyTargetFunction<MMPolicyT>& aModRm,
+ BYTE* aSubOpcode = nullptr) {
+ int numBytes = 1; // Start with 1 for mod r/m byte itself
+ switch (*aModRm & kMaskMod) {
+ case kModReg:
+ return numBytes;
+ case kModDisp8:
+ numBytes += 1;
+ break;
+ case kModDisp32:
+ numBytes += 4;
+ break;
+ case kModNoRegDisp:
+ if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32) {
+# if defined(_M_X64)
+ if (aSubOpcode) {
+ *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
+ }
+ return kModOperand64;
+# else
+ // On IA-32, all ModR/M instruction modes address memory relative to 0
+ numBytes += 4;
+# endif
+ } else if (((*aModRm & kMaskRm) == kRmNeedSib &&
+ (*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) {
+ numBytes += 4;
+ }
+ break;
+ default:
+ // This should not be reachable
+ MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits");
+ return kModUnknown;
+ }
+ if ((*aModRm & kMaskRm) == kRmNeedSib) {
+ // SIB byte
+ numBytes += 1;
+ }
+ if (aSubOpcode) {
+ *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
+ }
+ return numBytes;
+ }
+
+# if defined(_M_X64)
+ enum class JumpType{Je, Jne, Jae, Jmp, Call};
+
+ static bool GenerateJump(Trampoline<MMPolicyT>& aTramp,
+ uintptr_t aAbsTargetAddress, const JumpType aType) {
+ // Near call, absolute indirect, address given in r/m32
+ if (aType == JumpType::Call) {
+ // CALL [RIP+0]
+ aTramp.WriteByte(0xff);
+ aTramp.WriteByte(0x15);
+ // The offset to jump destination -- 2 bytes after the current position.
+ aTramp.WriteInteger(2);
+ aTramp.WriteByte(0xeb); // JMP + 8 (jump over target address)
+ aTramp.WriteByte(8);
+ aTramp.WritePointer(aAbsTargetAddress);
+ return !!aTramp;
+ }
+
+ // Write an opposite conditional jump because the destination branches
+ // are swapped.
+ if (aType == JumpType::Je) {
+ // JNE RIP+14
+ aTramp.WriteByte(0x75);
+ aTramp.WriteByte(14);
+ } else if (aType == JumpType::Jne) {
+ // JE RIP+14
+ aTramp.WriteByte(0x74);
+ aTramp.WriteByte(14);
+ } else if (aType == JumpType::Jae) {
+ // JB RIP+14
+ aTramp.WriteByte(0x72);
+ aTramp.WriteByte(14);
+ }
+
+ // Near jmp, absolute indirect, address given in r/m32
+ // JMP [RIP+0]
+ aTramp.WriteByte(0xff);
+ aTramp.WriteByte(0x25);
+ // The offset to jump destination is 0
+ aTramp.WriteInteger(0);
+ aTramp.WritePointer(aAbsTargetAddress);
+
+ return !!aTramp;
+ }
+# endif
+
+ enum ePrefixGroupBits{eNoPrefixes = 0, ePrefixGroup1 = (1 << 0),
+ ePrefixGroup2 = (1 << 1), ePrefixGroup3 = (1 << 2),
+ ePrefixGroup4 = (1 << 3)};
+
+ int CountPrefixBytes(const ReadOnlyTargetFunction<MMPolicyT>& aBytes,
+ unsigned char* aOutGroupBits) {
+ unsigned char& groupBits = *aOutGroupBits;
+ groupBits = eNoPrefixes;
+ int index = 0;
+ while (true) {
+ switch (aBytes[index]) {
+ // Group 1
+ case 0xF0: // LOCK
+ case 0xF2: // REPNZ
+ case 0xF3: // REP / REPZ
+ if (groupBits & ePrefixGroup1) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup1;
+ ++index;
+ break;
+
+ // Group 2
+ case 0x2E: // CS override / branch not taken
+ case 0x36: // SS override
+ case 0x3E: // DS override / branch taken
+ case 0x64: // FS override
+ case 0x65: // GS override
+ if (groupBits & ePrefixGroup2) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup2;
+ ++index;
+ break;
+
+ // Group 3
+ case 0x66: // operand size override
+ if (groupBits & ePrefixGroup3) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup3;
+ ++index;
+ break;
+
+ // Group 4
+ case 0x67: // Address size override
+ if (groupBits & ePrefixGroup4) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup4;
+ ++index;
+ break;
+
+ default:
+ return index;
+ }
+ }
+ }
+
+ // Return a ModR/M byte made from the 2 Mod bits, the register used for the
+ // reg bits and the register used for the R/M bits.
+ BYTE BuildModRmByte(BYTE aModBits, BYTE aReg, BYTE aRm) {
+ MOZ_ASSERT((aRm & kMaskRm) == aRm);
+ MOZ_ASSERT((aModBits & kMaskMod) == aModBits);
+ MOZ_ASSERT(((aReg << kRegFieldShift) & kMaskReg) ==
+ (aReg << kRegFieldShift));
+ return aModBits | (aReg << kRegFieldShift) | aRm;
+ }
+
+#endif // !defined(_M_ARM64)
+
+ // If originalFn is a recognized trampoline then patch it to call aDest,
+ // set *aTramp and *aOutTramp to that trampoline's target and return true.
+ bool PatchIfTargetIsRecognizedTrampoline(
+ Trampoline<MMPolicyT>& aTramp,
+ ReadOnlyTargetFunction<MMPolicyT>& aOriginalFn, intptr_t aDest,
+ void** aOutTramp) {
+#if defined(_M_X64)
+ // Variation 1:
+ // 48 b8 imm64 mov rax, imm64
+ // ff e0 jmp rax
+ //
+ // Variation 2:
+ // 48 b8 imm64 mov rax, imm64
+ // 50 push rax
+ // c3 ret
+ if ((aOriginalFn[0] == 0x48) && (aOriginalFn[1] == 0xB8) &&
+ ((aOriginalFn[10] == 0xFF && aOriginalFn[11] == 0xE0) ||
+ (aOriginalFn[10] == 0x50 && aOriginalFn[11] == 0xC3))) {
+ uintptr_t originalTarget =
+ (aOriginalFn + 2).template ChasePointer<uintptr_t>();
+
+ // Skip the first two bytes (48 b8) so that we can overwrite the imm64
+ WritableTargetFunction<MMPolicyT> target(aOriginalFn.Promote(8, 2));
+ if (!target) {
+ return false;
+ }
+
+ // Write the new JMP target address.
+ target.WritePointer(aDest);
+ if (!target.Commit()) {
+ return false;
+ }
+
+ // Store the old target address so we can restore it when we're cleared
+ aTramp.WritePointer(originalTarget);
+ if (!aTramp) {
+ return false;
+ }
+
+ *aOutTramp = reinterpret_cast<void*>(originalTarget);
+ return true;
+ }
+#endif // defined(_M_X64)
+
+ return false;
+ }
+
+#if defined(_M_ARM64)
+ bool Apply4BytePatch(TrampPoolT* aTrampPool, void* aTrampPtr,
+ WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+ MOZ_ASSERT(aTrampPool);
+ if (!aTrampPool) {
+ return false;
+ }
+
+ uintptr_t hookDest = arm64::MakeVeneer(*aTrampPool, aTrampPtr, aDest);
+ if (!hookDest) {
+ return false;
+ }
+
+ Maybe<uint32_t> branchImm = arm64::BuildUnconditionalBranchImm(
+ target.GetCurrentAddress(), hookDest);
+ if (!branchImm) {
+ return false;
+ }
+
+ target.WriteLong(branchImm.value());
+
+ return true;
+ }
+#endif // defined(_M_ARM64)
+
+#if defined(_M_X64)
+ bool Apply10BytePatch(TrampPoolT* aTrampPool, void* aTrampPtr,
+ WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+ // 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.
+ Maybe<Trampoline<MMPolicyT>> maybeCallTramp(
+ aTrampPool->GetNextTrampoline());
+ if (!maybeCallTramp) {
+ return false;
+ }
+
+ Trampoline<MMPolicyT> callTramp(std::move(maybeCallTramp.ref()));
+
+ // 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(aTrampPtr);
+ 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 false;
+ }
+
+ target.WriteByte(0xB8); // MOV EAX, IMM32
+
+ // Assert that the topmost 33 bits are 0
+ MOZ_ASSERT(
+ !(reinterpret_cast<uintptr_t>(callTrampStart) & (~0x7FFFFFFFULL)));
+
+ target.WriteLong(static_cast<uint32_t>(
+ reinterpret_cast<uintptr_t>(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
+
+ return true;
+ }
+#endif // defined(_M_X64)
+
+ void CreateTrampoline(ReadOnlyTargetFunction<MMPolicyT>& origBytes,
+ TrampPoolT* aTrampPool, Trampoline<MMPolicyT>& aTramp,
+ intptr_t aDest, void** aOutTramp) {
+ *aOutTramp = nullptr;
+
+ Trampoline<MMPolicyT>& tramp = aTramp;
+ if (!tramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_INVALID_TRAMPOLINE);
+ return;
+ }
+
+ // The beginning of the trampoline contains two pointer-width slots:
+ // [0]: |this|, so that we know whether the trampoline belongs to us;
+ // [1]: Pointer to original function, so that we can reset the hooked
+ // function to its original behavior upon destruction. In rare cases
+ // where the function was already a different trampoline, this is
+ // just a pointer to that trampoline's target address.
+ tramp.WriteEncodedPointer(this);
+ if (!tramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_WRITE_POINTER_ERROR);
+ return;
+ }
+
+ auto clearInstanceOnFailure = MakeScopeExit([this, aOutTramp, &tramp,
+ &origBytes]() -> void {
+ // *aOutTramp is not set until CreateTrampoline has completed
+ // successfully, so we can use that to check for success.
+ if (*aOutTramp) {
+ return;
+ }
+
+ // Clear the instance pointer so that we don't try to reset a
+ // nonexistent hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+
+#if defined(NIGHTLY_BUILD)
+ origBytes.Rewind();
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR);
+ DetourError& lastError = *this->mVMPolicy.mLastError;
+ size_t bytesToCapture = std::min(
+ ArrayLength(lastError.mOrigBytes),
+ static_cast<size_t>(PrimitiveT::GetWorstCaseRequiredBytesToPatch()));
+# if defined(_M_ARM64)
+ size_t numInstructionsToCapture = bytesToCapture / sizeof(uint32_t);
+ auto origBytesDst = reinterpret_cast<uint32_t*>(lastError.mOrigBytes);
+ for (size_t i = 0; i < numInstructionsToCapture; ++i) {
+ origBytesDst[i] = origBytes.ReadNextInstruction();
+ }
+# else
+ for (size_t i = 0; i < bytesToCapture; ++i) {
+ lastError.mOrigBytes[i] = origBytes[i];
+ }
+# endif // defined(_M_ARM64)
+#else
+ // Silence -Wunused-lambda-capture in non-Nightly.
+ Unused << this;
+ Unused << origBytes;
+#endif // defined(NIGHTLY_BUILD)
+ });
+
+ tramp.WritePointer(origBytes.AsEncodedPtr());
+ if (!tramp) {
+ return;
+ }
+
+ if (PatchIfTargetIsRecognizedTrampoline(tramp, origBytes, aDest,
+ aOutTramp)) {
+ return;
+ }
+
+ tramp.StartExecutableCode();
+
+ constexpr uint32_t kWorstCaseBytesRequired =
+ PrimitiveT::GetWorstCaseRequiredBytesToPatch();
+
+#if defined(_M_IX86)
+ int pJmp32 = -1;
+ while (origBytes.GetOffset() < kWorstCaseBytesRequired) {
+ // Understand some simple instructions that might be found in a
+ // prologue; we might need to extend this as necessary.
+ //
+ // Note! If we ever need to understand jump instructions, we'll
+ // need to rewrite the displacement argument.
+ unsigned char prefixGroups;
+ int numPrefixBytes = CountPrefixBytes(origBytes, &prefixGroups);
+ if (numPrefixBytes < 0 ||
+ (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
+ // Either the prefix sequence was bad, or there are prefixes that
+ // we don't currently support (groups 3 and 4)
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+
+ origBytes += numPrefixBytes;
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various MOVs
+ ++origBytes;
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ origBytes += len;
+ } else if (*origBytes == 0x0f &&
+ (origBytes[1] == 0x10 || origBytes[1] == 0x11)) {
+ // SSE: movups xmm, xmm/m128
+ // movups xmm/m128, xmm
+ origBytes += 2;
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ origBytes += len;
+ } else if (*origBytes == 0xA1) {
+ // MOV eax, [seg:offset]
+ origBytes += 5;
+ } else if (*origBytes == 0xB8) {
+ // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
+ origBytes += 5;
+ } else if (*origBytes == 0x33 && (origBytes[1] & kMaskMod) == kModReg) {
+ // XOR r32, r32
+ origBytes += 2;
+ } else if ((*origBytes & 0xf8) == 0x40) {
+ // INC r32
+ origBytes += 1;
+ } else if (*origBytes == 0x83) {
+ uint8_t mod = static_cast<uint8_t>(origBytes[1]) & kMaskMod;
+ uint8_t rm = static_cast<uint8_t>(origBytes[1]) & kMaskRm;
+ if (mod == kModReg) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
+ origBytes += 3;
+ } else if (mod == kModDisp8 && rm != kRmNeedSib) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP [r+disp8], imm8
+ origBytes += 4;
+ } else {
+ // bail
+ MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x68) {
+ // PUSH with 4-byte operand
+ origBytes += 5;
+ } else if ((*origBytes & 0xf0) == 0x50) {
+ // 1-byte PUSH/POP
+ ++origBytes;
+ } else if (*origBytes == 0x6A) {
+ // PUSH imm8
+ origBytes += 2;
+ } else if (*origBytes == 0xe9) {
+ pJmp32 = origBytes.GetOffset();
+ // jmp 32bit offset
+ origBytes += 5;
+ } else if (*origBytes == 0xff && origBytes[1] == 0x25) {
+ // jmp [disp32]
+ origBytes += 6;
+ } else if (*origBytes == 0xc2) {
+ // ret imm16. We can't handle this but it happens. We don't ASSERT but
+ // we do fail to hook.
+# if defined(MOZILLA_INTERNAL_API)
+ NS_WARNING("Cannot hook method -- RET opcode found");
+# endif
+ return;
+ } else {
+ // printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n",
+ // *origBytes);
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ }
+
+ // The trampoline is a copy of the instructions that we just traced,
+ // followed by a jump that we add below.
+ tramp.CopyFrom(origBytes.GetBaseAddress(), origBytes.GetOffset());
+ if (!tramp) {
+ return;
+ }
+#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.value() & DetourFlags::eTestOnlyForceShortPatch) ==
+ DetourFlags::eTestOnlyForceShortPatch;
+ const uint32_t bytesRequired =
+ use10BytePatch ? 10 : kWorstCaseBytesRequired;
+
+ while (origBytes.GetOffset() < bytesRequired) {
+ // 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
+ // instructions after CALL and JE, I don't think I need that.
+ // The only real value of this condition is that if code follows a JMP
+ // then its _probably_ the target of a JMP somewhere else and we
+ // will be overwriting it, which would be tragic. This seems
+ // highly unlikely.
+ if (foundJmp) {
+ if (*origBytes == 0x90 || *origBytes == 0xcc) {
+ ++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 (aTrampPool && aTrampPool->IsInLowest2GB() &&
+ origBytes.GetOffset() >= 10) {
+ use10BytePatch = true;
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
+ return;
+ }
+ if (*origBytes == 0x0f) {
+ COPY_CODES(1);
+ if (*origBytes == 0x1f) {
+ // nop (multibyte)
+ COPY_CODES(1);
+ if ((*origBytes & 0xc0) == 0x40 && (*origBytes & 0x7) == 0x04) {
+ COPY_CODES(3);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x05) {
+ // syscall
+ COPY_CODES(1);
+ } else if (*origBytes == 0x10 || *origBytes == 0x11) {
+ // SSE: movups xmm, xmm/m128
+ // movups xmm/m128, xmm
+ COPY_CODES(1);
+ int nModRmSibBytes = CountModRmSib(origBytes);
+ if (nModRmSibBytes < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ } else {
+ COPY_CODES(nModRmSibBytes);
+ }
+ } else if (*origBytes >= 0x83 && *origBytes <= 0x85) {
+ // 0f 83 cd JAE rel32
+ // 0f 84 cd JE rel32
+ // 0f 85 cd JNE rel32
+ const JumpType kJumpTypes[] = {JumpType::Jae, JumpType::Je,
+ JumpType::Jne};
+ auto jumpType = kJumpTypes[*origBytes - 0x83];
+ ++origBytes;
+ --tramp; // overwrite the 0x0f we copied above
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ jumpType)) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various 32-bit MOVs
+ COPY_CODES(1);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else if (*origBytes == 0x40 || *origBytes == 0x41) {
+ // Plain REX or REX.B
+ COPY_CODES(1);
+ if ((*origBytes & 0xf0) == 0x50) {
+ // push/pop with Rx register
+ COPY_CODES(1);
+ } else if (*origBytes >= 0xb8 && *origBytes <= 0xbf) {
+ // mov r32, imm32
+ COPY_CODES(5);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x44) {
+ // REX.R
+ COPY_CODES(1);
+
+ // TODO: Combine with the "0x89" case below in the REX.W section
+ if (*origBytes == 0x89) {
+ // mov r/m32, r32
+ COPY_CODES(1);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x45) {
+ // REX.R & REX.B
+ COPY_CODES(1);
+
+ if (*origBytes == 0x33) {
+ // xor r32, r32
+ COPY_CODES(2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if ((*origBytes & 0xfa) == 0x48) {
+ // REX.W | REX.WR | REX.WRB | REX.WB
+ COPY_CODES(1);
+
+ if (*origBytes == 0x81 && (origBytes[1] & 0xf8) == 0xe8) {
+ // sub r, dword
+ COPY_CODES(6);
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0xe8) {
+ // sub r, byte
+ COPY_CODES(3);
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & (kMaskMod | kMaskReg)) == kModReg) {
+ // add r, byte
+ COPY_CODES(3);
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0x60) {
+ // and [r+d], imm8
+ COPY_CODES(5);
+ } else if (*origBytes == 0x2b && (origBytes[1] & kMaskMod) == kModReg) {
+ // sub r64, r64
+ COPY_CODES(2);
+ } else if (*origBytes == 0x85) {
+ // 85 /r => TEST r/m32, r32
+ if ((origBytes[1] & 0xc0) == 0xc0) {
+ COPY_CODES(2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if ((*origBytes & 0xfd) == 0x89) {
+ // MOV r/m64, r64 | MOV r64, r/m64
+ BYTE reg;
+ int len = CountModRmSib(origBytes + 1, &reg);
+ if (len < 0) {
+ MOZ_ASSERT(len == kModOperand64);
+ if (len != kModOperand64) {
+ return;
+ }
+ origBytes += 2; // skip the MOV and MOD R/M bytes
+
+ // The instruction MOVs 64-bit data from a RIP-relative memory
+ // address (determined with a 32-bit offset from RIP) into a
+ // 64-bit register.
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+
+ if (reg == kRegAx) {
+ // Destination is RAX. Encode instruction as MOVABS with a
+ // 64-bit absolute address as its immediate operand.
+ tramp.WriteByte(0xa1);
+ tramp.WritePointer(absAddr);
+ } else {
+ // The MOV must be done in two steps. First, we MOVABS the
+ // absolute 64-bit address into our target register.
+ // Then, we MOV from that address into the register
+ // using register-indirect addressing.
+ tramp.WriteByte(0xb8 + reg);
+ tramp.WritePointer(absAddr);
+ tramp.WriteByte(0x48);
+ tramp.WriteByte(0x8b);
+ tramp.WriteByte(BuildModRmByte(kModNoRegDisp, reg, reg));
+ }
+ } else {
+ COPY_CODES(len + 1);
+ }
+ } else if ((*origBytes & 0xf8) == 0xb8) {
+ // MOV r64, imm64
+ COPY_CODES(9);
+ } else if (*origBytes == 0xc7) {
+ // MOV r/m64, imm32
+ if (origBytes[1] == 0x44) {
+ // MOV [r64+disp8], imm32
+ // ModR/W + SIB + disp8 + imm32
+ COPY_CODES(8);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0xff) {
+ // JMP /4
+ if ((origBytes[1] & 0xc0) == 0x0 && (origBytes[1] & 0x07) == 0x5) {
+ origBytes += 2;
+ --tramp; // overwrite the REX.W/REX.RW we copied above
+
+ if (!GenerateJump(tramp, origBytes.ChasePointerFromDisp(),
+ JumpType::Jmp)) {
+ return;
+ }
+
+ foundJmp = true;
+ } else {
+ // not support yet!
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x8d) {
+ // LEA reg, addr
+ if ((origBytes[1] & kMaskMod) == 0x0 &&
+ (origBytes[1] & kMaskRm) == 0x5) {
+ // [rip+disp32]
+ // convert 32bit offset to 64bit direct and convert instruction
+ // to a simple 64-bit mov
+ BYTE reg = (origBytes[1] & kMaskReg) >> kRegFieldShift;
+ origBytes += 2;
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+ tramp.WriteByte(0xb8 + reg); // move
+ tramp.WritePointer(absAddr);
+ } else {
+ // Above we dealt with RIP-relative instructions. Any other
+ // operand form can simply be copied.
+ int len = CountModRmSib(origBytes + 1);
+ // We handled the kModOperand64 -- ie RIP-relative -- case above
+ MOZ_ASSERT(len > 0);
+ COPY_CODES(len + 1);
+ }
+ } else if (*origBytes == 0x63 && (origBytes[1] & kMaskMod) == kModReg) {
+ // movsxd r64, r32 (move + sign extend)
+ COPY_CODES(2);
+ } else {
+ // not support yet!
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x66) {
+ // operand override prefix
+ COPY_CODES(1);
+ // This is the same as the x86 version
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various MOVs
+ unsigned char b = origBytes[1];
+ if (((b & 0xc0) == 0xc0) ||
+ (((b & 0xc0) == 0x00) && ((b & 0x07) != 0x04) &&
+ ((b & 0x07) != 0x05))) {
+ // REG=r, R/M=r or REG=r, R/M=[r]
+ COPY_CODES(2);
+ } else if ((b & 0xc0) == 0x40) {
+ if ((b & 0x07) == 0x04) {
+ // REG=r, R/M=[SIB + disp8]
+ COPY_CODES(4);
+ } else {
+ // REG=r, R/M=[r + disp8]
+ COPY_CODES(3);
+ }
+ } else {
+ // complex MOV, bail
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x44 && origBytes[1] == 0x89) {
+ // mov word ptr [reg+disp8], reg
+ COPY_CODES(2);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ // no way to support this yet.
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ }
+ } else if ((*origBytes & 0xf0) == 0x50) {
+ // 1-byte push/pop
+ COPY_CODES(1);
+ } else if (*origBytes == 0x65) {
+ // GS prefix
+ //
+ // The entry of GetKeyState on Windows 10 has the following code.
+ // 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
+ // (GS prefix + REX + MOV (0x8b) ...)
+ if (origBytes[1] == 0x48 &&
+ (origBytes[2] >= 0x88 && origBytes[2] <= 0x8b)) {
+ COPY_CODES(3);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ // no way to support this yet.
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x80 && origBytes[1] == 0x3d) {
+ origBytes += 2;
+
+ // cmp byte ptr [rip-relative address], imm8
+ // We'll compute the absolute address and do the cmp in r11
+
+ // push r11 (to save the old value)
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x53);
+
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+
+ // mov r11, absolute address
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0xbb);
+ tramp.WritePointer(absAddr);
+
+ // cmp byte ptr [r11],...
+ tramp.WriteByte(0x41);
+ tramp.WriteByte(0x80);
+ tramp.WriteByte(0x3b);
+
+ // ...imm8
+ COPY_CODES(1);
+
+ // pop r11 (doesn't affect the flags from the cmp)
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x5b);
+ } else if (*origBytes == 0x90) {
+ // nop
+ COPY_CODES(1);
+ } else if ((*origBytes & 0xf8) == 0xb8) {
+ // MOV r32, imm32
+ COPY_CODES(5);
+ } else if (*origBytes == 0x33) {
+ // xor r32, r/m32
+ COPY_CODES(2);
+ } else if (*origBytes == 0xf6) {
+ // test r/m8, imm8 (used by ntdll on Windows 10 x64)
+ // (no flags are affected by near jmp since there is no task switch,
+ // so it is ok for a jmp to be written immediately after a test)
+ BYTE subOpcode = 0;
+ int nModRmSibBytes = CountModRmSib(origBytes + 1, &subOpcode);
+ if (nModRmSibBytes < 0 || subOpcode != 0) {
+ // Unsupported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(2 + nModRmSibBytes);
+ } else if (*origBytes == 0x85) {
+ // test r/m32, r32
+ int nModRmSibBytes = CountModRmSib(origBytes + 1);
+ if (nModRmSibBytes < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(1 + nModRmSibBytes);
+ } else if (*origBytes == 0xd1 && (origBytes[1] & kMaskMod) == kModReg) {
+ // bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
+ // (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
+ COPY_CODES(2);
+ } else if (*origBytes == 0x83 && (origBytes[1] & kMaskMod) == kModReg) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
+ COPY_CODES(3);
+ } else if (*origBytes == 0xc3) {
+ // ret
+ COPY_CODES(1);
+ } else if (*origBytes == 0xcc) {
+ // int 3
+ COPY_CODES(1);
+ } else if (*origBytes == 0xe8 || *origBytes == 0xe9) {
+ // CALL (0xe8) or JMP (0xe9) 32bit offset
+ foundJmp = *origBytes == 0xe9;
+ ++origBytes;
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ foundJmp ? JumpType::Jmp : JumpType::Call)) {
+ return;
+ }
+ } else if (*origBytes >= 0x73 && *origBytes <= 0x75) {
+ // 73 cb JAE rel8
+ // 74 cb JE rel8
+ // 75 cb JNE rel8
+ const JumpType kJumpTypes[] = {JumpType::Jae, JumpType::Je,
+ JumpType::Jne};
+ auto jumpType = kJumpTypes[*origBytes - 0x73];
+ uint8_t offset = origBytes[1];
+
+ origBytes += 2;
+
+ if (!GenerateJump(tramp, origBytes.OffsetToAbsolute(offset),
+ jumpType)) {
+ return;
+ }
+ } else if (*origBytes == 0xff) {
+ uint8_t mod = origBytes[1] & kMaskMod;
+ uint8_t reg = (origBytes[1] & kMaskReg) >> kRegFieldShift;
+ uint8_t rm = origBytes[1] & kMaskRm;
+ if (mod == kModReg && (reg == 0 || reg == 1 || reg == 2 || reg == 6)) {
+ // INC|DEC|CALL|PUSH r64
+ COPY_CODES(2);
+ } else if (mod == kModNoRegDisp && reg == 2 &&
+ rm == kRmNoRegDispDisp32) {
+ // FF 15 CALL [disp32]
+ origBytes += 2;
+ if (!GenerateJump(tramp, origBytes.ChasePointerFromDisp(),
+ JumpType::Call)) {
+ return;
+ }
+ } else if (reg == 4) {
+ // FF /4 (Opcode=ff, REG=4): JMP r/m
+ if (mod == kModNoRegDisp && rm == kRmNoRegDispDisp32) {
+ // FF 25 JMP [disp32]
+ foundJmp = true;
+
+ origBytes += 2;
+
+ uintptr_t jmpDest = origBytes.ChasePointerFromDisp();
+
+ if (!GenerateJump(tramp, jmpDest, JumpType::Jmp)) {
+ return;
+ }
+ } else {
+ // JMP r/m except JMP [disp32]
+ 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;
+ }
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0x60) {
+ // and [r+d], imm8
+ COPY_CODES(5);
+ } else if (*origBytes == 0xc6) {
+ // mov [r+d], imm8
+ int len = CountModRmSib(origBytes + 1);
+ if (len < 0) {
+ // RIP-relative not yet supported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len + 2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ }
+#elif defined(_M_ARM64)
+
+ // The number of bytes required to facilitate a detour depends on the
+ // proximity of the hook function to the target function. In the best case,
+ // we can branch within +/- 128MB of the current location, requiring only
+ // 4 bytes. In the worst case, we need 16 bytes to load an absolute address
+ // into a register and then branch to it.
+ const uint32_t bytesRequiredFromDecode =
+ (mFlags.value() & DetourFlags::eTestOnlyForceShortPatch)
+ ? 4
+ : kWorstCaseBytesRequired;
+
+ while (origBytes.GetOffset() < bytesRequiredFromDecode) {
+ uintptr_t curPC = origBytes.GetCurrentAbsolute();
+ uint32_t curInst = origBytes.ReadNextInstruction();
+
+ Result<arm64::LoadOrBranch, arm64::PCRelCheckError> pcRelInfo =
+ arm64::CheckForPCRel(curPC, curInst);
+ if (pcRelInfo.isErr()) {
+ if (pcRelInfo.unwrapErr() ==
+ arm64::PCRelCheckError::InstructionNotPCRel) {
+ // Instruction is not PC-relative, we can just copy it verbatim
+ tramp.WriteInstruction(curInst);
+ continue;
+ }
+
+ // At this point we have determined that there is no decoder available
+ // for the current, PC-relative, instruction.
+
+ // origBytes is now pointing one instruction past the one that we
+ // need the trampoline to jump back to.
+ if (!origBytes.BackUpOneInstruction()) {
+ return;
+ }
+
+ break;
+ }
+
+ // We need to load an absolute address into a particular register
+ tramp.WriteLoadLiteral(pcRelInfo.inspect().mAbsAddress,
+ pcRelInfo.inspect().mDestReg);
+ }
+
+#else
+# error "Unknown processor type"
+#endif
+
+ if (origBytes.GetOffset() > 100) {
+ // printf ("Too big!");
+ return;
+ }
+
+#if defined(_M_IX86)
+ if (pJmp32 >= 0) {
+ // Jump directly to the original target of the jump instead of jumping to
+ // the original function. Adjust jump target displacement to jump location
+ // in the trampoline.
+ tramp.AdjustDisp32AtOffset(pJmp32 + 1, origBytes.GetBaseAddress());
+ } else {
+ tramp.WriteByte(0xe9); // jmp
+ tramp.WriteDisp32(origBytes.GetAddress());
+ }
+#elif defined(_M_X64)
+ // If we found a Jmp, we don't need to add another instruction. However,
+ // if we found a _conditional_ jump or a CALL (or no control operations
+ // at all) then we still need to run the rest of aOriginalFunction.
+ if (!foundJmp) {
+ if (!GenerateJump(tramp, origBytes.GetAddress(), JumpType::Jmp)) {
+ return;
+ }
+ }
+#elif defined(_M_ARM64)
+ // Let's find out how many bytes we have available to us for patching
+ uint32_t numBytesForPatching = tramp.GetCurrentExecutableCodeLen();
+
+ if (!numBytesForPatching) {
+ // There's nothing we can do
+ return;
+ }
+
+ if (tramp.IsNull()) {
+ // Recursive case
+ HMODULE targetModule = nullptr;
+
+ if (numBytesForPatching < kWorstCaseBytesRequired) {
+ if (!::GetModuleHandleExW(
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCWSTR>(origBytes.GetBaseAddress()),
+ &targetModule)) {
+ return;
+ }
+ }
+
+ Maybe<TrampPoolT> maybeTrampPool = DoReserve(targetModule);
+ MOZ_ASSERT(maybeTrampPool);
+ if (!maybeTrampPool) {
+ return;
+ }
+
+ Maybe<Trampoline<MMPolicyT>> maybeRealTramp(
+ maybeTrampPool.ref().GetNextTrampoline());
+ if (!maybeRealTramp) {
+ return;
+ }
+
+ origBytes.Rewind();
+ CreateTrampoline(origBytes, maybeTrampPool.ptr(), maybeRealTramp.ref(),
+ aDest, aOutTramp);
+ return;
+ }
+
+ // Write the branch from the trampoline back to the original code
+
+ tramp.WriteLoadLiteral(origBytes.GetAddress(), 16);
+ tramp.WriteInstruction(arm64::BuildUnconditionalBranchToRegister(16));
+#else
+# error "Unsupported processor architecture"
+#endif
+
+ // The trampoline is now complete.
+ void* trampPtr = tramp.EndExecutableCode();
+ if (!trampPtr) {
+ return;
+ }
+
+#ifdef _M_X64
+ if constexpr (MMPolicyT::kSupportsUnwindInfo) {
+ DebugOnly<bool> unwindInfoAdded = tramp.AddUnwindInfo(
+ origBytes.GetBaseAddress(), origBytes.GetOffset());
+ MOZ_ASSERT(unwindInfoAdded);
+ }
+#endif // _M_X64
+
+ WritableTargetFunction<MMPolicyT> target(origBytes.Promote());
+ if (!target) {
+ return;
+ }
+
+ do {
+ // Now patch the original function.
+ // When we're instructed to apply a non-default patch, apply it and exit.
+ // If non-default patching fails, bail out, no fallback.
+ // Otherwise, we go straight to the default patch.
+
+#if defined(_M_X64)
+ if (use10BytePatch) {
+ if (!Apply10BytePatch(aTrampPool, trampPtr, target, aDest)) {
+ return;
+ }
+ break;
+ }
+#elif defined(_M_ARM64)
+ if (numBytesForPatching < kWorstCaseBytesRequired) {
+ if (!Apply4BytePatch(aTrampPool, trampPtr, target, aDest)) {
+ return;
+ }
+ break;
+ }
+#endif
+
+ PrimitiveT::ApplyDefaultPatch(target, aDest);
+ } while (false);
+
+ if (!target.Commit()) {
+ return;
+ }
+
+ // Output the trampoline, thus signalling that this call was a success
+ *aOutTramp = trampPtr;
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_PatcherDetour_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h b/toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h
new file mode 100644
index 0000000000..deee87e0f8
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h
@@ -0,0 +1,205 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_PatcherNopSpace_h
+#define mozilla_interceptor_PatcherNopSpace_h
+
+#if defined(_M_IX86)
+
+# include "mozilla/interceptor/PatcherBase.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename VMPolicy>
+class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy> {
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
+
+ // For remembering the addresses of functions we've patched.
+ mozilla::Vector<void*> mPatchedFns;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllNopSpacePatcher(Args&&... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
+
+ ~WindowsDllNopSpacePatcher() { Clear(); }
+
+ WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
+ WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
+ WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) =
+ delete;
+ WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
+
+ void Clear() {
+ // Restore the mov edi, edi to the beginning of each function we patched.
+
+ for (auto&& ptr : mPatchedFns) {
+ WritableTargetFunction<MMPolicyT> fn(
+ this->mVMPolicy, reinterpret_cast<uintptr_t>(ptr), sizeof(uint16_t));
+ if (!fn) {
+ continue;
+ }
+
+ // mov edi, edi
+ fn.CommitAndWriteShort(0xff8b);
+ }
+
+ mPatchedFns.clear();
+ }
+
+ /**
+ * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
+ * in our address space. There is a bug in Detours 2.x that causes it to
+ * patch at the wrong address when attempting to detour code that is already
+ * NOP space patched. This function is an effort to detect the presence of
+ * this NVIDIA code in our address space and disable NOP space patching if it
+ * is. We also check AppInit_DLLs since this is the mechanism that the Optimus
+ * drivers use to inject into our process.
+ */
+ static bool IsCompatible() {
+ // These DLLs are known to have bad interactions with this style of patching
+ const wchar_t* kIncompatibleDLLs[] = {L"detoured.dll", L"_etoured.dll",
+ L"nvd3d9wrap.dll", L"nvdxgiwrap.dll"};
+ // See if the infringing DLLs are already loaded
+ for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
+ if (GetModuleHandleW(kIncompatibleDLLs[i])) {
+ return false;
+ }
+ }
+ if (GetModuleHandleW(L"user32.dll")) {
+ // user32 is loaded but the infringing DLLs are not, assume we're safe to
+ // proceed.
+ return true;
+ }
+ // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
+ // won't be loaded once user32 is initialized.
+ HKEY hkey = NULL;
+ if (!RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0,
+ KEY_QUERY_VALUE, &hkey)) {
+ nsAutoRegKey key(hkey);
+ DWORD numBytes = 0;
+ const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
+ // Query for required buffer size
+ LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
+ nullptr, &numBytes);
+ mozilla::UniquePtr<wchar_t[]> data;
+ if (!status) {
+ // Allocate the buffer and query for the actual data
+ data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t));
+ status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
+ (LPBYTE)data.get(), &numBytes);
+ }
+ if (!status) {
+ // For each token, split up the filename components and then check the
+ // name of the file.
+ const wchar_t kDelimiters[] = L", ";
+ wchar_t* tokenContext = nullptr;
+ wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
+ while (token) {
+ wchar_t fname[_MAX_FNAME] = {0};
+ if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0, fname,
+ mozilla::ArrayLength(fname), nullptr, 0)) {
+ // nvinit.dll is responsible for bootstrapping the DLL injection, so
+ // that is the library that we check for here
+ const wchar_t kNvInitName[] = L"nvinit";
+ if (!_wcsnicmp(fname, kNvInitName,
+ mozilla::ArrayLength(kNvInitName))) {
+ return false;
+ }
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+ }
+ }
+ return true;
+ }
+
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
+ if (!IsCompatible()) {
+# if defined(MOZILLA_INTERNAL_API)
+ NS_WARNING("NOP space patching is unavailable for compatibility reasons");
+# endif
+ return false;
+ }
+
+ MOZ_ASSERT(aTargetFn);
+ if (!aTargetFn) {
+ return false;
+ }
+
+ ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
+ this->ResolveRedirectedAddress(aTargetFn));
+
+ if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
+ return false;
+ }
+
+ return mPatchedFns.append(
+ reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
+ }
+
+ bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
+ intptr_t aHookDest, void** aOrigFunc) {
+ // Ensure we can read and write starting at fn - 5 (for the long jmp we're
+ // going to write) and ending at fn + 2 (for the short jmp up to the long
+ // jmp). These bytes may span two pages with different protection.
+ WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5));
+ if (!writableFn) {
+ return false;
+ }
+
+ // Check that the 5 bytes before the function are NOP's or INT 3's,
+ const uint8_t nopOrBp[] = {0x90, 0xCC};
+ if (!writableFn.template VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) {
+ return false;
+ }
+
+ // ... and that the first 2 bytes of the function are mov(edi, edi).
+ // There are two ways to encode the same thing:
+ //
+ // 0x89 0xff == mov r/m, r
+ // 0x8b 0xff == mov r, r/m
+ //
+ // where "r" is register and "r/m" is register or memory.
+ // Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
+
+ // (These look backwards because little-endian)
+ const uint16_t possibleEncodings[] = {0xFF8B, 0xFF89};
+ if (!writableFn.template VerifyValuesAreOneOf<uint16_t, 1>(
+ possibleEncodings, 5)) {
+ return false;
+ }
+
+ // Write a long jump into the space above the function.
+ writableFn.WriteByte(0xe9); // jmp
+ if (!writableFn) {
+ return false;
+ }
+
+ writableFn.WriteDisp32(aHookDest); // target
+ if (!writableFn) {
+ return false;
+ }
+
+ // Set aOrigFunc here, because after this point, aHookDest might be called,
+ // and aHookDest might use the aOrigFunc pointer.
+ *aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() +
+ sizeof(uint16_t));
+
+ // Short jump up into our long jump.
+ return writableFn.CommitAndWriteShort(0xF9EB); // jmp $-5
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // defined(_M_IX86)
+
+#endif // mozilla_interceptor_PatcherNopSpace_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h b/toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h
new file mode 100644
index 0000000000..d45d031613
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h
@@ -0,0 +1,142 @@
+/* -*- 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_RangeMap_h
+#define mozilla_interceptor_RangeMap_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+#include <algorithm>
+
+namespace mozilla {
+namespace interceptor {
+
+/**
+ * This class maintains a vector of VMSharingPolicyUnique objects, sorted on
+ * the memory range that is used for reserving each object.
+ *
+ * This is used by VMSharingPolicyShared for creating and looking up VM regions
+ * that are within proximity of the applicable range.
+ *
+ * VMSharingPolicyUnique objects managed by this class are reused whenever
+ * possible. If no range is required, we just return the first available
+ * policy.
+ *
+ * If no range is required and no policies have yet been allocated, we create
+ * a new one with a null range as a default.
+ */
+template <typename MMPolicyT>
+class RangeMap final {
+ private:
+ /**
+ * This class is used as the comparison key for sorting and insertion.
+ */
+ class Range {
+ public:
+ constexpr Range() : mBase(0), mLimit(0) {}
+
+ explicit Range(const Maybe<Span<const uint8_t>>& aBounds)
+ : mBase(aBounds ? reinterpret_cast<const uintptr_t>(
+ MMPolicyT::GetLowerBound(aBounds.ref()))
+ : 0),
+ mLimit(aBounds ? reinterpret_cast<const uintptr_t>(
+ MMPolicyT::GetUpperBoundIncl(aBounds.ref()))
+ : 0) {}
+
+ Range& operator=(const Range&) = default;
+ Range(const Range&) = default;
+ Range(Range&&) = default;
+ Range& operator=(Range&&) = default;
+
+ bool operator<(const Range& aOther) const {
+ return mBase < aOther.mBase ||
+ (mBase == aOther.mBase && mLimit < aOther.mLimit);
+ }
+
+ bool Contains(const Range& aOther) const {
+ return mBase <= aOther.mBase && mLimit >= aOther.mLimit;
+ }
+
+ private:
+ uintptr_t mBase;
+ uintptr_t mLimit;
+ };
+
+ class PolicyInfo final : public Range {
+ public:
+ explicit PolicyInfo(const Range& aRange)
+ : Range(aRange),
+ mPolicy(MakeUnique<VMSharingPolicyUnique<MMPolicyT>>()) {}
+
+ PolicyInfo(const PolicyInfo&) = delete;
+ PolicyInfo& operator=(const PolicyInfo&) = delete;
+
+ PolicyInfo(PolicyInfo&& aOther) = default;
+ PolicyInfo& operator=(PolicyInfo&& aOther) = default;
+
+ VMSharingPolicyUnique<MMPolicyT>* GetPolicy() { return mPolicy.get(); }
+
+ private:
+ UniquePtr<VMSharingPolicyUnique<MMPolicyT>> mPolicy;
+ };
+
+ using VectorType = Vector<PolicyInfo, 0, InfallibleAllocPolicy>;
+
+ public:
+ constexpr RangeMap() : mPolicies(nullptr) {}
+
+ VMSharingPolicyUnique<MMPolicyT>* GetPolicy(
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ Range testRange(aBounds);
+
+ if (!mPolicies) {
+ mPolicies = new VectorType();
+ }
+
+ // If no bounds are specified, we just use the first available policy
+ if (!aBounds) {
+ if (mPolicies->empty()) {
+ if (!mPolicies->append(PolicyInfo(testRange))) {
+ return nullptr;
+ }
+ }
+
+ return GetFirstPolicy();
+ }
+
+ // mPolicies is sorted, so we search
+ auto itr =
+ std::lower_bound(mPolicies->begin(), mPolicies->end(), testRange);
+ if (itr != mPolicies->end() && itr->Contains(testRange)) {
+ return itr->GetPolicy();
+ }
+
+ itr = mPolicies->insert(itr, PolicyInfo(testRange));
+
+ MOZ_ASSERT(std::is_sorted(mPolicies->begin(), mPolicies->end()));
+
+ return itr->GetPolicy();
+ }
+
+ private:
+ VMSharingPolicyUnique<MMPolicyT>* GetFirstPolicy() {
+ MOZ_RELEASE_ASSERT(mPolicies && !mPolicies->empty());
+ return mPolicies->begin()->GetPolicy();
+ }
+
+ private:
+ VectorType* mPolicies;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_RangeMap_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h b/toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h
new file mode 100644
index 0000000000..40be1ad08b
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h
@@ -0,0 +1,1000 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_TargetFunction_h
+#define mozilla_interceptor_TargetFunction_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#include <memory>
+#include <type_traits>
+
+namespace mozilla {
+namespace interceptor {
+
+#if defined(_M_IX86)
+
+template <typename T>
+bool CommitAndWriteShortInternal(const T& aMMPolicy, void* aDest,
+ uint16_t aValue);
+
+template <>
+inline bool CommitAndWriteShortInternal<MMPolicyInProcess>(
+ const MMPolicyInProcess& aMMPolicy, void* aDest, uint16_t aValue) {
+ return aMMPolicy.WriteAtomic(aDest, aValue);
+}
+
+template <>
+inline bool CommitAndWriteShortInternal<MMPolicyOutOfProcess>(
+ const MMPolicyOutOfProcess& aMMPolicy, void* aDest, uint16_t aValue) {
+ return aMMPolicy.Write(aDest, &aValue, sizeof(uint16_t));
+}
+
+#endif // defined(_M_IX86)
+
+// Forward declaration
+template <typename MMPolicy>
+class ReadOnlyTargetFunction;
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS WritableTargetFunction final {
+ class AutoProtect final {
+ using ProtectParams = Tuple<uintptr_t, uint32_t>;
+
+ public:
+ explicit AutoProtect(const MMPolicy& aMMPolicy) : mMMPolicy(aMMPolicy) {}
+
+ AutoProtect(const MMPolicy& aMMPolicy, uintptr_t aAddr, size_t aNumBytes,
+ uint32_t aNewProt)
+ : mMMPolicy(aMMPolicy) {
+ const uint32_t pageSize = mMMPolicy.GetPageSize();
+ const uintptr_t limit = aAddr + aNumBytes - 1;
+ const uintptr_t limitPageNum = limit / pageSize;
+ const uintptr_t basePageNum = aAddr / pageSize;
+ const uintptr_t numPagesToChange = limitPageNum - basePageNum + 1;
+
+ // We'll use the base address of the page instead of aAddr
+ uintptr_t curAddr = basePageNum * pageSize;
+
+ // Now change the protection on each page
+ for (uintptr_t curPage = 0; curPage < numPagesToChange;
+ ++curPage, curAddr += pageSize) {
+ uint32_t prevProt;
+ if (!aMMPolicy.Protect(reinterpret_cast<void*>(curAddr), pageSize,
+ aNewProt, &prevProt)) {
+ Clear();
+ return;
+ }
+
+ // Save the previous protection for curAddr so that we can revert this
+ // in the destructor.
+ if (!mProtects.append(MakeTuple(curAddr, prevProt))) {
+ Clear();
+ return;
+ }
+ }
+ }
+
+ AutoProtect(AutoProtect&& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mProtects(std::move(aOther.mProtects)) {
+ aOther.mProtects.clear();
+ }
+
+ ~AutoProtect() { Clear(); }
+
+ explicit operator bool() const { return !mProtects.empty(); }
+
+ AutoProtect(const AutoProtect&) = delete;
+ AutoProtect& operator=(const AutoProtect&) = delete;
+ AutoProtect& operator=(AutoProtect&&) = delete;
+
+ private:
+ void Clear() {
+ const uint32_t pageSize = mMMPolicy.GetPageSize();
+ for (auto&& entry : mProtects) {
+ uint32_t prevProt;
+ DebugOnly<bool> ok =
+ mMMPolicy.Protect(reinterpret_cast<void*>(Get<0>(entry)), pageSize,
+ Get<1>(entry), &prevProt);
+ MOZ_ASSERT(ok);
+ }
+
+ mProtects.clear();
+ }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ // We include two entries of inline storage as that is most common in the
+ // worst case.
+ Vector<ProtectParams, 2> mProtects;
+ };
+
+ public:
+ /**
+ * Used to initialize an invalid WritableTargetFunction, thus signalling an
+ * error.
+ */
+ explicit WritableTargetFunction(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy),
+ mFunc(0),
+ mNumBytes(0),
+ mOffset(0),
+ mStartWriteOffset(0),
+ mAccumulatedStatus(false),
+ mProtect(aMMPolicy) {}
+
+ WritableTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc,
+ size_t aNumBytes)
+ : mMMPolicy(aMMPolicy),
+ mFunc(aFunc),
+ mNumBytes(aNumBytes),
+ mOffset(0),
+ mStartWriteOffset(0),
+ mAccumulatedStatus(true),
+ mProtect(aMMPolicy, aFunc, aNumBytes, PAGE_EXECUTE_READWRITE) {}
+
+ WritableTargetFunction(WritableTargetFunction&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mFunc(aOther.mFunc),
+ mNumBytes(aOther.mNumBytes),
+ mOffset(aOther.mOffset),
+ mStartWriteOffset(aOther.mStartWriteOffset),
+ mLocalBytes(std::move(aOther.mLocalBytes)),
+ mAccumulatedStatus(aOther.mAccumulatedStatus),
+ mProtect(std::move(aOther.mProtect)) {
+ aOther.mAccumulatedStatus = false;
+ }
+
+ ~WritableTargetFunction() {
+ MOZ_ASSERT(mLocalBytes.empty(), "Did you forget to call Commit?");
+ }
+
+ WritableTargetFunction(const WritableTargetFunction&) = delete;
+ WritableTargetFunction& operator=(const WritableTargetFunction&) = delete;
+ WritableTargetFunction& operator=(WritableTargetFunction&&) = delete;
+
+ /**
+ * @return true if data was successfully committed.
+ */
+ bool Commit() {
+ if (!(*this)) {
+ return false;
+ }
+
+ if (mLocalBytes.empty()) {
+ // Nothing to commit, treat like success
+ return true;
+ }
+
+ bool ok =
+ mMMPolicy.Write(reinterpret_cast<void*>(mFunc + mStartWriteOffset),
+ mLocalBytes.begin(), mLocalBytes.length());
+ if (!ok) {
+ return false;
+ }
+
+ mMMPolicy.FlushInstructionCache();
+
+ mStartWriteOffset += mLocalBytes.length();
+
+ mLocalBytes.clear();
+ return true;
+ }
+
+ explicit operator bool() const { return mProtect && mAccumulatedStatus; }
+
+ void WriteByte(const uint8_t& aValue) {
+ if (!mLocalBytes.append(aValue)) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint8_t);
+ }
+
+ Maybe<uint8_t> ReadByte() {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uint8_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uint8_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uint8_t);
+ mStartWriteOffset += sizeof(uint8_t);
+ return Some(value);
+ }
+
+ Maybe<uintptr_t> 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<const void*>(mFunc + mOffset),
+ sizeof(uintptr_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uintptr_t);
+ mStartWriteOffset += sizeof(uintptr_t);
+ return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(value));
+ }
+
+ Maybe<uint32_t> 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<const void*>(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<const uint8_t*>(&aValue),
+ sizeof(uint16_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint16_t);
+ }
+
+#if defined(_M_IX86)
+ public:
+ /**
+ * Commits any dirty writes, and then writes a short, atomically if possible.
+ * This call may succeed in both inproc and outproc cases, but atomicity
+ * is only guaranteed in the inproc case.
+ */
+ bool CommitAndWriteShort(const uint16_t aValue) {
+ // First, commit everything that has been written until now
+ if (!Commit()) {
+ return false;
+ }
+
+ // Now immediately write the short, atomically if inproc
+ bool ok = CommitAndWriteShortInternal(
+ mMMPolicy, reinterpret_cast<void*>(mFunc + mStartWriteOffset), aValue);
+ if (!ok) {
+ return false;
+ }
+
+ mMMPolicy.FlushInstructionCache();
+ mStartWriteOffset += sizeof(uint16_t);
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ void WriteDisp32(const uintptr_t aAbsTarget) {
+ intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
+ static_cast<intptr_t>(mFunc + mOffset + sizeof(int32_t));
+
+ CheckedInt<int32_t> checkedDisp(diff);
+ MOZ_ASSERT(checkedDisp.isValid());
+ if (!checkedDisp.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ int32_t disp = checkedDisp.value();
+ if (!mLocalBytes.append(reinterpret_cast<uint8_t*>(&disp),
+ sizeof(int32_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(int32_t);
+ }
+
+#if defined(_M_X64) || defined(_M_ARM64)
+ void WriteLong(const uint32_t aValue) {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&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<const uint8_t*>(&aAbsTarget),
+ sizeof(uintptr_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uintptr_t);
+ }
+
+ /**
+ * @param aValues N-sized array of type T that specifies the set of values
+ * that are permissible in the first M bytes of the target
+ * function at aOffset.
+ * @return true if M values of type T in the function are members of the
+ * set specified by aValues.
+ */
+ template <typename T, size_t M, size_t N>
+ bool VerifyValuesAreOneOf(const T (&aValues)[N], const uint8_t aOffset = 0) {
+ T buf[M];
+ if (!mMMPolicy.Read(
+ buf, reinterpret_cast<const void*>(mFunc + mOffset + aOffset),
+ M * sizeof(T))) {
+ return false;
+ }
+
+ for (auto&& fnValue : buf) {
+ bool match = false;
+ for (auto&& testValue : aValues) {
+ match |= (fnValue == testValue);
+ }
+
+ if (!match) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ uintptr_t GetCurrentAddress() const { return mFunc + mOffset; }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ const uintptr_t mFunc;
+ const size_t mNumBytes;
+ uint32_t mOffset;
+ uint32_t mStartWriteOffset;
+
+ // In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
+ // to match the minimum bytes that we need to write in in order to patch the
+ // target function. Since the actual opcodes will often require us to pull in
+ // extra bytes above that minimum, we set the inline storage to be larger than
+ // those minima in an effort to give the Vector extra wiggle room before it
+ // needs to touch the heap.
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ static const size_t kInlineStorage = 32;
+#endif
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ bool mAccumulatedStatus;
+ AutoProtect mProtect;
+};
+
+template <typename MMPolicy>
+class ReadOnlyTargetBytes {
+ public:
+ ReadOnlyTargetBytes(const MMPolicy& aMMPolicy, const void* aBase)
+ : mMMPolicy(aMMPolicy), mBase(reinterpret_cast<const uint8_t*>(aBase)) {}
+
+ ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase) {}
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
+ const uint32_t aOffsetFromOther = 0)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase + aOffsetFromOther) {}
+
+ void EnsureLimit(uint32_t aDesiredLimit) {
+ // In the out-proc case we use this function to read the target function's
+ // bytes in the other process into a local buffer. We don't need that for
+ // the in-process case because we already have direct access to our target
+ // function's bytes.
+ }
+
+ uint32_t TryEnsureLimit(uint32_t aDesiredLimit) {
+ // Same as EnsureLimit above. We don't need to ensure for the in-process.
+ return aDesiredLimit;
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ if (!aOffset) {
+ return true;
+ }
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
+ uintptr_t adjusted = base + aOffset;
+ uint32_t pageSize = mMMPolicy.GetPageSize();
+
+ // If |adjusted| is within the same page as |mBase|, we're still valid
+ if ((base / pageSize) == (adjusted / pageSize)) {
+ return true;
+ }
+
+ // Otherwise, let's query |adjusted|
+ return mMMPolicy.IsPageAccessible(adjusted);
+ }
+
+ /**
+ * This returns a pointer to a *potentially local copy* of the target
+ * function's bytes. The returned pointer should not be used for any
+ * pointer arithmetic relating to the target function.
+ */
+ const uint8_t* GetLocalBytes() const { return mBase; }
+
+ /**
+ * This returns a pointer to the target function's bytes. The returned pointer
+ * may possibly belong to another process, so while it should be used for
+ * pointer arithmetic, it *must not* be dereferenced.
+ */
+ uintptr_t GetBase() const { return reinterpret_cast<uintptr_t>(mBase); }
+
+ const MMPolicy& GetMMPolicy() const { return mMMPolicy; }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+ private:
+ const MMPolicy& mMMPolicy;
+ uint8_t const* const mBase;
+};
+
+template <>
+class ReadOnlyTargetBytes<MMPolicyOutOfProcess> {
+ public:
+ ReadOnlyTargetBytes(const MMPolicyOutOfProcess& aMMPolicy, const void* aBase)
+ : mMMPolicy(aMMPolicy), mBase(reinterpret_cast<const uint8_t*>(aBase)) {}
+
+ ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mLocalBytes(std::move(aOther.mLocalBytes)),
+ mBase(aOther.mBase) {}
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase) {
+ Unused << mLocalBytes.appendAll(aOther.mLocalBytes);
+ }
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
+ const uint32_t aOffsetFromOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase + aOffsetFromOther) {
+ if (aOffsetFromOther >= aOther.mLocalBytes.length()) {
+ return;
+ }
+
+ Unused << mLocalBytes.append(aOther.mLocalBytes.begin() + aOffsetFromOther,
+ aOther.mLocalBytes.end());
+ }
+
+ void EnsureLimit(uint32_t aDesiredLimit) {
+ size_t prevSize = mLocalBytes.length();
+ if (aDesiredLimit < prevSize) {
+ return;
+ }
+
+ size_t newSize = aDesiredLimit + 1;
+ if (newSize < kInlineStorage) {
+ // Always try to read as much memory as we can at once
+ newSize = kInlineStorage;
+ }
+
+ bool resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ bool ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
+ newSize - prevSize);
+ if (ok) {
+ return;
+ }
+
+ // We couldn't pull more bytes than needed (which may happen if those extra
+ // bytes are not accessible). In this case, we try just to get the bare
+ // minimum.
+ newSize = aDesiredLimit + 1;
+ resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
+ newSize - prevSize);
+ MOZ_RELEASE_ASSERT(ok);
+ }
+
+ // This function tries to ensure as many bytes as possible up to
+ // |aDesiredLimit| bytes, returning how many bytes were actually ensured.
+ // As EnsureLimit does, we allocate an extra byte in local to make sure
+ // mLocalBytes always has at least one byte even though the target memory
+ // was inaccessible at all.
+ uint32_t TryEnsureLimit(uint32_t aDesiredLimit) {
+ size_t prevSize = mLocalBytes.length();
+ if (aDesiredLimit < prevSize) {
+ return aDesiredLimit;
+ }
+
+ size_t newSize = aDesiredLimit;
+ if (newSize < kInlineStorage) {
+ // Always try to read as much memory as we can at once
+ newSize = kInlineStorage;
+ }
+
+ bool resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ size_t bytesRead = mMMPolicy.TryRead(&mLocalBytes[prevSize],
+ mBase + prevSize, newSize - prevSize);
+
+ newSize = prevSize + bytesRead;
+
+ resizeOk = mLocalBytes.resize(newSize + 1);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ mLocalBytes[newSize] = 0;
+ return newSize;
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ if (!aOffset) {
+ return true;
+ }
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
+ uintptr_t adjusted = base + aOffset;
+ uint32_t pageSize = mMMPolicy.GetPageSize();
+
+ // If |adjusted| is within the same page as |mBase|, we're still valid
+ if ((base / pageSize) == (adjusted / pageSize)) {
+ return true;
+ }
+
+ // Otherwise, let's query |adjusted|
+ return mMMPolicy.IsPageAccessible(adjusted);
+ }
+
+ /**
+ * This returns a pointer to a *potentially local copy* of the target
+ * function's bytes. The returned pointer should not be used for any
+ * pointer arithmetic relating to the target function.
+ */
+ const uint8_t* GetLocalBytes() const {
+ if (mLocalBytes.empty()) {
+ return nullptr;
+ }
+
+ return mLocalBytes.begin();
+ }
+
+ /**
+ * This returns a pointer to the target function's bytes. The returned pointer
+ * may possibly belong to another process, so while it should be used for
+ * pointer arithmetic, it *must not* be dereferenced.
+ */
+ uintptr_t GetBase() const { return reinterpret_cast<uintptr_t>(mBase); }
+
+ const MMPolicyOutOfProcess& GetMMPolicy() const { return mMMPolicy; }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+ private:
+ // In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
+ // to match the minimum bytes that we need to write in in order to patch the
+ // target function. Since the actual opcodes will often require us to pull in
+ // extra bytes above that minimum, we set the inline storage to be larger than
+ // those minima in an effort to give the Vector extra wiggle room before it
+ // needs to touch the heap.
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ static const size_t kInlineStorage = 32;
+#endif
+
+ const MMPolicyOutOfProcess& mMMPolicy;
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ uint8_t const* const mBase;
+};
+
+template <typename MMPolicy>
+class TargetBytesPtr {
+ public:
+ typedef TargetBytesPtr<MMPolicy> Type;
+
+ static Type Make(const MMPolicy& aMMPolicy, const void* aFunc) {
+ return TargetBytesPtr(aMMPolicy, aFunc);
+ }
+
+ static Type CopyFromOffset(const TargetBytesPtr& aOther,
+ const uint32_t aOffsetFromOther) {
+ return TargetBytesPtr(aOther, aOffsetFromOther);
+ }
+
+ ReadOnlyTargetBytes<MMPolicy>* operator->() { return &mTargetBytes; }
+
+ TargetBytesPtr(TargetBytesPtr&& aOther)
+ : mTargetBytes(std::move(aOther.mTargetBytes)) {}
+
+ TargetBytesPtr(const TargetBytesPtr& aOther)
+ : mTargetBytes(aOther.mTargetBytes) {}
+
+ TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
+ TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
+
+ private:
+ TargetBytesPtr(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(aMMPolicy, aFunc) {}
+
+ TargetBytesPtr(const TargetBytesPtr& aOther, const uint32_t aOffsetFromOther)
+ : mTargetBytes(aOther.mTargetBytes, aOffsetFromOther) {}
+
+ ReadOnlyTargetBytes<MMPolicy> mTargetBytes;
+};
+
+template <>
+class TargetBytesPtr<MMPolicyOutOfProcess> {
+ public:
+ typedef std::shared_ptr<ReadOnlyTargetBytes<MMPolicyOutOfProcess>> Type;
+
+ static Type Make(const MMPolicyOutOfProcess& aMMPolicy, const void* aFunc) {
+ return std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
+ aMMPolicy, aFunc);
+ }
+
+ static Type CopyFromOffset(const Type& aOther,
+ const uint32_t aOffsetFromOther) {
+ return std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
+ *aOther, aOffsetFromOther);
+ }
+};
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS ReadOnlyTargetFunction final {
+ public:
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc)),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, FARPROC aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(
+ aMMPolicy, reinterpret_cast<const void*>(aFunc))),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(
+ aMMPolicy, reinterpret_cast<const void*>(aFunc))),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(ReadOnlyTargetFunction&& aOther)
+ : mTargetBytes(std::move(aOther.mTargetBytes)), mOffset(aOther.mOffset) {}
+
+ ReadOnlyTargetFunction& operator=(const ReadOnlyTargetFunction&) = delete;
+ ReadOnlyTargetFunction& operator=(ReadOnlyTargetFunction&&) = delete;
+
+ ~ReadOnlyTargetFunction() = default;
+
+ ReadOnlyTargetFunction operator+(const uint32_t aOffset) const {
+ return ReadOnlyTargetFunction(*this, mOffset + aOffset);
+ }
+
+ uintptr_t GetBaseAddress() const { return mTargetBytes->GetBase(); }
+
+ uintptr_t GetAddress() const { return mTargetBytes->GetBase() + mOffset; }
+
+ uintptr_t AsEncodedPtr() const {
+ return EncodePtr(
+ reinterpret_cast<void*>(mTargetBytes->GetBase() + mOffset));
+ }
+
+ static uintptr_t EncodePtr(void* aPtr) {
+ return reinterpret_cast<uintptr_t>(::EncodePointer(aPtr));
+ }
+
+ static uintptr_t DecodePtr(uintptr_t aEncodedPtr) {
+ return reinterpret_cast<uintptr_t>(
+ ::DecodePointer(reinterpret_cast<PVOID>(aEncodedPtr)));
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ return mTargetBytes->IsValidAtOffset(aOffset);
+ }
+
+#if defined(_M_ARM64)
+
+ uint32_t ReadNextInstruction() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(uint32_t));
+ uint32_t instruction = *reinterpret_cast<const uint32_t*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ mOffset += sizeof(uint32_t);
+ return instruction;
+ }
+
+ bool BackUpOneInstruction() {
+ if (mOffset < sizeof(uint32_t)) {
+ return false;
+ }
+
+ mOffset -= sizeof(uint32_t);
+ return true;
+ }
+
+#else
+
+ uint8_t const& operator*() const {
+ mTargetBytes->EnsureLimit(mOffset);
+ return *(mTargetBytes->GetLocalBytes() + mOffset);
+ }
+
+ uint8_t const& operator[](uint32_t aIndex) const {
+ mTargetBytes->EnsureLimit(mOffset + aIndex);
+ return *(mTargetBytes->GetLocalBytes() + mOffset + aIndex);
+ }
+
+ ReadOnlyTargetFunction& operator++() {
+ ++mOffset;
+ return *this;
+ }
+
+ ReadOnlyTargetFunction& operator+=(uint32_t aDelta) {
+ mOffset += aDelta;
+ return *this;
+ }
+
+ uintptr_t ReadDisp32AsAbsolute() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
+ int32_t disp = *reinterpret_cast<const int32_t*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ uintptr_t result =
+ mTargetBytes->GetBase() + mOffset + sizeof(int32_t) + disp;
+ mOffset += sizeof(int32_t);
+ return result;
+ }
+
+ bool IsRelativeShortJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xeb) {
+ int8_t offset = static_cast<int8_t>((*this)[1]);
+ *aOutTarget = GetAddress() + 2 + offset;
+ return true;
+ }
+ return false;
+ }
+
+# if defined(_M_X64)
+ // Currently this function is used only in x64.
+ bool IsRelativeNearJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xe9) {
+ *aOutTarget = (*this + 1).ReadDisp32AsAbsolute();
+ return true;
+ }
+ return false;
+ }
+# endif // defined(_M_X64)
+
+ bool IsIndirectNearJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xff && (*this)[1] == 0x25) {
+# if defined(_M_X64)
+ *aOutTarget = (*this + 2).ChasePointerFromDisp();
+# else
+ *aOutTarget = (*this + 2).template ChasePointer<uintptr_t*>();
+# endif // defined(_M_X64)
+ return true;
+ }
+# if defined(_M_X64)
+ else if ((*this)[0] == 0x48 && (*this)[1] == 0xff && (*this)[2] == 0x25) {
+ // According to Intel SDM, JMP does not have REX.W except JMP m16:64,
+ // but CPU can execute JMP r/m32 with REX.W. We handle it just in case.
+ *aOutTarget = (*this + 3).ChasePointerFromDisp();
+ return true;
+ }
+# endif // defined(_M_X64)
+ return false;
+ }
+
+#endif // defined(_M_ARM64)
+
+ void Rewind() { mOffset = 0; }
+
+ uint32_t GetOffset() const { return mOffset; }
+
+ uintptr_t OffsetToAbsolute(const uint8_t aOffset) const {
+ return mTargetBytes->GetBase() + mOffset + aOffset;
+ }
+
+ uintptr_t GetCurrentAbsolute() const { return OffsetToAbsolute(0); }
+
+ /**
+ * This method promotes the code referenced by this object to be writable.
+ *
+ * @param aLen The length of the function's code to make writable. If set
+ * to zero, this object's current offset is used as the length.
+ * @param aOffset The result's base address will be offset from this
+ * object's base address by |aOffset| bytes. This value may be
+ * negative.
+ */
+ WritableTargetFunction<MMPolicy> Promote(const uint32_t aLen = 0,
+ const int8_t aOffset = 0) const {
+ const uint32_t effectiveLength = aLen ? aLen : mOffset;
+ MOZ_RELEASE_ASSERT(effectiveLength,
+ "Cannot Promote a zero-length function");
+
+ if (!mTargetBytes->IsValidAtOffset(aOffset)) {
+ return WritableTargetFunction<MMPolicy>(mTargetBytes->GetMMPolicy());
+ }
+
+ WritableTargetFunction<MMPolicy> result(mTargetBytes->GetMMPolicy(),
+ mTargetBytes->GetBase() + aOffset,
+ effectiveLength);
+
+ return result;
+ }
+
+ private:
+ template <typename T>
+ struct ChasePointerHelper {
+ template <typename MMPolicy_>
+ static T Result(const MMPolicy_&, T aValue) {
+ return aValue;
+ }
+ };
+
+ template <typename T>
+ struct ChasePointerHelper<T*> {
+ template <typename MMPolicy_>
+ static auto Result(const MMPolicy_& aPolicy, T* aValue) {
+ ReadOnlyTargetFunction<MMPolicy_> ptr(aPolicy, aValue);
+ return ptr.template ChasePointer<T>();
+ }
+ };
+
+ public:
+ // Keep chasing pointers until T is not a pointer type anymore
+ template <typename T>
+ auto ChasePointer() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(T));
+ const std::remove_cv_t<T> result =
+ *reinterpret_cast<const std::remove_cv_t<T>*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ return ChasePointerHelper<std::remove_cv_t<T>>::Result(
+ mTargetBytes->GetMMPolicy(), result);
+ }
+
+ uintptr_t ChasePointerFromDisp() {
+ uintptr_t ptrFromDisp = ReadDisp32AsAbsolute();
+ ReadOnlyTargetFunction<MMPolicy> ptr(
+ mTargetBytes->GetMMPolicy(),
+ reinterpret_cast<const void*>(ptrFromDisp));
+ return ptr.template ChasePointer<uintptr_t>();
+ }
+
+ private:
+ ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther)
+ : mTargetBytes(aOther.mTargetBytes), mOffset(aOther.mOffset) {}
+
+ ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther,
+ const uint32_t aOffsetFromOther)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::CopyFromOffset(
+ aOther.mTargetBytes, aOffsetFromOther)),
+ mOffset(0) {}
+
+ private:
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+ uint32_t mOffset;
+};
+
+template <typename MMPolicy, typename T>
+class MOZ_STACK_CLASS TargetObject {
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+
+ TargetObject(const MMPolicy& aMMPolicy, const void* aBaseAddress)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aBaseAddress)) {
+ mTargetBytes->EnsureLimit(sizeof(T));
+ }
+
+ public:
+ explicit TargetObject(const MMPolicy& aMMPolicy)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, nullptr)) {}
+
+ TargetObject(const MMPolicy& aMMPolicy, uintptr_t aBaseAddress)
+ : TargetObject(aMMPolicy, reinterpret_cast<const void*>(aBaseAddress)) {}
+
+ TargetObject(const TargetObject&) = delete;
+ TargetObject(TargetObject&&) = delete;
+ TargetObject& operator=(const TargetObject&) = delete;
+ TargetObject& operator=(TargetObject&&) = delete;
+
+ explicit operator bool() const {
+ return mTargetBytes->GetBase() && mTargetBytes->GetLocalBytes();
+ }
+
+ const T* operator->() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+
+ const T* GetLocalBase() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+};
+
+template <typename MMPolicy, typename T>
+class MOZ_STACK_CLASS TargetObjectArray {
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+ size_t mNumOfItems;
+
+ TargetObjectArray(const MMPolicy& aMMPolicy, const void* aBaseAddress,
+ size_t aNumOfItems)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aBaseAddress)),
+ mNumOfItems(aNumOfItems) {
+ uint32_t itemsRead =
+ mTargetBytes->TryEnsureLimit(sizeof(T) * mNumOfItems) / sizeof(T);
+ // itemsRead may be bigger than the requested amount because of buffering,
+ // but mNumOfItems should not include extra bytes of buffering.
+ if (itemsRead < mNumOfItems) {
+ mNumOfItems = itemsRead;
+ }
+ }
+
+ const T* GetLocalBase() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+
+ public:
+ explicit TargetObjectArray(const MMPolicy& aMMPolicy)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, nullptr)),
+ mNumOfItems(0) {}
+
+ TargetObjectArray(const MMPolicy& aMMPolicy, uintptr_t aBaseAddress,
+ size_t aNumOfItems)
+ : TargetObjectArray(aMMPolicy,
+ reinterpret_cast<const void*>(aBaseAddress),
+ aNumOfItems) {}
+
+ TargetObjectArray(const TargetObjectArray&) = delete;
+ TargetObjectArray(TargetObjectArray&&) = delete;
+ TargetObjectArray& operator=(const TargetObjectArray&) = delete;
+ TargetObjectArray& operator=(TargetObjectArray&&) = delete;
+
+ explicit operator bool() const {
+ return mTargetBytes->GetBase() && mNumOfItems;
+ }
+
+ const T* operator[](size_t aIndex) const {
+ if (aIndex >= mNumOfItems) {
+ return nullptr;
+ }
+
+ return &GetLocalBase()[aIndex];
+ }
+
+ template <typename Comparator>
+ bool BinarySearchIf(const Comparator& aCompare,
+ size_t* aMatchOrInsertionPoint) const {
+ return mozilla::BinarySearchIf(GetLocalBase(), 0, mNumOfItems, aCompare,
+ aMatchOrInsertionPoint);
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_TargetFunction_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h b/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h
new file mode 100644
index 0000000000..befbd47215
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h
@@ -0,0 +1,773 @@
+/* -*- 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
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h b/toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h
new file mode 100644
index 0000000000..8f93f5c1ad
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h
@@ -0,0 +1,285 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_interceptor_VMSharingPolicies_h
+#define mozilla_interceptor_VMSharingPolicies_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+
+/**
+ * This class is an abstraction of a reservation of virtual address space that
+ * has been obtained from a VMSharingPolicy via the policy's |Reserve| method.
+ *
+ * TrampolinePool allows us to obtain a trampoline without needing to concern
+ * ourselves with the underlying implementation of the VM sharing policy.
+ *
+ * For example, VMSharingPolicyShared delegates to VMSharingPolicyUnique, but
+ * also requires taking a lock before doing so. By invoking |GetNextTrampoline|
+ * on a TrampolinePool, the caller does not need to concern themselves with
+ * that detail.
+ *
+ * We accompolish this with a recursive implementation that provides an inner
+ * TrampolinePool that is provided by the delegated VMSharingPolicy.
+ */
+template <typename VMPolicyT, typename InnerT>
+class MOZ_STACK_CLASS TrampolinePool final {
+ public:
+ TrampolinePool(TrampolinePool&& aOther) = default;
+
+ TrampolinePool(VMPolicyT& aVMPolicy, InnerT&& aInner)
+ : mVMPolicy(aVMPolicy), mInner(std::move(aInner)) {}
+
+ TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
+ TrampolinePool(const TrampolinePool&) = delete;
+ TrampolinePool& operator=(const TrampolinePool&) = delete;
+
+ using MMPolicyT = typename VMPolicyT::MMPolicyT;
+
+ Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
+ return mVMPolicy.GetNextTrampoline(mInner);
+ }
+
+#if defined(_M_X64)
+ bool IsInLowest2GB() const {
+ return mVMPolicy.IsTrampolineSpaceInLowest2GB(mInner);
+ }
+#endif // defined(_M_X64)
+
+ private:
+ VMPolicyT& mVMPolicy;
+ InnerT mInner;
+};
+
+/**
+ * This specialization is the base case for TrampolinePool, and is used by
+ * VMSharingPolicyUnique (since that policy does not delegate anything).
+ */
+template <typename VMPolicyT>
+class MOZ_STACK_CLASS TrampolinePool<VMPolicyT, decltype(nullptr)> final {
+ public:
+ explicit TrampolinePool(VMPolicyT& aVMPolicy) : mVMPolicy(aVMPolicy) {}
+
+ TrampolinePool(TrampolinePool&& aOther) = default;
+
+ TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
+ TrampolinePool(const TrampolinePool&) = delete;
+ TrampolinePool& operator=(const TrampolinePool&) = delete;
+
+ using MMPolicyT = typename VMPolicyT::MMPolicyT;
+
+ Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
+ return mVMPolicy.GetNextTrampoline();
+ }
+
+#if defined(_M_X64)
+ bool IsInLowest2GB() const {
+ return mVMPolicy.IsTrampolineSpaceInLowest2GB();
+ }
+#endif // defined(_M_X64)
+
+ private:
+ VMPolicyT& mVMPolicy;
+};
+
+template <typename MMPolicy>
+class VMSharingPolicyUnique : public MMPolicy {
+ using ThisType = VMSharingPolicyUnique<MMPolicy>;
+
+ public:
+ using PoolType = TrampolinePool<ThisType, decltype(nullptr)>;
+
+ template <typename... Args>
+ explicit VMSharingPolicyUnique(Args&&... aArgs)
+ : MMPolicy(std::forward<Args>(aArgs)...), mNextChunkIndex(0) {}
+
+ Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) {
+ // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
+ // utilize that entire 64KiB reservation.
+ uint32_t len = MMPolicy::GetAllocGranularity();
+
+ Maybe<Span<const uint8_t>> maybeBounds = MMPolicy::SpanFromPivotAndDistance(
+ len, aPivotAddr, aMaxDistanceFromPivot);
+
+ return Reserve(len, maybeBounds);
+ }
+
+ Maybe<PoolType> Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ uint32_t bytesReserved = MMPolicy::Reserve(aSize, aBounds);
+ if (!bytesReserved) {
+ return Nothing();
+ }
+
+ return Some(PoolType(*this));
+ }
+
+ TrampolineCollection<MMPolicy> Items() const {
+ return TrampolineCollection<MMPolicy>(*this, this->GetLocalView(),
+ this->GetRemoteView(), kChunkSize,
+ mNextChunkIndex);
+ }
+
+ void Clear() { mNextChunkIndex = 0; }
+
+ ~VMSharingPolicyUnique() = default;
+
+ VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
+ VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
+
+ VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
+ : MMPolicy(std::move(aOther)), mNextChunkIndex(aOther.mNextChunkIndex) {
+ aOther.mNextChunkIndex = 0;
+ }
+
+ VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther) {
+ static_cast<MMPolicy&>(*this) = std::move(aOther);
+ mNextChunkIndex = aOther.mNextChunkIndex;
+ aOther.mNextChunkIndex = 0;
+ return *this;
+ }
+
+ protected:
+ // In VMSharingPolicyUnique we do not implement the overload that accepts
+ // an inner trampoline pool, as this policy is expected to be the
+ // implementation of the base case.
+ Maybe<Trampoline<MMPolicy>> GetNextTrampoline() {
+ uint32_t offset = mNextChunkIndex * kChunkSize;
+ if (!this->MaybeCommitNextPage(offset, kChunkSize)) {
+ return Nothing();
+ }
+
+ Trampoline<MMPolicy> result(this, this->GetLocalView() + offset,
+ this->GetRemoteView() + offset, kChunkSize);
+ if (!!result) {
+ ++mNextChunkIndex;
+ }
+
+ return Some(std::move(result));
+ }
+
+ private:
+ uint32_t mNextChunkIndex;
+ static const uint32_t kChunkSize = 128;
+
+ template <typename VMPolicyT, typename FriendT>
+ friend class TrampolinePool;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+// We don't include RangeMap.h until this point because it depends on the
+// TrampolinePool definitions from above.
+#include "mozilla/interceptor/RangeMap.h"
+
+namespace mozilla {
+namespace interceptor {
+
+// We only support this policy for in-proc MMPolicy.
+class MOZ_TRIVIAL_CTOR_DTOR VMSharingPolicyShared : public MMPolicyInProcess {
+ typedef VMSharingPolicyUnique<MMPolicyInProcess> UniquePolicyT;
+ typedef VMSharingPolicyShared ThisType;
+
+ public:
+ using PoolType = TrampolinePool<ThisType, UniquePolicyT::PoolType>;
+ using MMPolicyT = MMPolicyInProcess;
+
+ constexpr VMSharingPolicyShared() {}
+
+ bool ShouldUnhookUponDestruction() const { return false; }
+
+ Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) {
+ // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
+ // utilize that entire 64KiB reservation.
+ uint32_t len = this->GetAllocGranularity();
+
+ Maybe<Span<const uint8_t>> maybeBounds =
+ MMPolicyInProcess::SpanFromPivotAndDistance(len, aPivotAddr,
+ aMaxDistanceFromPivot);
+
+ AutoCriticalSection lock(GetCS());
+ VMSharingPolicyUnique<MMPolicyT>* uniquePol = sVMMap.GetPolicy(maybeBounds);
+ MOZ_ASSERT(uniquePol);
+ if (!uniquePol) {
+ return Nothing();
+ }
+
+ Maybe<UniquePolicyT::PoolType> maybeUnique =
+ uniquePol->Reserve(len, maybeBounds);
+ if (!maybeUnique) {
+ return Nothing();
+ }
+
+ return Some(PoolType(*this, std::move(maybeUnique.ref())));
+ }
+
+ TrampolineCollection<MMPolicyInProcess> Items() const {
+ // Since ShouldUnhookUponDestruction returns false, this can be empty
+ return TrampolineCollection<MMPolicyInProcess>(*this);
+ }
+
+ void Clear() {
+ // This must be a no-op for shared VM policy; we can't have one interceptor
+ // wiping out trampolines for all interceptors in the process.
+ }
+
+ VMSharingPolicyShared(const VMSharingPolicyShared&) = delete;
+ VMSharingPolicyShared(VMSharingPolicyShared&&) = delete;
+ VMSharingPolicyShared& operator=(const VMSharingPolicyShared&) = delete;
+ VMSharingPolicyShared& operator=(VMSharingPolicyShared&&) = delete;
+
+ private:
+ static CRITICAL_SECTION* GetCS() {
+ static const bool isAlloc = []() -> bool {
+ DWORD flags = 0;
+#if defined(RELEASE_OR_BETA)
+ flags |= CRITICAL_SECTION_NO_DEBUG_INFO;
+#endif // defined(RELEASE_OR_BETA)
+ ::InitializeCriticalSectionEx(&sCS, 4000, flags);
+ return true;
+ }();
+ Unused << isAlloc;
+
+ return &sCS;
+ }
+
+ // In VMSharingPolicyShared, we only implement the overload that accepts
+ // a VMSharingPolicyUnique trampoline pool as |aInner|, since we require the
+ // former policy to wrap the latter.
+ Maybe<Trampoline<MMPolicyInProcess>> GetNextTrampoline(
+ UniquePolicyT::PoolType& aInner) {
+ AutoCriticalSection lock(GetCS());
+ return aInner.GetNextTrampoline();
+ }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB(
+ const UniquePolicyT::PoolType& aInner) const {
+ AutoCriticalSection lock(GetCS());
+ return aInner.IsInLowest2GB();
+ }
+#endif // defined(_M_X64)
+
+ private:
+ template <typename VMPolicyT, typename InnerT>
+ friend class TrampolinePool;
+
+ inline static RangeMap<MMPolicyInProcess> sVMMap;
+ inline static CRITICAL_SECTION sCS;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_VMSharingPolicies_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/moz.build b/toolkit/xre/dllservices/mozglue/interceptor/moz.build
new file mode 100644
index 0000000000..561e33b147
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/moz.build
@@ -0,0 +1,26 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+EXPORTS.mozilla.interceptor += [
+ "Arm64.h",
+ "MMPolicies.h",
+ "PatcherBase.h",
+ "PatcherDetour.h",
+ "PatcherNopSpace.h",
+ "RangeMap.h",
+ "TargetFunction.h",
+ "Trampoline.h",
+ "VMSharingPolicies.h",
+]
+
+if CONFIG["CPU_ARCH"] == "aarch64":
+ Library("interceptor")
+
+ FINAL_LIBRARY = "mozglue"
+
+ UNIFIED_SOURCES += [
+ "Arm64.cpp",
+ ]
diff --git a/toolkit/xre/dllservices/mozglue/moz.build b/toolkit/xre/dllservices/mozglue/moz.build
new file mode 100644
index 0000000000..ea7b0eb7d5
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/moz.build
@@ -0,0 +1,80 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("dllservices_mozglue")
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"]:
+ SOURCES += [
+ # This file contains a |using namespace mozilla;| statement
+ "WindowsDllBlocklist.cpp",
+ ]
+
+ UNIFIED_SOURCES += [
+ "Authenticode.cpp",
+ "LoaderObserver.cpp",
+ "ModuleLoadFrame.cpp",
+ "WindowsFallbackLoaderAPI.cpp",
+ ]
+
+if not CONFIG["JS_STANDALONE"]:
+ UNIFIED_SOURCES += [
+ "CacheNtDllThunk.cpp",
+ ]
+
+OS_LIBS += [
+ "crypt32",
+ "ntdll",
+ "version",
+ "wintrust",
+]
+
+DELAYLOAD_DLLS += [
+ "crypt32.dll",
+ "wintrust.dll",
+]
+
+EXPORTS += [
+ "nsWindowsDllInterceptor.h",
+]
+
+EXPORTS.mozilla += [
+ "Authenticode.h",
+ "CacheNtDllThunk.h",
+ "LoaderAPIInterfaces.h",
+ "ModuleLoadInfo.h",
+ "WindowsDllBlocklist.h",
+ "WindowsDllBlocklistCommon.h",
+ "WindowsDllBlocklistInfo.h",
+]
+
+EXPORTS.mozilla.glue += [
+ "SharedSection.h",
+ "WindowsDllServices.h",
+]
+
+# Generate DLL Blocklists
+blocklist_header_types = ["A11y", "Launcher", "Legacy", "Test"]
+blocklist_file_leaf_tpl = "WindowsDllBlocklist{0}Defs.h"
+blocklist_files = tuple(
+ [blocklist_file_leaf_tpl.format(type) for type in blocklist_header_types]
+)
+
+GeneratedFile(
+ *blocklist_files,
+ script="gen_dll_blocklist_defs.py",
+ entry_point="gen_blocklists",
+ inputs=["WindowsDllBlocklistDefs.in"]
+)
+
+EXPORTS.mozilla += ["!" + hdr for hdr in blocklist_files]
+
+DIRS += [
+ "interceptor",
+]
+
+FINAL_LIBRARY = "mozglue"
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h b/toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h
new file mode 100644
index 0000000000..d6afc9bb7f
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h
@@ -0,0 +1,819 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
+#define NS_WINDOWS_DLL_INTERCEPTOR_H_
+
+#include <wchar.h>
+#include <windows.h>
+#include <winternl.h>
+
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/PatcherDetour.h"
+#include "mozilla/interceptor/PatcherNopSpace.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+#include "nsWindowsHelpers.h"
+
+/*
+ * Simple function interception.
+ *
+ * We have two separate mechanisms for intercepting a function: We can use the
+ * built-in nop space, if it exists, or we can create a detour.
+ *
+ * Using the built-in nop space works as follows: On x86-32, DLL functions
+ * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
+ * NOP instructions.
+ *
+ * When we detect a function with this prelude, we do the following:
+ *
+ * 1. Write a long jump to our interceptor function into the five bytes of NOPs
+ * before the function.
+ *
+ * 2. Write a short jump -5 into the two-byte nop at the beginning of the
+ * function.
+ *
+ * This mechanism is nice because it's thread-safe. It's even safe to do if
+ * another thread is currently running the function we're modifying!
+ *
+ * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
+ * but not the long jump, so re-intercepting the same function won't work,
+ * because its prelude won't match.
+ *
+ *
+ * Unfortunately nop space patching doesn't work on functions which don't have
+ * this magic prelude (and in particular, x86-64 never has the prelude). So
+ * when we can't use the built-in nop space, we fall back to using a detour,
+ * which works as follows:
+ *
+ * 1. Save first N bytes of OrigFunction to trampoline, where N is a
+ * number of bytes >= 5 that are instruction aligned.
+ *
+ * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
+ * function.
+ *
+ * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
+ * continue original program flow.
+ *
+ * 4. Hook function needs to call the trampoline during its execution,
+ * to invoke the original function (so address of trampoline is
+ * returned).
+ *
+ * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
+ * patched again to jump directly to the trampoline instead of going through
+ * the hook function. As such, re-intercepting the same function won't work, as
+ * jump instructions are not supported.
+ *
+ * Note that this is not thread-safe. Sad day.
+ *
+ */
+
+#if defined(_M_IX86) && defined(__clang__) && __has_declspec_attribute(guard)
+// On x86, nop-space patches return to the second instruction of their target.
+// This is a deliberate violation of Control Flow Guard, so disable the check.
+# define INTERCEPTOR_DISABLE_CFGUARD __declspec(guard(nocf))
+#else
+# define INTERCEPTOR_DISABLE_CFGUARD /* nothing */
+#endif
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename T>
+struct OriginalFunctionPtrTraits;
+
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R (*)(Args...)> {
+ using ReturnType = R;
+};
+
+#if defined(_M_IX86)
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R(__stdcall*)(Args...)> {
+ using ReturnType = R;
+};
+
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R(__fastcall*)(Args...)> {
+ using ReturnType = R;
+};
+#endif // defined(_M_IX86)
+
+template <typename InterceptorT, typename FuncPtrT>
+class FuncHook final {
+ public:
+ using ThisType = FuncHook<InterceptorT, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+ constexpr FuncHook() : mOrigFunc(nullptr), mInitOnce(INIT_ONCE_STATIC_INIT) {}
+
+ ~FuncHook() = default;
+
+ bool Set(InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, false);
+
+ return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ }
+
+ bool SetDetour(InterceptorT& aInterceptor, const char* aName,
+ FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, true);
+
+ return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ template <typename... ArgsType>
+ INTERCEPTOR_DISABLE_CFGUARD ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+ FuncPtrT GetStub() const { return mOrigFunc; }
+
+ // One-time init stuff cannot be moved or copied
+ FuncHook(const FuncHook&) = delete;
+ FuncHook(FuncHook&&) = delete;
+ FuncHook& operator=(const FuncHook&) = delete;
+ FuncHook& operator=(FuncHook&& aOther) = delete;
+
+ private:
+ struct MOZ_RAII InitOnceContext final {
+ InitOnceContext(ThisType* aHook, InterceptorT* aInterceptor,
+ const char* aName, FuncPtrT aHookDest, bool aForceDetour)
+ : mHook(aHook),
+ mInterceptor(aInterceptor),
+ mName(aName),
+ mHookDest(reinterpret_cast<void*>(aHookDest)),
+ mForceDetour(aForceDetour) {}
+
+ ThisType* mHook;
+ InterceptorT* mInterceptor;
+ const char* mName;
+ void* mHookDest;
+ bool mForceDetour;
+ };
+
+ private:
+ bool Apply(InterceptorT* aInterceptor, const char* aName, void* aHookDest) {
+ return aInterceptor->AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&mOrigFunc));
+ }
+
+ bool ApplyDetour(InterceptorT* aInterceptor, const char* aName,
+ void* aHookDest) {
+ return aInterceptor->AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&mOrigFunc));
+ }
+
+ static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
+ PVOID* aOutContext) {
+ MOZ_ASSERT(aOutContext);
+
+ bool result;
+ auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
+ if (ctx->mForceDetour) {
+ result = ctx->mHook->ApplyDetour(ctx->mInterceptor, ctx->mName,
+ ctx->mHookDest);
+ } else {
+ result = ctx->mHook->Apply(ctx->mInterceptor, ctx->mName, ctx->mHookDest);
+ }
+
+ *aOutContext =
+ result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
+ : nullptr;
+ return TRUE;
+ }
+
+ private:
+ FuncPtrT mOrigFunc;
+ INIT_ONCE mInitOnce;
+};
+
+template <typename InterceptorT, typename FuncPtrT>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FuncHookCrossProcess final {
+ public:
+ using ThisType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+#if defined(DEBUG)
+ FuncHookCrossProcess() {}
+#endif // defined(DEBUG)
+
+ bool Set(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
+ FuncPtrT origFunc;
+ if (!aInterceptor.AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&origFunc))) {
+ return false;
+ }
+
+ return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
+ }
+
+ bool SetDetour(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, const char* aName,
+ FuncPtrT aHookDest) {
+ FuncPtrT origFunc;
+ if (!aInterceptor.AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&origFunc))) {
+ return false;
+ }
+
+ return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ /**
+ * NB: This operator is only meaningful when invoked in the target process!
+ */
+ template <typename... ArgsType>
+ ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+#if defined(DEBUG)
+ FuncHookCrossProcess(const FuncHookCrossProcess&) = delete;
+ FuncHookCrossProcess(FuncHookCrossProcess&&) = delete;
+ FuncHookCrossProcess& operator=(const FuncHookCrossProcess&) = delete;
+ FuncHookCrossProcess& operator=(FuncHookCrossProcess&& aOther) = delete;
+#endif // defined(DEBUG)
+
+ private:
+ bool CopyStubToChildProcess(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, FuncPtrT aStub) {
+ LauncherVoidResult writeResult =
+ aTransferMgr.Transfer(&mOrigFunc, &aStub, sizeof(FuncPtrT));
+ if (writeResult.isErr()) {
+#ifdef MOZ_USE_LAUNCHER_ERROR
+ const mozilla::WindowsError& err = writeResult.inspectErr().mError;
+#else
+ const mozilla::WindowsError& err = writeResult.inspectErr();
+#endif
+ aInterceptor.SetLastDetourError(FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
+ err.AsHResult());
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ FuncPtrT mOrigFunc;
+};
+
+template <typename MMPolicyT, typename InterceptorT>
+struct TypeResolver;
+
+template <typename InterceptorT>
+struct TypeResolver<mozilla::interceptor::MMPolicyInProcess, InterceptorT> {
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHook<InterceptorT, FuncPtrT>;
+};
+
+template <typename InterceptorT>
+struct TypeResolver<mozilla::interceptor::MMPolicyOutOfProcess, InterceptorT> {
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
+};
+
+template <typename VMPolicy = mozilla::interceptor::VMSharingPolicyShared>
+class WindowsDllInterceptor final
+ : public TypeResolver<typename VMPolicy::MMPolicyT,
+ WindowsDllInterceptor<VMPolicy>> {
+ typedef WindowsDllInterceptor<VMPolicy> ThisType;
+
+ interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
+#if defined(_M_IX86)
+ interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT>
+ mNopSpacePatcher;
+#endif // defined(_M_IX86)
+
+ HMODULE mModule;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllInterceptor(Args&&... aArgs)
+ : mDetourPatcher(std::forward<Args>(aArgs)...)
+#if defined(_M_IX86)
+ ,
+ mNopSpacePatcher(std::forward<Args>(aArgs)...)
+#endif // defined(_M_IX86)
+ ,
+ mModule(nullptr) {
+ }
+
+ WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
+ WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
+
+ ~WindowsDllInterceptor() { Clear(); }
+
+ template <size_t N>
+ void Init(const char (&aModuleName)[N]) {
+ wchar_t moduleName[N];
+
+ for (size_t i = 0; i < N; ++i) {
+ MOZ_ASSERT(!(aModuleName[i] & 0x80),
+ "Use wide-character overload for non-ASCII module names");
+ moduleName[i] = aModuleName[i];
+ }
+
+ Init(moduleName);
+ }
+
+ void Init(const wchar_t* aModuleName) {
+ if (mModule) {
+ return;
+ }
+
+ mModule = ::LoadLibraryW(aModuleName);
+ }
+
+ /** Force a specific configuration for testing purposes. NOT to be used in
+ production code! **/
+ void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags) {
+ Init(aModuleName);
+ mDetourPatcher.Init(aFlags);
+ }
+
+ void Clear() {
+ if (!mModule) {
+ return;
+ }
+
+#if defined(_M_IX86)
+ mNopSpacePatcher.Clear();
+#endif // defined(_M_IX86)
+ mDetourPatcher.Clear();
+
+ // NB: We intentionally leak mModule
+ }
+
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& GetLastDetourError() const {
+ return mDetourPatcher.GetLastDetourError();
+ }
+#endif // defined(NIGHTLY_BUILD)
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ return mDetourPatcher.SetLastDetourError(std::forward<Args>(aArgs)...);
+ }
+
+ constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
+ return WindowsDllDetourPatcherPrimitive<
+ typename VMPolicy::MMPolicyT>::GetWorstCaseRequiredBytesToPatch();
+ }
+
+ private:
+ /**
+ * Hook/detour the method aName from the DLL we set in Init so that it calls
+ * aHookDest instead. Returns the original method pointer in aOrigFunc
+ * and returns true if successful.
+ *
+ * IMPORTANT: If you use this method, please add your case to the
+ * TestDllInterceptor in order to detect future failures. Even if this
+ * succeeds now, updates to the hooked DLL could cause it to fail in
+ * the future.
+ */
+ bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
+ // Use a nop space patch if possible, otherwise fall back to a detour.
+ // This should be the preferred method for adding hooks.
+ if (!mModule) {
+ mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
+ return false;
+ }
+
+ FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
+ if (!proc) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
+ return false;
+ }
+
+#if defined(_M_IX86)
+ if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ return AddDetour(proc, aHookDest, aOrigFunc);
+ }
+
+ /**
+ * Detour the method aName from the DLL we set in Init so that it calls
+ * aHookDest instead. Returns the original method pointer in aOrigFunc
+ * and returns true if successful.
+ *
+ * IMPORTANT: If you use this method, please add your case to the
+ * TestDllInterceptor in order to detect future failures. Even if this
+ * succeeds now, updates to the detoured DLL could cause it to fail in
+ * the future.
+ */
+ bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
+ // Generally, code should not call this method directly. Use AddHook unless
+ // there is a specific need to avoid nop space patches.
+ if (!mModule) {
+ mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
+ return false;
+ }
+
+ FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
+ if (!proc) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
+ return false;
+ }
+
+ return AddDetour(proc, aHookDest, aOrigFunc);
+ }
+
+ bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc) {
+ MOZ_ASSERT(mModule && aProc);
+
+ if (!mDetourPatcher.Initialized()) {
+ DetourFlags flags = DetourFlags::eDefault;
+#if defined(_M_X64)
+ // NTDLL hooks should attempt to use a 10-byte patch because some
+ // injected DLLs do the same and interfere with our stuff.
+ bool needs10BytePatch = (mModule == ::GetModuleHandleW(L"ntdll.dll"));
+
+ bool isWin8Or81 = IsWin8OrLater() && (!IsWin10OrLater());
+ bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater());
+
+ bool isKernel32Dll = (mModule == ::GetModuleHandleW(L"kernel32.dll"));
+
+ bool isDuplicateHandle = (reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&::DuplicateHandle));
+
+ // CloseHandle on Windows 8/8.1 only accomodates 10-byte patches.
+ needs10BytePatch |= isWin8Or81 && isKernel32Dll &&
+ (reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&CloseHandle));
+
+ // CreateFileA and DuplicateHandle on Windows 8 require 10-byte patches.
+ needs10BytePatch |= isWin8 && isKernel32Dll &&
+ ((reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&::CreateFileA)) ||
+ isDuplicateHandle);
+
+ if (needs10BytePatch) {
+ flags |= DetourFlags::eEnable10BytePatch;
+ }
+
+ if (isWin8 && isDuplicateHandle) {
+ // Because we can't detour Win8's KERNELBASE!DuplicateHandle,
+ // we detour kernel32!DuplicateHandle (See bug 1659398).
+ flags |= DetourFlags::eDontResolveRedirection;
+ }
+#endif // defined(_M_X64)
+
+ mDetourPatcher.Init(flags);
+ }
+
+ return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
+ }
+
+ private:
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHook;
+
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHookCrossProcess;
+};
+
+/**
+ * IAT patching is intended for use when we only want to intercept a function
+ * call originating from a specific module.
+ */
+class WindowsIATPatcher final {
+ public:
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHook<WindowsIATPatcher, FuncPtrT>;
+
+ private:
+ static bool CheckASCII(const char* aInStr) {
+ while (*aInStr) {
+ if (*aInStr & 0x80) {
+ return false;
+ }
+ ++aInStr;
+ }
+ return true;
+ }
+
+ static bool AddHook(HMODULE aFromModule, const char* aToModuleName,
+ const char* aTargetFnName, void* aHookDest,
+ Atomic<void*>* aOutOrigFunc) {
+ if (!aFromModule || !aToModuleName || !aTargetFnName || !aOutOrigFunc) {
+ return false;
+ }
+
+ // PE Spec requires ASCII names for imported module names
+ const bool isModuleNameAscii = CheckASCII(aToModuleName);
+ MOZ_ASSERT(isModuleNameAscii);
+ if (!isModuleNameAscii) {
+ return false;
+ }
+
+ // PE Spec requires ASCII names for imported function names
+ const bool isTargetFnNameAscii = CheckASCII(aTargetFnName);
+ MOZ_ASSERT(isTargetFnNameAscii);
+ if (!isTargetFnNameAscii) {
+ return false;
+ }
+
+ nt::PEHeaders headers(aFromModule);
+ if (!headers) {
+ return false;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR impDesc =
+ headers.GetImportDescriptor(aToModuleName);
+ if (!nt::PEHeaders::IsValid(impDesc)) {
+ // Either aFromModule does not import aToModuleName at load-time, or
+ // aToModuleName is a (currently unsupported) delay-load import.
+ return false;
+ }
+
+ // Resolve the import name table (INT).
+ auto firstINTThunk = headers.template RVAToPtr<PIMAGE_THUNK_DATA>(
+ impDesc->OriginalFirstThunk);
+ if (!nt::PEHeaders::IsValid(firstINTThunk)) {
+ return false;
+ }
+
+ Maybe<ptrdiff_t> thunkIndex;
+
+ // Scan the INT for the location of the thunk for the function named
+ // 'aTargetFnName'.
+ for (PIMAGE_THUNK_DATA curINTThunk = firstINTThunk;
+ nt::PEHeaders::IsValid(curINTThunk); ++curINTThunk) {
+ if (IMAGE_SNAP_BY_ORDINAL(curINTThunk->u1.Ordinal)) {
+ // Currently not supporting import by ordinal; this isn't hard to add,
+ // but we won't bother unless necessary.
+ continue;
+ }
+
+ PIMAGE_IMPORT_BY_NAME curThunkFnName =
+ headers.template RVAToPtr<PIMAGE_IMPORT_BY_NAME>(
+ curINTThunk->u1.AddressOfData);
+ MOZ_ASSERT(curThunkFnName);
+ if (!curThunkFnName) {
+ // Looks like we have a bad name descriptor. Try to continue.
+ continue;
+ }
+
+ // Function name checks MUST be case-sensitive!
+ if (!strcmp(aTargetFnName, curThunkFnName->Name)) {
+ // We found the thunk. Save the index of this thunk, as the IAT thunk
+ // is located at the same index in that table as in the INT.
+ thunkIndex = Some(curINTThunk - firstINTThunk);
+ break;
+ }
+ }
+
+ if (thunkIndex.isNothing()) {
+ // We never found a thunk for that function. Perhaps it's not imported?
+ return false;
+ }
+
+ if (thunkIndex.value() < 0) {
+ // That's just wrong.
+ return false;
+ }
+
+ auto firstIATThunk =
+ headers.template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
+ if (!nt::PEHeaders::IsValid(firstIATThunk)) {
+ return false;
+ }
+
+ // Resolve the IAT thunk for the function we want
+ PIMAGE_THUNK_DATA targetThunk = &firstIATThunk[thunkIndex.value()];
+ if (!nt::PEHeaders::IsValid(targetThunk)) {
+ return false;
+ }
+
+ auto fnPtr = reinterpret_cast<Atomic<void*>*>(&targetThunk->u1.Function);
+
+ // Now we can just change out its pointer with our hook function.
+ AutoVirtualProtect prot(fnPtr, sizeof(void*), PAGE_EXECUTE_READWRITE);
+ if (!prot) {
+ return false;
+ }
+
+ // We do the exchange this way to ensure that *aOutOrigFunc is always valid
+ // once the atomic exchange has taken place.
+ void* tmp;
+
+ do {
+ tmp = *fnPtr;
+ *aOutOrigFunc = tmp;
+ } while (!fnPtr->compareExchange(tmp, aHookDest));
+
+ return true;
+ }
+
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHook;
+};
+
+template <typename FuncPtrT>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS
+ FuncHook<WindowsIATPatcher, FuncPtrT>
+ final {
+ public:
+ using ThisType = FuncHook<WindowsIATPatcher, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+ constexpr FuncHook()
+ : mInitOnce(INIT_ONCE_STATIC_INIT),
+ mFromModule(nullptr),
+ mOrigFunc(nullptr) {}
+
+#if defined(DEBUG)
+ ~FuncHook() = default;
+#endif // defined(DEBUG)
+
+ bool Set(const wchar_t* aFromModuleName, const char* aToModuleName,
+ const char* aFnName, FuncPtrT aHookDest) {
+ nsModuleHandle fromModule(::LoadLibraryW(aFromModuleName));
+ if (!fromModule) {
+ return false;
+ }
+
+ return Set(fromModule, aToModuleName, aFnName, aHookDest);
+ }
+
+ // We offer this overload in case the client wants finer-grained control over
+ // loading aFromModule.
+ bool Set(nsModuleHandle& aFromModule, const char* aToModuleName,
+ const char* aFnName, FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, aFromModule, aToModuleName, aFnName, aHookDest);
+
+ bool result = ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ if (!result) {
+ return result;
+ }
+
+ // If we successfully set the hook then we must retain a strong reference
+ // to the module that we modified.
+ mFromModule = aFromModule.disown();
+ return result;
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ template <typename... ArgsType>
+ ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+ FuncPtrT GetStub() const { return mOrigFunc; }
+
+#if defined(DEBUG)
+ // One-time init stuff cannot be moved or copied
+ FuncHook(const FuncHook&) = delete;
+ FuncHook(FuncHook&&) = delete;
+ FuncHook& operator=(const FuncHook&) = delete;
+ FuncHook& operator=(FuncHook&& aOther) = delete;
+#endif // defined(DEBUG)
+
+ private:
+ struct MOZ_RAII InitOnceContext final {
+ InitOnceContext(ThisType* aHook, const nsModuleHandle& aFromModule,
+ const char* aToModuleName, const char* aFnName,
+ FuncPtrT aHookDest)
+ : mHook(aHook),
+ mFromModule(aFromModule),
+ mToModuleName(aToModuleName),
+ mFnName(aFnName),
+ mHookDest(reinterpret_cast<void*>(aHookDest)) {}
+
+ ThisType* mHook;
+ const nsModuleHandle& mFromModule;
+ const char* mToModuleName;
+ const char* mFnName;
+ void* mHookDest;
+ };
+
+ private:
+ bool Apply(const nsModuleHandle& aFromModule, const char* aToModuleName,
+ const char* aFnName, void* aHookDest) {
+ return WindowsIATPatcher::AddHook(
+ aFromModule, aToModuleName, aFnName, aHookDest,
+ reinterpret_cast<Atomic<void*>*>(&mOrigFunc));
+ }
+
+ static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
+ PVOID* aOutContext) {
+ MOZ_ASSERT(aOutContext);
+
+ auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
+ bool result = ctx->mHook->Apply(ctx->mFromModule, ctx->mToModuleName,
+ ctx->mFnName, ctx->mHookDest);
+
+ *aOutContext =
+ result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
+ : nullptr;
+ return TRUE;
+ }
+
+ private:
+ INIT_ONCE mInitOnce;
+ HMODULE mFromModule; // never freed
+ FuncPtrT mOrigFunc;
+};
+
+/**
+ * This class applies an irreversible patch to jump to a target function
+ * without backing up the original function.
+ */
+class WindowsDllEntryPointInterceptor final {
+ using DllMainFn = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID);
+ using MMPolicyT = MMPolicyInProcessEarlyStage;
+
+ MMPolicyT mMMPolicy;
+
+ public:
+ explicit WindowsDllEntryPointInterceptor(
+ const MMPolicyT::Kernel32Exports& aK32Exports)
+ : mMMPolicy(aK32Exports) {}
+
+ bool Set(const nt::PEHeaders& aHeaders, DllMainFn aDestination) {
+ if (!aHeaders) {
+ return false;
+ }
+
+ WindowsDllDetourPatcherPrimitive<MMPolicyT> patcher;
+ return patcher.AddIrreversibleHook(
+ mMMPolicy, aHeaders.GetEntryPoint(),
+ reinterpret_cast<uintptr_t>(aDestination));
+ }
+};
+
+} // namespace interceptor
+
+using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
+
+using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyOutOfProcess>>;
+
+using WindowsIATPatcher = interceptor::WindowsIATPatcher;
+
+} // namespace mozilla
+
+#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
diff --git a/toolkit/xre/dllservices/tests/AssemblyPayloads.h b/toolkit/xre/dllservices/tests/AssemblyPayloads.h
new file mode 100644
index 0000000000..811bf88412
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/AssemblyPayloads.h
@@ -0,0 +1,241 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+/* These assembly functions represent patterns that were already hooked by
+ * another application before our detour.
+ */
+
+#ifndef mozilla_AssemblyPayloads_h
+#define mozilla_AssemblyPayloads_h
+
+#include <cstdint>
+
+#define PADDING_256_NOP \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;"
+
+extern "C" {
+
+#if defined(__clang__)
+# if defined(_M_X64)
+constexpr uintptr_t JumpDestination = 0x7fff00000000;
+
+__declspec(dllexport) __attribute__((naked)) void MovPushRet() {
+ asm volatile(
+ "mov %0, %%rax;"
+ "push %%rax;"
+ "ret;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovRaxJump() {
+ asm volatile(
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void DoubleJump() {
+ asm volatile(
+ "jmp label1;"
+
+ "label2:"
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+
+ // 0x100 bytes padding to generate jmp rel32 instead of jmp rel8
+ PADDING_256_NOP
+
+ "label1:"
+ "jmp label2;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void NearJump() {
+ asm volatile(
+ "jae label3;"
+ "je label3;"
+ "jne label3;"
+
+ "label4:"
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+
+ // 0x100 bytes padding to generate jae rel32 instead of jae rel8
+ PADDING_256_NOP
+
+ "label3:"
+ "jmp label4;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void OpcodeFF() {
+ // Skip PUSH (FF /6) because clang prefers Opcode 50+rd
+ // to translate PUSH r64 rather than Opcode FF.
+ asm volatile(
+ "incl %eax;"
+ "decl %ebx;"
+ "call *%rcx;"
+ "jmp *(%rip);" // Indirect jump to 0xcccccccc`cccccccc
+ "int $3;int $3;int $3;int $3;"
+ "int $3;int $3;int $3;int $3;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void IndirectCall() {
+ asm volatile(
+ "call *(%rip);" // Indirect call to 0x90909090`90909090
+ "nop;nop;nop;nop;nop;nop;nop;nop;"
+ "ret;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovImm64() {
+ asm volatile(
+ "mov $0x1234567812345678, %r10;"
+ "nop;nop;nop");
+}
+
+# if !defined(MOZ_CODE_COVERAGE)
+// This code reproduces bug 1798787: it uses the same prologue, the same unwind
+// info, and it has a call instruction that starts within the 13 first bytes.
+__attribute__((naked)) void DetouredCallCode(uintptr_t aCallee) {
+ asm volatile(
+ "subq $0x28, %rsp;"
+ "testq %rcx, %rcx;"
+ "jz exit;"
+ "callq *%rcx;"
+ "exit:"
+ "addq $0x28, %rsp;"
+ "retq;");
+}
+constexpr uint8_t gDetouredCallCodeSize = 16; // size of function in bytes
+alignas(uint32_t) uint8_t gDetouredCallUnwindInfo[] = {
+ 0x01, // Version (1), Flags (0)
+ 0x04, // SizeOfProlog (4)
+ 0x01, // CountOfUnwindCodes (1)
+ 0x00, // FrameRegister (0), FrameOffset (0)
+ // UnwindCodes[0]
+ 0x04, // .OffsetInProlog (4)
+ 0x42, // .UnwindOpCode(UWOP_ALLOC_SMALL=2), .UnwindInfo (4)
+};
+
+// This points to the same code as DetouredCallCode, but dynamically generated
+// so that it can have custom unwinding info. See TestDllInterceptor.cpp.
+extern decltype(&DetouredCallCode) gDetouredCall;
+
+// This is just a jumper: our hooking code will thus detour the jump target
+// DetouredCall -- it will not detour DetouredCallJumper. We need to do this to
+// point our hooking code to the dynamic code, because our hooking API needs an
+// exported function name.
+//
+// guard(nocf) ensures that our generated code is a recognized jumper:
+// jmp qword ptr [rip+offset DetouredCall]
+// rather than:
+// mov rax, qword ptr [rip+offset DetouredCall]
+// mov rdx, qword ptr [rip+offset __guard_dispatch_icall_fptr]
+// jmp rdx
+__declspec(dllexport noinline guard(nocf)) void DetouredCallJumper(
+ uintptr_t aCallee) {
+ gDetouredCall(aCallee);
+}
+# endif // !defined(MOZ_CODE_COVERAGE)
+
+# elif defined(_M_IX86)
+constexpr uintptr_t JumpDestination = 0x7fff0000;
+
+__declspec(dllexport) __attribute__((naked)) void PushRet() {
+ asm volatile(
+ "push %0;"
+ "ret;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovEaxJump() {
+ asm volatile(
+ "mov %0, %%eax;"
+ "jmp *%%eax;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void Opcode83() {
+ asm volatile(
+ "xor $0x42, %eax;"
+ "cmpl $1, 0xc(%ebp);");
+}
+
+__declspec(dllexport) __attribute__((naked)) void LockPrefix() {
+ // Test an instruction with a LOCK prefix (0xf0) at a non-zero offset
+ asm volatile(
+ "push $0x7c;"
+ "lock push $0x7c;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void LooksLikeLockPrefix() {
+ // This is for a regression scenario of bug 1625452, where we double-counted
+ // the offset in CountPrefixBytes. When we count prefix bytes in front of
+ // the 2nd PUSH located at offset 2, we mistakenly started counting from
+ // the byte 0xf0 at offset 4, which is considered as LOCK, thus we try to
+ // detour the next byte 0xcc and it fails.
+ //
+ // 0: 6a7c push 7Ch
+ // 2: 68ccf00000 push 0F0CCh
+ //
+ asm volatile(
+ "push $0x7c;"
+ "push $0x0000f0cc;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void DoubleJump() {
+ asm volatile(
+ "jmp label1;"
+
+ "label2:"
+ "mov %0, %%eax;"
+ "jmp *%%eax;"
+
+ // 0x100 bytes padding to generate jmp rel32 instead of jmp rel8
+ PADDING_256_NOP
+
+ "label1:"
+ "jmp label2;"
+ :
+ : "i"(JumpDestination));
+}
+# endif
+
+# if !defined(_M_ARM64)
+__declspec(dllexport) __attribute__((naked)) void UnsupportedOp() {
+ asm volatile(
+ "ud2;"
+ "nop;nop;nop;nop;nop;nop;nop;nop;"
+ "nop;nop;nop;nop;nop;nop;nop;nop;");
+}
+# endif // !defined(_M_ARM64)
+
+#endif // defined(__clang__)
+
+} // extern "C"
+
+#endif // mozilla_AssemblyPayloads_h
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp
new file mode 100644
index 0000000000..3c58132a34
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp
@@ -0,0 +1,1416 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <shlobj.h>
+#include <stdio.h>
+#include <commdlg.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <wininet.h>
+#include <schnlsp.h>
+#include <winternl.h>
+#include <processthreadsapi.h>
+
+#include "AssemblyPayloads.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/MozProcessMitigationDynamicCodePolicy.h"
+
+NTSTATUS NTAPI NtFlushBuffersFile(HANDLE, PIO_STATUS_BLOCK);
+NTSTATUS NTAPI NtReadFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER,
+ PULONG);
+NTSTATUS NTAPI NtReadFileScatter(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PFILE_SEGMENT_ELEMENT, ULONG,
+ PLARGE_INTEGER, PULONG);
+NTSTATUS NTAPI NtWriteFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER,
+ PULONG);
+NTSTATUS NTAPI NtWriteFileGather(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PFILE_SEGMENT_ELEMENT, ULONG,
+ PLARGE_INTEGER, PULONG);
+NTSTATUS NTAPI NtQueryFullAttributesFile(POBJECT_ATTRIBUTES, PVOID);
+NTSTATUS NTAPI LdrLoadDll(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName, PHANDLE handle);
+NTSTATUS NTAPI LdrUnloadDll(HMODULE);
+
+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);
+void __fastcall BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
+ void* aThreadParam);
+
+BOOL WINAPI ApiSetQueryApiSetPresence(PCUNICODE_STRING, PBOOLEAN);
+
+#if (_WIN32_WINNT < 0x0602)
+BOOL WINAPI
+SetProcessMitigationPolicy(PROCESS_MITIGATION_POLICY aMitigationPolicy,
+ PVOID aBuffer, SIZE_T aBufferLen);
+#endif // (_WIN32_WINNT < 0x0602)
+
+using namespace mozilla;
+
+struct payload {
+ UINT64 a;
+ UINT64 b;
+ UINT64 c;
+
+ bool operator==(const payload& other) const {
+ return (a == other.a && b == other.b && c == other.c);
+ }
+};
+
+extern "C" __declspec(dllexport) __declspec(noinline) payload
+ rotatePayload(payload p) {
+ UINT64 tmp = p.a;
+ p.a = p.b;
+ p.b = p.c;
+ p.c = tmp;
+ return p;
+}
+
+// payloadNotHooked is a target function for a test to expect a negative result.
+// We cannot use rotatePayload for that purpose because our detour cannot hook
+// a function detoured already. Please keep this function always unhooked.
+extern "C" __declspec(dllexport) __declspec(noinline) payload
+ payloadNotHooked(payload p) {
+ // Do something different from rotatePayload to avoid ICF.
+ p.a ^= p.b;
+ p.b ^= p.c;
+ p.c ^= p.a;
+ return p;
+}
+
+// Declared as volatile to prevent optimizers from incorrectly eliding accesses
+// to it. (See bug 1769001 for a motivating example.)
+static volatile bool patched_func_called = false;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&rotatePayload)>
+ orig_rotatePayload;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&payloadNotHooked)>
+ orig_payloadNotHooked;
+
+static payload patched_rotatePayload(payload p) {
+ patched_func_called = true;
+ return orig_rotatePayload(p);
+}
+
+// Invoke aFunc by taking aArg's contents and using them as aFunc's arguments
+template <typename OrigFuncT, typename... Args,
+ typename ArgTuple = Tuple<Args...>, size_t... Indices>
+decltype(auto) Apply(OrigFuncT& aFunc, ArgTuple&& aArgs,
+ std::index_sequence<Indices...>) {
+ return aFunc(Get<Indices>(std::forward<ArgTuple>(aArgs))...);
+}
+
+#define DEFINE_TEST_FUNCTION(calling_convention) \
+ template <typename R, typename... Args, typename... TestArgs> \
+ bool TestFunction(R(calling_convention* aFunc)(Args...), bool (*aPred)(R), \
+ TestArgs&&... aArgs) { \
+ using ArgTuple = Tuple<Args...>; \
+ using Indices = std::index_sequence_for<Args...>; \
+ ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
+ patched_func_called = false; \
+ return aPred(Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices())) && \
+ patched_func_called; \
+ } \
+ \
+ /* Specialization for functions returning void */ \
+ template <typename PredT, typename... Args, typename... TestArgs> \
+ bool TestFunction(void(calling_convention * aFunc)(Args...), PredT, \
+ TestArgs&&... aArgs) { \
+ using ArgTuple = Tuple<Args...>; \
+ using Indices = std::index_sequence_for<Args...>; \
+ ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
+ patched_func_called = false; \
+ Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices()); \
+ return patched_func_called; \
+ }
+
+// C++11 allows empty arguments to macros. clang works just fine. MSVC does the
+// right thing, but it also throws up warning C4003.
+#if defined(_MSC_VER) && !defined(__clang__)
+DEFINE_TEST_FUNCTION(__cdecl)
+#else
+DEFINE_TEST_FUNCTION()
+#endif
+
+#ifdef _M_IX86
+DEFINE_TEST_FUNCTION(__stdcall)
+DEFINE_TEST_FUNCTION(__fastcall)
+#endif // _M_IX86
+
+// Test the hooked function against the supplied predicate
+template <typename OrigFuncT, typename PredicateT, typename... Args>
+bool CheckHook(OrigFuncT& aOrigFunc, const char* aDllName,
+ const char* aFuncName, PredicateT&& aPred, Args&&... aArgs) {
+ if (TestFunction(aOrigFunc, std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...)) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Executed hooked function %s from %s\n",
+ aFuncName, aDllName);
+ fflush(stdout);
+ return true;
+ }
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to execute hooked function %s from %s\n",
+ aFuncName, aDllName);
+ return false;
+}
+
+struct InterceptorFunction {
+ static const size_t EXEC_MEMBLOCK_SIZE = 64 * 1024; // 64K
+
+ static InterceptorFunction& Create() {
+ // Make sure the executable memory is allocated
+ if (!sBlock) {
+ Init();
+ }
+ MOZ_ASSERT(sBlock);
+
+ // Make sure we aren't making more functions than we allocated room for
+ MOZ_RELEASE_ASSERT((sNumInstances + 1) * sizeof(InterceptorFunction) <=
+ EXEC_MEMBLOCK_SIZE);
+
+ // Grab the next InterceptorFunction from executable memory
+ InterceptorFunction& ret = *reinterpret_cast<InterceptorFunction*>(
+ sBlock + (sNumInstances++ * sizeof(InterceptorFunction)));
+
+ // Set the InterceptorFunction to the code template.
+ auto funcCode = &ret[0];
+ memcpy(funcCode, sInterceptorTemplate, TemplateLength);
+
+ // Fill in the patched_func_called pointer in the template.
+ auto pfPtr =
+ reinterpret_cast<volatile bool**>(&ret[PatchedFuncCalledIndex]);
+ *pfPtr = &patched_func_called;
+ return ret;
+ }
+
+ uint8_t& operator[](size_t i) { return mFuncCode[i]; }
+
+ uint8_t* GetFunction() { return mFuncCode; }
+
+ void SetStub(uintptr_t aStub) {
+ auto pfPtr = reinterpret_cast<uintptr_t*>(&mFuncCode[StubFuncIndex]);
+ *pfPtr = aStub;
+ }
+
+ private:
+ // We intercept functions with short machine-code functions that set a boolean
+ // and run the stub that launches the original function. Each entry in the
+ // array is the code for one of those interceptor functions. We cannot
+ // free this memory until the test shuts down.
+ // The templates have spots for the address of patched_func_called
+ // and for the address of the stub function. Their indices in the byte
+ // array are given as constants below and they appear as blocks of
+ // 0xff bytes in the templates.
+#if defined(_M_X64)
+ // 0: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &patched_func_called
+ // a: c6 00 01 mov BYTE PTR [rax],0x1
+ // d: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &stub_func_ptr
+ // 17: ff e0 jmp rax
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC6, 0x00, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0};
+ static const size_t PatchedFuncCalledIndex = 0x2;
+ static const size_t StubFuncIndex = 0xf;
+#elif defined(_M_IX86)
+ // 0: c6 05 ff ff ff ff 01 mov BYTE PTR &patched_func_called, 0x1
+ // 7: 68 ff ff ff ff push &stub_func_ptr
+ // c: c3 ret
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0xC6, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
+ 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3};
+ static const size_t PatchedFuncCalledIndex = 0x2;
+ static const size_t StubFuncIndex = 0x8;
+#elif defined(_M_ARM64)
+ // 0: 31 00 80 52 movz w17, #0x1
+ // 4: 90 00 00 58 ldr x16, #16
+ // 8: 11 02 00 39 strb w17, [x16]
+ // c: 90 00 00 58 ldr x16, #16
+ // 10: 00 02 1F D6 br x16
+ // 14: &patched_func_called
+ // 1c: &stub_func_ptr
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0x31, 0x00, 0x80, 0x52, 0x90, 0x00, 0x00, 0x58, 0x11, 0x02, 0x00, 0x39,
+ 0x90, 0x00, 0x00, 0x58, 0x00, 0x02, 0x1F, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ static const size_t PatchedFuncCalledIndex = 0x14;
+ static const size_t StubFuncIndex = 0x1c;
+#else
+# error "Missing template for architecture"
+#endif
+
+ static const size_t TemplateLength = sizeof(sInterceptorTemplate);
+ uint8_t mFuncCode[TemplateLength];
+
+ InterceptorFunction() = delete;
+ InterceptorFunction(const InterceptorFunction&) = delete;
+ InterceptorFunction& operator=(const InterceptorFunction&) = delete;
+
+ static void Init() {
+ MOZ_ASSERT(!sBlock);
+ sBlock = reinterpret_cast<uint8_t*>(
+ ::VirtualAlloc(nullptr, EXEC_MEMBLOCK_SIZE, MEM_RESERVE | MEM_COMMIT,
+ PAGE_EXECUTE_READWRITE));
+ }
+
+ static uint8_t* sBlock;
+ static size_t sNumInstances;
+};
+
+uint8_t* InterceptorFunction::sBlock = nullptr;
+size_t InterceptorFunction::sNumInstances = 0;
+
+constexpr uint8_t InterceptorFunction::sInterceptorTemplate[];
+
+// Hook the function and optionally attempt calling it
+template <typename OrigFuncT, size_t N, typename PredicateT, typename... Args>
+bool TestHook(const char (&dll)[N], const char* func, PredicateT&& aPred,
+ Args&&... aArgs) {
+ auto orig_func(
+ mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
+ wchar_t dllW[N];
+ std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
+
+ bool successful = false;
+ WindowsDllInterceptor TestIntercept;
+ TestIntercept.Init(dll);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ successful = orig_func->Set(
+ TestIntercept, func,
+ reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
+
+ if (successful) {
+ auto stub = reinterpret_cast<uintptr_t>(orig_func->GetStub());
+ interceptorFunc.SetStub(stub);
+ printf("TEST-PASS | WindowsDllInterceptor | Could hook %s from %s\n", func,
+ dll);
+ fflush(stdout);
+
+ // Test the DLL function we just hooked.
+ HMODULE module = ::LoadLibraryW(dllW);
+ FARPROC funcAddr = ::GetProcAddress(module, func);
+ if (!funcAddr) {
+ return false;
+ }
+
+// Check that unwind information has been added if and only if it was present
+// for the original function.
+#ifdef _M_X64
+
+ auto funcBytes = reinterpret_cast<uint8_t*>(funcAddr);
+
+ // If the function we are hooking is a jumper, we need to lookup the
+ // destination of the jump to find the unwind information.
+ auto realFuncAddr = reinterpret_cast<uintptr_t>(funcAddr);
+ // jmp qword ptr[rip+offset]
+ if (funcBytes[0] == 0xff && funcBytes[1] == 0x25) {
+ realFuncAddr = *reinterpret_cast<uintptr_t*>(
+ realFuncAddr + 6 + *reinterpret_cast<int32_t*>(realFuncAddr + 2));
+ }
+ // rex.jmp qword ptr[rip+offset]
+ else if (funcBytes[0] == 0x48 && funcBytes[1] == 0xff &&
+ funcBytes[2] == 0x25) {
+ realFuncAddr = *reinterpret_cast<uintptr_t*>(
+ realFuncAddr + 7 + *reinterpret_cast<int32_t*>(realFuncAddr + 3));
+ }
+
+ uintptr_t funcImageBase = 0;
+ auto funcEntry =
+ RtlLookupFunctionEntry(realFuncAddr, &funcImageBase, nullptr);
+ bool funcHasUnwindInfo = bool(funcEntry);
+
+ uintptr_t stubImageBase = 0;
+ auto stubEntry = RtlLookupFunctionEntry(stub, &stubImageBase, nullptr);
+ bool stubHasUnwindInfo = bool(stubEntry);
+
+ if (funcHasUnwindInfo == stubHasUnwindInfo) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s and "
+ "the original function are coherent with respect to unwind info: "
+ "funcHasUnwindInfo (%d) == stubHasUnwindInfo (%d).\n",
+ func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook for %s from %s "
+ "and the original function are not coherent with respect to unwind "
+ "info: "
+ "funcHasUnwindInfo (%d) != stubHasUnwindInfo (%d).\n",
+ func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
+ fflush(stdout);
+ return false;
+ }
+
+ if (stubHasUnwindInfo) {
+ if (stub == (stubImageBase + stubEntry->BeginAddress)) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s has "
+ "coherent unwind info.\n",
+ func, dll);
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | The hook for %s "
+ " from %s has incoherent unwind info.\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+ }
+
+#endif // _M_X64
+
+ if (!aPred) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | "
+ "Will not attempt to execute patched %s.\n",
+ func);
+ fflush(stdout);
+ return true;
+ }
+
+ return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
+ std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+
+ // Print out the function's bytes so that we can easily analyze the error.
+ nsModuleHandle mod(::LoadLibraryW(dllW));
+ FARPROC funcAddr = ::GetProcAddress(mod, func);
+ if (funcAddr) {
+ const uint32_t kNumBytesToDump =
+ WindowsDllInterceptor::GetWorstCaseRequiredBytesToPatch();
+
+ printf("\tFirst %u bytes of function:\n\t", kNumBytesToDump);
+
+ auto code = reinterpret_cast<const uint8_t*>(funcAddr);
+ for (uint32_t i = 0; i < kNumBytesToDump; ++i) {
+ char suffix = (i < (kNumBytesToDump - 1)) ? ' ' : '\n';
+ printf("%02hhX%c", code[i], suffix);
+ }
+
+ fflush(stdout);
+ }
+ return false;
+ }
+}
+
+// Detour the function and optionally attempt calling it
+template <typename OrigFuncT, size_t N, typename PredicateT>
+bool TestDetour(const char (&dll)[N], const char* func, PredicateT&& aPred) {
+ auto orig_func(
+ mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
+ wchar_t dllW[N];
+ std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
+
+ bool successful = false;
+ WindowsDllInterceptor TestIntercept;
+ TestIntercept.Init(dll);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ successful = orig_func->Set(
+ TestIntercept, func,
+ reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
+
+ if (successful) {
+ interceptorFunc.SetStub(reinterpret_cast<uintptr_t>(orig_func->GetStub()));
+ printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n",
+ func, dll);
+ fflush(stdout);
+ if (!aPred) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | "
+ "Will not attempt to execute patched %s.\n",
+ func);
+ fflush(stdout);
+ return true;
+ }
+
+ // Test the DLL function we just hooked.
+ HMODULE module = ::LoadLibraryW(dllW);
+ FARPROC funcAddr = ::GetProcAddress(module, func);
+ if (!funcAddr) {
+ return false;
+ }
+
+ return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
+ std::forward<PredicateT>(aPred));
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s "
+ "from %s\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+}
+
+// If a function pointer's type returns void*, this template converts that type
+// to return uintptr_t instead, for the purposes of predicates.
+template <typename FuncT>
+struct SubstituteForVoidPtr {
+ using Type = FuncT;
+};
+
+template <typename... Args>
+struct SubstituteForVoidPtr<void* (*)(Args...)> {
+ using Type = uintptr_t (*)(Args...);
+};
+
+#ifdef _M_IX86
+template <typename... Args>
+struct SubstituteForVoidPtr<void*(__stdcall*)(Args...)> {
+ using Type = uintptr_t(__stdcall*)(Args...);
+};
+
+template <typename... Args>
+struct SubstituteForVoidPtr<void*(__fastcall*)(Args...)> {
+ using Type = uintptr_t(__fastcall*)(Args...);
+};
+#endif // _M_IX86
+
+// Determines the function's return type
+template <typename FuncT>
+struct ReturnType;
+
+template <typename R, typename... Args>
+struct ReturnType<R (*)(Args...)> {
+ using Type = R;
+};
+
+#ifdef _M_IX86
+template <typename R, typename... Args>
+struct ReturnType<R(__stdcall*)(Args...)> {
+ using Type = R;
+};
+
+template <typename R, typename... Args>
+struct ReturnType<R(__fastcall*)(Args...)> {
+ using Type = R;
+};
+#endif // _M_IX86
+
+// Predicates that may be supplied during tests
+template <typename FuncT>
+struct Predicates {
+ using ArgType = typename ReturnType<FuncT>::Type;
+
+ template <ArgType CompVal>
+ static bool Equals(ArgType aValue) {
+ return CompVal == aValue;
+ }
+
+ template <ArgType CompVal>
+ static bool NotEquals(ArgType aValue) {
+ return CompVal != aValue;
+ }
+
+ template <ArgType CompVal>
+ static bool Ignore(ArgType aValue) {
+ return true;
+ }
+};
+
+// Functions that return void should be ignored, so we specialize the
+// Ignore predicate for that case. Use nullptr as the value to compare against.
+template <typename... Args>
+struct Predicates<void (*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+
+#ifdef _M_IX86
+template <typename... Args>
+struct Predicates<void(__stdcall*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+
+template <typename... Args>
+struct Predicates<void(__fastcall*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+#endif // _M_IX86
+
+// The standard test. Hook |func|, and then try executing it with all zero
+// arguments, using |pred| and |comp| to determine whether the call successfully
+// executed. In general, you want set pred and comp such that they return true
+// when the function is returning whatever value is expected with all-zero
+// arguments.
+//
+// Note: When |func| returns void, you must supply |Ignore| and |nullptr| as the
+// |pred| and |comp| arguments, respectively.
+#define TEST_HOOK(dll, func, pred, comp) \
+ TestHook<decltype(&func)>(dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+// We need to special-case functions that return INVALID_HANDLE_VALUE
+// (ie, CreateFile). Our template machinery for comparing values doesn't work
+// with integer constants passed as pointers (well, it works on MSVC, but not
+// clang, because that is not standard-compliant).
+#define TEST_HOOK_FOR_INVALID_HANDLE_VALUE(dll, func) \
+ TestHook<SubstituteForVoidPtr<decltype(&func)>::Type>( \
+ dll, #func, \
+ &Predicates<SubstituteForVoidPtr<decltype(&func)>::Type>::Equals< \
+ uintptr_t(-1)>)
+
+// This variant allows you to explicitly supply arguments to the hooked function
+// during testing. You want to provide arguments that produce the conditions
+// that induce the function to return a value that is accepted by your
+// predicate.
+#define TEST_HOOK_PARAMS(dll, func, pred, comp, ...) \
+ TestHook<decltype(&func)>( \
+ dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+// This is for cases when we want to hook |func|, but it is unsafe to attempt
+// to execute the function in the context of a test.
+#define TEST_HOOK_SKIP_EXEC(dll, func) \
+ TestHook<decltype(&func)>( \
+ dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+// The following three variants are identical to the previous macros,
+// however the forcibly use a Detour on 32-bit Windows. On 64-bit Windows,
+// these macros are identical to their TEST_HOOK variants.
+#define TEST_DETOUR(dll, func, pred, comp) \
+ TestDetour<decltype(&func)>(dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define TEST_DETOUR_PARAMS(dll, func, pred, comp, ...) \
+ TestDetour<decltype(&func)>( \
+ dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+#define TEST_DETOUR_SKIP_EXEC(dll, func) \
+ TestDetour<decltype(&func)>( \
+ dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+template <typename OrigFuncT, size_t N, typename PredicateT, typename... Args>
+bool MaybeTestHook(const bool cond, const char (&dll)[N], const char* func,
+ PredicateT&& aPred, Args&&... aArgs) {
+ if (!cond) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+ return true;
+ }
+
+ return TestHook<OrigFuncT>(dll, func, std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...);
+}
+
+// Like TEST_HOOK, but the test is only executed when cond is true.
+#define MAYBE_TEST_HOOK(cond, dll, func, pred, comp) \
+ MaybeTestHook<decltype(&func)>(cond, dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define MAYBE_TEST_HOOK_PARAMS(cond, dll, func, pred, comp, ...) \
+ MaybeTestHook<decltype(&func)>( \
+ cond, dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+#define MAYBE_TEST_HOOK_SKIP_EXEC(cond, dll, func) \
+ MaybeTestHook<decltype(&func)>( \
+ cond, dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+bool ShouldTestTipTsf() {
+ if (!IsWin8OrLater()) {
+ return false;
+ }
+
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&SHGetKnownFolderPath)>
+ pSHGetKnownFolderPath(L"shell32.dll", "SHGetKnownFolderPath");
+ if (!pSHGetKnownFolderPath) {
+ return false;
+ }
+
+ PWSTR commonFilesPath = nullptr;
+ if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
+ &commonFilesPath))) {
+ return false;
+ }
+
+ wchar_t fullPath[MAX_PATH + 1] = {};
+ wcscpy(fullPath, commonFilesPath);
+ wcscat(fullPath, L"\\Microsoft Shared\\Ink\\tiptsf.dll");
+ CoTaskMemFree(commonFilesPath);
+
+ if (!LoadLibraryW(fullPath)) {
+ return false;
+ }
+
+ // Leak the module so that it's loaded for the interceptor test
+ return true;
+}
+
+static const wchar_t gEmptyUnicodeStringLiteral[] = L"";
+static UNICODE_STRING gEmptyUnicodeString;
+static BOOLEAN gIsPresent;
+
+bool HasApiSetQueryApiSetPresence() {
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&ApiSetQueryApiSetPresence)>
+ func(L"Api-ms-win-core-apiquery-l1-1-0.dll", "ApiSetQueryApiSetPresence");
+ if (!func) {
+ return false;
+ }
+
+ // Prepare gEmptyUnicodeString for the test
+ ::RtlInitUnicodeString(&gEmptyUnicodeString, gEmptyUnicodeStringLiteral);
+
+ return true;
+}
+
+// Set this to true to test function unhooking (currently broken).
+const bool ShouldTestUnhookFunction = false;
+
+#if defined(_M_X64) || defined(_M_ARM64)
+
+// Use VMSharingPolicyUnique for the ShortInterceptor, as it needs to
+// reserve its trampoline memory in a special location.
+using ShortInterceptor = mozilla::interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess>>;
+
+static ShortInterceptor::FuncHookType<decltype(&::NtMapViewOfSection)>
+ orig_NtMapViewOfSection;
+
+#endif // defined(_M_X64) || defined(_M_ARM64)
+
+bool TestShortDetour() {
+#if defined(_M_X64) || defined(_M_ARM64)
+ auto pNtMapViewOfSection = reinterpret_cast<decltype(&::NtMapViewOfSection)>(
+ ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtMapViewOfSection"));
+ if (!pNtMapViewOfSection) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to resolve ntdll!NtMapViewOfSection\n");
+ fflush(stdout);
+ return false;
+ }
+
+ { // Scope for shortInterceptor
+ ShortInterceptor shortInterceptor;
+ shortInterceptor.TestOnlyDetourInit(
+ L"ntdll.dll",
+ mozilla::interceptor::DetourFlags::eTestOnlyForceShortPatch);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ if (!orig_NtMapViewOfSection.SetDetour(
+ shortInterceptor, "NtMapViewOfSection",
+ reinterpret_cast<decltype(&::NtMapViewOfSection)>(
+ interceptorFunc.GetFunction()))) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to hook ntdll!NtMapViewOfSection via 10-byte patch\n");
+ fflush(stdout);
+ return false;
+ }
+
+ interceptorFunc.SetStub(
+ reinterpret_cast<uintptr_t>(orig_NtMapViewOfSection.GetStub()));
+
+ auto pred =
+ &Predicates<decltype(&::NtMapViewOfSection)>::Ignore<((NTSTATUS)0)>;
+
+ if (!CheckHook(pNtMapViewOfSection, "ntdll.dll", "NtMapViewOfSection",
+ pred)) {
+ // CheckHook has already printed the error message for us
+ return false;
+ }
+ }
+
+ // Now ensure that our hook cleanup worked
+ if (ShouldTestUnhookFunction) {
+ NTSTATUS status =
+ pNtMapViewOfSection(nullptr, nullptr, nullptr, 0, 0, nullptr, nullptr,
+ ((SECTION_INHERIT)0), 0, 0);
+ if (NT_SUCCESS(status)) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Unexpected successful call to ntdll!NtMapViewOfSection after "
+ "removing short-patched hook\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully unhooked ntdll!NtMapViewOfSection via short patch\n");
+ fflush(stdout);
+ }
+
+ return true;
+#else
+ return true;
+#endif
+}
+
+constexpr uintptr_t NoStubAddressCheck = 0;
+constexpr uintptr_t ExpectedFail = 1;
+struct TestCase {
+ const char* mFunctionName;
+ uintptr_t mExpectedStub;
+ bool mPatchedOnce;
+ explicit TestCase(const char* aFunctionName, uintptr_t aExpectedStub)
+ : mFunctionName(aFunctionName),
+ mExpectedStub(aExpectedStub),
+ mPatchedOnce(false) {}
+} g_AssemblyTestCases[] = {
+#if defined(__clang__)
+// We disable these testcases because the code coverage instrumentation injects
+// code in a way that WindowsDllInterceptor doesn't understand.
+# ifndef MOZ_CODE_COVERAGE
+# if defined(_M_X64)
+ // Since we have PatchIfTargetIsRecognizedTrampoline for x64, we expect the
+ // original jump destination is returned as a stub.
+ TestCase("MovPushRet", JumpDestination),
+ TestCase("MovRaxJump", JumpDestination),
+ TestCase("DoubleJump", JumpDestination),
+
+ // Passing NoStubAddressCheck as the following testcases return
+ // a trampoline address instead of the original destination.
+ TestCase("NearJump", NoStubAddressCheck),
+ TestCase("OpcodeFF", NoStubAddressCheck),
+ TestCase("IndirectCall", NoStubAddressCheck),
+ TestCase("MovImm64", NoStubAddressCheck),
+# elif defined(_M_IX86)
+ // Skip the stub address check as we always generate a trampoline for x86.
+ TestCase("PushRet", NoStubAddressCheck),
+ TestCase("MovEaxJump", NoStubAddressCheck),
+ TestCase("DoubleJump", NoStubAddressCheck),
+ TestCase("Opcode83", NoStubAddressCheck),
+ TestCase("LockPrefix", NoStubAddressCheck),
+ TestCase("LooksLikeLockPrefix", NoStubAddressCheck),
+# endif
+# if !defined(DEBUG)
+ // Skip on Debug build because it hits MOZ_ASSERT_UNREACHABLE.
+ TestCase("UnsupportedOp", ExpectedFail),
+# endif // !defined(DEBUG)
+# endif // MOZ_CODE_COVERAGE
+#endif // defined(__clang__)
+};
+
+template <typename InterceptorType>
+bool TestAssemblyFunctions() {
+ static const auto patchedFunction = []() { patched_func_called = true; };
+
+ InterceptorType interceptor;
+ interceptor.Init("TestDllInterceptor.exe");
+
+ for (auto& testCase : g_AssemblyTestCases) {
+ if (testCase.mExpectedStub == NoStubAddressCheck && testCase.mPatchedOnce) {
+ // For the testcases with NoStubAddressCheck, we revert a hook by
+ // jumping into the original stub, which is not detourable again.
+ continue;
+ }
+
+ typename InterceptorType::template FuncHookType<void (*)()> hook;
+ bool result =
+ hook.Set(interceptor, testCase.mFunctionName, patchedFunction);
+ if (testCase.mExpectedStub == ExpectedFail) {
+ if (result) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Unexpectedly succeeded to detour %s.\n",
+ testCase.mFunctionName);
+ return false;
+ }
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& maybeError = interceptor.GetLastDetourError();
+ if (maybeError.isNothing()) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "DetourError was not set on detour error.\n");
+ return false;
+ }
+ if (maybeError.ref().mErrorCode !=
+ DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "A wrong detour errorcode was set on detour error.\n");
+ return false;
+ }
+#endif // defined(NIGHTLY_BUILD)
+ printf("TEST-PASS | WindowsDllInterceptor | %s\n",
+ testCase.mFunctionName);
+ continue;
+ }
+
+ if (!result) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to detour %s.\n",
+ testCase.mFunctionName);
+ return false;
+ }
+
+ testCase.mPatchedOnce = true;
+
+ const auto actualStub = reinterpret_cast<uintptr_t>(hook.GetStub());
+ if (testCase.mExpectedStub != NoStubAddressCheck &&
+ actualStub != testCase.mExpectedStub) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Wrong stub was backed up for %s: %zx\n",
+ testCase.mFunctionName, actualStub);
+ return false;
+ }
+
+ patched_func_called = false;
+
+ auto originalFunction = reinterpret_cast<void (*)()>(
+ GetProcAddress(GetModuleHandleW(nullptr), testCase.mFunctionName));
+ originalFunction();
+
+ if (!patched_func_called) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Hook from %s was not called\n",
+ testCase.mFunctionName);
+ return false;
+ }
+
+ printf("TEST-PASS | WindowsDllInterceptor | %s\n", testCase.mFunctionName);
+ }
+
+ return true;
+}
+
+#if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+// We want to test hooking and unhooking with unwind information, so we need:
+// - a VMSharingPolicy such that ShouldUnhookUponDestruction() is true and
+// Items() is implemented;
+// - a MMPolicy such that ShouldUnhookUponDestruction() is true and
+// kSupportsUnwindInfo is true.
+using DetouredCallInterceptor = mozilla::interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess>>;
+
+struct DetouredCallChunk {
+ alignas(uint32_t) RUNTIME_FUNCTION functionTable[1];
+ alignas(uint32_t) uint8_t unwindInfo[sizeof(gDetouredCallUnwindInfo)];
+ uint8_t code[gDetouredCallCodeSize];
+};
+
+// Unfortunately using RtlAddFunctionTable for static code that lives within
+// a module doesn't seem to work. Presumably it conflicts with the static
+// function tables. So we recreate gDetouredCall as dynamic code to be able to
+// associate it with unwind information.
+decltype(&DetouredCallCode) gDetouredCall =
+ []() -> decltype(&DetouredCallCode) {
+ auto detouredCallChunk = reinterpret_cast<DetouredCallChunk*>(
+ VirtualAlloc(nullptr, sizeof(DetouredCallChunk), MEM_RESERVE | MEM_COMMIT,
+ PAGE_READWRITE));
+ if (!detouredCallChunk) {
+ return nullptr;
+ }
+
+ detouredCallChunk->functionTable[0].BeginAddress =
+ offsetof(DetouredCallChunk, code);
+ detouredCallChunk->functionTable[0].EndAddress =
+ offsetof(DetouredCallChunk, code) + gDetouredCallCodeSize;
+ detouredCallChunk->functionTable[0].UnwindData =
+ offsetof(DetouredCallChunk, unwindInfo);
+ memcpy(reinterpret_cast<void*>(&detouredCallChunk->unwindInfo),
+ reinterpret_cast<void*>(gDetouredCallUnwindInfo),
+ sizeof(detouredCallChunk->unwindInfo));
+ memcpy(reinterpret_cast<void*>(&detouredCallChunk->code[0]),
+ reinterpret_cast<void*>(DetouredCallCode),
+ sizeof(detouredCallChunk->code));
+
+ DWORD oldProtect = 0;
+ if (!VirtualProtect(reinterpret_cast<void*>(detouredCallChunk),
+ sizeof(DetouredCallChunk), PAGE_EXECUTE_READ,
+ &oldProtect)) {
+ VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
+ return nullptr;
+ }
+
+ if (!RtlAddFunctionTable(detouredCallChunk->functionTable, 1,
+ reinterpret_cast<uintptr_t>(detouredCallChunk))) {
+ VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
+ return nullptr;
+ }
+
+ return reinterpret_cast<decltype(&DetouredCallCode)>(detouredCallChunk->code);
+}();
+
+// We use our own variable instead of patched_func_called because the callee of
+// gDetouredCall could end up calling other, already hooked functions could
+// change patched_func_called.
+static volatile bool sCalledPatchedDetouredCall = false;
+static volatile bool sCalledDetouredCallCallee = false;
+static volatile bool sCouldUnwindFromDetouredCallCallee = false;
+void DetouredCallCallee() {
+ sCalledDetouredCallCallee = true;
+
+ // Check that we can fully unwind the stack
+ CONTEXT contextRecord{};
+ RtlCaptureContext(&contextRecord);
+ if (!contextRecord.Rip) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "DetouredCallCallee was unable to get an initial context to work "
+ "with\n");
+ fflush(stdout);
+ return;
+ }
+ while (contextRecord.Rip) {
+ DWORD64 imageBase = 0;
+ auto FunctionEntry =
+ RtlLookupFunctionEntry(contextRecord.Rip, &imageBase, nullptr);
+ if (!FunctionEntry) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "DetouredCallCallee was unable to get unwind info for ControlPc=%p\n",
+ reinterpret_cast<void*>(contextRecord.Rip));
+ fflush(stdout);
+ return;
+ }
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "DetouredCallCallee was able to get unwind info for ControlPc=%p\n",
+ reinterpret_cast<void*>(contextRecord.Rip));
+ fflush(stdout);
+ void* handlerData = nullptr;
+ DWORD64 establisherFrame = 0;
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, contextRecord.Rip,
+ FunctionEntry, &contextRecord, &handlerData,
+ &establisherFrame, nullptr);
+ }
+ sCouldUnwindFromDetouredCallCallee = true;
+}
+
+static DetouredCallInterceptor::FuncHookType<decltype(&DetouredCallCode)>
+ orig_DetouredCall;
+
+static void patched_DetouredCall(uintptr_t aCallee) {
+ sCalledPatchedDetouredCall = true;
+ return orig_DetouredCall(aCallee);
+}
+
+bool TestCallingDetouredCall(const char* aTestDescription,
+ bool aExpectCalledPatchedDetouredCall) {
+ sCalledPatchedDetouredCall = false;
+ sCalledDetouredCallCallee = false;
+ sCouldUnwindFromDetouredCallCallee = false;
+ DetouredCallJumper(reinterpret_cast<uintptr_t>(DetouredCallCallee));
+
+ if (aExpectCalledPatchedDetouredCall != sCalledPatchedDetouredCall) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: expectCalledPatchedDetouredCall (%d) differs from "
+ "sCalledPatchedDetouredCall (%d)\n",
+ aTestDescription, aExpectCalledPatchedDetouredCall,
+ sCalledPatchedDetouredCall);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: expectCalledPatchedDetouredCall (%d) matches with "
+ "sCalledPatchedDetouredCall (%d)\n",
+ aTestDescription, aExpectCalledPatchedDetouredCall,
+ sCalledPatchedDetouredCall);
+ fflush(stdout);
+
+ if (!sCalledDetouredCallCallee) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: gDetouredCall failed to call its callee\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: gDetouredCall successfully called its callee\n",
+ aTestDescription);
+ fflush(stdout);
+
+ if (!sCouldUnwindFromDetouredCallCallee) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: the callee of gDetouredCall failed to unwind\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: the callee of gDetouredCall successfully unwinded\n",
+ aTestDescription);
+ fflush(stdout);
+ return true;
+}
+
+// Test that detouring a call preserves unwind information (bug 1798787).
+bool TestDetouredCallUnwindInfo() {
+ if (!gDetouredCall) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to generate dynamic gDetouredCall code\n");
+ fflush(stdout);
+ return false;
+ }
+
+ uintptr_t imageBase = 0;
+ if (!RtlLookupFunctionEntry(reinterpret_cast<uintptr_t>(gDetouredCall),
+ &imageBase, nullptr)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to find unwind information for dynamic gDetouredCall code\n");
+ fflush(stdout);
+ return false;
+ }
+
+ // We first double check that we manage to unwind when we *do not* detour
+ if (!TestCallingDetouredCall("Before hooking", false)) {
+ return false;
+ }
+
+ uintptr_t StubAddress = 0;
+
+ // The real test starts here: let's detour and check if we can still unwind
+ {
+ DetouredCallInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+ if (!orig_DetouredCall.Set(ExeIntercept, "DetouredCallJumper",
+ &patched_DetouredCall)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to hook the detoured call jumper.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully hooked the detoured call jumper.\n");
+ fflush(stdout);
+
+ StubAddress = reinterpret_cast<uintptr_t>(orig_DetouredCall.GetStub());
+ if (!RtlLookupFunctionEntry(StubAddress, &imageBase, nullptr)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to find unwind information for detoured code of "
+ "gDetouredCall\n");
+ fflush(stdout);
+ return false;
+ }
+
+ TestCallingDetouredCall("After hooking", true);
+ }
+
+ // Check that we can still unwind after clearing the hook.
+ return TestCallingDetouredCall("After unhooking", false);
+}
+#endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+
+bool TestDynamicCodePolicy() {
+ if (!IsWin8Point1OrLater()) {
+ // Skip if a platform does not support this policy.
+ return true;
+ }
+
+ MOZ_PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {};
+ policy.ProhibitDynamicCode = true;
+
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&SetProcessMitigationPolicy)>
+ pSetProcessMitigationPolicy(L"kernel32.dll",
+ "SetProcessMitigationPolicy");
+ if (!pSetProcessMitigationPolicy) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "SetProcessMitigationPolicy does not exist.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ if (!pSetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy,
+ sizeof(policy))) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Fail to enable ProcessDynamicCodePolicy.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ WindowsDllInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+
+ // Make sure we fail to hook a function if ProcessDynamicCodePolicy is on
+ // because we cannot create an executable trampoline region.
+ if (orig_payloadNotHooked.Set(ExeIntercept, "payloadNotHooked",
+ &patched_rotatePayload)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "ProcessDynamicCodePolicy is not working.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully passed TestDynamicCodePolicy.\n");
+ fflush(stdout);
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ LARGE_INTEGER start;
+ QueryPerformanceCounter(&start);
+
+ // We disable this part of the test because the code coverage instrumentation
+ // injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
+ // understand.
+#ifndef MOZ_CODE_COVERAGE
+ payload initial = {0x12345678, 0xfc4e9d31, 0x87654321};
+ payload p0, p1;
+ ZeroMemory(&p0, sizeof(p0));
+ ZeroMemory(&p1, sizeof(p1));
+
+ p0 = rotatePayload(initial);
+
+ {
+ WindowsDllInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+ if (orig_rotatePayload.Set(ExeIntercept, "rotatePayload",
+ &patched_rotatePayload)) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook added\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to add "
+ "hook\n");
+ fflush(stdout);
+ return 1;
+ }
+
+ p1 = rotatePayload(initial);
+
+ if (patched_func_called) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook called\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was not "
+ "called\n");
+ fflush(stdout);
+ return 1;
+ }
+
+ if (p0 == p1) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook works properly\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook didn't return "
+ "the right information\n");
+ fflush(stdout);
+ return 1;
+ }
+ }
+
+ patched_func_called = false;
+ ZeroMemory(&p1, sizeof(p1));
+
+ p1 = rotatePayload(initial);
+
+ if (ShouldTestUnhookFunction != patched_func_called) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | Hook was %scalled after "
+ "unregistration\n",
+ ShouldTestUnhookFunction ? "not " : "");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was %scalled "
+ "after unregistration\n",
+ ShouldTestUnhookFunction ? "" : "not ");
+ fflush(stdout);
+ return 1;
+ }
+
+ if (p0 == p1) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | Original function worked "
+ "properly\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Original function "
+ "didn't return the right information\n");
+ fflush(stdout);
+ return 1;
+ }
+#endif
+
+ CredHandle credHandle;
+ memset(&credHandle, 0, sizeof(CredHandle));
+ OBJECT_ATTRIBUTES attributes = {};
+
+ // NB: These tests should be ordered such that lower-level APIs are tested
+ // before higher-level APIs.
+ if (TestShortDetour() &&
+ // Run <ShortInterceptor> first because <WindowsDllInterceptor>
+ // does not clean up hooks.
+#if defined(_M_X64)
+ TestAssemblyFunctions<ShortInterceptor>() &&
+#endif
+ TestAssemblyFunctions<WindowsDllInterceptor>() &&
+#ifdef _M_IX86
+ // We keep this test to hook complex code on x86. (Bug 850957)
+ TEST_HOOK("ntdll.dll", NtFlushBuffersFile, NotEquals, 0) &&
+#endif
+ TEST_HOOK("ntdll.dll", NtCreateFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtReadFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtReadFileScatter, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtWriteFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtWriteFileGather, NotEquals, 0) &&
+ TEST_HOOK_PARAMS("ntdll.dll", NtQueryFullAttributesFile, NotEquals, 0,
+ &attributes, nullptr) &&
+ TEST_DETOUR_SKIP_EXEC("ntdll.dll", LdrLoadDll) &&
+ TEST_HOOK("ntdll.dll", LdrUnloadDll, NotEquals, 0) &&
+ MAYBE_TEST_HOOK_SKIP_EXEC(IsWin8OrLater(), "ntdll.dll",
+ LdrResolveDelayLoadedAPI) &&
+ MAYBE_TEST_HOOK_PARAMS(HasApiSetQueryApiSetPresence(),
+ "Api-ms-win-core-apiquery-l1-1-0.dll",
+ ApiSetQueryApiSetPresence, Equals, FALSE,
+ &gEmptyUnicodeString, &gIsPresent) &&
+ TEST_HOOK("kernelbase.dll", QueryDosDeviceW, Equals, 0) &&
+ TEST_HOOK("kernel32.dll", GetFileAttributesW, Equals,
+ INVALID_FILE_ATTRIBUTES) &&
+#if !defined(_M_ARM64)
+# ifndef MOZ_ASAN
+ // Bug 733892: toolkit/crashreporter/nsExceptionHandler.cpp
+ // This fails on ASan because the ASan runtime already hooked this
+ // function
+ TEST_HOOK("kernel32.dll", SetUnhandledExceptionFilter, Ignore, nullptr) &&
+# endif
+#endif // !defined(_M_ARM64)
+#ifdef _M_IX86
+ TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileW) &&
+#endif
+#if !defined(_M_ARM64)
+ TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileA) &&
+#endif // !defined(_M_ARM64)
+#if !defined(_M_ARM64)
+ TEST_HOOK("kernel32.dll", TlsAlloc, NotEquals, TLS_OUT_OF_INDEXES) &&
+ TEST_HOOK_PARAMS("kernel32.dll", TlsFree, Equals, FALSE,
+ TLS_OUT_OF_INDEXES) &&
+ TEST_HOOK("kernel32.dll", CloseHandle, Equals, FALSE) &&
+ TEST_HOOK("kernel32.dll", DuplicateHandle, Equals, FALSE) &&
+#endif // !defined(_M_ARM64)
+ TEST_DETOUR_SKIP_EXEC("kernel32.dll", BaseThreadInitThunk) &&
+#if defined(_M_X64) || defined(_M_ARM64)
+ MAYBE_TEST_HOOK(!IsWin8OrLater(), "kernel32.dll",
+ RtlInstallFunctionTableCallback, Equals, FALSE) &&
+ TEST_HOOK("user32.dll", GetKeyState, Ignore, 0) && // see Bug 1316415
+#endif
+ TEST_HOOK("user32.dll", GetWindowInfo, Equals, FALSE) &&
+ TEST_HOOK("user32.dll", TrackPopupMenu, Equals, FALSE) &&
+ TEST_DETOUR("user32.dll", CreateWindowExW, Equals, nullptr) &&
+ TEST_HOOK("user32.dll", InSendMessageEx, Equals, ISMEX_NOSEND) &&
+ TEST_HOOK("user32.dll", SendMessageTimeoutW, Equals, 0) &&
+ TEST_HOOK("user32.dll", SetCursorPos, NotEquals, FALSE) &&
+#if !defined(_M_ARM64)
+ TEST_HOOK("imm32.dll", ImmGetContext, Equals, nullptr) &&
+#endif // !defined(_M_ARM64)
+ TEST_HOOK("imm32.dll", ImmGetCompositionStringW, Ignore, 0) &&
+ TEST_HOOK_SKIP_EXEC("imm32.dll", ImmSetCandidateWindow) &&
+ TEST_HOOK("imm32.dll", ImmNotifyIME, Equals, 0) &&
+ TEST_HOOK("comdlg32.dll", GetSaveFileNameW, Ignore, FALSE) &&
+ TEST_HOOK("comdlg32.dll", GetOpenFileNameW, Ignore, FALSE) &&
+#if defined(_M_X64)
+ TEST_HOOK("comdlg32.dll", PrintDlgW, Ignore, 0) &&
+#endif
+ MAYBE_TEST_HOOK(ShouldTestTipTsf(), "tiptsf.dll", ProcessCaretEvents,
+ Ignore, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetOpenA, NotEquals, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetCloseHandle, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetConnectA, Equals, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetQueryDataAvailable, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetReadFile, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetWriteFile, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetSetOptionA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpAddRequestHeadersA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpOpenRequestA, Equals, nullptr) &&
+ TEST_HOOK("wininet.dll", HttpQueryInfoA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpSendRequestA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpSendRequestExA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpEndRequestA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetQueryOptionA, Equals, FALSE) &&
+ TEST_HOOK("sspicli.dll", AcquireCredentialsHandleA, NotEquals,
+ SEC_E_OK) &&
+ TEST_HOOK_PARAMS("sspicli.dll", QueryCredentialsAttributesA, Equals,
+ SEC_E_INVALID_HANDLE, &credHandle, 0, nullptr) &&
+ TEST_HOOK_PARAMS("sspicli.dll", FreeCredentialsHandle, Equals,
+ SEC_E_INVALID_HANDLE, &credHandle) &&
+#if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+ TestDetouredCallUnwindInfo() &&
+#endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+ // Run TestDynamicCodePolicy() at the end because the policy is
+ // irreversible.
+ TestDynamicCodePolicy()) {
+ printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
+
+ LARGE_INTEGER end, freq;
+ QueryPerformanceCounter(&end);
+
+ QueryPerformanceFrequency(&freq);
+
+ LARGE_INTEGER result;
+ result.QuadPart = end.QuadPart - start.QuadPart;
+ result.QuadPart *= 1000000;
+ result.QuadPart /= freq.QuadPart;
+
+ printf("Elapsed time: %lld microseconds\n", result.QuadPart);
+
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest b/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest
new file mode 100644
index 0000000000..11287012c5
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
+ manifestVersion="1.0"
+ xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <assemblyIdentity type="win32"
+ name="TestDllInterceptor"
+ version="1.0.0.0" />
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Need this to use functions in WindowsVersion.h -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Win10 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Win8.1 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> <!-- Win8 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> <!-- Win7 -->
+ </application>
+ </compatibility>
+</assembly>
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp b/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp
new file mode 100644
index 0000000000..32cf90bac1
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp
@@ -0,0 +1,159 @@
+/* -*- 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/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include <string>
+
+using std::wstring;
+
+extern "C" __declspec(dllexport) int ReturnResult() { return 2; }
+
+static mozilla::CrossProcessDllInterceptor::FuncHookType<
+ decltype(&ReturnResult)>
+ gOrigReturnResult;
+
+static int ReturnResultHook() {
+ if (gOrigReturnResult() != 2) {
+ return 3;
+ }
+
+ return 0;
+}
+
+int ParentMain(int argc, wchar_t* argv[]) {
+ mozilla::SetArgv0ToFullBinaryPath(argv);
+
+ // We'll add the child process to a job so that, in the event of a failure in
+ // this parent process, the child process will be automatically terminated.
+ nsAutoHandle job(::CreateJobObjectW(nullptr, nullptr));
+ if (!job) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job creation "
+ "failed\n");
+ return 1;
+ }
+
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
+ jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ if (!::SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
+ &jobInfo, sizeof(jobInfo))) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job config "
+ "failed\n");
+ return 1;
+ }
+
+ wchar_t childArgv_1[] = L"-child";
+
+ wchar_t* childArgv[] = {argv[0], childArgv_1};
+
+ mozilla::UniquePtr<wchar_t[]> cmdLine(
+ mozilla::MakeCommandLine(mozilla::ArrayLength(childArgv), childArgv));
+
+ STARTUPINFOW si = {sizeof(si)};
+ PROCESS_INFORMATION pi;
+ if (!::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
+ CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to spawn "
+ "child process\n");
+ return 1;
+ }
+
+ nsAutoHandle childProcess(pi.hProcess);
+ nsAutoHandle childMainThread(pi.hThread);
+
+ if (!::AssignProcessToJobObject(job.get(), childProcess.get())) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to assign "
+ "child process to job\n");
+ ::TerminateProcess(childProcess.get(), 1);
+ return 1;
+ }
+
+ mozilla::nt::CrossExecTransferManager transferMgr(childProcess);
+ if (!transferMgr) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | "
+ "CrossExecTransferManager instantiation failed.\n");
+ return 1;
+ }
+
+ mozilla::CrossProcessDllInterceptor intcpt(childProcess.get());
+ intcpt.Init("TestDllInterceptorCrossProcess.exe");
+
+ if (!gOrigReturnResult.Set(transferMgr, intcpt, "ReturnResult",
+ &ReturnResultHook)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to add "
+ "hook\n");
+ return 1;
+ }
+
+ printf("TEST-PASS | DllInterceptorCrossProcess | Hook added\n");
+
+ if (::ResumeThread(childMainThread.get()) == static_cast<DWORD>(-1)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to resume "
+ "child thread\n");
+ return 1;
+ }
+
+ BOOL remoteDebugging;
+ bool debugging =
+ ::IsDebuggerPresent() ||
+ (::CheckRemoteDebuggerPresent(childProcess.get(), &remoteDebugging) &&
+ remoteDebugging);
+
+ DWORD waitResult =
+ ::WaitForSingleObject(childProcess.get(), debugging ? INFINITE : 60000);
+ if (waitResult != WAIT_OBJECT_0) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process "
+ "failed to finish\n");
+ return 1;
+ }
+
+ DWORD childExitCode;
+ if (!::GetExitCodeProcess(childProcess.get(), &childExitCode)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to obtain "
+ "child process exit code\n");
+ return 1;
+ }
+
+ if (childExitCode) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process "
+ "exit code is %lu instead of 0\n",
+ childExitCode);
+ return 1;
+ }
+
+ printf(
+ "TEST-PASS | DllInterceptorCrossProcess | Child process exit code is "
+ "zero\n");
+ return 0;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ if (argc > 1) {
+ // clang keeps inlining this call despite every attempt to force it to do
+ // otherwise. We'll use GetProcAddress and call its function pointer
+ // instead.
+ auto pReturnResult = reinterpret_cast<decltype(&ReturnResult)>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "ReturnResult"));
+ return pReturnResult();
+ }
+
+ return ParentMain(argc, argv);
+}
diff --git a/toolkit/xre/dllservices/tests/TestIATPatcher.cpp b/toolkit/xre/dllservices/tests/TestIATPatcher.cpp
new file mode 100644
index 0000000000..2f84c0541a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestIATPatcher.cpp
@@ -0,0 +1,121 @@
+/* -*- 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/. */
+
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include <shlwapi.h>
+
+static int NormalImport() { return ::GetSystemMetrics(SM_CYCAPTION); }
+
+static bool DelayLoadImport() {
+ return !!::UrlIsW(L"http://example.com/", URLIS_FILEURL);
+}
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::GetSystemMetrics)>
+ gGetSystemMetricsHook;
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::MessageBoxA)>
+ gMessageBoxAHook;
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::UrlIsW)> gUrlIsHook;
+
+static bool gGetSystemMetricsHookCalled = false;
+
+static int WINAPI GetSystemMetricsHook(int aIndex) {
+ MOZ_DIAGNOSTIC_ASSERT(aIndex == SM_CYCAPTION);
+ gGetSystemMetricsHookCalled = true;
+ return 0;
+}
+
+static bool gUrlIsHookCalled = false;
+
+static BOOL WINAPI UrlIsWHook(PCWSTR aUrl, URLIS aFlags) {
+ gUrlIsHookCalled = true;
+ return TRUE;
+}
+
+static HMODULE GetStrongReferenceToExeModule() {
+ HMODULE result;
+ if (!::GetModuleHandleExW(0, nullptr, &result)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+#define PRINT_FAIL(msg) printf("TEST-UNEXPECTED-FAIL | IATPatcher | " msg "\n")
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ nsModuleHandle ourModule1(GetStrongReferenceToExeModule());
+ if (!ourModule1) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ if (!gGetSystemMetricsHook.Set(ourModule1, "user32.dll", "GetSystemMetrics",
+ &GetSystemMetricsHook)) {
+ PRINT_FAIL("Failed setting GetSystemMetrics hook");
+ return 1;
+ }
+
+ if (NormalImport() || !gGetSystemMetricsHookCalled) {
+ PRINT_FAIL("GetSystemMetrics hook was not called");
+ return 1;
+ }
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::GetSystemMetrics)>
+ pRealGetSystemMetrics(L"user32.dll", "GetSystemMetrics");
+ if (!pRealGetSystemMetrics) {
+ PRINT_FAIL("Failed resolving real GetSystemMetrics pointer");
+ return 1;
+ }
+
+ if (gGetSystemMetricsHook.GetStub() != pRealGetSystemMetrics) {
+ PRINT_FAIL(
+ "GetSystemMetrics hook stub pointer does not match real "
+ "GetSystemMetrics pointer");
+ return 1;
+ }
+
+ nsModuleHandle ourModule2(GetStrongReferenceToExeModule());
+ if (!ourModule2) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ // This should fail becuase the test never calls, and thus never imports,
+ // MessageBoxA
+ if (gMessageBoxAHook.Set(ourModule2, "user32.dll", "MessageBoxA", nullptr)) {
+ PRINT_FAIL("Setting MessageBoxA hook succeeded when it should have failed");
+ return 1;
+ }
+
+ nsModuleHandle ourModule3(GetStrongReferenceToExeModule());
+ if (!ourModule3) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ // These tests involve a delay-loaded import, which are not supported; we
+ // expect these tests to FAIL.
+
+ if (gUrlIsHook.Set(ourModule3, "shlwapi.dll", "UrlIsW", &UrlIsWHook)) {
+ PRINT_FAIL("gUrlIsHook.Set should have failed");
+ return 1;
+ }
+
+ if (DelayLoadImport() || gUrlIsHookCalled) {
+ PRINT_FAIL("gUrlIsHook should not have been called");
+ return 1;
+ }
+
+ printf("TEST-PASS | IATPatcher | All tests passed.\n");
+ return 0;
+}
diff --git a/toolkit/xre/dllservices/tests/TestMMPolicy.cpp b/toolkit/xre/dllservices/tests/TestMMPolicy.cpp
new file mode 100644
index 0000000000..1ae93a4ed1
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestMMPolicy.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindowsDllInterceptor.h"
+
+#include <functional>
+
+mozilla::interceptor::MMPolicyInProcess gPolicy;
+
+void DepleteVirtualAddress(
+ uint8_t* aStart, size_t aSize,
+ const std::function<void(void*)>& aPostAllocCallback) {
+ const DWORD granularity = gPolicy.GetAllocGranularity();
+ if (aStart == 0 || aSize < granularity) {
+ return;
+ }
+
+ uint8_t* alignedStart = reinterpret_cast<uint8_t*>(
+ (((reinterpret_cast<uintptr_t>(aStart) - 1) / granularity) + 1) *
+ granularity);
+ aSize -= (alignedStart - aStart);
+ if (auto p = VirtualAlloc(alignedStart, aSize, MEM_RESERVE, PAGE_NOACCESS)) {
+ aPostAllocCallback(p);
+ return;
+ }
+
+ uintptr_t mask = ~(static_cast<uintptr_t>(granularity) - 1);
+ size_t halfSize = (aSize >> 1) & mask;
+ if (halfSize == 0) {
+ return;
+ }
+
+ DepleteVirtualAddress(aStart, halfSize, aPostAllocCallback);
+ DepleteVirtualAddress(aStart + halfSize, aSize - halfSize,
+ aPostAllocCallback);
+}
+
+bool ValidateFreeRegion(LPVOID aRegion, size_t aDesiredLen) {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(aRegion, &mbi, sizeof(mbi)) != sizeof(mbi)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualQuery(%p) failed - %08lx\n",
+ aRegion, GetLastError());
+ return false;
+ }
+
+ if (mbi.State != MEM_FREE) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "%p is not within a free region\n",
+ aRegion);
+ return false;
+ }
+
+ if (aRegion != mbi.BaseAddress ||
+ reinterpret_cast<uintptr_t>(mbi.BaseAddress) %
+ gPolicy.GetAllocGranularity()) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "%p is not a region's start address\n",
+ aRegion);
+ return false;
+ }
+
+ LPVOID allocated = VirtualAlloc(aRegion, aDesiredLen,
+ MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+ if (!allocated) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualAlloc(%p) failed - %08lx\n",
+ aRegion, GetLastError());
+ return false;
+ }
+
+ if (!VirtualFree(allocated, 0, MEM_RELEASE)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualFree(%p) failed - %08lx\n",
+ allocated, GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool TestFindRegion() {
+ // Skip the near-null addresses
+ uint8_t* minAddr = reinterpret_cast<uint8_t*>(
+ std::max(gPolicy.GetAllocGranularity(), 0x1000000ul));
+ // 64bit address space is too large to deplete. 32bit space is enough.
+ uint8_t* maxAddr = reinterpret_cast<uint8_t*>(std::min(
+ gPolicy.GetMaxUserModeAddress(), static_cast<uintptr_t>(0xffffffff)));
+
+ // Keep one of the regions we allocate so that we can release it later.
+ void* lastResort = nullptr;
+
+ // Reserve all free regions in the range [minAddr, maxAddr]
+ for (uint8_t* address = minAddr; address <= maxAddr;) {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(address, &mbi, sizeof(mbi)) != sizeof(mbi)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualQuery(%p) failed - %08lx\n",
+ address, GetLastError());
+ break;
+ }
+
+ address = reinterpret_cast<uint8_t*>(mbi.BaseAddress);
+ if (mbi.State == MEM_FREE) {
+ DepleteVirtualAddress(address, mbi.RegionSize,
+ [&lastResort](void* aAllocated) {
+ // Pick the first address we allocate to make sure
+ // FindRegion scans the full range.
+ if (!lastResort) {
+ lastResort = aAllocated;
+ }
+ });
+ }
+
+ address += mbi.RegionSize;
+ }
+
+ if (!lastResort) {
+ printf(
+ "TEST-SKIPPED | TestMMPolicy | "
+ "No free region in [%p - %p]. Skipping the testcase.\n",
+ minAddr, maxAddr);
+ return true;
+ }
+
+ // Make sure there are no free regions
+ PVOID freeRegion =
+ gPolicy.FindRegion(GetCurrentProcess(), 1, minAddr, maxAddr);
+ if (freeRegion) {
+ if (reinterpret_cast<uintptr_t>(freeRegion) %
+ gPolicy.GetAllocGranularity()) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "MMPolicyBase::FindRegion returned an unaligned address %p.\n",
+ freeRegion);
+ return false;
+ }
+
+ printf(
+ "TEST-SKIPPED | TestMMPolicy | "
+ "%p was freed after depletion. Skipping the testcase.\n",
+ freeRegion);
+ return true;
+ }
+
+ // Free one region, and thus we can expect FindRegion finds this region
+ if (!VirtualFree(lastResort, 0, MEM_RELEASE)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualFree(%p) failed - %08lx\n",
+ lastResort, GetLastError());
+ return false;
+ }
+ printf("The region starting from %p has been freed.\n", lastResort);
+
+ // Run the function several times because it uses a randon number inside
+ // and its result is nondeterministic.
+ for (int i = 0; i < 50; ++i) {
+ // Because one region was freed, a desire up to one region
+ // should be fulfilled.
+ const size_t desiredLengths[] = {1, gPolicy.GetAllocGranularity()};
+
+ for (auto desiredLen : desiredLengths) {
+ freeRegion =
+ gPolicy.FindRegion(GetCurrentProcess(), desiredLen, minAddr, maxAddr);
+ if (!freeRegion) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "Failed to find a free region.\n");
+ return false;
+ }
+
+ if (!ValidateFreeRegion(freeRegion, desiredLen)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ // Preload delayload modules (e.g. advapi32.dll, bcryptPrimitives.dll, etc.)
+ // by calling rand_s(), which is used in MMPolicy::FindRegion, before
+ // depleting the process memory during the test.
+ unsigned int rnd = 0;
+ rand_s(&rnd);
+
+ if (!TestFindRegion()) {
+ return 1;
+ }
+
+ printf("TEST-PASS | TestMMPolicy | All tests passed.\n");
+ return 0;
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp
new file mode 100644
index 0000000000..5f141b22ae
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp
@@ -0,0 +1,223 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+#include <winternl.h>
+
+#include <process.h>
+
+#include "gtest/gtest.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Char16.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWindowsHelpers.h"
+
+static nsString GetFullPath(const nsAString& aLeaf) {
+ nsCOMPtr<nsIFile> f;
+
+ EXPECT_TRUE(NS_SUCCEEDED(
+ NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(f))));
+
+ EXPECT_NS_SUCCEEDED(f->Append(aLeaf));
+
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(f->Exists(&exists)) && exists);
+
+ nsString ret;
+ EXPECT_NS_SUCCEEDED(f->GetPath(ret));
+ return ret;
+}
+
+TEST(TestDllBlocklist, BlockDllByName)
+{
+ // The DLL name has capital letters, so this also tests that the comparison
+ // is case-insensitive.
+ constexpr auto kLeafName = u"TestDllBlocklist_MatchByName.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE));
+ // Mapped as MEM_MAPPED + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, BlockDllByVersion)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_MatchByVersion.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(
+ ::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE));
+ // Mapped as MEM_IMAGE + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, AllowDllByVersion)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_AllowByVersion.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, SocketProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_SocketProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, UtilityProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_UtilityProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+// RedirectToNoOpEntryPoint needs the launcher process.
+#if defined(MOZ_LAUNCHER_PROCESS)
+TEST(TestDllBlocklist, NoOpEntryPoint)
+{
+ // DllMain of this dll has MOZ_RELEASE_ASSERT. This test makes sure we load
+ // the module successfully without running DllMain.
+ constexpr auto kLeafName = u"TestDllBlocklist_NoOpEntryPoint.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+# if defined(MOZ_ASAN)
+ // With ASAN, the test uses mozglue's blocklist where
+ // REDIRECT_TO_NOOP_ENTRYPOINT is ignored. So LoadLibraryW
+ // is expected to fail.
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+# else
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+# endif
+}
+
+// User blocklist needs the launcher process
+TEST(TestDllBlocklist, UserBlocked)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_UserBlocked.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+// With ASAN, the test uses mozglue's blocklist where
+// the user blocklist is not used.
+# if !defined(MOZ_ASAN)
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+# endif
+ hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE));
+ // Mapped as MEM_MAPPED + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE const char*
+#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
+
+TEST(TestDllBlocklist, BlocklistIntegrity)
+{
+ nsTArray<DLL_BLOCKLIST_STRING_TYPE> dupes;
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(pFirst);
+ DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(pLast);
+
+ EXPECT_FALSE(pLast->mName || pLast->mMaxVersion || pLast->mFlags);
+
+ for (size_t i = 0; i < mozilla::ArrayLength(gWindowsDllBlocklist) - 1; ++i) {
+ auto pEntry = pFirst + i;
+
+ // Validate name
+ EXPECT_TRUE(!!pEntry->mName);
+ EXPECT_GT(strlen(pEntry->mName), 3U);
+
+ // Check the filename for valid characters.
+ for (auto pch = pEntry->mName; *pch != 0; ++pch) {
+ EXPECT_FALSE(*pch >= 'A' && *pch <= 'Z');
+ }
+
+ // Check for duplicate entries
+ for (auto&& dupe : dupes) {
+ EXPECT_NE(stricmp(dupe, pEntry->mName), 0);
+ }
+
+ dupes.AppendElement(pEntry->mName);
+ }
+}
+
+TEST(TestDllBlocklist, BlockThreadWithLoadLibraryEntryPoint)
+{
+ // Only supported on Nightly
+#if defined(NIGHTLY_BUILD)
+ using ThreadProc = unsigned(__stdcall*)(void*);
+
+ constexpr auto kLeafNameW = u"TestDllBlocklist_MatchByVersion.dll"_ns;
+
+ nsString fullPathW = GetFullPath(kLeafNameW);
+ EXPECT_FALSE(fullPathW.IsEmpty());
+
+ nsAutoHandle threadW(reinterpret_cast<HANDLE>(
+ _beginthreadex(nullptr, 0, reinterpret_cast<ThreadProc>(&::LoadLibraryW),
+ (void*)fullPathW.get(), 0, nullptr)));
+
+ EXPECT_TRUE(!!threadW);
+ EXPECT_EQ(::WaitForSingleObject(threadW, INFINITE), WAIT_OBJECT_0);
+
+# if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ DWORD exitCode;
+ EXPECT_TRUE(::GetExitCodeThread(threadW, &exitCode) && !exitCode);
+# endif // !defined(MOZ_ASAN)
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafNameW.get()));
+
+ const NS_LossyConvertUTF16toASCII fullPathA(fullPathW);
+ EXPECT_FALSE(fullPathA.IsEmpty());
+
+ nsAutoHandle threadA(reinterpret_cast<HANDLE>(
+ _beginthreadex(nullptr, 0, reinterpret_cast<ThreadProc>(&::LoadLibraryA),
+ (void*)fullPathA.get(), 0, nullptr)));
+
+ EXPECT_TRUE(!!threadA);
+ EXPECT_EQ(::WaitForSingleObject(threadA, INFINITE), WAIT_OBJECT_0);
+# if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ EXPECT_TRUE(::GetExitCodeThread(threadA, &exitCode) && !exitCode);
+# endif // !defined(MOZ_ASAN)
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafNameW.get()));
+#endif // defined(NIGHTLY_BUILD)
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc
new file mode 100644
index 0000000000..f56aa099ff
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,6
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_AllowByVersion.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build
new file mode 100644
index 0000000000..0987cdde1a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_AllowByVersion")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_AllowByVersion.cpp",
+]
+
+RCFILE = "TestDllBlocklist_AllowByVersion.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_AllowByVersion.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build
new file mode 100644
index 0000000000..f34931898a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MatchByName")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MatchByName.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_MatchByName.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc
new file mode 100644
index 0000000000..7390c1cb34
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_MatchByVersion.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build
new file mode 100644
index 0000000000..38e10524c7
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_MatchByVersion")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MatchByVersion.cpp",
+]
+
+RCFILE = "TestDllBlocklist_MatchByVersion.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_MatchByVersion.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp
new file mode 100644
index 0000000000..2505b8b700
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+#include "mozilla/Assertions.h"
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) {
+ MOZ_RELEASE_ASSERT(0);
+ return TRUE;
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc
new file mode 100644
index 0000000000..7c79dac373
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "mozilla.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_NoOpEntryPoint.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build
new file mode 100644
index 0000000000..e9a10a150a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build
@@ -0,0 +1,21 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_NoOpEntryPoint")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_NoOpEntryPoint.cpp",
+]
+
+RCFILE = "TestDllBlocklist_NoOpEntryPoint.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_NoOpEntryPoint.dll"]
+
+OS_LIBS += [
+ "uuid",
+]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build
new file mode 100644
index 0000000000..dc93544e1b
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_SocketProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_SocketProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_SocketProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build
new file mode 100644
index 0000000000..31996c5cb2
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_UserBlocked")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_UserBlocked.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UserBlocked.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build
new file mode 100644
index 0000000000..913d0f155c
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestDllBlocklist_UtilityProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_UtilityProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UtilityProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp
new file mode 100644
index 0000000000..f7865a2ed0
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp
@@ -0,0 +1,461 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+
+#include "js/RegExp.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/UntrustedModulesProcessor.h"
+#include "mozilla/WinDllServices.h"
+#include "nsContentUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "TelemetryFixture.h"
+#include "UntrustedModulesBackupService.h"
+#include "UntrustedModulesDataSerializer.h"
+
+using namespace mozilla;
+
+class ModuleLoadCounter final {
+ nsTHashMap<nsStringCaseInsensitiveHashKey, int> mCounters;
+
+ public:
+ template <size_t N>
+ ModuleLoadCounter(const nsString (&aNames)[N], const int (&aCounts)[N])
+ : mCounters(N) {
+ for (size_t i = 0; i < N; ++i) {
+ mCounters.InsertOrUpdate(aNames[i], aCounts[i]);
+ }
+ }
+
+ template <size_t N>
+ bool Remains(const nsString (&aNames)[N], const int (&aCounts)[N]) {
+ EXPECT_EQ(mCounters.Count(), N);
+ if (mCounters.Count() != N) {
+ return false;
+ }
+
+ bool result = true;
+ for (size_t i = 0; i < N; ++i) {
+ auto entry = mCounters.Lookup(aNames[i]);
+ if (!entry) {
+ wprintf(L"%s is not registered.\n",
+ static_cast<const wchar_t*>(aNames[i].get()));
+ result = false;
+ } else if (*entry != aCounts[i]) {
+ // We can return false, but let's print out all unmet modules
+ // which may be helpful to investigate test failures.
+ wprintf(L"%s:%4d\n", static_cast<const wchar_t*>(aNames[i].get()),
+ *entry);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ bool IsDone() const {
+ bool allZero = true;
+ for (const auto& data : mCounters.Values()) {
+ if (data < 0) {
+ // If any counter is negative, we know the test fails.
+ // No need to continue.
+ return true;
+ }
+ if (data > 0) {
+ allZero = false;
+ }
+ }
+ // If all counters are zero, the test finished nicely. Otherwise, those
+ // counters are expected to be decremented later. Let's continue.
+ return allZero;
+ }
+
+ void Decrement(const nsString& aName) {
+ if (auto entry = mCounters.Lookup(aName)) {
+ --(*entry);
+ }
+ }
+};
+
+class UntrustedModulesCollector {
+ static constexpr int kMaximumPendingQueries = 500;
+ Vector<UntrustedModulesData> mData;
+
+ public:
+ Vector<UntrustedModulesData>& Data() { return mData; }
+
+ nsresult Collect(ModuleLoadCounter& aChecker) {
+ nsresult rv = NS_OK;
+
+ mData.clear();
+ int pendingQueries = 0;
+
+ EXPECT_TRUE(SpinEventLoopUntil(
+ "xre:UntrustedModulesCollector"_ns,
+ [this, &pendingQueries, &aChecker, &rv]() {
+ // Some of expected loaded modules are still missing
+ // after kMaximumPendingQueries queries were submitted.
+ // Giving up here to avoid an infinite loop.
+ if (pendingQueries >= kMaximumPendingQueries) {
+ rv = NS_ERROR_ABORT;
+ return true;
+ }
+
+ ++pendingQueries;
+
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->GetUntrustedModulesData()->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ [this, &pendingQueries,
+ &aChecker](Maybe<UntrustedModulesData>&& aResult) {
+ EXPECT_GT(pendingQueries, 0);
+ --pendingQueries;
+
+ if (aResult.isSome()) {
+ wprintf(L"Received data. (pendingQueries=%d)\n",
+ pendingQueries);
+ for (auto item : aResult.ref().mEvents) {
+ aChecker.Decrement(item->mEvent.mRequestedDllName);
+ }
+ EXPECT_TRUE(mData.emplaceBack(std::move(aResult.ref())));
+ }
+ },
+ [&pendingQueries, &rv](nsresult aReason) {
+ EXPECT_GT(pendingQueries, 0);
+ --pendingQueries;
+
+ wprintf(L"GetUntrustedModulesData() failed - %08x\n", aReason);
+ EXPECT_TRUE(false);
+ rv = aReason;
+ });
+
+ // Keep calling GetUntrustedModulesData() until we meet the condition.
+ return aChecker.IsDone();
+ }));
+
+ EXPECT_TRUE(SpinEventLoopUntil(
+ "xre:UntrustedModulesCollector(pendingQueries)"_ns,
+ [&pendingQueries]() { return pendingQueries <= 0; }));
+
+ return rv;
+ }
+};
+
+class UntrustedModulesFixture : public TelemetryTestFixture {
+ static constexpr int kLoadCountBeforeDllServices = 5;
+ static constexpr int kLoadCountAfterDllServices = 5;
+ static constexpr uint32_t kMaxModulesArrayLen = 10;
+
+ // One of the important test scenarios is to load modules before DllServices
+ // is initialized and to make sure those loading events are forwarded when
+ // DllServices is initialized.
+ // However, GTest instantiates a Fixture class every testcase and there is
+ // no way to re-enable DllServices and UntrustedModulesProcessor once it's
+ // disabled, which means no matter how many testcases we have, only the
+ // first testcase exercises that scenario. That's why we implement that
+ // test scenario in InitialModuleLoadOnce as a static member and runs it
+ // in the first testcase to be executed.
+ static INIT_ONCE sInitLoadOnce;
+ static UntrustedModulesCollector sInitLoadDataCollector;
+
+ static nsString PrependWorkingDir(const nsAString& aLeaf) {
+ nsCOMPtr<nsIFile> file;
+ EXPECT_TRUE(NS_SUCCEEDED(NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR,
+ getter_AddRefs(file))));
+ EXPECT_NS_SUCCEEDED(file->Append(aLeaf));
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(file->Exists(&exists)) && exists);
+ nsString fullPath;
+ EXPECT_NS_SUCCEEDED(file->GetPath(fullPath));
+ return fullPath;
+ }
+
+ static BOOL CALLBACK InitialModuleLoadOnce(PINIT_ONCE, void*, void**);
+
+ protected:
+ static constexpr int kInitLoadCount =
+ kLoadCountBeforeDllServices + kLoadCountAfterDllServices;
+ static const nsString kTestModules[];
+
+ static void ValidateUntrustedModules(const UntrustedModulesData& aData,
+ bool aIsTruncatedData = false);
+
+ static void LoadAndFree(const nsAString& aLeaf) {
+ nsModuleHandle dll(::LoadLibraryW(PrependWorkingDir(aLeaf).get()));
+ EXPECT_TRUE(!!dll);
+ }
+
+ virtual void SetUp() override {
+ TelemetryTestFixture::SetUp();
+ ::InitOnceExecuteOnce(&sInitLoadOnce, InitialModuleLoadOnce, nullptr,
+ nullptr);
+ }
+
+ static const Vector<UntrustedModulesData>& GetInitLoadData() {
+ return sInitLoadDataCollector.Data();
+ }
+
+ // This method is useful if we want a new instance of UntrustedModulesData
+ // which is not copyable.
+ static UntrustedModulesData CollectSingleData() {
+ // If we call LoadAndFree more than once, those loading events are
+ // likely to be merged into an instance of UntrustedModulesData,
+ // meaning the length of the collector's vector is at least one but
+ // the exact number is unknown.
+ LoadAndFree(kTestModules[0]);
+
+ UntrustedModulesCollector collector;
+ ModuleLoadCounter waitForOne({kTestModules[0]}, {1});
+ EXPECT_NS_SUCCEEDED(collector.Collect(waitForOne));
+ EXPECT_TRUE(waitForOne.Remains({kTestModules[0]}, {0}));
+ EXPECT_EQ(collector.Data().length(), 1U);
+
+ // Cannot "return collector.Data()[0]" as copy ctor is deleted.
+ return UntrustedModulesData(std::move(collector.Data()[0]));
+ }
+
+ template <typename DataFetcherT>
+ void ValidateJSValue(const char16_t* aPattern, size_t aPatternLength,
+ DataFetcherT&& aDataFetcher) {
+ AutoJSContextWithGlobal cx(mCleanGlobal);
+ mozilla::Telemetry::UntrustedModulesDataSerializer serializer(
+ cx.GetJSContext(), kMaxModulesArrayLen);
+ EXPECT_TRUE(!!serializer);
+ aDataFetcher(serializer);
+
+ JS::Rooted<JS::Value> jsval(cx.GetJSContext());
+ serializer.GetObject(&jsval);
+
+ nsAutoString json;
+ EXPECT_TRUE(nsContentUtils::StringifyJSON(cx.GetJSContext(), &jsval, json));
+
+ JS::Rooted<JSObject*> re(
+ cx.GetJSContext(),
+ JS::NewUCRegExpObject(cx.GetJSContext(), aPattern, aPatternLength,
+ JS::RegExpFlag::Global));
+ EXPECT_TRUE(!!re);
+
+ JS::Rooted<JS::Value> matchResult(cx.GetJSContext(), JS::NullValue());
+ size_t idx = 0;
+ EXPECT_TRUE(JS::ExecuteRegExpNoStatics(cx.GetJSContext(), re, json.get(),
+ json.Length(), &idx, true,
+ &matchResult));
+ // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean
+ // true. If no match, ExecuteRegExpNoStatics returns Null.
+ EXPECT_TRUE(matchResult.isBoolean() && matchResult.toBoolean());
+ if (!matchResult.isBoolean() || !matchResult.toBoolean()) {
+ // If match failed, print out the actual JSON kindly.
+ wprintf(L"JSON: %s\n", static_cast<const wchar_t*>(json.get()));
+ wprintf(L"RE: %s\n", aPattern);
+ }
+ }
+};
+
+const nsString UntrustedModulesFixture::kTestModules[] = {
+ // Sorted for binary-search
+ u"TestUntrustedModules_Dll1.dll"_ns,
+ u"TestUntrustedModules_Dll2.dll"_ns,
+};
+
+INIT_ONCE UntrustedModulesFixture::sInitLoadOnce = INIT_ONCE_STATIC_INIT;
+UntrustedModulesCollector UntrustedModulesFixture::sInitLoadDataCollector;
+
+void UntrustedModulesFixture::ValidateUntrustedModules(
+ const UntrustedModulesData& aData, bool aIsTruncatedData) {
+ // This defines a list of modules which are listed on our blocklist and
+ // thus its loading status is not expected to be Status::Loaded.
+ // Although the UntrustedModulesFixture test does not touch any of them,
+ // the current process might have run a test like TestDllBlocklist where
+ // we try to load and block them.
+ const struct {
+ const wchar_t* mName;
+ ModuleLoadInfo::Status mStatus;
+ } kKnownModules[] = {
+ // Sorted by mName for binary-search
+ {L"TestDllBlocklist_MatchByName.dll", ModuleLoadInfo::Status::Blocked},
+ {L"TestDllBlocklist_MatchByVersion.dll", ModuleLoadInfo::Status::Blocked},
+ {L"TestDllBlocklist_NoOpEntryPoint.dll",
+ ModuleLoadInfo::Status::Redirected},
+#if !defined(MOZ_ASAN)
+ // With ASAN, the test uses mozglue's blocklist where
+ // the user blocklist is not used. So only check for this
+ // DLL in the non-ASAN case.
+ {L"TestDllBlocklist_UserBlocked.dll", ModuleLoadInfo::Status::Blocked},
+#endif // !defined(MOZ_ASAN)
+ };
+
+ EXPECT_EQ(aData.mProcessType, GeckoProcessType_Default);
+ EXPECT_EQ(aData.mPid, ::GetCurrentProcessId());
+
+ nsTHashtable<nsPtrHashKey<void>> moduleSet;
+ for (const RefPtr<ModuleRecord>& module : aData.mModules.Values()) {
+ moduleSet.PutEntry(module);
+ }
+
+ size_t numBlockedEvents = 0;
+ for (auto item : aData.mEvents) {
+ const auto& evt = item->mEvent;
+ const nsDependentSubstring leafName =
+ nt::GetLeafName(evt.mModule->mResolvedNtName);
+ const nsAutoString leafNameStr(leafName.Data(), leafName.Length());
+ const ModuleLoadInfo::Status loadStatus =
+ static_cast<ModuleLoadInfo::Status>(evt.mLoadStatus);
+ if (loadStatus == ModuleLoadInfo::Status::Blocked) {
+ ++numBlockedEvents;
+ }
+
+ size_t match;
+ if (BinarySearchIf(
+ kKnownModules, 0, ArrayLength(kKnownModules),
+ [&leafNameStr](const auto& aVal) {
+ return _wcsicmp(leafNameStr.get(), aVal.mName);
+ },
+ &match)) {
+ EXPECT_EQ(loadStatus, kKnownModules[match].mStatus);
+ } else {
+ EXPECT_EQ(evt.mLoadStatus, 0U);
+ }
+
+ if (BinarySearchIf(
+ kTestModules, 0, ArrayLength(kTestModules),
+ [&leafNameStr](const auto& aVal) {
+ return _wcsicmp(leafNameStr.get(), aVal.get());
+ },
+ &match)) {
+ // We know the test modules are loaded in the main thread,
+ // but we don't know about other modules.
+ EXPECT_EQ(evt.mThreadId, ::GetCurrentThreadId());
+ }
+
+ // Make sure mModule is pointing to an entry of mModules.
+ EXPECT_TRUE(moduleSet.Contains(evt.mModule));
+ EXPECT_FALSE(evt.mIsDependent);
+ }
+
+ // No check for the mXULLoadDurationMS field because the field has a value
+ // in CCov build GTest, but it is empty in non-CCov build (bug 1681936).
+ EXPECT_EQ(aData.mNumEvents, aData.mEvents.length());
+ EXPECT_GT(aData.mNumEvents, 0U);
+ if (aIsTruncatedData) {
+ EXPECT_EQ(aData.mStacks.GetModuleCount(), 0U);
+ EXPECT_LE(aData.mNumEvents, UntrustedModulesData::kMaxEvents);
+ } else if (numBlockedEvents == aData.mNumEvents) {
+ // If all loading events were blocked or aData is truncated,
+ // the stacks are empty.
+ EXPECT_EQ(aData.mStacks.GetModuleCount(), 0U);
+ } else {
+ EXPECT_GT(aData.mStacks.GetModuleCount(), 0U);
+ }
+ EXPECT_EQ(aData.mSanitizationFailures, 0U);
+ EXPECT_EQ(aData.mTrustTestFailures, 0U);
+}
+
+BOOL CALLBACK UntrustedModulesFixture::InitialModuleLoadOnce(PINIT_ONCE, void*,
+ void**) {
+ for (int i = 0; i < kLoadCountBeforeDllServices; ++i) {
+ for (const auto& mod : kTestModules) {
+ LoadAndFree(mod);
+ }
+ }
+
+ RefPtr<DllServices> dllSvc(DllServices::Get());
+ dllSvc->StartUntrustedModulesProcessor(true);
+
+ for (int i = 0; i < kLoadCountAfterDllServices; ++i) {
+ for (const auto& mod : kTestModules) {
+ LoadAndFree(mod);
+ }
+ }
+
+ ModuleLoadCounter waitForTwo(kTestModules, {kInitLoadCount, kInitLoadCount});
+ EXPECT_EQ(sInitLoadDataCollector.Collect(waitForTwo), NS_OK);
+ EXPECT_TRUE(waitForTwo.Remains(kTestModules, {0, 0}));
+
+ for (const auto& event : GetInitLoadData()) {
+ ValidateUntrustedModules(event);
+ }
+
+ // Data was removed when retrieved. No data is retrieved again.
+ UntrustedModulesCollector collector;
+ ModuleLoadCounter waitOnceForEach(kTestModules, {1, 1});
+ EXPECT_EQ(collector.Collect(waitOnceForEach), NS_ERROR_ABORT);
+ EXPECT_TRUE(waitOnceForEach.Remains(kTestModules, {1, 1}));
+
+ return TRUE;
+}
+
+#define PROCESS_OBJ(TYPE, PID) \
+ u"\"" TYPE u"\\." PID u"\":{" \
+ u"\"processType\":\"" TYPE u"\",\"elapsed\":\\d+\\.\\d+," \
+ u"\"sanitizationFailures\":0,\"trustTestFailures\":0," \
+ u"\"events\":\\[{" \
+ u"\"processUptimeMS\":\\d+,\"loadDurationMS\":\\d+\\.\\d+," \
+ u"\"threadID\":\\d+,\"threadName\":\"Main Thread\"," \
+ u"\"baseAddress\":\"0x[0-9a-f]+\",\"moduleIndex\":0," \
+ u"\"isDependent\":false,\"loadStatus\":0}\\]," \
+ u"\"combinedStacks\":{" \
+ u"\"memoryMap\":\\[\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\"\\]" \
+ u"(,\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\\\"\\])*\\]," \
+ u"\"stacks\":\\[\\[\\[(-1|\\d+),\\d+\\]" \
+ u"(,\\[(-1|\\d+),\\d+\\])*\\]\\]}}"
+
+TEST_F(UntrustedModulesFixture, Serialize) {
+ // clang-format off
+ const char16_t kPattern[] = u"{\"structVersion\":1,"
+ u"\"modules\":\\[{"
+ u"\"resolvedDllName\":\"TestUntrustedModules_Dll1\\.dll\","
+ u"\"fileVersion\":\"1\\.2\\.3\\.4\","
+ u"\"companyName\":\"Mozilla Corporation\",\"trustFlags\":0}\\],"
+ u"\"blockedModules\":\\[.*?\\]," // allow for the case where there are some blocked modules
+ u"\"processes\":{"
+ PROCESS_OBJ(u"browser", u"0xabc") u","
+ PROCESS_OBJ(u"browser", u"0x4") u","
+ PROCESS_OBJ(u"rdd", u"0x4")
+ u"}}";
+ // clang-format on
+
+ UntrustedModulesBackupData backup1, backup2;
+ {
+ UntrustedModulesData data1 = CollectSingleData();
+ UntrustedModulesData data2 = CollectSingleData();
+ UntrustedModulesData data3 = CollectSingleData();
+
+ data1.mPid = 0xabc;
+ data2.mPid = 0x4;
+ data2.mProcessType = GeckoProcessType_RDD;
+ data3.mPid = 0x4;
+
+ backup1.Add(std::move(data1));
+ backup2.Add(std::move(data2));
+ backup1.Add(std::move(data3));
+ }
+
+ ValidateJSValue(kPattern, ArrayLength(kPattern) - 1,
+ [&backup1, &backup2](
+ Telemetry::UntrustedModulesDataSerializer& aSerializer) {
+ EXPECT_NS_SUCCEEDED(aSerializer.Add(backup1));
+ EXPECT_NS_SUCCEEDED(aSerializer.Add(backup2));
+ });
+}
+
+TEST_F(UntrustedModulesFixture, Backup) {
+ RefPtr<UntrustedModulesBackupService> backupSvc(
+ UntrustedModulesBackupService::Get());
+ for (int i = 0; i < 100; ++i) {
+ backupSvc->Backup(CollectSingleData());
+ }
+
+ backupSvc->SettleAllStagingData();
+ EXPECT_TRUE(backupSvc->Staging().IsEmpty());
+
+ for (const auto& entry : backupSvc->Settled()) {
+ const RefPtr<UntrustedModulesDataContainer>& container = entry.GetData();
+ EXPECT_TRUE(!!container);
+ const UntrustedModulesData& data = container->mData;
+ EXPECT_EQ(entry.GetKey(), ProcessHashKey(data.mProcessType, data.mPid));
+ ValidateUntrustedModules(data, /*aIsTruncatedData*/ true);
+ }
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
new file mode 100644
index 0000000000..2358b88b93
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,2,3,4 // This field will be collected
+ PRODUCTVERSION 5,6,7,8
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "Mozilla Corporation"
+ VALUE "OriginalFilename", "TestUntrustedModules_Dll1.dll"
+ VALUE "ProductName", "Test DLL"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build
new file mode 100644
index 0000000000..57fc59ca8a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestUntrustedModules_Dll1")
+
+UNIFIED_SOURCES = [
+ "TestUntrustedModules_Dll1.cpp",
+]
+
+RCFILE = "TestUntrustedModules_Dll1.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll1.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
@@ -0,0 +1,7 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <windows.h>
+
+BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build
new file mode 100644
index 0000000000..fcefe41329
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIST_INSTALL = False
+
+SharedLibrary("TestUntrustedModules_Dll2")
+
+UNIFIED_SOURCES = [
+ "TestUntrustedModules_Dll2.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll2.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/moz.build b/toolkit/xre/dllservices/tests/gtest/moz.build
new file mode 100644
index 0000000000..525eba0c38
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/moz.build
@@ -0,0 +1,35 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("dllservicestest")
+
+UNIFIED_SOURCES += [
+ "TestDLLBlocklist.cpp",
+]
+
+if CONFIG["CPU_ARCH"] != "x86":
+ UNIFIED_SOURCES += [
+ "TestUntrustedModules.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/telemetry/other",
+ "/toolkit/components/telemetry/tests/gtest",
+]
+
+TEST_DIRS += [
+ "TestDllBlocklist_AllowByVersion",
+ "TestDllBlocklist_MatchByName",
+ "TestDllBlocklist_MatchByVersion",
+ "TestDllBlocklist_NoOpEntryPoint",
+ "TestDllBlocklist_SocketProcessOnly",
+ "TestDllBlocklist_UserBlocked",
+ "TestDllBlocklist_UtilityProcessOnly",
+ "TestUntrustedModules_Dll1",
+ "TestUntrustedModules_Dll2",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/xre/dllservices/tests/moz.build b/toolkit/xre/dllservices/tests/moz.build
new file mode 100644
index 0000000000..eee1b22ae3
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/moz.build
@@ -0,0 +1,46 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+GeckoCppUnitTests(
+ [
+ "TestDllInterceptor",
+ "TestIATPatcher",
+ "TestMMPolicy",
+ ],
+ linkage=None,
+)
+
+if CONFIG["CPU_ARCH"] in ("x86", "x86_64"):
+ # Cross-process interceptors not yet supported on aarch64
+ GeckoCppUnitTests(
+ [
+ "TestDllInterceptorCrossProcess",
+ ],
+ linkage=None,
+ )
+
+OS_LIBS += [
+ "advapi32",
+ "ntdll",
+ "ole32",
+ "shlwapi",
+ "user32",
+ "uuid",
+]
+
+DELAYLOAD_DLLS += [
+ "shlwapi.dll",
+]
+
+if CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]
+
+TEST_DIRS += [
+ "gtest",
+]