summaryrefslogtreecommitdiffstats
path: root/browser/app/winlauncher
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/app/winlauncher/DllBlocklistInit.cpp237
-rw-r--r--browser/app/winlauncher/DllBlocklistInit.h32
-rw-r--r--browser/app/winlauncher/ErrorHandler.cpp782
-rw-r--r--browser/app/winlauncher/ErrorHandler.h54
-rw-r--r--browser/app/winlauncher/LaunchUnelevated.cpp290
-rw-r--r--browser/app/winlauncher/LaunchUnelevated.h32
-rw-r--r--browser/app/winlauncher/LauncherProcessWin.cpp536
-rw-r--r--browser/app/winlauncher/LauncherProcessWin.h70
-rw-r--r--browser/app/winlauncher/NtLoaderAPI.cpp33
-rw-r--r--browser/app/winlauncher/ProcThreadAttributes.h159
-rw-r--r--browser/app/winlauncher/SameBinary.h146
-rw-r--r--browser/app/winlauncher/freestanding/CheckForCaller.h36
-rw-r--r--browser/app/winlauncher/freestanding/DllBlocklist.cpp641
-rw-r--r--browser/app/winlauncher/freestanding/DllBlocklist.h39
-rw-r--r--browser/app/winlauncher/freestanding/Freestanding.h67
-rw-r--r--browser/app/winlauncher/freestanding/LoaderPrivateAPI.cpp293
-rw-r--r--browser/app/winlauncher/freestanding/LoaderPrivateAPI.h62
-rw-r--r--browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp144
-rw-r--r--browser/app/winlauncher/freestanding/ModuleLoadFrame.h97
-rw-r--r--browser/app/winlauncher/freestanding/SafeThreadLocal.h96
-rw-r--r--browser/app/winlauncher/freestanding/SharedSection.cpp368
-rw-r--r--browser/app/winlauncher/freestanding/SharedSection.h212
-rw-r--r--browser/app/winlauncher/freestanding/gen_ntdll_freestanding_lib.py28
-rw-r--r--browser/app/winlauncher/freestanding/moz.build56
-rw-r--r--browser/app/winlauncher/freestanding/ntdll_freestanding.def25
-rw-r--r--browser/app/winlauncher/moz.build61
-rw-r--r--browser/app/winlauncher/test/TestCrossProcessWin.cpp703
-rw-r--r--browser/app/winlauncher/test/TestSafeThreadLocal.cpp84
-rw-r--r--browser/app/winlauncher/test/TestSameBinary.cpp255
-rw-r--r--browser/app/winlauncher/test/moz.build30
30 files changed, 5668 insertions, 0 deletions
diff --git a/browser/app/winlauncher/DllBlocklistInit.cpp b/browser/app/winlauncher/DllBlocklistInit.cpp
new file mode 100644
index 0000000000..fc4958339f
--- /dev/null
+++ b/browser/app/winlauncher/DllBlocklistInit.cpp
@@ -0,0 +1,237 @@
+/* -*- 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 "nsWindowsDllInterceptor.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/ImportDir.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/PolicyChecks.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsDllBlocklist.h"
+#include "mozilla/WindowsStackCookie.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+#include "DllBlocklistInit.h"
+#include "freestanding/DllBlocklist.h"
+#include "freestanding/SharedSection.h"
+
+namespace mozilla {
+
+#if defined(MOZ_ASAN) || defined(_M_ARM64)
+
+// This DLL blocking code is incompatible with ASAN because
+// it is able to execute before ASAN itself has even initialized.
+// Also, AArch64 has not been tested with this.
+LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ const IMAGE_THUNK_DATA*, const GeckoProcessType aProcessType) {
+ return mozilla::Ok();
+}
+
+LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ const bool aDisableDynamicBlocklist,
+ Maybe<std::wstring> aBlocklistFileName) {
+ return mozilla::Ok();
+}
+
+#else
+
+static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
+ const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr,
+ const IMAGE_THUNK_DATA* aCachedNtdllThunk,
+ const GeckoProcessType aProcessType) {
+ CrossProcessDllInterceptor intcpt(aTransferMgr.RemoteProcess());
+ intcpt.Init(L"ntdll.dll");
+
+# if defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__)
+ // This debug check preserves compatibility with third-parties (see bug
+ // 1733532).
+ MOZ_ASSERT(!HasStackCookieCheck(
+ reinterpret_cast<uintptr_t>(&freestanding::patched_NtMapViewOfSection)));
+# endif // #if defined(DEBUG) && defined(_M_X64) && !defined(__MINGW64__)
+
+ bool ok = freestanding::stub_NtMapViewOfSection.SetDetour(
+ aTransferMgr, intcpt, "NtMapViewOfSection",
+ &freestanding::patched_NtMapViewOfSection);
+ if (!ok) {
+ return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError());
+ }
+
+ ok = freestanding::stub_LdrLoadDll.SetDetour(
+ aTransferMgr, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll);
+ if (!ok) {
+ return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError());
+ }
+
+ // Because aChildProcess has just been created in a suspended state, its
+ // dynamic linker has not yet been initialized, thus its executable has
+ // not yet been linked with ntdll.dll. If the blocklist hook intercepts a
+ // library load prior to the link, the hook will be unable to invoke any
+ // ntdll.dll functions.
+ //
+ // We know that the executable for our *current* process's binary is already
+ // linked into ntdll, so we obtain the IAT from our own executable and graft
+ // it onto the child process's IAT, thus enabling the child process's hook to
+ // safely make its ntdll calls.
+
+ const nt::PEHeaders& ourExeImage = aTransferMgr.LocalPEHeaders();
+
+ // As part of our mitigation of binary tampering, copy our import directory
+ // from the original in our executable file.
+ LauncherVoidResult importDirRestored =
+ RestoreImportDirectory(aFullImagePath, aTransferMgr);
+ if (importDirRestored.isErr()) {
+ return importDirRestored;
+ }
+
+ mozilla::nt::PEHeaders ntdllImage(::GetModuleHandleW(L"ntdll.dll"));
+ if (!ntdllImage) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ // If we have a cached IAT i.e. |aCachedNtdllThunk| is non-null, we can
+ // safely copy it to |aChildProcess| even if the local IAT has been modified.
+ // If |aCachedNtdllThunk| is null, we've failed to cache the IAT or we're in
+ // the launcher process where there is no chance to cache the IAT. In those
+ // cases, we retrieve the IAT with the boundary check to avoid a modified IAT
+ // from being copied into |aChildProcess|.
+ Maybe<Span<IMAGE_THUNK_DATA> > ntdllThunks;
+ if (aCachedNtdllThunk) {
+ ntdllThunks = ourExeImage.GetIATThunksForModule("ntdll.dll");
+ } else {
+ Maybe<Range<const uint8_t> > ntdllBoundaries = ntdllImage.GetBounds();
+ if (!ntdllBoundaries) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ // We can use GetIATThunksForModule() to check whether IAT is modified
+ // or not because no functions exported from ntdll.dll is forwarded.
+ ntdllThunks =
+ ourExeImage.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr());
+ }
+ if (!ntdllThunks) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
+ }
+
+ { // Scope for prot
+ PIMAGE_THUNK_DATA firstIatThunkDst = ntdllThunks.value().data();
+ const IMAGE_THUNK_DATA* firstIatThunkSrc =
+ aCachedNtdllThunk ? aCachedNtdllThunk : firstIatThunkDst;
+ SIZE_T iatLength = ntdllThunks.value().LengthBytes();
+
+ AutoVirtualProtect prot =
+ aTransferMgr.Protect(firstIatThunkDst, iatLength, PAGE_READWRITE);
+ if (!prot) {
+ return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
+ }
+
+ LauncherVoidResult writeResult =
+ aTransferMgr.Transfer(firstIatThunkDst, firstIatThunkSrc, iatLength);
+ if (writeResult.isErr()) {
+ return writeResult.propagateErr();
+ }
+ }
+
+ // Tell the mozglue blocklist that we have bootstrapped
+ uint32_t newFlags = eDllBlocklistInitFlagWasBootstrapped;
+
+ if (gBlocklistInitFlags & eDllBlocklistInitFlagWasBootstrapped) {
+ // If we ourselves were bootstrapped, then we are starting a child process
+ // and need to set the appropriate flag.
+ newFlags |= eDllBlocklistInitFlagIsChildProcess;
+ }
+
+ SetDllBlocklistProcessTypeFlags(newFlags, aProcessType);
+
+ LauncherVoidResult writeResult =
+ aTransferMgr.Transfer(&gBlocklistInitFlags, &newFlags, sizeof(newFlags));
+ if (writeResult.isErr()) {
+ return writeResult.propagateErr();
+ }
+
+ return Ok();
+}
+
+LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ const IMAGE_THUNK_DATA* aCachedNtdllThunk,
+ const GeckoProcessType aProcessType) {
+ nt::CrossExecTransferManager transferMgr(aChildProcess);
+ if (!transferMgr) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ // We come here when the browser process launches a sandbox process.
+ // If the launcher process already failed to bootstrap the browser process,
+ // we should not attempt to bootstrap a child process because it's likely
+ // to fail again. Instead, we only restore the import directory entry.
+ if (!(gBlocklistInitFlags & eDllBlocklistInitFlagWasBootstrapped)) {
+ return RestoreImportDirectory(aFullImagePath, transferMgr);
+ }
+
+ // Transfer a readonly handle to the child processes because all information
+ // are already written to the section by the launcher and main process.
+ LauncherVoidResult transferResult =
+ freestanding::gSharedSection.TransferHandle(transferMgr, GENERIC_READ);
+ if (transferResult.isErr()) {
+ return transferResult.propagateErr();
+ }
+
+ return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr,
+ aCachedNtdllThunk, aProcessType);
+}
+
+LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ const bool aDisableDynamicBlocklist,
+ Maybe<std::wstring> aBlocklistFileName) {
+ nt::CrossExecTransferManager transferMgr(aChildProcess);
+ if (!transferMgr) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ // The launcher process initializes a section object, whose handle is
+ // transferred to the browser process, and that transferred handle in
+ // the browser process is transferred to the sandbox processes.
+ LauncherVoidResultWithLineInfo result = freestanding::gSharedSection.Init();
+ if (result.isErr()) {
+ return result;
+ }
+
+ if (aBlocklistFileName.isSome() &&
+ !PolicyCheckBoolean(L"DisableThirdPartyModuleBlocking")) {
+ DynamicBlockList blockList(aBlocklistFileName->c_str());
+ result = freestanding::gSharedSection.SetBlocklist(
+ blockList, aDisableDynamicBlocklist);
+ if (result.isErr()) {
+ return result;
+ }
+ }
+
+ // Transfer a writable handle to the main process because it needs to append
+ // dependent module paths to the section.
+ LauncherVoidResult transferResult =
+ freestanding::gSharedSection.TransferHandle(transferMgr,
+ GENERIC_READ | GENERIC_WRITE);
+ if (transferResult.isErr()) {
+ return transferResult.propagateErr();
+ }
+
+ auto clearInstance = MakeScopeExit([]() {
+ // After transfer, the launcher process does not need the object anymore.
+ freestanding::gSharedSection.Reset(nullptr);
+ });
+ return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr, nullptr,
+ GeckoProcessType_Default);
+}
+
+#endif // defined(MOZ_ASAN) || defined(_M_ARM64)
+
+} // namespace mozilla
diff --git a/browser/app/winlauncher/DllBlocklistInit.h b/browser/app/winlauncher/DllBlocklistInit.h
new file mode 100644
index 0000000000..9e5e1a181c
--- /dev/null
+++ b/browser/app/winlauncher/DllBlocklistInit.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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_DllBlocklistInit_h
+#define mozilla_DllBlocklistInit_h
+
+#include <windows.h>
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+# include "mozilla/LauncherRegistryInfo.h"
+#endif
+#include "mozilla/ProcessType.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ const IMAGE_THUNK_DATA* aCachedNtdllThunk,
+ const GeckoProcessType aProcessType);
+
+LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ const bool aDisableDynamicBlocklist,
+ Maybe<std::wstring> aBlocklistFileName);
+
+} // namespace mozilla
+
+#endif // mozilla_DllBlocklistInit_h
diff --git a/browser/app/winlauncher/ErrorHandler.cpp b/browser/app/winlauncher/ErrorHandler.cpp
new file mode 100644
index 0000000000..1286e0f90f
--- /dev/null
+++ b/browser/app/winlauncher/ErrorHandler.cpp
@@ -0,0 +1,782 @@
+/* -*- 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 "ErrorHandler.h"
+
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/JSONWriter.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WinTokenUtils.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/mscom/ProcessRuntime.h"
+#include "nsWindowsHelpers.h"
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+# include "mozilla/LauncherRegistryInfo.h"
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+#include <algorithm>
+#include <process.h>
+#include <sstream>
+#include <string>
+#include <stdio.h>
+#include <string.h>
+#include <time.h>
+
+#include <objbase.h>
+#include <rpc.h>
+#if !defined(__MINGW32__)
+# include <comutil.h>
+# include <iwscapi.h>
+# include <wscapi.h>
+#endif // !defined(__MINGW32__)
+#include <tlhelp32.h>
+
+#if !defined(RRF_SUBKEY_WOW6464KEY)
+# define RRF_SUBKEY_WOW6464KEY 0x00010000
+#endif // !defined(RRF_SUBKEY_WOW6464KEY)
+
+#define QUOTE_ME2(x) #x
+#define QUOTE_ME(x) QUOTE_ME2(x)
+
+#define TELEMETRY_BASE_URL L"https://incoming.telemetry.mozilla.org/submit"
+#define TELEMETRY_NAMESPACE L"/firefox-launcher-process"
+#define TELEMETRY_LAUNCHER_PING_DOCTYPE L"/launcher-process-failure"
+#define TELEMETRY_LAUNCHER_PING_VERSION L"/1"
+
+static const wchar_t kUrl[] = TELEMETRY_BASE_URL TELEMETRY_NAMESPACE
+ TELEMETRY_LAUNCHER_PING_DOCTYPE TELEMETRY_LAUNCHER_PING_VERSION L"/";
+static const uint32_t kGuidCharLenWithNul = 39;
+static const uint32_t kGuidCharLenNoBracesNoNul = 36;
+static const mozilla::StaticXREAppData* gAppData;
+
+// Ordinarily, errors are only reported to the Windows Event Log when they are
+// not reported upstream via telemetry (usually due either to telemetry being
+// disabled or to network failure).
+//
+// If `--log-launcher-error` is given at the command line, launcher errors will
+// always be reported to the Windows Event Log, regardless of whether or not
+// they're sent upstream.
+static bool gForceEventLog = false;
+
+namespace {
+
+constexpr wchar_t kEventSourceName[] = L"" MOZ_APP_DISPLAYNAME " Launcher";
+
+struct EventSourceDeleter {
+ using pointer = HANDLE;
+
+ void operator()(pointer aEvtSrc) { ::DeregisterEventSource(aEvtSrc); }
+};
+
+using EventLog = mozilla::UniquePtr<HANDLE, EventSourceDeleter>;
+
+struct SerializedEventData {
+ HRESULT mHr;
+ uint32_t mLine;
+ char mFile[1];
+};
+
+} // anonymous namespace
+
+static void PostErrorToLog(const mozilla::LauncherError& aError) {
+ // This is very bare-bones; just enough to spit out an HRESULT to the
+ // Application event log.
+ EventLog log(::RegisterEventSourceW(nullptr, kEventSourceName));
+
+ if (!log) {
+ return;
+ }
+
+ size_t fileLen = strlen(aError.mFile);
+ size_t dataLen = sizeof(HRESULT) + sizeof(uint32_t) + fileLen;
+ auto evtDataBuf = mozilla::MakeUnique<char[]>(dataLen);
+ SerializedEventData& evtData =
+ *reinterpret_cast<SerializedEventData*>(evtDataBuf.get());
+ evtData.mHr = aError.mError.AsHResult();
+ evtData.mLine = aError.mLine;
+ // Since this is binary data, we're not concerning ourselves with null
+ // terminators.
+ memcpy(evtData.mFile, aError.mFile, fileLen);
+
+ ::ReportEventW(log.get(), EVENTLOG_ERROR_TYPE, 0, aError.mError.AsHResult(),
+ nullptr, 0, dataLen, nullptr, evtDataBuf.get());
+}
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+
+namespace {
+
+// This JSONWriteFunc writes directly to a temp file. By creating this file
+// with the FILE_ATTRIBUTE_TEMPORARY attribute, we hint to the OS that this
+// file is short-lived. The OS will try to avoid flushing it to disk if at
+// all possible.
+class TempFileWriter final : public mozilla::JSONWriteFunc {
+ public:
+ TempFileWriter() : mFailed(false), mSuccessfulHandoff(false) {
+ wchar_t name[MAX_PATH + 1] = {};
+ if (_wtmpnam_s(name)) {
+ mFailed = true;
+ return;
+ }
+
+ mTempFileName = name;
+
+ mTempFile.own(::CreateFileW(name, GENERIC_WRITE, FILE_SHARE_READ, nullptr,
+ CREATE_NEW, FILE_ATTRIBUTE_TEMPORARY, nullptr));
+ if (mTempFile.get() == INVALID_HANDLE_VALUE) {
+ mFailed = true;
+ }
+ }
+
+ ~TempFileWriter() {
+ if (mSuccessfulHandoff) {
+ // It is no longer our responsibility to delete the temp file if we have
+ // successfully handed it off to pingsender.
+ return;
+ }
+
+ mTempFile.reset();
+ ::DeleteFileW(mTempFileName.c_str());
+ }
+
+ explicit operator bool() const { return !mFailed; }
+
+ void Write(const mozilla::Span<const char>& aStr) final {
+ if (mFailed) {
+ return;
+ }
+
+ DWORD bytesWritten = 0;
+ if (!::WriteFile(mTempFile, aStr.data(), aStr.size(), &bytesWritten,
+ nullptr) ||
+ bytesWritten != aStr.size()) {
+ mFailed = true;
+ }
+ }
+
+ const std::wstring& GetFileName() const { return mTempFileName; }
+
+ void SetSuccessfulHandoff() { mSuccessfulHandoff = true; }
+
+ private:
+ bool mFailed;
+ bool mSuccessfulHandoff;
+ std::wstring mTempFileName;
+ nsAutoHandle mTempFile;
+};
+
+using SigMap = mozilla::Vector<std::wstring, 0, InfallibleAllocPolicy>;
+
+} // anonymous namespace
+
+// This is the guideline for maximum string length for telemetry intake
+static const size_t kMaxStrLen = 80;
+
+static mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr,
+ const size_t aStrLenExclNul) {
+ // Yes, this might not handle surrogate pairs correctly. Let's just let
+ // WideCharToMultiByte fail in that unlikely case.
+ size_t cvtLen = std::min(aStrLenExclNul, kMaxStrLen);
+
+ int numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, cvtLen, nullptr, 0,
+ nullptr, nullptr);
+ if (!numConv) {
+ return nullptr;
+ }
+
+ // Include room for the null terminator by adding one
+ auto buf = mozilla::MakeUnique<char[]>(numConv + 1);
+
+ numConv = ::WideCharToMultiByte(CP_UTF8, 0, aStr, cvtLen, buf.get(), numConv,
+ nullptr, nullptr);
+ if (!numConv) {
+ return nullptr;
+ }
+
+ // Add null termination. numConv does not include the terminator, so we don't
+ // subtract 1 when indexing into buf.
+ buf[numConv] = 0;
+
+ return buf;
+}
+
+static mozilla::UniquePtr<char[]> WideToUTF8(const wchar_t* aStr) {
+ return WideToUTF8(aStr, wcslen(aStr));
+}
+
+static mozilla::UniquePtr<char[]> WideToUTF8(const std::wstring& aStr) {
+ return WideToUTF8(aStr.c_str(), aStr.length());
+}
+
+// MinGW does not support the Windows Security Center APIs.
+# if !defined(__MINGW32__)
+
+static mozilla::UniquePtr<char[]> WideToUTF8(const _bstr_t& aStr) {
+ return WideToUTF8(static_cast<const wchar_t*>(aStr), aStr.length());
+}
+
+namespace {
+
+struct ProviderKey {
+ WSC_SECURITY_PROVIDER mProviderType;
+ const char* mKey;
+};
+
+} // anonymous namespace
+
+static bool EnumWSCProductList(RefPtr<IWSCProductList>& aProdList,
+ mozilla::JSONWriter& aJson) {
+ LONG count;
+ HRESULT hr = aProdList->get_Count(&count);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ // Unlikely, but put a bound on the max length of the output array for the
+ // purposes of telemetry intake.
+ count = std::min(count, 1000L);
+
+ // Record the name(s) of each active registered product in this category
+ for (LONG index = 0; index < count; ++index) {
+ RefPtr<IWscProduct> product;
+ hr = aProdList->get_Item(index, getter_AddRefs(product));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ WSC_SECURITY_PRODUCT_STATE state;
+ hr = product->get_ProductState(&state);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ // We only care about products that are active
+ if (state == WSC_SECURITY_PRODUCT_STATE_OFF ||
+ state == WSC_SECURITY_PRODUCT_STATE_SNOOZED ||
+ state == WSC_SECURITY_PRODUCT_STATE_EXPIRED) {
+ continue;
+ }
+
+ _bstr_t bName;
+ hr = product->get_ProductName(bName.GetAddress());
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ auto buf = WideToUTF8(bName);
+ if (!buf) {
+ return false;
+ }
+
+ aJson.StringElement(mozilla::MakeStringSpan(buf.get()));
+ }
+
+ return true;
+}
+
+static const ProviderKey gProvKeys[] = {
+ {WSC_SECURITY_PROVIDER_ANTIVIRUS, "av"},
+ {WSC_SECURITY_PROVIDER_ANTISPYWARE, "antispyware"},
+ {WSC_SECURITY_PROVIDER_FIREWALL, "firewall"}};
+
+static bool AddWscInfo(mozilla::JSONWriter& aJson) {
+ if (!mozilla::IsWin8OrLater()) {
+ // We haven't written anything yet, so we can return true here and continue
+ // capturing data.
+ return true;
+ }
+
+ // We need COM for this. Using ProcessRuntime so that process-global COM
+ // configuration is done correctly
+ mozilla::mscom::ProcessRuntime mscom(
+ mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher);
+ if (!mscom) {
+ // We haven't written anything yet, so we can return true here and continue
+ // capturing data.
+ return true;
+ }
+
+ aJson.StartObjectProperty("security");
+
+ const CLSID clsid = __uuidof(WSCProductList);
+ const IID iid = __uuidof(IWSCProductList);
+
+ for (uint32_t index = 0; index < mozilla::ArrayLength(gProvKeys); ++index) {
+ // NB: A separate instance of IWSCProductList is needed for each distinct
+ // security provider type; MSDN says that we cannot reuse the same object
+ // and call Initialize() to pave over the previous data.
+ RefPtr<IWSCProductList> prodList;
+ HRESULT hr = ::CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER, iid,
+ getter_AddRefs(prodList));
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ hr = prodList->Initialize(gProvKeys[index].mProviderType);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ aJson.StartArrayProperty(mozilla::MakeStringSpan(gProvKeys[index].mKey));
+
+ if (!EnumWSCProductList(prodList, aJson)) {
+ return false;
+ }
+
+ aJson.EndArray();
+ }
+
+ aJson.EndObject();
+
+ return true;
+}
+# endif // !defined(__MINGW32__)
+
+// Max array length for telemetry intake.
+static const size_t kMaxArrayLen = 1000;
+
+static bool AddModuleInfo(const nsAutoHandle& aSnapshot,
+ mozilla::JSONWriter& aJson) {
+ if (aSnapshot.get() == INVALID_HANDLE_VALUE) {
+ // We haven't written anything yet, so we can return true here and continue
+ // capturing data.
+ return true;
+ }
+
+ SigMap signatures;
+ size_t moduleCount = 0;
+
+ MODULEENTRY32W module = {sizeof(module)};
+ if (!::Module32FirstW(aSnapshot, &module)) {
+ // We haven't written anything yet, so we can return true here and continue
+ // capturing data.
+ return true;
+ }
+
+ mozilla::glue::BasicDllServices dllServices;
+
+ aJson.StartObjectProperty("modules");
+
+ // For each module, add its version number (or empty string if not present),
+ // followed by an optional index into the signatures array
+ do {
+ ++moduleCount;
+
+ wchar_t leaf[_MAX_FNAME] = {};
+ if (::_wsplitpath_s(module.szExePath, nullptr, 0, nullptr, 0, leaf,
+ mozilla::ArrayLength(leaf), nullptr, 0)) {
+ return false;
+ }
+
+ if (_wcslwr_s(leaf, mozilla::ArrayLength(leaf))) {
+ return false;
+ }
+
+ auto leafUtf8 = WideToUTF8(leaf);
+ if (!leafUtf8) {
+ return false;
+ }
+
+ aJson.StartArrayProperty(mozilla::MakeStringSpan(leafUtf8.get()));
+
+ std::string version;
+ DWORD verInfoSize = ::GetFileVersionInfoSizeW(module.szExePath, nullptr);
+ if (verInfoSize) {
+ auto verInfoBuf = mozilla::MakeUnique<BYTE[]>(verInfoSize);
+
+ if (::GetFileVersionInfoW(module.szExePath, 0, verInfoSize,
+ verInfoBuf.get())) {
+ VS_FIXEDFILEINFO* fixedInfo = nullptr;
+ UINT fixedInfoLen = 0;
+
+ if (::VerQueryValueW(verInfoBuf.get(), L"\\",
+ reinterpret_cast<LPVOID*>(&fixedInfo),
+ &fixedInfoLen)) {
+ std::ostringstream oss;
+ oss << HIWORD(fixedInfo->dwFileVersionMS) << '.'
+ << LOWORD(fixedInfo->dwFileVersionMS) << '.'
+ << HIWORD(fixedInfo->dwFileVersionLS) << '.'
+ << LOWORD(fixedInfo->dwFileVersionLS);
+ version = oss.str();
+ }
+ }
+ }
+
+ aJson.StringElement(version);
+
+ mozilla::Maybe<ptrdiff_t> sigIndex;
+ auto signedBy = dllServices.GetBinaryOrgName(module.szExePath);
+ if (signedBy) {
+ std::wstring strSignedBy(signedBy.get());
+ auto entry = std::find(signatures.begin(), signatures.end(), strSignedBy);
+ if (entry == signatures.end()) {
+ mozilla::Unused << signatures.append(std::move(strSignedBy));
+ entry = &signatures.back();
+ }
+
+ sigIndex = mozilla::Some(entry - signatures.begin());
+ }
+
+ if (sigIndex) {
+ aJson.IntElement(sigIndex.value());
+ }
+
+ aJson.EndArray();
+ } while (moduleCount < kMaxArrayLen && ::Module32NextW(aSnapshot, &module));
+
+ aJson.EndObject();
+
+ aJson.StartArrayProperty("signatures");
+
+ // Serialize each entry in the signatures array
+ for (auto&& itr : signatures) {
+ auto sigUtf8 = WideToUTF8(itr);
+ if (!sigUtf8) {
+ continue;
+ }
+
+ aJson.StringElement(mozilla::MakeStringSpan(sigUtf8.get()));
+ }
+
+ aJson.EndArray();
+
+ return true;
+}
+
+namespace {
+
+struct PingThreadContext {
+ explicit PingThreadContext(const mozilla::LauncherError& aError,
+ const char* aProcessType)
+ : mLauncherError(aError),
+ mModulesSnapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0)),
+ mProcessType(aProcessType ? aProcessType : "") {}
+ mozilla::LauncherError mLauncherError;
+ nsAutoHandle mModulesSnapshot;
+ std::string mProcessType;
+};
+
+} // anonymous namespace
+
+static bool PrepPing(const PingThreadContext& aContext, const std::wstring& aId,
+ mozilla::JSONWriter& aJson) {
+# if defined(DEBUG)
+ const mozilla::JSONWriter::CollectionStyle style =
+ mozilla::JSONWriter::MultiLineStyle;
+# else
+ const mozilla::JSONWriter::CollectionStyle style =
+ mozilla::JSONWriter::SingleLineStyle;
+# endif // defined(DEBUG)
+
+ aJson.Start(style);
+
+ aJson.StringProperty("type", "launcher-process-failure");
+ aJson.IntProperty("version", 1);
+
+ auto idUtf8 = WideToUTF8(aId);
+ if (idUtf8) {
+ aJson.StringProperty("id", mozilla::MakeStringSpan(idUtf8.get()));
+ }
+
+ time_t now;
+ time(&now);
+ tm gmTm;
+ if (!gmtime_s(&gmTm, &now)) {
+ char isoTimeBuf[32] = {};
+ if (strftime(isoTimeBuf, mozilla::ArrayLength(isoTimeBuf), "%FT%T.000Z",
+ &gmTm)) {
+ aJson.StringProperty("creationDate", isoTimeBuf);
+ }
+ }
+
+ aJson.StringProperty("update_channel", QUOTE_ME(MOZ_UPDATE_CHANNEL));
+
+ if (gAppData) {
+ aJson.StringProperty("build_id",
+ mozilla::MakeStringSpan(gAppData->buildID));
+ aJson.StringProperty("build_version",
+ mozilla::MakeStringSpan(gAppData->version));
+ }
+
+ OSVERSIONINFOEXW osv = {sizeof(osv)};
+ if (::GetVersionExW(reinterpret_cast<OSVERSIONINFOW*>(&osv))) {
+ std::ostringstream oss;
+ oss << osv.dwMajorVersion << "." << osv.dwMinorVersion << "."
+ << osv.dwBuildNumber;
+
+ if (osv.dwMajorVersion == 10 && osv.dwMinorVersion == 0) {
+ // Get the "Update Build Revision" (UBR) value
+ DWORD ubrValue;
+ DWORD ubrValueLen = sizeof(ubrValue);
+ LSTATUS ubrOk =
+ ::RegGetValueW(HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion",
+ L"UBR", RRF_RT_DWORD | RRF_SUBKEY_WOW6464KEY, nullptr,
+ &ubrValue, &ubrValueLen);
+ if (ubrOk == ERROR_SUCCESS) {
+ oss << "." << ubrValue;
+ }
+ }
+
+ if (oss) {
+ aJson.StringProperty("os_version", oss.str());
+ }
+
+ bool isServer = osv.wProductType == VER_NT_DOMAIN_CONTROLLER ||
+ osv.wProductType == VER_NT_SERVER;
+ aJson.BoolProperty("server_os", isServer);
+ }
+
+ WCHAR localeName[LOCALE_NAME_MAX_LENGTH] = {};
+ int localeNameLen =
+ ::GetUserDefaultLocaleName(localeName, mozilla::ArrayLength(localeName));
+ if (localeNameLen) {
+ auto localeNameUtf8 = WideToUTF8(localeName, localeNameLen - 1);
+ if (localeNameUtf8) {
+ aJson.StringProperty("os_locale",
+ mozilla::MakeStringSpan(localeNameUtf8.get()));
+ }
+ }
+
+ SYSTEM_INFO sysInfo;
+ ::GetNativeSystemInfo(&sysInfo);
+ aJson.IntProperty("cpu_arch", sysInfo.wProcessorArchitecture);
+ aJson.IntProperty("num_logical_cpus", sysInfo.dwNumberOfProcessors);
+
+ mozilla::LauncherResult<bool> isAdminWithoutUac =
+ mozilla::IsAdminWithoutUac();
+ if (isAdminWithoutUac.isOk()) {
+ aJson.BoolProperty("is_admin_without_uac", isAdminWithoutUac.unwrap());
+ }
+
+ if (!aContext.mProcessType.empty()) {
+ aJson.StringProperty("process_type", aContext.mProcessType);
+ }
+
+ MEMORYSTATUSEX memStatus = {sizeof(memStatus)};
+ if (::GlobalMemoryStatusEx(&memStatus)) {
+ aJson.StartObjectProperty("memory");
+ aJson.IntProperty("total_phys", memStatus.ullTotalPhys);
+ aJson.IntProperty("avail_phys", memStatus.ullAvailPhys);
+ aJson.IntProperty("avail_page_file", memStatus.ullAvailPageFile);
+ aJson.IntProperty("avail_virt", memStatus.ullAvailVirtual);
+ aJson.EndObject();
+ }
+
+ aJson.StringProperty("xpcom_abi", TARGET_XPCOM_ABI);
+
+ aJson.StartObjectProperty("launcher_error", style);
+
+ std::string srcFileLeaf(aContext.mLauncherError.mFile);
+ // Obtain the leaf name of the file for privacy reasons
+ // (In case this is somebody's local build)
+ auto pos = srcFileLeaf.find_last_of("/\\");
+ if (pos != std::string::npos) {
+ srcFileLeaf = srcFileLeaf.substr(pos + 1);
+ }
+
+ aJson.StringProperty("source_file", srcFileLeaf);
+
+ aJson.IntProperty("source_line", aContext.mLauncherError.mLine);
+ aJson.IntProperty("hresult", aContext.mLauncherError.mError.AsHResult());
+
+# if defined(NIGHTLY_BUILD)
+ if (aContext.mLauncherError.mDetourError.isSome()) {
+ static const char* kHexMap = "0123456789abcdef";
+ char hexStr[sizeof(mozilla::DetourError::mOrigBytes) * 2 + 1];
+ int cnt = 0;
+ for (uint8_t byte : aContext.mLauncherError.mDetourError->mOrigBytes) {
+ hexStr[cnt++] = kHexMap[(byte >> 4) & 0x0f];
+ hexStr[cnt++] = kHexMap[byte & 0x0f];
+ }
+ hexStr[cnt] = 0;
+ aJson.StringProperty("detour_orig_bytes", hexStr);
+ }
+# endif // defined(NIGHTLY_BUILD)
+
+ aJson.EndObject();
+
+# if !defined(__MINGW32__)
+ if (!AddWscInfo(aJson)) {
+ return false;
+ }
+# endif // !defined(__MINGW32__)
+
+ if (!AddModuleInfo(aContext.mModulesSnapshot, aJson)) {
+ return false;
+ }
+
+ aJson.End();
+
+ return true;
+}
+
+static bool DoSendPing(const PingThreadContext& aContext) {
+ TempFileWriter tempFile;
+ mozilla::JSONWriter json(tempFile);
+
+ UUID uuid;
+ if (::UuidCreate(&uuid) != RPC_S_OK) {
+ return false;
+ }
+
+ wchar_t guidBuf[kGuidCharLenWithNul] = {};
+ if (::StringFromGUID2(uuid, guidBuf, kGuidCharLenWithNul) !=
+ kGuidCharLenWithNul) {
+ return false;
+ }
+
+ // Strip the curly braces off of the guid
+ std::wstring guidNoBraces(guidBuf + 1, kGuidCharLenNoBracesNoNul);
+
+ // Populate json with the ping information
+ if (!PrepPing(aContext, guidNoBraces, json)) {
+ return false;
+ }
+
+ // Obtain the name of the temp file that we have written
+ const std::wstring& fileName = tempFile.GetFileName();
+
+ // Using the path to our executable binary, construct the path to
+ // pingsender.exe
+ mozilla::UniquePtr<wchar_t[]> exePath(mozilla::GetFullBinaryPath());
+
+ wchar_t drive[_MAX_DRIVE] = {};
+ wchar_t dir[_MAX_DIR] = {};
+ if (_wsplitpath_s(exePath.get(), drive, mozilla::ArrayLength(drive), dir,
+ mozilla::ArrayLength(dir), nullptr, 0, nullptr, 0)) {
+ return false;
+ }
+
+ wchar_t pingSenderPath[MAX_PATH + 1] = {};
+ if (_wmakepath_s(pingSenderPath, mozilla::ArrayLength(pingSenderPath), drive,
+ dir, L"pingsender", L"exe")) {
+ return false;
+ }
+
+ // Construct the telemetry URL
+ wchar_t urlBuf[mozilla::ArrayLength(kUrl) + kGuidCharLenNoBracesNoNul] = {};
+ if (wcscpy_s(urlBuf, kUrl)) {
+ return false;
+ }
+
+ if (wcscat_s(urlBuf, guidNoBraces.c_str())) {
+ return false;
+ }
+
+ // Now build the command line arguments to pingsender
+ wchar_t* pingSenderArgv[] = {pingSenderPath, urlBuf,
+ const_cast<wchar_t*>(fileName.c_str())};
+
+ mozilla::UniquePtr<wchar_t[]> pingSenderCmdLine(mozilla::MakeCommandLine(
+ mozilla::ArrayLength(pingSenderArgv), pingSenderArgv));
+
+ // Now start pingsender to handle the rest
+ PROCESS_INFORMATION pi;
+
+ STARTUPINFOW si = {sizeof(si)};
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = SW_HIDE;
+
+ if (!::CreateProcessW(pingSenderPath, pingSenderCmdLine.get(), nullptr,
+ nullptr, FALSE, 0, nullptr, nullptr, &si, &pi)) {
+ return false;
+ }
+
+ tempFile.SetSuccessfulHandoff();
+
+ nsAutoHandle proc(pi.hProcess);
+ nsAutoHandle thread(pi.hThread);
+
+ return true;
+}
+
+static unsigned __stdcall SendPingThread(void* aContext) {
+ mozilla::UniquePtr<PingThreadContext> context(
+ reinterpret_cast<PingThreadContext*>(aContext));
+
+ if (!DoSendPing(*context) || gForceEventLog) {
+ PostErrorToLog(context->mLauncherError);
+ }
+
+ return 0;
+}
+
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+static bool SendPing(const mozilla::LauncherError& aError,
+ const char* aProcessType) {
+#if defined(MOZ_TELEMETRY_REPORTING)
+# if defined(MOZ_LAUNCHER_PROCESS)
+ mozilla::LauncherRegistryInfo regInfo;
+ mozilla::LauncherResult<bool> telemetryEnabled = regInfo.IsTelemetryEnabled();
+ if (telemetryEnabled.isErr() || !telemetryEnabled.unwrap()) {
+ // Do not send anything if telemetry has been opted out
+ return false;
+ }
+# endif // defined(MOZ_LAUNCHER_PROCESS)
+
+ // We send this ping when the launcher process fails. After we start the
+ // SendPingThread, this thread falls back from running as the launcher process
+ // to running as the browser main thread. Once this happens, it will be unsafe
+ // to set up PoisonIOInterposer (since we have already spun up a background
+ // thread).
+ mozilla::SaveToEnv("MOZ_DISABLE_POISON_IO_INTERPOSER=1");
+
+ // Capture aError and our module list into context for processing on another
+ // thread.
+ auto thdParam = mozilla::MakeUnique<PingThreadContext>(aError, aProcessType);
+
+ // The ping does a lot of file I/O. Since we want this thread to continue
+ // executing browser startup, we should gather that information on a
+ // background thread.
+ uintptr_t thdHandle =
+ _beginthreadex(nullptr, 0, &SendPingThread, thdParam.get(),
+ STACK_SIZE_PARAM_IS_A_RESERVATION, nullptr);
+ if (!thdHandle) {
+ return false;
+ }
+
+ // We have handed off thdParam to the background thread
+ mozilla::Unused << thdParam.release();
+
+ ::CloseHandle(reinterpret_cast<HANDLE>(thdHandle));
+ return true;
+#else
+ return false;
+#endif
+}
+
+namespace mozilla {
+
+void HandleLauncherError(const LauncherError& aError,
+ const char* aProcessType) {
+#if defined(MOZ_LAUNCHER_PROCESS)
+ LauncherRegistryInfo regInfo;
+ Unused << regInfo.DisableDueToFailure();
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+ if (!SendPing(aError, aProcessType)) {
+ // couldn't (or shouldn't) send telemetry; fall back to event log
+ PostErrorToLog(aError);
+ }
+}
+
+void SetLauncherErrorAppData(const StaticXREAppData& aAppData) {
+ gAppData = &aAppData;
+}
+
+void SetLauncherErrorForceEventLog() { gForceEventLog = true; }
+
+} // namespace mozilla
diff --git a/browser/app/winlauncher/ErrorHandler.h b/browser/app/winlauncher/ErrorHandler.h
new file mode 100644
index 0000000000..71fe72cf07
--- /dev/null
+++ b/browser/app/winlauncher/ErrorHandler.h
@@ -0,0 +1,54 @@
+/* -*- 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_ErrorHandler_h
+#define mozilla_ErrorHandler_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+
+/**
+ * All launcher process error handling should live in the implementation of
+ * this function.
+ */
+void HandleLauncherError(const LauncherError& aError,
+ const char* aProcessType = nullptr);
+
+// This function is simply a convenience overload that automatically unwraps
+// the LauncherError from the provided LauncherResult and then forwards it to
+// the main implementation.
+template <typename T>
+inline void HandleLauncherError(const LauncherResult<T>& aResult,
+ const char* aProcessType = nullptr) {
+ MOZ_ASSERT(aResult.isErr());
+ if (aResult.isOk()) {
+ return;
+ }
+
+ HandleLauncherError(aResult.inspectErr(), aProcessType);
+}
+
+// This function is simply a convenience overload that unwraps the provided
+// GenericErrorResult<LauncherError> and forwards it to the main implementation.
+inline void HandleLauncherError(
+ const GenericErrorResult<LauncherError>& aResult,
+ const char* aProcessType = nullptr) {
+ LauncherVoidResult r(aResult);
+ HandleLauncherError(r, aProcessType);
+}
+
+// Forward declaration
+struct StaticXREAppData;
+
+void SetLauncherErrorAppData(const StaticXREAppData& aAppData);
+
+void SetLauncherErrorForceEventLog();
+
+} // namespace mozilla
+
+#endif // mozilla_ErrorHandler_h
diff --git a/browser/app/winlauncher/LaunchUnelevated.cpp b/browser/app/winlauncher/LaunchUnelevated.cpp
new file mode 100644
index 0000000000..3cf6ac837c
--- /dev/null
+++ b/browser/app/winlauncher/LaunchUnelevated.cpp
@@ -0,0 +1,290 @@
+/* -*- 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 "LaunchUnelevated.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/mscom/ProcessRuntime.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ShellHeaderOnlyUtils.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "../BrowserDefines.h"
+#include "nsWindowsHelpers.h"
+
+#include <windows.h>
+
+#if !defined(RRF_SUBKEY_WOW6464KEY)
+# define RRF_SUBKEY_WOW6464KEY 0x00010000
+#endif // !defined(RRF_SUBKEY_WOW6464KEY)
+
+static mozilla::LauncherResult<bool> IsHighIntegrity(
+ const nsAutoHandle& aToken) {
+ DWORD reqdLen;
+ if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, nullptr, 0,
+ &reqdLen)) {
+ DWORD err = ::GetLastError();
+ if (err != ERROR_INSUFFICIENT_BUFFER) {
+ return LAUNCHER_ERROR_FROM_WIN32(err);
+ }
+ }
+
+ auto buf = mozilla::MakeUnique<char[]>(reqdLen);
+
+ if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, buf.get(),
+ reqdLen, &reqdLen)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ auto tokenLabel = reinterpret_cast<PTOKEN_MANDATORY_LABEL>(buf.get());
+
+ DWORD subAuthCount = *::GetSidSubAuthorityCount(tokenLabel->Label.Sid);
+ DWORD integrityLevel =
+ *::GetSidSubAuthority(tokenLabel->Label.Sid, subAuthCount - 1);
+ return integrityLevel > SECURITY_MANDATORY_MEDIUM_RID;
+}
+
+static mozilla::LauncherResult<HANDLE> GetMediumIntegrityToken(
+ const nsAutoHandle& aProcessToken) {
+ HANDLE rawResult;
+ if (!::DuplicateTokenEx(aProcessToken.get(), 0, nullptr,
+ SecurityImpersonation, TokenPrimary, &rawResult)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ nsAutoHandle result(rawResult);
+
+ BYTE mediumIlSid[SECURITY_MAX_SID_SIZE];
+ DWORD mediumIlSidSize = sizeof(mediumIlSid);
+ if (!::CreateWellKnownSid(WinMediumLabelSid, nullptr, mediumIlSid,
+ &mediumIlSidSize)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ TOKEN_MANDATORY_LABEL integrityLevel = {};
+ integrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
+ integrityLevel.Label.Sid = reinterpret_cast<PSID>(mediumIlSid);
+
+ if (!::SetTokenInformation(rawResult, TokenIntegrityLevel, &integrityLevel,
+ sizeof(integrityLevel))) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return result.disown();
+}
+
+static mozilla::LauncherResult<bool> IsAdminByAppCompat(
+ HKEY aRootKey, const wchar_t* aExecutablePath) {
+ static const wchar_t kPathToLayers[] =
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
+ L"AppCompatFlags\\Layers";
+
+ DWORD dataLength = 0;
+ LSTATUS status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath,
+ RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY,
+ nullptr, nullptr, &dataLength);
+ if (status == ERROR_FILE_NOT_FOUND) {
+ return false;
+ } else if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ auto valueData = mozilla::MakeUnique<wchar_t[]>(dataLength);
+ if (!valueData) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_OUTOFMEMORY);
+ }
+
+ status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath,
+ RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr,
+ valueData.get(), &dataLength);
+ if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ const wchar_t kRunAsAdmin[] = L"RUNASADMIN";
+ const wchar_t kDelimiters[] = L" ";
+ wchar_t* tokenContext = nullptr;
+ const wchar_t* token = wcstok_s(valueData.get(), kDelimiters, &tokenContext);
+ while (token) {
+ if (!_wcsnicmp(token, kRunAsAdmin, mozilla::ArrayLength(kRunAsAdmin))) {
+ return true;
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+
+ return false;
+}
+
+namespace mozilla {
+
+// If we're running at an elevated integrity level, re-run ourselves at the
+// user's normal integrity level. We do this by locating the active explorer
+// shell, and then asking it to do a ShellExecute on our behalf. We do it this
+// way to ensure that the child process runs as the original user in the active
+// session; an elevated process could be running with different credentials than
+// those of the session.
+// See https://devblogs.microsoft.com/oldnewthing/20131118-00/?p=2643
+
+LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]) {
+ // We need COM to talk to Explorer. Using ProcessRuntime so that
+ // process-global COM configuration is done correctly
+ mozilla::mscom::ProcessRuntime mscom(
+ mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher);
+ if (!mscom) {
+ return LAUNCHER_ERROR_FROM_HRESULT(mscom.GetHResult());
+ }
+
+ // Omit the original argv[0] because ShellExecute doesn't need it. Insert
+ // ATTEMPTING_DEELEVATION_FLAG so that we know not to attempt to restart
+ // ourselves if deelevation fails.
+ UniquePtr<wchar_t[]> cmdLine = [&]() {
+ constexpr wchar_t const* kTagArg = L"--" ATTEMPTING_DEELEVATION_FLAG;
+
+ // This should have already been checked, but just in case...
+ EnsureBrowserCommandlineSafe(aArgc, aArgv);
+
+ if (mozilla::CheckArg(aArgc, aArgv, "osint", nullptr, CheckArgFlag::None)) {
+ // If the command line contains -osint, we have to arrange things in a
+ // particular order.
+ //
+ // (We can't just replace -osint with kTagArg, unfortunately: there is
+ // code in the browser which behaves differently in the presence of an
+ // `-osint` tag, but which will not have had a chance to react to this.
+ // See, _e.g._, bug 1243603.)
+ auto const aArgvCopy = MakeUnique<wchar_t const*[]>(aArgc + 1);
+ aArgvCopy[0] = aArgv[1];
+ aArgvCopy[1] = kTagArg;
+ for (int i = 2; i < aArgc; ++i) {
+ aArgvCopy[i] = aArgv[i];
+ }
+ aArgvCopy[aArgc] = nullptr; // because argv[argc] is NULL
+ return MakeCommandLine(aArgc, aArgvCopy.get(), 0, nullptr);
+ } else {
+ // Otherwise, just tack it on at the end.
+ constexpr wchar_t const* const kTagArgArray[] = {kTagArg};
+ return MakeCommandLine(aArgc - 1, aArgv + 1, 1, kTagArgArray);
+ }
+ }();
+ if (!cmdLine) {
+ return LAUNCHER_ERROR_GENERIC();
+ }
+
+ _bstr_t cmd;
+
+ UniquePtr<wchar_t[]> packageFamilyName = mozilla::GetPackageFamilyName();
+ if (packageFamilyName) {
+ int cmdLen =
+ // 22 for the prefix + suffix + null terminator below
+ 22 + wcslen(packageFamilyName.get());
+ wchar_t appCmd[cmdLen];
+ swprintf(appCmd, cmdLen, L"shell:appsFolder\\%s!App",
+ packageFamilyName.get());
+ cmd = appCmd;
+ } else {
+ cmd = aArgv[0];
+ }
+
+ _variant_t args(cmdLine.get());
+ _variant_t operation(L"open");
+ _variant_t directory;
+ _variant_t showCmd(SW_SHOWNORMAL);
+ return ShellExecuteByExplorer(cmd, args, operation, directory, showCmd);
+}
+
+LauncherResult<ElevationState> GetElevationState(
+ const wchar_t* aExecutablePath, mozilla::LauncherFlags aFlags,
+ nsAutoHandle& aOutMediumIlToken) {
+ aOutMediumIlToken.reset();
+
+ const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE |
+ TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY;
+ HANDLE rawToken;
+ if (!::OpenProcessToken(::GetCurrentProcess(), tokenFlags, &rawToken)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ nsAutoHandle token(rawToken);
+
+ LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token);
+ if (elevationType.isErr()) {
+ return elevationType.propagateErr();
+ }
+
+ Maybe<ElevationState> elevationState;
+ switch (elevationType.unwrap()) {
+ case TokenElevationTypeLimited:
+ return ElevationState::eNormalUser;
+ case TokenElevationTypeFull:
+ elevationState = Some(ElevationState::eElevated);
+ break;
+ case TokenElevationTypeDefault: {
+ // In this case, UAC is disabled. We do not yet know whether or not we
+ // are running at high integrity. If we are at high integrity, we can't
+ // relaunch ourselves in a non-elevated state via Explorer, as we would
+ // just end up in an infinite loop of launcher processes re-launching
+ // themselves.
+ LauncherResult<bool> isHighIntegrity = IsHighIntegrity(token);
+ if (isHighIntegrity.isErr()) {
+ return isHighIntegrity.propagateErr();
+ }
+
+ if (!isHighIntegrity.unwrap()) {
+ return ElevationState::eNormalUser;
+ }
+
+ elevationState = Some(ElevationState::eHighIntegrityNoUAC);
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
+ return LAUNCHER_ERROR_GENERIC();
+ }
+
+ MOZ_ASSERT(elevationState.isSome() &&
+ elevationState.value() != ElevationState::eNormalUser,
+ "Should have returned earlier for the eNormalUser case.");
+
+ LauncherResult<bool> isAdminByAppCompat =
+ IsAdminByAppCompat(HKEY_CURRENT_USER, aExecutablePath);
+ if (isAdminByAppCompat.isErr()) {
+ return isAdminByAppCompat.propagateErr();
+ }
+
+ if (isAdminByAppCompat.unwrap()) {
+ elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
+ } else {
+ isAdminByAppCompat =
+ IsAdminByAppCompat(HKEY_LOCAL_MACHINE, aExecutablePath);
+ if (isAdminByAppCompat.isErr()) {
+ return isAdminByAppCompat.propagateErr();
+ }
+
+ if (isAdminByAppCompat.unwrap()) {
+ elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
+ }
+ }
+
+ // A medium IL token is not needed in the following cases.
+ // 1) We keep the process elevated (= LauncherFlags::eNoDeelevate)
+ // 2) The process was elevated by UAC (= ElevationState::eElevated)
+ // AND the launcher process doesn't wait for the browser process
+ if ((aFlags & mozilla::LauncherFlags::eNoDeelevate) ||
+ (elevationState.value() == ElevationState::eElevated &&
+ !(aFlags & mozilla::LauncherFlags::eWaitForBrowser))) {
+ return elevationState.value();
+ }
+
+ LauncherResult<HANDLE> tokenResult = GetMediumIntegrityToken(token);
+ if (tokenResult.isOk()) {
+ aOutMediumIlToken.own(tokenResult.unwrap());
+ } else {
+ return tokenResult.propagateErr();
+ }
+
+ return elevationState.value();
+}
+
+} // namespace mozilla
diff --git a/browser/app/winlauncher/LaunchUnelevated.h b/browser/app/winlauncher/LaunchUnelevated.h
new file mode 100644
index 0000000000..8c6679edf3
--- /dev/null
+++ b/browser/app/winlauncher/LaunchUnelevated.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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_LaunchUnelevated_h
+#define mozilla_LaunchUnelevated_h
+
+#include "LauncherProcessWin.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/Maybe.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+
+enum class ElevationState {
+ eNormalUser = 0,
+ eElevated = (1 << 0),
+ eHighIntegrityNoUAC = (1 << 1),
+ eHighIntegrityByAppCompat = (1 << 2),
+};
+
+LauncherResult<ElevationState> GetElevationState(
+ const wchar_t* aExecutablePath, LauncherFlags aFlags,
+ nsAutoHandle& aOutMediumIlToken);
+
+LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]);
+
+} // namespace mozilla
+
+#endif // mozilla_LaunchUnelevated_h
diff --git a/browser/app/winlauncher/LauncherProcessWin.cpp b/browser/app/winlauncher/LauncherProcessWin.cpp
new file mode 100644
index 0000000000..082ada9ea7
--- /dev/null
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -0,0 +1,536 @@
+/* -*- 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 "LauncherProcessWin.h"
+
+#include <string.h>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/GeckoArgs.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/SafeMode.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsConsole.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsWindowsHelpers.h"
+
+#include <windows.h>
+#include <processthreadsapi.h>
+
+#include "DllBlocklistInit.h"
+#include "ErrorHandler.h"
+#include "LaunchUnelevated.h"
+#include "ProcThreadAttributes.h"
+#include "../BrowserDefines.h"
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+# include "mozilla/LauncherRegistryInfo.h"
+# include "SameBinary.h"
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+#if defined(MOZ_SANDBOX)
+# include "mozilla/sandboxing/SandboxInitialization.h"
+#endif
+
+namespace mozilla {
+// "const" because nothing in this process modifies it.
+// "volatile" because something in another process may.
+const volatile DeelevationStatus gDeelevationStatus =
+ DeelevationStatus::DefaultStaticValue;
+} // namespace mozilla
+
+/**
+ * At this point the child process has been created in a suspended state. Any
+ * additional startup work (eg, blocklist setup) should go here.
+ *
+ * @return Ok if browser startup should proceed
+ */
+static mozilla::LauncherVoidResult PostCreationSetup(
+ const wchar_t* aFullImagePath, HANDLE aChildProcess,
+ HANDLE aChildMainThread, mozilla::DeelevationStatus aDStatus,
+ const bool aIsSafeMode, const bool aDisableDynamicBlocklist,
+ mozilla::Maybe<std::wstring> aBlocklistFileName) {
+ /* scope for txManager */ {
+ mozilla::nt::CrossExecTransferManager txManager(aChildProcess);
+ if (!txManager) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
+ }
+
+ using mozilla::gDeelevationStatus;
+
+ void* targetAddress = (LPVOID)&gDeelevationStatus;
+
+ auto const guard = txManager.Protect(
+ targetAddress, sizeof(gDeelevationStatus), PAGE_READWRITE);
+
+ mozilla::LauncherVoidResult result =
+ txManager.Transfer(targetAddress, &aDStatus, sizeof(aDStatus));
+ if (result.isErr()) {
+ return result;
+ }
+ }
+
+ return mozilla::InitializeDllBlocklistOOPFromLauncher(
+ aFullImagePath, aChildProcess, aDisableDynamicBlocklist,
+ aBlocklistFileName);
+}
+
+/**
+ * Create a new Job object and assign |aProcess| to it. If something fails
+ * in this function, we return nullptr but continue without recording
+ * a launcher failure because it's not a critical problem to launch
+ * the browser process.
+ */
+static nsReturnRef<HANDLE> CreateJobAndAssignProcess(HANDLE aProcess) {
+ nsAutoHandle empty;
+ nsAutoHandle job(::CreateJobObjectW(nullptr, nullptr));
+
+ // Set JOB_OBJECT_LIMIT_BREAKAWAY_OK to allow the browser process
+ // to put child processes into a job on Win7, which does not support
+ // nested jobs. See CanUseJob() in sandboxBroker.cpp.
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
+ jobInfo.BasicLimitInformation.LimitFlags =
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE | JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+ if (!::SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
+ &jobInfo, sizeof(jobInfo))) {
+ return empty.out();
+ }
+
+ if (!::AssignProcessToJobObject(job.get(), aProcess)) {
+ return empty.out();
+ }
+
+ return job.out();
+}
+
+#if !defined( \
+ PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON)
+# define PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON \
+ (0x00000001ULL << 60)
+#endif // !defined(PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON)
+
+#if !defined(PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF)
+# define PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF \
+ (0x00000002ULL << 40)
+#endif // !defined(PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF)
+
+#if (_WIN32_WINNT < 0x0602)
+BOOL WINAPI
+SetProcessMitigationPolicy(PROCESS_MITIGATION_POLICY aMitigationPolicy,
+ PVOID aBuffer, SIZE_T aBufferLen);
+#endif // (_WIN32_WINNT >= 0x0602)
+
+/**
+ * Any mitigation policies that should be set on the browser process should go
+ * here.
+ */
+static void SetMitigationPolicies(mozilla::ProcThreadAttributes& aAttrs,
+ const bool aIsSafeMode) {
+ if (mozilla::IsWin10AnniversaryUpdateOrLater()) {
+ aAttrs.AddMitigationPolicy(
+ PROCESS_CREATION_MITIGATION_POLICY_IMAGE_LOAD_PREFER_SYSTEM32_ALWAYS_ON);
+ }
+
+#if defined(_M_ARM64)
+ // Disable CFG on older versions of ARM64 Windows to avoid a crash in COM.
+ if (!mozilla::IsWin10Sep2018UpdateOrLater()) {
+ aAttrs.AddMitigationPolicy(
+ PROCESS_CREATION_MITIGATION_POLICY_CONTROL_FLOW_GUARD_ALWAYS_OFF);
+ }
+#endif // defined(_M_ARM64)
+}
+
+static mozilla::LauncherFlags ProcessCmdLine(int& aArgc, wchar_t* aArgv[]) {
+ mozilla::LauncherFlags result = mozilla::LauncherFlags::eNone;
+
+ if (mozilla::CheckArg(aArgc, aArgv, "wait-for-browser", nullptr,
+ mozilla::CheckArgFlag::RemoveArg) ==
+ mozilla::ARG_FOUND ||
+ mozilla::CheckArg(aArgc, aArgv, "marionette", nullptr,
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
+ mozilla::CheckArg(aArgc, aArgv, "backgroundtask", nullptr,
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
+ mozilla::CheckArg(aArgc, aArgv, "headless", nullptr,
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
+ mozilla::CheckArg(aArgc, aArgv, "remote-debugging-port", nullptr,
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND ||
+ mozilla::EnvHasValue("MOZ_AUTOMATION") ||
+ mozilla::EnvHasValue("MOZ_HEADLESS")) {
+ result |= mozilla::LauncherFlags::eWaitForBrowser;
+ }
+
+ if (mozilla::CheckArg(aArgc, aArgv, "no-deelevate") == mozilla::ARG_FOUND) {
+ result |= mozilla::LauncherFlags::eNoDeelevate;
+ }
+
+ if (mozilla::CheckArg(aArgc, aArgv, ATTEMPTING_DEELEVATION_FLAG) ==
+ mozilla::ARG_FOUND) {
+ result |= mozilla::LauncherFlags::eDeelevating;
+ }
+
+ return result;
+}
+
+static void MaybeBreakForBrowserDebugging() {
+ if (mozilla::EnvHasValue("MOZ_DEBUG_BROWSER_PROCESS")) {
+ ::DebugBreak();
+ return;
+ }
+
+ const wchar_t* pauseLenS = _wgetenv(L"MOZ_DEBUG_BROWSER_PAUSE");
+ if (!pauseLenS || !(*pauseLenS)) {
+ return;
+ }
+
+ DWORD pauseLenMs = wcstoul(pauseLenS, nullptr, 10) * 1000;
+ printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n debug me @ %lu\n\n",
+ ::GetCurrentProcessId());
+ ::Sleep(pauseLenMs);
+}
+
+static bool DoLauncherProcessChecks(int& argc, wchar_t** argv) {
+ // NB: We run all tests in this function instead of returning early in order
+ // to ensure that all side effects take place, such as clearing environment
+ // variables.
+ bool result = false;
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+ // We still prefer to compare file ids. Comparing NT paths i.e. passing
+ // CompareNtPathsOnly to IsSameBinaryAsParentProcess is much faster, but
+ // we're not 100% sure that NT path comparison perfectly prevents the
+ // launching loop of the launcher process.
+ mozilla::LauncherResult<bool> isSame = mozilla::IsSameBinaryAsParentProcess();
+ if (isSame.isOk()) {
+ result = !isSame.unwrap();
+ } else {
+ HandleLauncherError(isSame.unwrapErr());
+ }
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+ if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
+ mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
+ result = true;
+ }
+
+ result |=
+ mozilla::CheckArg(argc, argv, "launcher", nullptr,
+ mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
+
+ return result;
+}
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+static mozilla::Maybe<bool> RunAsLauncherProcess(
+ mozilla::LauncherRegistryInfo& aRegInfo, int& argc, wchar_t** argv) {
+#else
+static mozilla::Maybe<bool> RunAsLauncherProcess(int& argc, wchar_t** argv) {
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+ bool runAsLauncher = DoLauncherProcessChecks(argc, argv);
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+ bool forceLauncher =
+ runAsLauncher &&
+ mozilla::CheckArg(argc, argv, "force-launcher", nullptr,
+ mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
+
+ mozilla::LauncherRegistryInfo::ProcessType desiredType =
+ runAsLauncher ? mozilla::LauncherRegistryInfo::ProcessType::Launcher
+ : mozilla::LauncherRegistryInfo::ProcessType::Browser;
+
+ mozilla::LauncherRegistryInfo::CheckOption checkOption =
+ forceLauncher ? mozilla::LauncherRegistryInfo::CheckOption::Force
+ : mozilla::LauncherRegistryInfo::CheckOption::Default;
+
+ mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType>
+ runAsType = aRegInfo.Check(desiredType, checkOption);
+
+ if (runAsType.isErr()) {
+ mozilla::HandleLauncherError(runAsType);
+ return mozilla::Nothing();
+ }
+
+ runAsLauncher = runAsType.unwrap() ==
+ mozilla::LauncherRegistryInfo::ProcessType::Launcher;
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+ if (!runAsLauncher) {
+ // In this case, we will be proceeding to run as the browser.
+ // We should check MOZ_DEBUG_BROWSER_* env vars.
+ MaybeBreakForBrowserDebugging();
+ }
+
+ return mozilla::Some(runAsLauncher);
+}
+
+namespace mozilla {
+
+Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
+ const StaticXREAppData& aAppData) {
+ EnsureBrowserCommandlineSafe(argc, argv);
+
+ SetLauncherErrorAppData(aAppData);
+
+ if (CheckArg(argc, argv, "log-launcher-error", nullptr,
+ mozilla::CheckArgFlag::RemoveArg) == ARG_FOUND) {
+ SetLauncherErrorForceEventLog();
+ }
+
+ // return fast when we're a child process.
+ // (The remainder of this function has some side effects that are
+ // undesirable for content processes)
+ if (mozilla::CheckArg(argc, argv, "contentproc", nullptr,
+ mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND) {
+ // A child process should not instantiate LauncherRegistryInfo.
+ return Nothing();
+ }
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+ LauncherRegistryInfo regInfo;
+ Maybe<bool> runAsLauncher = RunAsLauncherProcess(regInfo, argc, argv);
+ LauncherResult<std::wstring> blocklistFileNameResult =
+ regInfo.GetBlocklistFileName();
+ Maybe<std::wstring> blocklistFileName =
+ blocklistFileNameResult.isOk() ? Some(blocklistFileNameResult.unwrap())
+ : Nothing();
+#else
+ Maybe<bool> runAsLauncher = RunAsLauncherProcess(argc, argv);
+ Maybe<std::wstring> blocklistFileName = Nothing();
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+ if (!runAsLauncher || !runAsLauncher.value()) {
+#if defined(MOZ_LAUNCHER_PROCESS)
+ // Update the registry as Browser
+ LauncherVoidResult commitResult = regInfo.Commit();
+ if (commitResult.isErr()) {
+ mozilla::HandleLauncherError(commitResult);
+ }
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+ return Nothing();
+ }
+
+ // Make sure that the launcher process itself has image load policies set
+ if (IsWin10AnniversaryUpdateOrLater()) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&SetProcessMitigationPolicy)>
+ pSetProcessMitigationPolicy(L"kernel32.dll",
+ "SetProcessMitigationPolicy");
+ if (pSetProcessMitigationPolicy) {
+ PROCESS_MITIGATION_IMAGE_LOAD_POLICY imgLoadPol = {};
+ imgLoadPol.PreferSystem32Images = 1;
+
+ DebugOnly<BOOL> setOk = pSetProcessMitigationPolicy(
+ ProcessImageLoadPolicy, &imgLoadPol, sizeof(imgLoadPol));
+ MOZ_ASSERT(setOk);
+ }
+ }
+
+#if defined(MOZ_SANDBOX)
+ // Ensure the relevant mitigations are enforced.
+ mozilla::sandboxing::ApplyParentProcessMitigations();
+#endif
+
+ mozilla::UseParentConsole();
+
+ if (!SetArgv0ToFullBinaryPath(argv)) {
+ HandleLauncherError(LAUNCHER_ERROR_GENERIC());
+ return Nothing();
+ }
+
+ LauncherFlags flags = ProcessCmdLine(argc, argv);
+
+ nsAutoHandle mediumIlToken;
+ LauncherResult<ElevationState> elevationState =
+ GetElevationState(argv[0], flags, mediumIlToken);
+ if (elevationState.isErr()) {
+ HandleLauncherError(elevationState);
+ return Nothing();
+ }
+
+ // Distill deelevation status, and/or attempt to perform launcher deelevation
+ // via an indirect relaunch.
+ DeelevationStatus deelevationStatus = DeelevationStatus::Unknown;
+ if (mediumIlToken.get()) {
+ // Rather than indirectly relaunch the launcher, we'll attempt to directly
+ // launch the main process with a reduced-privilege security token.
+ deelevationStatus = DeelevationStatus::PartiallyDeelevated;
+ } else if (elevationState.unwrap() == ElevationState::eElevated) {
+ if (flags & LauncherFlags::eWaitForBrowser) {
+ // An indirect relaunch won't provide a process-handle to block on,
+ // so we have to continue onwards with this process.
+ deelevationStatus = DeelevationStatus::DeelevationProhibited;
+ } else if (flags & LauncherFlags::eNoDeelevate) {
+ // Our invoker (hopefully, the user) has explicitly requested that the
+ // launcher not deelevate itself.
+ deelevationStatus = DeelevationStatus::DeelevationProhibited;
+ } else if (flags & LauncherFlags::eDeelevating) {
+ // We've already tried to deelevate, to no effect. Continue onward.
+ deelevationStatus = DeelevationStatus::UnsuccessfullyDeelevated;
+ } else {
+ // Otherwise, attempt to relaunch the launcher process itself via the
+ // shell, which hopefully will not be elevated. (But see bug 1733821.)
+ LauncherVoidResult launchedUnelevated = LaunchUnelevated(argc, argv);
+ if (launchedUnelevated.isErr()) {
+ // On failure, don't even try for a launcher process. Continue onwards
+ // in this one. (TODO: why? This isn't technically fatal...)
+ HandleLauncherError(launchedUnelevated);
+ return Nothing();
+ }
+ // Otherwise, tell our caller to exit with a success code.
+ return Some(0);
+ }
+ } else if (elevationState.unwrap() == ElevationState::eNormalUser) {
+ if (flags & LauncherFlags::eDeelevating) {
+ // Deelevation appears to have been successful!
+ deelevationStatus = DeelevationStatus::SuccessfullyDeelevated;
+ } else {
+ // We haven't done anything and we don't need to.
+ deelevationStatus = DeelevationStatus::StartedUnprivileged;
+ }
+ } else {
+ // Some other elevation state with no medium-integrity token.
+ // (This should probably not happen.)
+ deelevationStatus = DeelevationStatus::Unknown;
+ }
+
+#if defined(MOZ_LAUNCHER_PROCESS)
+ // Update the registry as Launcher
+ LauncherVoidResult commitResult = regInfo.Commit();
+ if (commitResult.isErr()) {
+ mozilla::HandleLauncherError(commitResult);
+ return Nothing();
+ }
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+ // Now proceed with setting up the parameters for process creation
+ UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(argc, argv));
+ if (!cmdLine) {
+ HandleLauncherError(LAUNCHER_ERROR_GENERIC());
+ return Nothing();
+ }
+
+ const Maybe<bool> isSafeMode =
+ IsSafeModeRequested(argc, argv, SafeModeFlag::NoKeyPressCheck);
+ if (!isSafeMode) {
+ HandleLauncherError(LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_PARAMETER));
+ return Nothing();
+ }
+
+ ProcThreadAttributes attrs;
+ SetMitigationPolicies(attrs, isSafeMode.value());
+
+ HANDLE stdHandles[] = {::GetStdHandle(STD_INPUT_HANDLE),
+ ::GetStdHandle(STD_OUTPUT_HANDLE),
+ ::GetStdHandle(STD_ERROR_HANDLE)};
+
+ attrs.AddInheritableHandles(stdHandles);
+
+ DWORD creationFlags = CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT;
+
+ STARTUPINFOEXW siex;
+ LauncherResult<bool> attrsOk = attrs.AssignTo(siex);
+ if (attrsOk.isErr()) {
+ HandleLauncherError(attrsOk);
+ return Nothing();
+ }
+
+ BOOL inheritHandles = FALSE;
+
+ if (attrsOk.unwrap()) {
+ creationFlags |= EXTENDED_STARTUPINFO_PRESENT;
+
+ if (attrs.HasInheritableHandles()) {
+ siex.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
+ siex.StartupInfo.hStdInput = stdHandles[0];
+ siex.StartupInfo.hStdOutput = stdHandles[1];
+ siex.StartupInfo.hStdError = stdHandles[2];
+
+ // Since attrsOk == true, we have successfully set the handle inheritance
+ // whitelist policy, so only the handles added to attrs will be inherited.
+ inheritHandles = TRUE;
+ }
+ }
+
+ // Pass on the path of the shortcut used to launch this process, if any.
+ STARTUPINFOW currentStartupInfo = {.cb = sizeof(STARTUPINFOW)};
+ GetStartupInfoW(&currentStartupInfo);
+ if ((currentStartupInfo.dwFlags & STARTF_TITLEISLINKNAME) &&
+ currentStartupInfo.lpTitle) {
+ siex.StartupInfo.dwFlags |= STARTF_TITLEISLINKNAME;
+ siex.StartupInfo.lpTitle = currentStartupInfo.lpTitle;
+ }
+
+ PROCESS_INFORMATION pi = {};
+ BOOL createOk;
+
+ if (mediumIlToken.get()) {
+ createOk =
+ ::CreateProcessAsUserW(mediumIlToken.get(), argv[0], cmdLine.get(),
+ nullptr, nullptr, inheritHandles, creationFlags,
+ nullptr, nullptr, &siex.StartupInfo, &pi);
+ } else {
+ createOk = ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr,
+ inheritHandles, creationFlags, nullptr, nullptr,
+ &siex.StartupInfo, &pi);
+ }
+
+ if (!createOk) {
+ HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
+ return Nothing();
+ }
+
+ nsAutoHandle process(pi.hProcess);
+ nsAutoHandle mainThread(pi.hThread);
+
+ nsAutoHandle job;
+ if (flags & LauncherFlags::eWaitForBrowser) {
+ job = CreateJobAndAssignProcess(process.get());
+ }
+
+ bool disableDynamicBlocklist = IsDynamicBlocklistDisabled(
+ isSafeMode.value(),
+ mozilla::CheckArg(
+ argc, argv, mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch,
+ nullptr, mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND);
+ LauncherVoidResult setupResult = PostCreationSetup(
+ argv[0], process.get(), mainThread.get(), deelevationStatus,
+ isSafeMode.value(), disableDynamicBlocklist, blocklistFileName);
+ if (setupResult.isErr()) {
+ HandleLauncherError(setupResult);
+ ::TerminateProcess(process.get(), 1);
+ return Nothing();
+ }
+
+ if (::ResumeThread(mainThread.get()) == static_cast<DWORD>(-1)) {
+ HandleLauncherError(LAUNCHER_ERROR_FROM_LAST());
+ ::TerminateProcess(process.get(), 1);
+ return Nothing();
+ }
+
+ if (flags & LauncherFlags::eWaitForBrowser) {
+ DWORD exitCode;
+ if (::WaitForSingleObject(process.get(), INFINITE) == WAIT_OBJECT_0 &&
+ ::GetExitCodeProcess(process.get(), &exitCode)) {
+ // Propagate the browser process's exit code as our exit code.
+ return Some(static_cast<int>(exitCode));
+ }
+ } else {
+ const DWORD timeout =
+ ::IsDebuggerPresent() ? INFINITE : kWaitForInputIdleTimeoutMS;
+
+ // Keep the current process around until the callback process has created
+ // its message queue, to avoid the launched process's windows being forced
+ // into the background.
+ mozilla::WaitForInputIdle(process.get(), timeout);
+ }
+
+ return Some(0);
+}
+
+} // namespace mozilla
diff --git a/browser/app/winlauncher/LauncherProcessWin.h b/browser/app/winlauncher/LauncherProcessWin.h
new file mode 100644
index 0000000000..8fd53d7d3f
--- /dev/null
+++ b/browser/app/winlauncher/LauncherProcessWin.h
@@ -0,0 +1,70 @@
+/* -*- 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_LauncherProcessWin_h
+#define mozilla_LauncherProcessWin_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/TypedEnumBits.h"
+
+#include <stdint.h>
+
+namespace mozilla {
+
+// Forward declaration
+struct StaticXREAppData;
+
+/**
+ * Determine whether or not the current process should be run as the launcher
+ * process, and run if so. If we are not supposed to run as the launcher
+ * process, or in the event of a launcher process failure, return Nothing, thus
+ * indicating that we should continue on the original startup code path.
+ */
+Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
+ const StaticXREAppData& aAppData);
+
+enum class LauncherFlags : uint32_t {
+ eNone = 0,
+ eWaitForBrowser = (1 << 0), // Launcher should block until browser finishes
+ eNoDeelevate = (1 << 1), // If elevated, do not attempt to de-elevate
+ eDeelevating = (1 << 2), // A de-elevation attempt has been made
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(LauncherFlags);
+
+enum class DeelevationStatus : uint32_t {
+ // The deelevation status could not be determined. Should never actually be
+ // the value of `gDeelevationStatus`.
+ Unknown = 0,
+
+ // Deelevation did not need to be performed because the process was started
+ // without administrative privileges.
+ StartedUnprivileged = 1,
+ // Deelevation would have been performed, but was prohibited due to a flag.
+ DeelevationProhibited = 2,
+ // The launcher process was successfully deelevated.
+ SuccessfullyDeelevated = 3,
+ // The launcher process was not successfully deelevated, but a
+ // medium-integrity token was used to launch the main process.
+ PartiallyDeelevated = 4,
+ // Deelevation was attempted, but failed completely. The main process is
+ // running with administrative privileges.
+ UnsuccessfullyDeelevated = 5,
+
+ // This is the static initial value of `gDeelevationStatus`; it acts as a
+ // sentinel to determine whether the launcher has set it at all. (It's
+ // therefore the normal value of `gDeelevationStatus` when the launcher is
+ // disabled.)
+ DefaultStaticValue = 0x55AA55AA,
+};
+
+// The result of the deelevation attempt. Set by the launcher process in the
+// main process when the two are distinct.
+extern const volatile DeelevationStatus gDeelevationStatus;
+
+} // namespace mozilla
+
+#endif // mozilla_LauncherProcessWin_h
diff --git a/browser/app/winlauncher/NtLoaderAPI.cpp b/browser/app/winlauncher/NtLoaderAPI.cpp
new file mode 100644
index 0000000000..97f8a20186
--- /dev/null
+++ b/browser/app/winlauncher/NtLoaderAPI.cpp
@@ -0,0 +1,33 @@
+/* -*- 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/LoaderAPIInterfaces.h"
+
+#include "freestanding/CheckForCaller.h"
+#include "freestanding/LoaderPrivateAPI.h"
+
+namespace mozilla {
+
+extern "C" MOZ_EXPORT nt::LoaderAPI* GetNtLoaderAPI(
+ nt::LoaderObserver* aNewObserver) {
+ // Make sure the caller is inside mozglue.dll - we don't want to allow
+ // external access to this function, as it contains details about
+ // the SharedSection which is used to sandbox future child processes.
+ const bool isCallerMozglue =
+ CheckForAddress(RETURN_ADDRESS(), L"mozglue.dll");
+ MOZ_ASSERT(isCallerMozglue);
+ if (!isCallerMozglue) {
+ return nullptr;
+ }
+
+ freestanding::EnsureInitialized();
+ freestanding::LoaderPrivateAPI& api = freestanding::gLoaderPrivateAPI;
+ api.SetObserver(aNewObserver);
+
+ return &api;
+}
+
+} // namespace mozilla
diff --git a/browser/app/winlauncher/ProcThreadAttributes.h b/browser/app/winlauncher/ProcThreadAttributes.h
new file mode 100644
index 0000000000..74d5fee06c
--- /dev/null
+++ b/browser/app/winlauncher/ProcThreadAttributes.h
@@ -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/. */
+
+#ifndef mozilla_ProcThreadAttributes_h
+#define mozilla_ProcThreadAttributes_h
+
+#include <windows.h>
+
+#include <utility>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+namespace mozilla {
+
+class MOZ_RAII ProcThreadAttributes final {
+ struct ProcThreadAttributeListDeleter {
+ void operator()(LPPROC_THREAD_ATTRIBUTE_LIST aList) {
+ ::DeleteProcThreadAttributeList(aList);
+ delete[] reinterpret_cast<char*>(aList);
+ }
+ };
+
+ using ProcThreadAttributeListPtr =
+ UniquePtr<_PROC_THREAD_ATTRIBUTE_LIST, ProcThreadAttributeListDeleter>;
+
+ public:
+ ProcThreadAttributes() : mMitigationPolicies(0) {}
+
+ ~ProcThreadAttributes() = default;
+
+ ProcThreadAttributes(const ProcThreadAttributes&) = delete;
+ ProcThreadAttributes(ProcThreadAttributes&&) = delete;
+ ProcThreadAttributes& operator=(const ProcThreadAttributes&) = delete;
+ ProcThreadAttributes& operator=(ProcThreadAttributes&&) = delete;
+
+ void AddMitigationPolicy(DWORD64 aPolicy) { mMitigationPolicies |= aPolicy; }
+
+ bool AddInheritableHandle(HANDLE aHandle) {
+ DWORD type = ::GetFileType(aHandle);
+ if (type != FILE_TYPE_DISK && type != FILE_TYPE_PIPE) {
+ return false;
+ }
+
+ if (!::SetHandleInformation(aHandle, HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT)) {
+ return false;
+ }
+
+ return mInheritableHandles.append(aHandle);
+ }
+
+ template <size_t N>
+ bool AddInheritableHandles(HANDLE (&aHandles)[N]) {
+ bool ok = true;
+ for (auto handle : aHandles) {
+ ok &= AddInheritableHandle(handle);
+ }
+
+ return ok;
+ }
+
+ bool HasMitigationPolicies() const { return !!mMitigationPolicies; }
+
+ bool HasInheritableHandles() const { return !mInheritableHandles.empty(); }
+
+ /**
+ * @return false if the STARTUPINFOEXW::lpAttributeList was set to null
+ * as expected based on the state of |this|;
+ * true if the STARTUPINFOEXW::lpAttributeList was set to
+ * non-null;
+ */
+ LauncherResult<bool> AssignTo(STARTUPINFOEXW& aSiex) {
+ ZeroMemory(&aSiex, sizeof(STARTUPINFOEXW));
+
+ // We'll set the size to sizeof(STARTUPINFOW) until we determine whether the
+ // extended fields will be used.
+ aSiex.StartupInfo.cb = sizeof(STARTUPINFOW);
+
+ DWORD numAttributes = 0;
+ if (HasMitigationPolicies()) {
+ ++numAttributes;
+ }
+
+ if (HasInheritableHandles()) {
+ ++numAttributes;
+ }
+
+ if (!numAttributes) {
+ return false;
+ }
+
+ SIZE_T listSize = 0;
+ if (!::InitializeProcThreadAttributeList(nullptr, numAttributes, 0,
+ &listSize)) {
+ DWORD err = ::GetLastError();
+ if (err != ERROR_INSUFFICIENT_BUFFER) {
+ return LAUNCHER_ERROR_FROM_WIN32(err);
+ }
+ }
+
+ auto buf = MakeUnique<char[]>(listSize);
+
+ LPPROC_THREAD_ATTRIBUTE_LIST tmpList =
+ reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buf.get());
+
+ if (!::InitializeProcThreadAttributeList(tmpList, numAttributes, 0,
+ &listSize)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ // Transfer buf to a ProcThreadAttributeListPtr - now that the list is
+ // initialized, we are no longer dealing with a plain old char array. We
+ // must now deinitialize the attribute list before deallocating the
+ // underlying buffer.
+ ProcThreadAttributeListPtr attrList(
+ reinterpret_cast<LPPROC_THREAD_ATTRIBUTE_LIST>(buf.release()));
+
+ if (mMitigationPolicies) {
+ if (!::UpdateProcThreadAttribute(
+ attrList.get(), 0, PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
+ &mMitigationPolicies, sizeof(mMitigationPolicies), nullptr,
+ nullptr)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+ }
+
+ if (!mInheritableHandles.empty()) {
+ if (!::UpdateProcThreadAttribute(
+ attrList.get(), 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST,
+ mInheritableHandles.begin(),
+ mInheritableHandles.length() * sizeof(HANDLE), nullptr,
+ nullptr)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+ }
+
+ mAttrList = std::move(attrList);
+ aSiex.lpAttributeList = mAttrList.get();
+ aSiex.StartupInfo.cb = sizeof(STARTUPINFOEXW);
+ return true;
+ }
+
+ private:
+ static const uint32_t kNumInline = 3; // Inline storage for the std handles
+
+ DWORD64 mMitigationPolicies;
+ Vector<HANDLE, kNumInline> mInheritableHandles;
+ ProcThreadAttributeListPtr mAttrList;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ProcThreadAttributes_h
diff --git a/browser/app/winlauncher/SameBinary.h b/browser/app/winlauncher/SameBinary.h
new file mode 100644
index 0000000000..e8fa78600f
--- /dev/null
+++ b/browser/app/winlauncher/SameBinary.h
@@ -0,0 +1,146 @@
+/* -*- 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_SameBinary_h
+#define mozilla_SameBinary_h
+
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/NativeNt.h"
+#include "nsWindowsHelpers.h"
+
+namespace mozilla {
+
+class ProcessImagePath final {
+ PathType mType;
+ LauncherVoidResult mLastError;
+
+ // Using a larger buffer because an NT path may exceed MAX_PATH.
+ WCHAR mPathBuffer[(MAX_PATH * 2) + 1];
+
+ public:
+ // Initialize with an NT path string of a given process handle
+ explicit ProcessImagePath(const nsAutoHandle& aProcess)
+ : mType(PathType::eNtPath), mLastError(Ok()) {
+ DWORD len = mozilla::ArrayLength(mPathBuffer);
+ if (!::QueryFullProcessImageNameW(aProcess.get(), PROCESS_NAME_NATIVE,
+ mPathBuffer, &len)) {
+ mLastError = LAUNCHER_ERROR_FROM_LAST();
+ return;
+ }
+ }
+
+ // Initizlize with a DOS path string of a given imagebase address
+ explicit ProcessImagePath(HMODULE aImageBase)
+ : mType(PathType::eDosPath), mLastError(Ok()) {
+ DWORD len = ::GetModuleFileNameW(aImageBase, mPathBuffer,
+ mozilla::ArrayLength(mPathBuffer));
+ if (!len || len == mozilla::ArrayLength(mPathBuffer)) {
+ mLastError = LAUNCHER_ERROR_FROM_LAST();
+ return;
+ }
+ }
+
+ bool IsError() const { return mLastError.isErr(); }
+
+ const WindowsErrorType& GetError() const { return mLastError.inspectErr(); }
+
+ FileUniqueId GetId() const { return FileUniqueId(mPathBuffer, mType); }
+
+ bool CompareNtPaths(const ProcessImagePath& aOther) const {
+ if (mLastError.isErr() || aOther.mLastError.isErr() ||
+ mType != PathType::eNtPath || aOther.mType != PathType::eNtPath) {
+ return false;
+ }
+
+ UNICODE_STRING path1, path2;
+ ::RtlInitUnicodeString(&path1, mPathBuffer);
+ ::RtlInitUnicodeString(&path2, aOther.mPathBuffer);
+ return !!::RtlEqualUnicodeString(&path1, &path2, TRUE);
+ }
+};
+
+enum class ImageFileCompareOption {
+ Default,
+ CompareNtPathsOnly,
+};
+
+static inline mozilla::LauncherResult<bool> IsSameBinaryAsParentProcess(
+ ImageFileCompareOption aOption = ImageFileCompareOption::Default) {
+ mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
+ if (parentPid.isErr()) {
+ return parentPid.propagateErr();
+ }
+
+ nsAutoHandle parentProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
+ FALSE, parentPid.unwrap()));
+ if (!parentProcess.get()) {
+ DWORD err = ::GetLastError();
+ if (err == ERROR_INVALID_PARAMETER || err == ERROR_ACCESS_DENIED) {
+ // In the ERROR_INVALID_PARAMETER case, the process identified by
+ // parentPid has already exited. This is a common case when the parent
+ // process is not Firefox, thus we should return false instead of erroring
+ // out.
+ // The ERROR_ACCESS_DENIED case can happen when the parent process is
+ // something that we don't have permission to query. For example, we may
+ // encounter this when Firefox is launched by the Windows Task Scheduler.
+ return false;
+ }
+
+ return LAUNCHER_ERROR_FROM_WIN32(err);
+ }
+
+ ProcessImagePath parentExe(parentProcess);
+ if (parentExe.IsError()) {
+ return ::mozilla::Err(parentExe.GetError());
+ }
+
+ if (aOption == ImageFileCompareOption::Default) {
+ bool skipFileIdComparison = false;
+
+ FileUniqueId id1 = parentExe.GetId();
+ if (id1.IsError()) {
+ // We saw a number of Win7 users failed to call NtOpenFile with
+ // STATUS_OBJECT_PATH_NOT_FOUND for an unknown reason. In this
+ // particular case, we fall back to the logic to compare NT path
+ // strings instead of a file id which will not fail because we don't
+ // need to open a file handle.
+#if !defined(STATUS_OBJECT_PATH_NOT_FOUND)
+ constexpr NTSTATUS STATUS_OBJECT_PATH_NOT_FOUND = 0xc000003a;
+#endif
+ const LauncherError& err = id1.GetError();
+ if (err.mError !=
+ WindowsError::FromNtStatus(STATUS_OBJECT_PATH_NOT_FOUND)) {
+ return ::mozilla::Err(err);
+ }
+
+ skipFileIdComparison = true;
+ }
+
+ if (!skipFileIdComparison) {
+ ProcessImagePath ourExe(nullptr);
+ if (ourExe.IsError()) {
+ return ::mozilla::Err(ourExe.GetError());
+ }
+
+ FileUniqueId id2 = ourExe.GetId();
+ if (id2.IsError()) {
+ return ::mozilla::Err(id2.GetError());
+ }
+ return id1 == id2;
+ }
+ }
+
+ nsAutoHandle ourProcess(::GetCurrentProcess());
+ ProcessImagePath ourExeNt(ourProcess);
+ if (ourExeNt.IsError()) {
+ return ::mozilla::Err(ourExeNt.GetError());
+ }
+ return parentExe.CompareNtPaths(ourExeNt);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_SameBinary_h
diff --git a/browser/app/winlauncher/freestanding/CheckForCaller.h b/browser/app/winlauncher/freestanding/CheckForCaller.h
new file mode 100644
index 0000000000..799d16cd1f
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/CheckForCaller.h
@@ -0,0 +1,36 @@
+/* -*- 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_freestanding_CheckForCaller_h
+#define mozilla_freestanding_CheckForCaller_h
+
+namespace mozilla {
+
+#if defined(_MSC_VER)
+# include <intrin.h>
+# pragma intrinsic(_ReturnAddress)
+# define RETURN_ADDRESS() _ReturnAddress()
+#elif defined(__GNUC__) || defined(__clang__)
+# define RETURN_ADDRESS() \
+ __builtin_extract_return_addr(__builtin_return_address(0))
+#endif
+
+template <int N>
+bool CheckForAddress(void* aReturnAddress, const wchar_t (&aName)[N]) {
+ HMODULE callingModule;
+ if (!::GetModuleHandleExW(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCWSTR>(aReturnAddress),
+ &callingModule)) {
+ return false;
+ }
+
+ return callingModule && callingModule == ::GetModuleHandleW(aName);
+}
+
+} // namespace mozilla
+
+#endif // mozilla_freestanding_CheckForCaller_h
diff --git a/browser/app/winlauncher/freestanding/DllBlocklist.cpp b/browser/app/winlauncher/freestanding/DllBlocklist.cpp
new file mode 100644
index 0000000000..105ab6d918
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/DllBlocklist.cpp
@@ -0,0 +1,641 @@
+/* -*- 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/ArrayUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsDllBlocklist.h"
+
+#include "CrashAnnotations.h"
+#include "DllBlocklist.h"
+#include "LoaderPrivateAPI.h"
+#include "ModuleLoadFrame.h"
+#include "SharedSection.h"
+
+using mozilla::DllBlockInfoFlags;
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) \
+ {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING
+
+#if defined(MOZ_LAUNCHER_PROCESS) || defined(NIGHTLY_BUILD)
+# include "mozilla/WindowsDllBlocklistLauncherDefs.h"
+#else
+# include "mozilla/WindowsDllBlocklistCommon.h"
+DLL_BLOCKLIST_DEFINITIONS_BEGIN
+DLL_BLOCKLIST_DEFINITIONS_END
+#endif
+
+using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;
+
+class MOZ_STATIC_CLASS MOZ_TRIVIAL_CTOR_DTOR NativeNtBlockSet final {
+ struct NativeNtBlockSetEntry {
+ NativeNtBlockSetEntry() = default;
+ ~NativeNtBlockSetEntry() = default;
+ NativeNtBlockSetEntry(const UNICODE_STRING& aName, uint64_t aVersion,
+ NativeNtBlockSetEntry* aNext)
+ : mName(aName), mVersion(aVersion), mNext(aNext) {}
+ UNICODE_STRING mName;
+ uint64_t mVersion;
+ NativeNtBlockSetEntry* mNext;
+ };
+
+ public:
+ // Constructor and destructor MUST be trivial
+ constexpr NativeNtBlockSet() : mFirstEntry(nullptr) {}
+ ~NativeNtBlockSet() = default;
+
+ void Add(const UNICODE_STRING& aName, uint64_t aVersion);
+ void Write(WritableBuffer& buffer);
+
+ private:
+ static NativeNtBlockSetEntry* NewEntry(const UNICODE_STRING& aName,
+ uint64_t aVersion,
+ NativeNtBlockSetEntry* aNextEntry);
+
+ private:
+ NativeNtBlockSetEntry* mFirstEntry;
+ mozilla::nt::SRWLock mLock;
+};
+
+NativeNtBlockSet::NativeNtBlockSetEntry* NativeNtBlockSet::NewEntry(
+ const UNICODE_STRING& aName, uint64_t aVersion,
+ NativeNtBlockSet::NativeNtBlockSetEntry* aNextEntry) {
+ return mozilla::freestanding::RtlNew<NativeNtBlockSetEntry>(aName, aVersion,
+ aNextEntry);
+}
+
+void NativeNtBlockSet::Add(const UNICODE_STRING& aName, uint64_t aVersion) {
+ mozilla::nt::AutoExclusiveLock lock(mLock);
+
+ for (NativeNtBlockSetEntry* entry = mFirstEntry; entry;
+ entry = entry->mNext) {
+ if (::RtlEqualUnicodeString(&entry->mName, &aName, TRUE) &&
+ aVersion == entry->mVersion) {
+ return;
+ }
+ }
+
+ // Not present, add it
+ NativeNtBlockSetEntry* newEntry = NewEntry(aName, aVersion, mFirstEntry);
+ if (newEntry) {
+ mFirstEntry = newEntry;
+ }
+}
+
+void NativeNtBlockSet::Write(WritableBuffer& aBuffer) {
+ // NB: If this function is called, it is long after kernel32 is initialized,
+ // so it is safe to use Win32 calls here.
+ char buf[MAX_PATH];
+
+ // It would be nicer to use RAII here. However, its destructor
+ // might not run if an exception occurs, in which case we would never release
+ // the lock (MSVC warns about this possibility). So we acquire and release
+ // manually.
+ ::AcquireSRWLockExclusive(&mLock);
+
+ MOZ_SEH_TRY {
+ for (auto entry = mFirstEntry; entry; entry = entry->mNext) {
+ int convOk = ::WideCharToMultiByte(CP_UTF8, 0, entry->mName.Buffer,
+ entry->mName.Length / sizeof(wchar_t),
+ buf, sizeof(buf), nullptr, nullptr);
+ if (!convOk) {
+ continue;
+ }
+
+ // write name[,v.v.v.v];
+ aBuffer.Write(buf, convOk);
+
+ if (entry->mVersion != DllBlockInfo::ALL_VERSIONS) {
+ aBuffer.Write(",", 1);
+ uint16_t parts[4];
+ parts[0] = entry->mVersion >> 48;
+ parts[1] = (entry->mVersion >> 32) & 0xFFFF;
+ parts[2] = (entry->mVersion >> 16) & 0xFFFF;
+ parts[3] = entry->mVersion & 0xFFFF;
+ for (size_t p = 0; p < mozilla::ArrayLength(parts); ++p) {
+ _ltoa_s(parts[p], buf, sizeof(buf), 10);
+ aBuffer.Write(buf, strlen(buf));
+ if (p != mozilla::ArrayLength(parts) - 1) {
+ aBuffer.Write(".", 1);
+ }
+ }
+ }
+ aBuffer.Write(";", 1);
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {}
+
+ ::ReleaseSRWLockExclusive(&mLock);
+}
+
+static NativeNtBlockSet gBlockSet;
+
+extern "C" void MOZ_EXPORT
+NativeNtBlockSet_Write(CrashReporter::AnnotationWriter& aWriter) {
+ WritableBuffer buffer;
+ gBlockSet.Write(buffer);
+ aWriter.Write(CrashReporter::Annotation::BlockedDllList, buffer.Data(),
+ buffer.Length());
+}
+
+enum class BlockAction {
+ // Allow the DLL to be loaded.
+ Allow,
+ // Substitute in a different DLL to be loaded instead of this one?
+ // This is intended to be used for Layered Service Providers, which
+ // cannot be blocked in the normal way. Note that this doesn't seem
+ // to be actually implemented right now, and no entries in the blocklist
+ // use it.
+ SubstituteLSP,
+ // There was an error in determining whether we should block this DLL.
+ // It will be blocked.
+ Error,
+ // Block the DLL from loading.
+ Deny,
+ // Effectively block the DLL from loading by redirecting its DllMain
+ // to a stub version. This is needed for DLLs that add themselves to
+ // the executable's Import Table, since failing to load would mean the
+ // executable would fail to launch.
+ NoOpEntryPoint,
+};
+
+static BlockAction CheckBlockInfo(const DllBlockInfo* aInfo,
+ const mozilla::nt::PEHeaders& aHeaders,
+ uint64_t& aVersion) {
+ aVersion = DllBlockInfo::ALL_VERSIONS;
+
+ if (aInfo->mFlags & (DllBlockInfoFlags::BLOCK_WIN8_AND_OLDER |
+ DllBlockInfoFlags::BLOCK_WIN7_AND_OLDER)) {
+ RTL_OSVERSIONINFOW osv = {sizeof(osv)};
+ NTSTATUS ntStatus = ::RtlGetVersion(&osv);
+ if (!NT_SUCCESS(ntStatus)) {
+ return BlockAction::Error;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::BLOCK_WIN8_AND_OLDER) &&
+ (osv.dwMajorVersion > 6 ||
+ (osv.dwMajorVersion == 6 && osv.dwMinorVersion > 2))) {
+ return BlockAction::Allow;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::BLOCK_WIN7_AND_OLDER) &&
+ (osv.dwMajorVersion > 6 ||
+ (osv.dwMajorVersion == 6 && osv.dwMinorVersion > 1))) {
+ return BlockAction::Allow;
+ }
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::CHILD_PROCESSES_ONLY) &&
+ !(gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
+ return BlockAction::Allow;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::UTILITY_PROCESSES_ONLY) &&
+ !(gBlocklistInitFlags & eDllBlocklistInitFlagIsUtilityProcess)) {
+ return BlockAction::Allow;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::SOCKET_PROCESSES_ONLY) &&
+ !(gBlocklistInitFlags & eDllBlocklistInitFlagIsSocketProcess)) {
+ return BlockAction::Allow;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::GPU_PROCESSES_ONLY) &&
+ !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGPUProcess)) {
+ return BlockAction::Allow;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::BROWSER_PROCESS_ONLY) &&
+ (gBlocklistInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
+ return BlockAction::Allow;
+ }
+
+ if ((aInfo->mFlags & DllBlockInfoFlags::GMPLUGIN_PROCESSES_ONLY) &&
+ !(gBlocklistInitFlags & eDllBlocklistInitFlagIsGMPluginProcess)) {
+ return BlockAction::Allow;
+ }
+
+ if (aInfo->mMaxVersion == DllBlockInfo::ALL_VERSIONS) {
+ return BlockAction::Deny;
+ }
+
+ if (!aHeaders) {
+ return BlockAction::Error;
+ }
+
+ if (aInfo->mFlags & DllBlockInfoFlags::USE_TIMESTAMP) {
+ DWORD timestamp;
+ if (!aHeaders.GetTimeStamp(timestamp)) {
+ return BlockAction::Error;
+ }
+
+ if (timestamp > aInfo->mMaxVersion) {
+ return BlockAction::Allow;
+ }
+
+ return BlockAction::Deny;
+ }
+
+ // Else we try to get the file version information. Note that we don't have
+ // access to GetFileVersionInfo* APIs.
+ if (!aHeaders.GetVersionInfo(aVersion)) {
+ return BlockAction::Error;
+ }
+
+ if (aInfo->IsVersionBlocked(aVersion)) {
+ return BlockAction::Deny;
+ }
+
+ return BlockAction::Allow;
+}
+
+static BOOL WINAPI NoOp_DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; }
+
+// This helper function checks whether a given module is included
+// in the executable's Import Table. Because an injected module's
+// DllMain may revert the Import Table to the original state, we parse
+// the Import Table every time a module is loaded without creating a cache.
+static bool IsInjectedDependentModule(
+ const UNICODE_STRING& aModuleLeafName,
+ mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) {
+ mozilla::nt::PEHeaders exeHeaders(aK32Exports.mGetModuleHandleW(nullptr));
+ if (!exeHeaders || !exeHeaders.IsImportDirectoryTampered()) {
+ // If no tampering is detected, no need to enumerate the Import Table.
+ return false;
+ }
+
+ bool isDependent = false;
+ exeHeaders.EnumImportChunks(
+ [&isDependent, &aModuleLeafName, &exeHeaders](const char* aDepModule) {
+ // If |aDepModule| is within the PE image, it's not an injected module
+ // but a legitimate dependent module.
+ if (isDependent || exeHeaders.IsWithinImage(aDepModule)) {
+ return;
+ }
+
+ UNICODE_STRING depModuleLeafName;
+ mozilla::nt::AllocatedUnicodeString depModuleName(aDepModule);
+ mozilla::nt::GetLeafName(&depModuleLeafName, depModuleName);
+ isDependent = (::RtlCompareUnicodeString(
+ &aModuleLeafName, &depModuleLeafName, TRUE) == 0);
+ });
+ return isDependent;
+}
+
+// Allowing a module to be loaded but detour the entrypoint to NoOp_DllMain
+// so that the module has no chance to interact with our code. We need this
+// technique to safely block a module injected by IAT tampering because
+// blocking such a module makes a process fail to launch.
+static bool RedirectToNoOpEntryPoint(
+ const mozilla::nt::PEHeaders& aModule,
+ mozilla::freestanding::Kernel32ExportsSolver& aK32Exports) {
+ mozilla::interceptor::WindowsDllEntryPointInterceptor interceptor(
+ aK32Exports);
+ if (!interceptor.Set(aModule, NoOp_DllMain)) {
+ return false;
+ }
+
+ return true;
+}
+
+static BlockAction DetermineBlockAction(
+ const UNICODE_STRING& aLeafName, void* aBaseAddress,
+ mozilla::freestanding::Kernel32ExportsSolver* aK32Exports) {
+ if (mozilla::nt::Contains12DigitHexString(aLeafName) ||
+ mozilla::nt::IsFileNameAtLeast16HexDigits(aLeafName)) {
+ return BlockAction::Deny;
+ }
+
+ mozilla::nt::PEHeaders headers(aBaseAddress);
+ DWORD checksum = 0;
+ DWORD timestamp = 0;
+ DWORD imageSize = 0;
+ uint64_t version = 0;
+
+ // Block some malicious DLLs known for crashing our process (bug 1841751),
+ // based on matching the combination of version number + timestamp + image
+ // size. We further reduce the chances of collision with legit DLLs by
+ // checking for a checksum of 0 and the absence of debug information, both of
+ // which are unusual for production-ready DLLs.
+ if (headers.GetCheckSum(checksum) && checksum == 0 && !headers.GetPdbInfo() &&
+ headers.GetTimeStamp(timestamp)) {
+ struct KnownMaliciousCombination {
+ uint64_t mVersion;
+ uint32_t mTimestamp;
+ uint32_t mImageSize;
+ };
+ const KnownMaliciousCombination instances[]{
+ // 1.0.0.26638
+ {0x000100000000680e, 0x570B8A90, 0x62000},
+ // 1.0.0.26793
+ {0x00010000000068a9, 0x572B4CE4, 0x62000},
+ // 1.0.0.27567
+ {0x0001000000006baf, 0x57A725AC, 0x61000},
+ // 1.0.0.29915
+ {0x00010000000074db, 0x5A115D81, 0x5D000},
+ // 1.0.0.31122
+ {0x0001000000007992, 0x5CFF88B8, 0x5D000}};
+
+ // We iterate over timestamps, because they are unique and it is a quick
+ // field to fetch
+ for (const auto& instance : instances) {
+ if (instance.mTimestamp == timestamp) {
+ // Only fetch other fields in case we have a match. Then, we can exit
+ // the loop.
+ if (headers.GetImageSize(imageSize) &&
+ instance.mImageSize == imageSize &&
+ headers.GetVersionInfo(version) && instance.mVersion == version) {
+ return BlockAction::Deny;
+ }
+ break;
+ }
+ }
+ }
+
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
+ DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(infoNumEntries);
+
+ mozilla::freestanding::DllBlockInfoComparator comp(aLeafName);
+
+ size_t match = LowerBound(info, 0, infoNumEntries, comp);
+ bool builtinListHasLowerBound = match != infoNumEntries;
+ const DllBlockInfo* entry = nullptr;
+ BlockAction checkResult = BlockAction::Allow;
+ if (builtinListHasLowerBound) {
+ // There may be multiple entries on the list. Since LowerBound() returns
+ // the first entry that matches (if there are any matches),
+ // search forward from there.
+ while (match < infoNumEntries && (comp(info[match]) == 0)) {
+ entry = &info[match];
+ checkResult = CheckBlockInfo(entry, headers, version);
+ if (checkResult != BlockAction::Allow) {
+ break;
+ }
+ ++match;
+ }
+ }
+ mozilla::DebugOnly<bool> blockedByDynamicBlocklist = false;
+ // Make sure we handle a case that older versions are blocked by the static
+ // list, but the dynamic list blocks all versions.
+ if (checkResult == BlockAction::Allow) {
+ if (!mozilla::freestanding::gSharedSection.IsDisabled()) {
+ entry = mozilla::freestanding::gSharedSection.SearchBlocklist(aLeafName);
+ if (entry) {
+ checkResult = CheckBlockInfo(entry, headers, version);
+ blockedByDynamicBlocklist = checkResult != BlockAction::Allow;
+ }
+ }
+ }
+ if (checkResult == BlockAction::Allow) {
+ return BlockAction::Allow;
+ }
+
+ gBlockSet.Add(entry->mName, version);
+
+ if ((entry->mFlags & DllBlockInfoFlags::REDIRECT_TO_NOOP_ENTRYPOINT) &&
+ aK32Exports && RedirectToNoOpEntryPoint(headers, *aK32Exports)) {
+ MOZ_ASSERT(!blockedByDynamicBlocklist, "dynamic blocklist has redirect?");
+ return BlockAction::NoOpEntryPoint;
+ }
+
+ return checkResult;
+}
+
+namespace mozilla {
+namespace freestanding {
+
+CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll;
+
+NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
+ PUNICODE_STRING aDllName,
+ PHANDLE aOutHandle) {
+ ModuleLoadFrame frame(aDllName);
+
+ NTSTATUS ntStatus = stub_LdrLoadDll(aDllPath, aFlags, aDllName, aOutHandle);
+
+ return frame.SetLoadStatus(ntStatus, aOutHandle);
+}
+
+CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
+ stub_NtMapViewOfSection;
+
+// All the code for patched_NtMapViewOfSection that relies on checked stack
+// buffers (e.g. mbi, sectionFileName) should be put in this helper function
+// (see bug 1733532).
+MOZ_NEVER_INLINE NTSTATUS AfterMapViewOfExecutableSection(
+ HANDLE aProcess, PVOID* aBaseAddress, NTSTATUS aStubStatus) {
+ // We don't care about mappings that aren't MEM_IMAGE.
+ MEMORY_BASIC_INFORMATION mbi;
+ NTSTATUS ntStatus =
+ ::NtQueryVirtualMemory(aProcess, *aBaseAddress, MemoryBasicInformation,
+ &mbi, sizeof(mbi), nullptr);
+ if (!NT_SUCCESS(ntStatus)) {
+ ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
+ return STATUS_ACCESS_DENIED;
+ }
+ if (!(mbi.Type & MEM_IMAGE)) {
+ return aStubStatus;
+ }
+
+ // Get the section name
+ nt::MemorySectionNameBuf sectionFileName(
+ gLoaderPrivateAPI.GetSectionNameBuffer(*aBaseAddress));
+ if (sectionFileName.IsEmpty()) {
+ ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
+ return STATUS_ACCESS_DENIED;
+ }
+
+ // Find the leaf name
+ UNICODE_STRING leafOnStack;
+ nt::GetLeafName(&leafOnStack, sectionFileName);
+
+ bool isInjectedDependent = false;
+ const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll");
+ Kernel32ExportsSolver* k32Exports = nullptr;
+ BlockAction blockAction;
+ // Trying to get the Kernel32Exports while loading kernel32.dll causes Firefox
+ // to crash. (but only during a profile-guided optimization run, oddly) We
+ // know we're never going to block kernel32.dll, so skip all this
+ if (::RtlCompareUnicodeString(&k32Name, &leafOnStack, TRUE) == 0) {
+ blockAction = BlockAction::Allow;
+ } else {
+ auto noSharedSectionReset{SharedSection::AutoNoReset()};
+ k32Exports = gSharedSection.GetKernel32Exports();
+ // Small optimization: Since loading a dependent module does not involve
+ // LdrLoadDll, we know isInjectedDependent is false if we hold a top frame.
+ if (k32Exports && !ModuleLoadFrame::ExistsTopFrame()) {
+ // Note that if a module is dependent but not injected, this means that
+ // the executable built against it, and it should be signed by Mozilla
+ // or Microsoft, so we don't need to worry about adding it to the list
+ // for CIG. (and users won't be able to block it) So the only special
+ // case here is a dependent module that has been injected.
+ isInjectedDependent = IsInjectedDependentModule(leafOnStack, *k32Exports);
+ }
+
+ if (isInjectedDependent) {
+ // Add an NT dv\path to the shared section so that a sandbox process can
+ // use it to bypass CIG. In a sandbox process, this addition fails
+ // because we cannot map the section to a writable region, but it's
+ // ignorable because the paths have been added by the browser process.
+ Unused << SharedSection::AddDependentModule(sectionFileName);
+
+ bool attemptToBlockViaRedirect;
+#if defined(NIGHTLY_BUILD)
+ // We enable automatic DLL blocking only in Nightly for now
+ // because it caused a compat issue (bug 1682304 and 1704373).
+ attemptToBlockViaRedirect = true;
+ // We will set blockAction below in the if (attemptToBlockViaRedirect)
+ // block, but I guess the compiler isn't smart enough to figure
+ // that out and complains about an uninitialized variable :-(
+ blockAction = BlockAction::NoOpEntryPoint;
+#else
+ // Check blocklist
+ blockAction =
+ DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports);
+ // If we were going to block this dependent module, try redirection
+ // instead of blocking it, since blocking it would cause the .exe not to
+ // launch.
+ // Note tht Deny and Error both end up blocking the module in a
+ // straightforward way, so those are the cases in which we need
+ // to redirect instead.
+ attemptToBlockViaRedirect =
+ blockAction == BlockAction::Deny || blockAction == BlockAction::Error;
+#endif
+ if (attemptToBlockViaRedirect) {
+ // For a dependent module, try redirection instead of blocking it.
+ // If we fail, we reluctantly allow the module for free.
+ mozilla::nt::PEHeaders headers(*aBaseAddress);
+ blockAction = RedirectToNoOpEntryPoint(headers, *k32Exports)
+ ? BlockAction::NoOpEntryPoint
+ : BlockAction::Allow;
+ }
+ } else {
+ // Check blocklist
+ blockAction =
+ DetermineBlockAction(leafOnStack, *aBaseAddress, k32Exports);
+ }
+ }
+
+ ModuleLoadInfo::Status loadStatus = ModuleLoadInfo::Status::Blocked;
+
+ switch (blockAction) {
+ case BlockAction::Allow:
+ loadStatus = ModuleLoadInfo::Status::Loaded;
+ break;
+
+ case BlockAction::NoOpEntryPoint:
+ loadStatus = ModuleLoadInfo::Status::Redirected;
+ break;
+
+ case BlockAction::SubstituteLSP:
+ // The process heap needs to be available here because
+ // NotifyLSPSubstitutionRequired below copies a given string into
+ // the heap. We use a soft assert here, assuming LSP load always
+ // occurs after the heap is initialized.
+ MOZ_ASSERT(nt::RtlGetProcessHeap());
+
+ // Notify patched_LdrLoadDll that it will be necessary to perform
+ // a substitution before returning.
+ ModuleLoadFrame::NotifyLSPSubstitutionRequired(&leafOnStack);
+ break;
+
+ default:
+ break;
+ }
+
+ if (nt::RtlGetProcessHeap()) {
+ ModuleLoadFrame::NotifySectionMap(
+ nt::AllocatedUnicodeString(sectionFileName), *aBaseAddress, aStubStatus,
+ loadStatus, isInjectedDependent);
+ }
+
+ if (loadStatus == ModuleLoadInfo::Status::Loaded ||
+ loadStatus == ModuleLoadInfo::Status::Redirected) {
+ return aStubStatus;
+ }
+
+ ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
+ return STATUS_ACCESS_DENIED;
+}
+
+// To preserve compatibility with third-parties, calling into this function
+// must not use checked stack buffers when reached through Thread32Next (see
+// bug 1733532). Hence this function is declared as MOZ_NO_STACK_PROTECTOR.
+// Ideally, all code relying on stack buffers should be put in the dedicated
+// helper function AfterMapViewOfExecutableImageSection, which does not have
+// the MOZ_NO_STACK_PROTECTOR attribute. The obi variable below is an
+// exception to this rule, as it is required to collect the information that
+// lets us decide whether we really need to go through the helper function.
+NTSTATUS NTAPI patched_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) {
+ // Save off the values currently in the out-pointers for later restoration if
+ // we decide not to permit this mapping.
+ auto const rollback =
+ [
+ // Avoid taking a reference to the stack frame, mostly out of
+ // paranoia. (The values of `aBaseAddress` et al. may have been
+ // crafted to point to our return address anyway...)
+ =,
+ // `NtMapViewOfSection` itself is mildly robust to invalid pointers;
+ // we can't easily do that, but we can at least check for `nullptr`.
+ baseAddress = aBaseAddress ? *aBaseAddress : nullptr,
+ sectionOffset = aSectionOffset ? *aSectionOffset : LARGE_INTEGER{},
+ viewSize = aViewSize ? *aViewSize : 0]() {
+ if (aBaseAddress) *aBaseAddress = baseAddress;
+ if (aSectionOffset) *aSectionOffset = sectionOffset;
+ if (aViewSize) *aViewSize = viewSize;
+ };
+
+ // We always map first, then we check for additional info after.
+ NTSTATUS stubStatus = stub_NtMapViewOfSection(
+ aSection, aProcess, aBaseAddress, aZeroBits, aCommitSize, aSectionOffset,
+ aViewSize, aInheritDisposition, aAllocationType, aProtectionFlags);
+ if (!NT_SUCCESS(stubStatus)) {
+ return stubStatus;
+ }
+
+ if (aProcess != nt::kCurrentProcess) {
+ // We're only interested in mapping for the current process.
+ return stubStatus;
+ }
+
+ PUBLIC_OBJECT_BASIC_INFORMATION obi;
+ NTSTATUS ntStatus = ::NtQueryObject(aSection, ObjectBasicInformation, &obi,
+ sizeof(obi), nullptr);
+ if (!NT_SUCCESS(ntStatus)) {
+ ::NtUnmapViewOfSection(aProcess, *aBaseAddress);
+ rollback();
+ return STATUS_ACCESS_DENIED;
+ }
+
+ // We don't care about sections for which the permission to map executable
+ // views was not asked at creation time. This early exit path is notably
+ // taken for:
+ // - calls to LoadLibraryExW using LOAD_LIBRARY_AS_DATAFILE or
+ // LOAD_LIBRARY_AS_IMAGE_RESOURCE (bug 1842088), thus allowing us to load
+ // blocked DLLs for analysis without executing them;
+ // - calls to Thread32Next (bug 1733532), thus avoiding the helper function
+ // with stack cookie checks.
+ if (!(obi.GrantedAccess & SECTION_MAP_EXECUTE)) {
+ return stubStatus;
+ }
+
+ NTSTATUS rv =
+ AfterMapViewOfExecutableSection(aProcess, aBaseAddress, stubStatus);
+ if (FAILED(rv)) {
+ rollback();
+ }
+ return rv;
+}
+
+} // namespace freestanding
+} // namespace mozilla
diff --git a/browser/app/winlauncher/freestanding/DllBlocklist.h b/browser/app/winlauncher/freestanding/DllBlocklist.h
new file mode 100644
index 0000000000..c0ae1d255f
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/DllBlocklist.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_freestanding_DllBlocklist_h
+#define mozilla_freestanding_DllBlocklist_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/NativeNt.h"
+#include "nsWindowsDllInterceptor.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+
+namespace mozilla {
+namespace freestanding {
+
+NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR aDllPath, PULONG aFlags,
+ PUNICODE_STRING aDllName, PHANDLE aOutHandle);
+
+MOZ_NO_STACK_PROTECTOR NTSTATUS NTAPI patched_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);
+
+using LdrLoadDllPtr = decltype(&::LdrLoadDll);
+
+extern CrossProcessDllInterceptor::FuncHookType<LdrLoadDllPtr> stub_LdrLoadDll;
+
+using NtMapViewOfSectionPtr = decltype(&::NtMapViewOfSection);
+
+extern CrossProcessDllInterceptor::FuncHookType<NtMapViewOfSectionPtr>
+ stub_NtMapViewOfSection;
+
+} // namespace freestanding
+} // namespace mozilla
+
+#endif // mozilla_freestanding_DllBlocklist_h
diff --git a/browser/app/winlauncher/freestanding/Freestanding.h b/browser/app/winlauncher/freestanding/Freestanding.h
new file mode 100644
index 0000000000..03999f231e
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/Freestanding.h
@@ -0,0 +1,67 @@
+/* -*- 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_freestanding_Freestanding_h
+#define mozilla_freestanding_Freestanding_h
+
+/**
+ * This header is automatically included in all source code residing in the
+ * /browser/app/winlauncher/freestanding directory.
+ */
+
+#if defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 1
+# error "This header should only be included by freestanding code"
+#endif // defined(__STDC_HOSTED__) && __STDC_HOSTED__ == 1
+
+#define MOZ_USE_LAUNCHER_ERROR
+#include "mozilla/NativeNt.h"
+
+namespace mozilla {
+namespace freestanding {
+
+/**
+ * Since this library is the only part of firefox.exe that needs special
+ * treatment with respect to the heap, we implement |RtlNew| and |RtlDelete|
+ * to be used instead of |new| and |delete| for any heap allocations inside
+ * the freestanding library.
+ */
+template <typename T, typename... Args>
+inline static T* RtlNew(Args&&... aArgs) {
+ HANDLE processHeap = nt::RtlGetProcessHeap();
+ if (!processHeap) {
+ // Handle the case where the process heap is not initialized because
+ // passing nullptr to RtlAllocateHeap crashes the process.
+ return nullptr;
+ }
+
+ void* ptr = ::RtlAllocateHeap(processHeap, 0, sizeof(T));
+ if (!ptr) {
+ return nullptr;
+ }
+
+ return new (ptr) T(std::forward<Args>(aArgs)...);
+}
+
+template <typename T>
+inline static void RtlDelete(T* aPtr) {
+ if (!aPtr) {
+ return;
+ }
+
+ aPtr->~T();
+ ::RtlFreeHeap(nt::RtlGetProcessHeap(), 0, aPtr);
+}
+
+} // namespace freestanding
+} // namespace mozilla
+
+// Initialization code for all statically-allocated data in freestanding is
+// placed into a separate section. This allows us to initialize any
+// freestanding statics without needing to initialize everything else in this
+// binary.
+#pragma init_seg(".freestd$g")
+
+#endif // mozilla_freestanding_Freestanding_h
diff --git a/browser/app/winlauncher/freestanding/LoaderPrivateAPI.cpp b/browser/app/winlauncher/freestanding/LoaderPrivateAPI.cpp
new file mode 100644
index 0000000000..266f2ec6a4
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/LoaderPrivateAPI.cpp
@@ -0,0 +1,293 @@
+/* -*- 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 "LoaderPrivateAPI.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "../DllBlocklistInit.h"
+#include "../ErrorHandler.h"
+#include "SharedSection.h"
+
+using GlobalInitializerFn = void(__cdecl*)(void);
+
+// Allocation of static initialization section for the freestanding library
+#pragma section(".freestd$a", read)
+__declspec(allocate(".freestd$a")) static const GlobalInitializerFn
+ FreeStdStart = reinterpret_cast<GlobalInitializerFn>(0);
+
+#pragma section(".freestd$z", read)
+__declspec(allocate(".freestd$z")) static const GlobalInitializerFn FreeStdEnd =
+ reinterpret_cast<GlobalInitializerFn>(0);
+
+namespace mozilla {
+namespace freestanding {
+
+static RTL_RUN_ONCE gRunOnce = RTL_RUN_ONCE_INIT;
+
+// The contract for this callback is identical to the InitOnceCallback from
+// Win32 land; we're just using ntdll-layer types instead.
+static ULONG NTAPI DoOneTimeInit(PRTL_RUN_ONCE aRunOnce, PVOID aParameter,
+ PVOID* aContext) {
+ // Invoke every static initializer in the .freestd section
+ const GlobalInitializerFn* cur = &FreeStdStart + 1;
+ while (cur < &FreeStdEnd) {
+ if (*cur) {
+ (*cur)();
+ }
+
+ ++cur;
+ }
+
+ return TRUE;
+}
+
+/**
+ * This observer is only used until the mozglue observer connects itself.
+ * All we do here is accumulate the module loads into a vector.
+ * As soon as mozglue connects, we call |Forward| on mozglue's LoaderObserver
+ * to pass our vector on for further processing. This object then becomes
+ * defunct.
+ */
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS DefaultLoaderObserver final
+ : public nt::LoaderObserver {
+ public:
+ constexpr DefaultLoaderObserver() : mModuleLoads(nullptr) {}
+
+ void OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) final {}
+ bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) final {
+ return false;
+ }
+ void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) final;
+ void Forward(nt::LoaderObserver* aNext) final;
+ void OnForward(ModuleLoadInfoVec&& aInfo) final {
+ MOZ_ASSERT_UNREACHABLE("Not valid in freestanding::DefaultLoaderObserver");
+ }
+
+ private:
+ mozilla::nt::SRWLock mLock;
+ ModuleLoadInfoVec* mModuleLoads;
+};
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS LoaderPrivateAPIImp final
+ : public LoaderPrivateAPI {
+ public:
+ // LoaderAPI
+ 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;
+
+ // LoaderPrivateAPI
+ void NotifyBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) final;
+ void NotifyBeginDllLoad(ModuleLoadInfo& aModuleLoadInfo, void** aContext,
+ PCUNICODE_STRING aRequestedDllName) final;
+ void SetObserver(nt::LoaderObserver* aNewObserver) final;
+ bool IsDefaultObserver() const final;
+ nt::MemorySectionNameBuf GetSectionNameBuffer(void* aSectionAddr) final;
+};
+
+static void Init() {
+ DebugOnly<NTSTATUS> ntStatus =
+ ::RtlRunOnceExecuteOnce(&gRunOnce, &DoOneTimeInit, nullptr, nullptr);
+ MOZ_ASSERT(NT_SUCCESS(ntStatus));
+}
+
+} // namespace freestanding
+} // namespace mozilla
+
+static mozilla::freestanding::DefaultLoaderObserver gDefaultObserver;
+static mozilla::freestanding::LoaderPrivateAPIImp gPrivateAPI;
+
+static mozilla::nt::SRWLock gLoaderObserverLock;
+static mozilla::nt::LoaderObserver* gLoaderObserver = &gDefaultObserver;
+
+namespace mozilla {
+namespace freestanding {
+
+LoaderPrivateAPI& gLoaderPrivateAPI = gPrivateAPI;
+
+void DefaultLoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) {
+ // If the DLL load failed, or if the DLL was loaded by a previous request
+ // and thus was not mapped by this request, we do not save the ModuleLoadInfo.
+ if (!NT_SUCCESS(aNtStatus) || !aModuleLoadInfo.WasMapped()) {
+ return;
+ }
+
+ nt::AutoExclusiveLock lock(mLock);
+ if (!mModuleLoads) {
+ mModuleLoads = RtlNew<ModuleLoadInfoVec>();
+ if (!mModuleLoads) {
+ return;
+ }
+ }
+
+ Unused << mModuleLoads->emplaceBack(
+ std::forward<ModuleLoadInfo>(aModuleLoadInfo));
+}
+
+/**
+ * Pass mModuleLoads's data off to |aNext| for further processing.
+ */
+void DefaultLoaderObserver::Forward(nt::LoaderObserver* aNext) {
+ MOZ_ASSERT(aNext);
+ if (!aNext) {
+ return;
+ }
+
+ ModuleLoadInfoVec* moduleLoads = nullptr;
+
+ { // Scope for lock
+ nt::AutoExclusiveLock lock(mLock);
+ moduleLoads = mModuleLoads;
+ mModuleLoads = nullptr;
+ }
+
+ if (!moduleLoads) {
+ return;
+ }
+
+ aNext->OnForward(std::move(*moduleLoads));
+ RtlDelete(moduleLoads);
+}
+
+ModuleLoadInfo LoaderPrivateAPIImp::ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) {
+ ModuleLoadInfo loadInfo(aRequestedDllName);
+
+ NotifyBeginDllLoad(loadInfo, aContext, aRequestedDllName);
+
+ return loadInfo;
+}
+
+bool LoaderPrivateAPIImp::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) {
+ nt::AutoSharedLock lock(gLoaderObserverLock);
+ return gLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle);
+}
+
+void LoaderPrivateAPIImp::NotifyEndDllLoad(void* aContext,
+ NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) {
+ aModuleLoadInfo.SetEndLoadTimeStamp();
+
+ if (NT_SUCCESS(aLoadNtStatus)) {
+ aModuleLoadInfo.CaptureBacktrace();
+ }
+
+ nt::AutoSharedLock lock(gLoaderObserverLock);
+
+ // We need to notify the observer that the DLL load has ended even when
+ // |aLoadNtStatus| indicates a failure. This is to ensure that any resources
+ // acquired by the observer during OnBeginDllLoad are cleaned up.
+ gLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus,
+ std::move(aModuleLoadInfo));
+}
+
+nt::AllocatedUnicodeString LoaderPrivateAPIImp::GetSectionName(
+ void* aSectionAddr) {
+ const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+
+ nt::MemorySectionNameBuf buf;
+ NTSTATUS ntStatus =
+ ::NtQueryVirtualMemory(kCurrentProcess, aSectionAddr, MemorySectionName,
+ &buf, sizeof(buf), nullptr);
+ if (!NT_SUCCESS(ntStatus)) {
+ return nt::AllocatedUnicodeString();
+ }
+
+ return nt::AllocatedUnicodeString(&buf.mSectionFileName);
+}
+
+nt::LoaderAPI::InitDllBlocklistOOPFnPtr
+LoaderPrivateAPIImp::GetDllBlocklistInitFn() {
+ return &InitializeDllBlocklistOOP;
+}
+
+nt::LoaderAPI::HandleLauncherErrorFnPtr
+LoaderPrivateAPIImp::GetHandleLauncherErrorFn() {
+ return &HandleLauncherError;
+}
+
+nt::SharedSection* LoaderPrivateAPIImp::GetSharedSection() {
+ return &gSharedSection;
+}
+
+nt::MemorySectionNameBuf LoaderPrivateAPIImp::GetSectionNameBuffer(
+ void* aSectionAddr) {
+ const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+
+ nt::MemorySectionNameBuf buf;
+ NTSTATUS ntStatus =
+ ::NtQueryVirtualMemory(kCurrentProcess, aSectionAddr, MemorySectionName,
+ &buf, sizeof(buf), nullptr);
+ if (!NT_SUCCESS(ntStatus)) {
+ return nt::MemorySectionNameBuf();
+ }
+
+ return buf;
+}
+
+void LoaderPrivateAPIImp::NotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) {
+ nt::AutoSharedLock lock(gLoaderObserverLock);
+ gLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName);
+}
+
+void LoaderPrivateAPIImp::NotifyBeginDllLoad(
+ ModuleLoadInfo& aModuleLoadInfo, void** aContext,
+ PCUNICODE_STRING aRequestedDllName) {
+ NotifyBeginDllLoad(aContext, aRequestedDllName);
+ aModuleLoadInfo.SetBeginLoadTimeStamp();
+}
+
+void LoaderPrivateAPIImp::SetObserver(nt::LoaderObserver* aNewObserver) {
+ nt::LoaderObserver* prevLoaderObserver = nullptr;
+
+ nt::AutoExclusiveLock lock(gLoaderObserverLock);
+
+ MOZ_ASSERT(aNewObserver);
+ if (!aNewObserver) {
+ // This is unlikely, but we always want a valid observer, so use the
+ // gDefaultObserver if necessary.
+ gLoaderObserver = &gDefaultObserver;
+ return;
+ }
+
+ prevLoaderObserver = gLoaderObserver;
+ gLoaderObserver = aNewObserver;
+
+ MOZ_ASSERT(prevLoaderObserver);
+ if (!prevLoaderObserver) {
+ return;
+ }
+
+ // Now that we have a new observer, the previous observer must forward its
+ // data on to the new observer for processing.
+ prevLoaderObserver->Forward(aNewObserver);
+}
+
+bool LoaderPrivateAPIImp::IsDefaultObserver() const {
+ nt::AutoSharedLock lock(gLoaderObserverLock);
+ return gLoaderObserver == &gDefaultObserver;
+}
+
+void EnsureInitialized() { Init(); }
+
+} // namespace freestanding
+} // namespace mozilla
diff --git a/browser/app/winlauncher/freestanding/LoaderPrivateAPI.h b/browser/app/winlauncher/freestanding/LoaderPrivateAPI.h
new file mode 100644
index 0000000000..f21472d689
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/LoaderPrivateAPI.h
@@ -0,0 +1,62 @@
+/* -*- 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_freestanding_LoaderPrivateAPI_h
+#define mozilla_freestanding_LoaderPrivateAPI_h
+
+#include "mozilla/LoaderAPIInterfaces.h"
+
+namespace mozilla {
+namespace freestanding {
+
+/**
+ * This part of the API is available only to the launcher process.
+ */
+class NS_NO_VTABLE LoaderPrivateAPI : public nt::LoaderAPI {
+ public:
+ /**
+ * Notify the nt::LoaderObserver that a module load is beginning
+ */
+ virtual void NotifyBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) = 0;
+ /**
+ * Notify the nt::LoaderObserver that a module load is beginning and set the
+ * begin load timestamp on |aModuleLoadInfo|.
+ */
+ virtual void NotifyBeginDllLoad(ModuleLoadInfo& aModuleLoadInfo,
+ void** aContext,
+ PCUNICODE_STRING aRequestedDllName) = 0;
+
+ /**
+ * Set a new nt::LoaderObserver to be used by the launcher process. NB: This
+ * should only happen while the current process is still single-threaded!
+ */
+ virtual void SetObserver(nt::LoaderObserver* aNewObserver) = 0;
+
+ /**
+ * Returns true if the current nt::LoaderObserver is the launcher process's
+ * built-in observer.
+ */
+ virtual bool IsDefaultObserver() const = 0;
+
+ /**
+ * Returns the name of a given mapped section address as a local instance of
+ * nt::MemorySectionNameBuf. This does not involve heap allocation.
+ */
+ virtual nt::MemorySectionNameBuf GetSectionNameBuffer(void* aSectionAddr) = 0;
+};
+
+/**
+ * Ensures that any statics in the freestanding library are initialized.
+ */
+void EnsureInitialized();
+
+extern LoaderPrivateAPI& gLoaderPrivateAPI;
+
+} // namespace freestanding
+} // namespace mozilla
+
+#endif // mozilla_freestanding_LoaderPrivateAPI_h
diff --git a/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp b/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp
new file mode 100644
index 0000000000..3aa043b8c7
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/ModuleLoadFrame.cpp
@@ -0,0 +1,144 @@
+/* -*- 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 "ModuleLoadFrame.h"
+
+#include "LoaderPrivateAPI.h"
+
+namespace mozilla {
+namespace freestanding {
+
+ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
+ : mPrev(sTopFrame.get()),
+ mContext(nullptr),
+ mLSPSubstitutionRequired(false),
+ mLoadNtStatus(STATUS_UNSUCCESSFUL),
+ mLoadInfo(aRequestedDllName) {
+ EnsureInitialized();
+ sTopFrame.set(this);
+
+ gLoaderPrivateAPI.NotifyBeginDllLoad(mLoadInfo, &mContext, aRequestedDllName);
+}
+
+ModuleLoadFrame::ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aMapBaseAddr, NTSTATUS aNtStatus,
+ ModuleLoadInfo::Status aLoadStatus,
+ bool aIsDependent)
+ : mPrev(sTopFrame.get()),
+ mContext(nullptr),
+ mLSPSubstitutionRequired(false),
+ mLoadNtStatus(aNtStatus),
+ mLoadInfo(std::move(aSectionName), aMapBaseAddr, aLoadStatus,
+ aIsDependent) {
+ sTopFrame.set(this);
+
+ gLoaderPrivateAPI.NotifyBeginDllLoad(&mContext, mLoadInfo.mSectionName);
+}
+
+ModuleLoadFrame::~ModuleLoadFrame() {
+ gLoaderPrivateAPI.NotifyEndDllLoad(mContext, mLoadNtStatus,
+ std::move(mLoadInfo));
+ sTopFrame.set(mPrev);
+}
+
+/* static */
+void ModuleLoadFrame::NotifyLSPSubstitutionRequired(
+ PCUNICODE_STRING aLeafName) {
+ ModuleLoadFrame* topFrame = sTopFrame.get();
+ if (!topFrame) {
+ return;
+ }
+
+ topFrame->SetLSPSubstitutionRequired(aLeafName);
+}
+
+void ModuleLoadFrame::SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName) {
+ MOZ_ASSERT(!mLoadInfo.mBaseAddr);
+ if (mLoadInfo.mBaseAddr) {
+ // If mBaseAddr is not null then |this| has already seen a module load. This
+ // should not be the case for a LSP substitution, so we bail.
+ return;
+ }
+
+ // Save aLeafName, as it will be used by SetLoadStatus when invoking
+ // SubstituteForLSP
+ mLoadInfo.mRequestedDllName = aLeafName;
+ mLSPSubstitutionRequired = true;
+}
+
+/* static */
+void ModuleLoadFrame::NotifySectionMap(
+ nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
+ NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus,
+ bool aIsDependent) {
+ ModuleLoadFrame* topFrame = sTopFrame.get();
+ if (!topFrame) {
+ // The only time that this data is useful is during initial mapping of
+ // the executable's dependent DLLs. If mozglue is present then
+ // IsDefaultObserver will return false, indicating that we are beyond
+ // initial process startup.
+ if (gLoaderPrivateAPI.IsDefaultObserver()) {
+ OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
+ aLoadStatus, aIsDependent);
+ }
+ return;
+ }
+
+ topFrame->OnSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
+ aLoadStatus, aIsDependent);
+}
+
+/* static */
+bool ModuleLoadFrame::ExistsTopFrame() { return !!sTopFrame.get(); }
+
+void ModuleLoadFrame::OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aMapBaseAddr,
+ NTSTATUS aMapNtStatus,
+ ModuleLoadInfo::Status aLoadStatus,
+ bool aIsDependent) {
+ if (mLoadInfo.mBaseAddr) {
+ // If mBaseAddr is not null then |this| has already seen a module load. This
+ // means that we are witnessing a bare section map.
+ OnBareSectionMap(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
+ aLoadStatus, aIsDependent);
+ return;
+ }
+
+ mLoadInfo.mSectionName = std::move(aSectionName);
+ mLoadInfo.mBaseAddr = aMapBaseAddr;
+ mLoadInfo.mStatus = aLoadStatus;
+}
+
+/* static */
+void ModuleLoadFrame::OnBareSectionMap(
+ nt::AllocatedUnicodeString&& aSectionName, const void* aMapBaseAddr,
+ NTSTATUS aMapNtStatus, ModuleLoadInfo::Status aLoadStatus,
+ bool aIsDependent) {
+ // We call the special constructor variant that is used for bare mappings.
+ ModuleLoadFrame frame(std::move(aSectionName), aMapBaseAddr, aMapNtStatus,
+ aLoadStatus, aIsDependent);
+}
+
+NTSTATUS ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus,
+ PHANDLE aOutHandle) {
+ mLoadNtStatus = aNtStatus;
+
+ if (!mLSPSubstitutionRequired) {
+ return aNtStatus;
+ }
+
+ if (!gLoaderPrivateAPI.SubstituteForLSP(mLoadInfo.mRequestedDllName,
+ aOutHandle)) {
+ return aNtStatus;
+ }
+
+ return STATUS_SUCCESS;
+}
+
+SafeThreadLocal<ModuleLoadFrame*> ModuleLoadFrame::sTopFrame;
+
+} // namespace freestanding
+} // namespace mozilla
diff --git a/browser/app/winlauncher/freestanding/ModuleLoadFrame.h b/browser/app/winlauncher/freestanding/ModuleLoadFrame.h
new file mode 100644
index 0000000000..51a179db99
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/ModuleLoadFrame.h
@@ -0,0 +1,97 @@
+/* -*- 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_freestanding_ModuleLoadFrame_h
+#define mozilla_freestanding_ModuleLoadFrame_h
+
+#include "mozilla/LoaderAPIInterfaces.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ThreadLocal.h"
+
+#include "SafeThreadLocal.h"
+
+namespace mozilla {
+namespace freestanding {
+
+/**
+ * This class holds information about a DLL load at a particular frame in the
+ * current thread's stack. Each instance adds itself to a thread-local linked
+ * list of ModuleLoadFrames, enabling us to query information about the
+ * previous module load on the stack.
+ */
+class MOZ_RAII ModuleLoadFrame final {
+ public:
+ /**
+ * This constructor is for use by the LdrLoadDll hook.
+ */
+ explicit ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName);
+ ~ModuleLoadFrame();
+
+ static void NotifyLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
+
+ /**
+ * This static method is called by the NtMapViewOfSection hook.
+ */
+ static void NotifySectionMap(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
+ ModuleLoadInfo::Status aLoadStatus,
+ bool aIsDependent);
+ static bool ExistsTopFrame();
+
+ /**
+ * Called by the LdrLoadDll hook to indicate the status of the load and for
+ * us to provide a substitute output handle if necessary.
+ */
+ NTSTATUS SetLoadStatus(NTSTATUS aNtStatus, PHANDLE aOutHandle);
+
+ ModuleLoadFrame(const ModuleLoadFrame&) = delete;
+ ModuleLoadFrame(ModuleLoadFrame&&) = delete;
+ ModuleLoadFrame& operator=(const ModuleLoadFrame&) = delete;
+ ModuleLoadFrame& operator=(ModuleLoadFrame&&) = delete;
+
+ private:
+ /**
+ * Called by OnBareSectionMap to construct a frame for a bare load.
+ */
+ ModuleLoadFrame(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aMapBaseAddr, NTSTATUS aNtStatus,
+ ModuleLoadInfo::Status aLoadStatus, bool aIsDependent);
+
+ void SetLSPSubstitutionRequired(PCUNICODE_STRING aLeafName);
+ void OnSectionMap(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
+ ModuleLoadInfo::Status aLoadStatus, bool aIsDependent);
+
+ /**
+ * A "bare" section mapping is one that was mapped without the code passing
+ * through a call to ntdll!LdrLoadDll. This method is invoked when we detect
+ * that condition.
+ */
+ static void OnBareSectionMap(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aMapBaseAddr, NTSTATUS aMapNtStatus,
+ ModuleLoadInfo::Status aLoadStatus,
+ bool aIsDependent);
+
+ private:
+ // Link to the previous frame
+ ModuleLoadFrame* mPrev;
+ // Pointer to context managed by the nt::LoaderObserver implementation
+ void* mContext;
+ // Set to |true| when we need to block a WinSock LSP
+ bool mLSPSubstitutionRequired;
+ // NTSTATUS code from the |LdrLoadDll| call
+ NTSTATUS mLoadNtStatus;
+ // Telemetry information that will be forwarded to the nt::LoaderObserver
+ ModuleLoadInfo mLoadInfo;
+
+ // Head of the linked list
+ static SafeThreadLocal<ModuleLoadFrame*> sTopFrame;
+};
+
+} // namespace freestanding
+} // namespace mozilla
+
+#endif // mozilla_freestanding_ModuleLoadFrame_h
diff --git a/browser/app/winlauncher/freestanding/SafeThreadLocal.h b/browser/app/winlauncher/freestanding/SafeThreadLocal.h
new file mode 100644
index 0000000000..e4b869f649
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/SafeThreadLocal.h
@@ -0,0 +1,96 @@
+/* -*- 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_freestanding_SafeThreadLocal_h
+#define mozilla_freestanding_SafeThreadLocal_h
+
+#include <type_traits>
+
+#include "mozilla/NativeNt.h"
+#include "mozilla/ThreadLocal.h"
+
+namespace mozilla {
+namespace freestanding {
+
+// We cannot fall back to the Tls* APIs because kernel32 might not have been
+// loaded yet.
+#if defined(__MINGW32__) && !defined(HAVE_THREAD_TLS_KEYWORD)
+# error "This code requires the compiler to have native TLS support"
+#endif // defined(__MINGW32__) && !defined(HAVE_THREAD_TLS_KEYWORD)
+
+/**
+ * This class holds data as a thread-local variable, or as a global variable
+ * if the thread local storage is not initialized yet. It should be safe
+ * because in that early stage we assume there is no more than a single thread.
+ */
+template <typename T>
+class SafeThreadLocal final {
+ static MOZ_THREAD_LOCAL(T) sThreadLocal;
+ static T sGlobal;
+ static bool sIsTlsUsed;
+
+ // In normal cases, TLS is always available and the class uses sThreadLocal
+ // without changing sMainThreadId. So sMainThreadId is likely to be 0.
+ //
+ // If TLS is not available, we use sGlobal instead and update sMainThreadId
+ // so that that thread keeps using sGlobal even after TLS is initialized
+ // later.
+ static DWORD sMainThreadId;
+
+ // Need non-inline accessors to prevent the compiler from generating code
+ // accessing sThreadLocal before checking a condition.
+ MOZ_NEVER_INLINE static void SetGlobalValue(T aValue) { sGlobal = aValue; }
+ MOZ_NEVER_INLINE static T GetGlobalValue() { return sGlobal; }
+
+ public:
+ static void set(T aValue) {
+ static_assert(std::is_pointer_v<T>,
+ "SafeThreadLocal must be used with a pointer");
+
+ if (sMainThreadId == mozilla::nt::RtlGetCurrentThreadId()) {
+ SetGlobalValue(aValue);
+ } else if (sIsTlsUsed) {
+ MOZ_ASSERT(mozilla::nt::RtlGetThreadLocalStoragePointer(),
+ "Once TLS is used, TLS should be available till the end.");
+ sThreadLocal.set(aValue);
+ } else if (mozilla::nt::RtlGetThreadLocalStoragePointer()) {
+ sIsTlsUsed = true;
+ sThreadLocal.set(aValue);
+ } else {
+ MOZ_ASSERT(sMainThreadId == 0,
+ "A second thread cannot be created before TLS is available.");
+ sMainThreadId = mozilla::nt::RtlGetCurrentThreadId();
+ SetGlobalValue(aValue);
+ }
+ }
+
+ static T get() {
+ if (sMainThreadId == mozilla::nt::RtlGetCurrentThreadId()) {
+ return GetGlobalValue();
+ } else if (sIsTlsUsed) {
+ return sThreadLocal.get();
+ }
+ return GetGlobalValue();
+ }
+};
+
+template <typename T>
+MOZ_THREAD_LOCAL(T)
+SafeThreadLocal<T>::sThreadLocal;
+
+template <typename T>
+T SafeThreadLocal<T>::sGlobal = nullptr;
+
+template <typename T>
+bool SafeThreadLocal<T>::sIsTlsUsed = false;
+
+template <typename T>
+DWORD SafeThreadLocal<T>::sMainThreadId = 0;
+
+} // namespace freestanding
+} // namespace mozilla
+
+#endif // mozilla_freestanding_SafeThreadLocal_h
diff --git a/browser/app/winlauncher/freestanding/SharedSection.cpp b/browser/app/winlauncher/freestanding/SharedSection.cpp
new file mode 100644
index 0000000000..eab0074f60
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/SharedSection.cpp
@@ -0,0 +1,368 @@
+/* -*- 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 "SharedSection.h"
+
+#include <algorithm>
+#include "CheckForCaller.h"
+#include "mozilla/BinarySearch.h"
+
+namespace {
+
+bool AddString(mozilla::Span<wchar_t> aBuffer, const UNICODE_STRING& aStr) {
+ size_t offsetElements = 0;
+ while (offsetElements < aBuffer.Length()) {
+ UNICODE_STRING uniStr;
+ ::RtlInitUnicodeString(&uniStr, aBuffer.data() + offsetElements);
+
+ if (uniStr.Length == 0) {
+ // Reached to the array's last item.
+ break;
+ }
+
+ if (::RtlCompareUnicodeString(&uniStr, &aStr, TRUE) == 0) {
+ // Already included in the array.
+ return true;
+ }
+
+ // Go to the next string.
+ offsetElements += uniStr.MaximumLength / sizeof(wchar_t);
+ }
+
+ // Ensure enough space including the last empty string at the end.
+ if (offsetElements * sizeof(wchar_t) + aStr.Length + sizeof(wchar_t) +
+ sizeof(wchar_t) >
+ aBuffer.LengthBytes()) {
+ return false;
+ }
+
+ auto newStr = aBuffer.Subspan(offsetElements);
+ memcpy(newStr.data(), aStr.Buffer, aStr.Length);
+ memset(newStr.data() + aStr.Length / sizeof(wchar_t), 0, sizeof(wchar_t));
+ return true;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+namespace freestanding {
+
+SharedSection gSharedSection;
+
+// Why don't we use ::GetProcAddress?
+// If the export table of kernel32.dll is tampered in the current process,
+// we cannot transfer an RVA because the function pointed by the RVA may not
+// exist in a target process.
+// We can use ::GetProcAddress with additional check to detect tampering, but
+// FindExportAddressTableEntry fits perfectly here because it returns nullptr
+// if the target entry is outside the image, which means it's tampered or
+// forwarded to another DLL.
+#define INIT_FUNCTION(exports, name) \
+ do { \
+ auto rvaToFunction = exports.FindExportAddressTableEntry(#name); \
+ if (!rvaToFunction) { \
+ return; \
+ } \
+ m##name = reinterpret_cast<decltype(m##name)>(*rvaToFunction); \
+ } while (0)
+
+#define RESOLVE_FUNCTION(base, name) \
+ m##name = reinterpret_cast<decltype(m##name)>( \
+ base + reinterpret_cast<uintptr_t>(m##name))
+
+void Kernel32ExportsSolver::Init() {
+ interceptor::MMPolicyInProcess policy;
+ auto k32Exports = nt::PEExportSection<interceptor::MMPolicyInProcess>::Get(
+ ::GetModuleHandleW(L"kernel32.dll"), policy);
+ if (!k32Exports) {
+ return;
+ }
+
+ // Please make sure these functions are not forwarded to another DLL.
+ INIT_FUNCTION(k32Exports, FlushInstructionCache);
+ INIT_FUNCTION(k32Exports, GetModuleHandleW);
+ INIT_FUNCTION(k32Exports, GetSystemInfo);
+ INIT_FUNCTION(k32Exports, VirtualProtect);
+}
+
+bool Kernel32ExportsSolver::Resolve() {
+ const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll");
+
+ // We cannot use GetModuleHandleW because this code can be called
+ // before IAT is resolved.
+ auto k32Module = nt::GetModuleHandleFromLeafName(k32Name);
+ if (k32Module.isErr()) {
+ // Probably this is called before kernel32.dll is loaded.
+ return false;
+ }
+
+ uintptr_t k32Base =
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(k32Module.unwrap());
+
+ RESOLVE_FUNCTION(k32Base, FlushInstructionCache);
+ RESOLVE_FUNCTION(k32Base, GetModuleHandleW);
+ RESOLVE_FUNCTION(k32Base, GetSystemInfo);
+ RESOLVE_FUNCTION(k32Base, VirtualProtect);
+
+ return true;
+}
+
+HANDLE SharedSection::sSectionHandle = nullptr;
+SharedSection::Layout* SharedSection::sWriteCopyView = nullptr;
+RTL_RUN_ONCE SharedSection::sEnsureOnce = RTL_RUN_ONCE_INIT;
+nt::SRWLock SharedSection::sLock;
+
+void SharedSection::Reset(HANDLE aNewSectionObject) {
+ nt::AutoExclusiveLock{sLock};
+ if (sWriteCopyView) {
+ nt::AutoMappedView view(sWriteCopyView);
+ sWriteCopyView = nullptr;
+ ::RtlRunOnceInitialize(&sEnsureOnce);
+ }
+
+ if (sSectionHandle != aNewSectionObject) {
+ if (sSectionHandle) {
+ ::CloseHandle(sSectionHandle);
+ }
+ sSectionHandle = aNewSectionObject;
+ }
+}
+
+void SharedSection::ConvertToReadOnly() {
+ if (!sSectionHandle) {
+ return;
+ }
+
+ HANDLE readonlyHandle;
+ if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
+ nt::kCurrentProcess, &readonlyHandle, GENERIC_READ,
+ FALSE, 0)) {
+ return;
+ }
+
+ Reset(readonlyHandle);
+}
+
+LauncherVoidResult SharedSection::Init() {
+ static_assert(
+ kSharedViewSize >= sizeof(Layout),
+ "kSharedViewSize is too small to represent SharedSection::Layout.");
+
+ HANDLE section =
+ ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
+ kSharedViewSize, nullptr);
+ if (!section) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+ Reset(section);
+
+ // The initial contents of the pages in a file mapping object backed by
+ // the operating system paging file are 0 (zero). No need to zero it out
+ // ourselves.
+ // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw
+ nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
+ if (!writableView) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ Layout* view = writableView.as<Layout>();
+ view->mK32Exports.Init();
+ view->mState = Layout::State::kInitialized;
+ // Leave view->mDependentModulePathArrayStart to be zero to indicate
+ // we can add blocklist entries
+ return Ok();
+}
+
+LauncherVoidResult SharedSection::AddDependentModule(PCUNICODE_STRING aNtPath) {
+ nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
+ if (!writableView) {
+ return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error());
+ }
+
+ Layout* view = writableView.as<Layout>();
+ if (!view->mDependentModulePathArrayStart) {
+ // This is the first time AddDependentModule is called. We set the initial
+ // value to mDependentModulePathArrayStart, which *closes* the blocklist.
+ // After this, AddBlocklist is no longer allowed.
+ view->mDependentModulePathArrayStart =
+ FIELD_OFFSET(Layout, mFirstBlockEntry) + sizeof(DllBlockInfo);
+ }
+
+ if (!AddString(view->GetDependentModules(), *aNtPath)) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ return Ok();
+}
+
+LauncherVoidResult SharedSection::SetBlocklist(
+ const DynamicBlockList& aBlocklist, bool isDisabled) {
+ if (!aBlocklist.GetPayloadSize()) {
+ return Ok();
+ }
+
+ nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
+ if (!writableView) {
+ return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error());
+ }
+
+ Layout* view = writableView.as<Layout>();
+ if (view->mDependentModulePathArrayStart > 0) {
+ // If the dependent module array is already available, we must not update
+ // the blocklist.
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_STATE);
+ }
+
+ view->mBlocklistIsDisabled = isDisabled ? 1 : 0;
+ uintptr_t bufferEnd = reinterpret_cast<uintptr_t>(view) + kSharedViewSize;
+ size_t bytesCopied = aBlocklist.CopyTo(
+ view->mFirstBlockEntry,
+ bufferEnd - reinterpret_cast<uintptr_t>(view->mFirstBlockEntry));
+ if (!bytesCopied) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
+ }
+
+ // Setting mDependentModulePathArrayStart to a non-zero value means
+ // we no longer accept blocklist entries
+ // Just to be safe, make sure we don't overwrite mFirstBlockEntry even
+ // if there are no entries.
+ view->mDependentModulePathArrayStart =
+ FIELD_OFFSET(Layout, mFirstBlockEntry) +
+ std::max(bytesCopied, sizeof(DllBlockInfo));
+ return Ok();
+}
+
+/* static */
+ULONG NTAPI SharedSection::EnsureWriteCopyViewOnce(PRTL_RUN_ONCE, PVOID,
+ PVOID*) {
+ if (!sWriteCopyView) {
+ nt::AutoMappedView view(sSectionHandle, PAGE_WRITECOPY);
+ if (!view) {
+ return TRUE;
+ }
+ sWriteCopyView = view.as<Layout>();
+ view.release();
+ }
+ return sWriteCopyView->Resolve() ? TRUE : FALSE;
+}
+
+SharedSection::Layout* SharedSection::EnsureWriteCopyView(
+ bool requireKernel32Exports /*= false */) {
+ ::RtlRunOnceExecuteOnce(&sEnsureOnce, &EnsureWriteCopyViewOnce, nullptr,
+ nullptr);
+ if (!sWriteCopyView) {
+ return nullptr;
+ }
+ auto requiredState = requireKernel32Exports
+ ? Layout::State::kResolved
+ : Layout::State::kLoadedDynamicBlocklistEntries;
+ return sWriteCopyView->mState >= requiredState ? sWriteCopyView : nullptr;
+}
+
+bool SharedSection::Layout::Resolve() {
+ if (mState == State::kResolved) {
+ return true;
+ }
+ if (mState == State::kUninitialized) {
+ return false;
+ }
+ if (mState == State::kInitialized) {
+ if (!mNumBlockEntries) {
+ uintptr_t arrayBase = reinterpret_cast<uintptr_t>(mFirstBlockEntry);
+ uint32_t numEntries = 0;
+ for (DllBlockInfo* entry = mFirstBlockEntry;
+ entry->mName.Length && numEntries < GetMaxNumBlockEntries();
+ ++entry) {
+ entry->mName.Buffer = reinterpret_cast<wchar_t*>(
+ arrayBase + reinterpret_cast<uintptr_t>(entry->mName.Buffer));
+ ++numEntries;
+ }
+ mNumBlockEntries = numEntries;
+ // Sort by name so that we can binary-search
+ std::sort(mFirstBlockEntry, mFirstBlockEntry + mNumBlockEntries,
+ [](const DllBlockInfo& a, const DllBlockInfo& b) {
+ return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) <
+ 0;
+ });
+ }
+ mState = State::kLoadedDynamicBlocklistEntries;
+ }
+
+ if (!mK32Exports.Resolve()) {
+ return false;
+ }
+
+ mState = State::kResolved;
+ return true;
+}
+
+Span<wchar_t> SharedSection::Layout::GetDependentModules() {
+ if (!mDependentModulePathArrayStart) {
+ return nullptr;
+ }
+ return Span<wchar_t>(
+ reinterpret_cast<wchar_t*>(reinterpret_cast<uintptr_t>(this) +
+ mDependentModulePathArrayStart),
+ (kSharedViewSize - mDependentModulePathArrayStart) / sizeof(wchar_t));
+}
+
+bool SharedSection::Layout::IsDisabled() const {
+ return !!mBlocklistIsDisabled;
+}
+
+const DllBlockInfo* SharedSection::Layout::SearchBlocklist(
+ const UNICODE_STRING& aLeafName) const {
+ MOZ_ASSERT(mState >= State::kLoadedDynamicBlocklistEntries);
+ DllBlockInfoComparator comp(aLeafName);
+ size_t match;
+ if (!BinarySearchIf(mFirstBlockEntry, 0, mNumBlockEntries, comp, &match)) {
+ return nullptr;
+ }
+ return &mFirstBlockEntry[match];
+}
+
+Kernel32ExportsSolver* SharedSection::GetKernel32Exports() {
+ Layout* writeCopyView = EnsureWriteCopyView(true);
+ return writeCopyView ? &writeCopyView->mK32Exports : nullptr;
+}
+
+Span<const wchar_t> SharedSection::GetDependentModules() {
+ Layout* writeCopyView = EnsureWriteCopyView();
+ return writeCopyView ? writeCopyView->GetDependentModules() : nullptr;
+}
+
+Span<const DllBlockInfo> SharedSection::GetDynamicBlocklist() {
+ Layout* writeCopyView = EnsureWriteCopyView();
+ return writeCopyView ? writeCopyView->GetModulePathArray() : nullptr;
+}
+
+const DllBlockInfo* SharedSection::SearchBlocklist(
+ const UNICODE_STRING& aLeafName) {
+ Layout* writeCopyView = EnsureWriteCopyView();
+ return writeCopyView ? writeCopyView->SearchBlocklist(aLeafName) : nullptr;
+}
+
+bool SharedSection::IsDisabled() {
+ Layout* writeCopyView = EnsureWriteCopyView();
+ return writeCopyView ? writeCopyView->IsDisabled() : false;
+}
+
+LauncherVoidResult SharedSection::TransferHandle(
+ nt::CrossExecTransferManager& aTransferMgr, DWORD aDesiredAccess,
+ HANDLE* aDestinationAddress) {
+ HANDLE remoteHandle;
+ if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
+ aTransferMgr.RemoteProcess(), &remoteHandle,
+ aDesiredAccess, FALSE, 0)) {
+ return LAUNCHER_ERROR_FROM_LAST();
+ }
+
+ return aTransferMgr.Transfer(aDestinationAddress, &remoteHandle,
+ sizeof(remoteHandle));
+}
+
+} // namespace freestanding
+} // namespace mozilla
diff --git a/browser/app/winlauncher/freestanding/SharedSection.h b/browser/app/winlauncher/freestanding/SharedSection.h
new file mode 100644
index 0000000000..4c3d1b8ef8
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/SharedSection.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 https://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_freestanding_SharedSection_h
+#define mozilla_freestanding_SharedSection_h
+
+#include "mozilla/DynamicBlocklist.h"
+#include "mozilla/glue/SharedSection.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/interceptor/MMPolicies.h"
+
+// clang-format off
+#define MOZ_LITERAL_UNICODE_STRING(s) \
+ { \
+ /* Length of the string in bytes, less the null terminator */ \
+ sizeof(s) - sizeof(wchar_t), \
+ /* Length of the string in bytes, including the null terminator */ \
+ sizeof(s), \
+ /* Pointer to the buffer */ \
+ const_cast<wchar_t*>(s) \
+ }
+// clang-format on
+
+namespace mozilla {
+namespace freestanding {
+class SharedSectionTestHelper;
+
+struct DllBlockInfoComparator {
+ explicit DllBlockInfoComparator(const UNICODE_STRING& aTarget)
+ : mTarget(&aTarget) {}
+
+ int operator()(const DllBlockInfo& aVal) const {
+ return static_cast<int>(
+ ::RtlCompareUnicodeString(mTarget, &aVal.mName, TRUE));
+ }
+
+ PCUNICODE_STRING mTarget;
+};
+
+// This class calculates RVAs of kernel32's functions and transfers them
+// to a target process, where the transferred RVAs are resolved into
+// function addresses so that the target process can use them after
+// kernel32.dll is loaded and before IAT is resolved.
+struct MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final
+ : interceptor::MMPolicyInProcessEarlyStage::Kernel32Exports {
+ void Init();
+ bool Resolve();
+};
+
+// This class manages a section which is created in the launcher process and
+// mapped in the browser process and the sandboxed processes. The section's
+// layout is represented as SharedSection::Layout.
+//
+// (1) Kernel32's functions required for MMPolicyInProcessEarlyStage
+// Formatted as Kernel32ExportsSolver.
+//
+// (2) Various flags and offsets
+//
+// (3) Entries in the dynamic blocklist, in DllBlockInfo format. There
+// are mNumBlockEntries of these, followed by one that has mName.Length
+// of 0. Note that the strings that contain
+// the names of the entries in the blocklist are stored concatenated
+// after the last entry. The mName pointers in each DllBlockInfo point
+// to these strings correctly in Resolve(), so clients don't need
+// to do anything special to read these strings.
+//
+// (4) Array of NT paths of the executable's dependent modules
+// Formatted as a null-delimited wide-character string set ending with
+// an empty string. These entries start at offset
+// mDependentModulePathArrayStart (in bytes) from the beginning
+// of the structure
+//
+// +--------------------------------------------------------------+
+// | (1) | FlushInstructionCache |
+// | | GetModuleHandleW |
+// | | GetSystemInfo |
+// | | VirtualProtect |
+// | | State [kUninitialized|kInitialized|kResolved] |
+// +--------------------------------------------------------------+
+// | (2) | (flags and offsets) |
+// +--------------------------------------------------------------+
+// | (3) | <DllBlockInfo for first entry in dynamic blocklist> |
+// | | <DllBlockInfo for second entry in dynamic blocklist> |
+// | | ... |
+// | | <DllBlockInfo for last entry in dynamic blocklist> |
+// | | <DllBlockInfo with mName.Length of 0> |
+// | | L"string1.dllstring2.dll...stringlast.dll" |
+// +--------------------------------------------------------------+
+// | (4) | L"NT path 1" |
+// | | L"NT path 2" |
+// | | ... |
+// | | L"" |
+// +--------------------------------------------------------------+
+class MOZ_TRIVIAL_CTOR_DTOR SharedSection final : public nt::SharedSection {
+ struct Layout final {
+ enum class State {
+ kUninitialized,
+ kInitialized,
+ kLoadedDynamicBlocklistEntries,
+ kResolved,
+ } mState;
+
+ Kernel32ExportsSolver mK32Exports;
+ // 1 if the blocklist is disabled, 0 otherwise.
+ // If the blocklist is disabled, the entries are still loaded to make it
+ // easy for the user to remove any they don't want, but none of the DLLs
+ // here are actually blocked.
+ // Stored as a uint32_t for alignment reasons.
+ uint32_t mBlocklistIsDisabled;
+ // The offset, in bytes, from the beginning of the Layout structure to the
+ // first dependent module entry.
+ // When the Layout object is created, this value is 0, indicating that no
+ // dependent modules have been added and it is safe to add DllBlockInfo
+ // entries.
+ // After this value is set to something non-0, no more DllBlockInfo entries
+ // can be added.
+ uint32_t mDependentModulePathArrayStart;
+ // The number of blocklist entries.
+ uint32_t mNumBlockEntries;
+ DllBlockInfo mFirstBlockEntry[1];
+
+ Span<DllBlockInfo> GetModulePathArray() {
+ return Span<DllBlockInfo>(
+ mFirstBlockEntry,
+ (kSharedViewSize - (reinterpret_cast<uintptr_t>(mFirstBlockEntry) -
+ reinterpret_cast<uintptr_t>(this))) /
+ sizeof(DllBlockInfo));
+ }
+ // Can be used to make sure we don't step past the end of the shared memory
+ // section.
+ static constexpr uint32_t GetMaxNumBlockEntries() {
+ return (kSharedViewSize - (offsetof(Layout, mFirstBlockEntry))) /
+ sizeof(DllBlockInfo);
+ }
+ Layout() = delete; // disallow instantiation
+ bool Resolve();
+ bool IsDisabled() const;
+ const DllBlockInfo* SearchBlocklist(const UNICODE_STRING& aLeafName) const;
+ Span<wchar_t> GetDependentModules();
+ };
+
+ // As we define a global variable of this class and use it in our blocklist
+ // which is excuted in a process's early stage. If we have a complex dtor,
+ // the static initializer tries to register that dtor with onexit() of
+ // ucrtbase.dll which is not loaded yet, resulting in crash. Thus, we have
+ // a raw handle and a pointer as a static variable and manually release them
+ // by calling Reset() where possible.
+ static HANDLE sSectionHandle;
+ static Layout* sWriteCopyView;
+ static RTL_RUN_ONCE sEnsureOnce;
+
+ // The sLock lock guarantees that while it is held, sSectionHandle will not
+ // change nor get closed, sEnsureOnce will not get reinitialized, and
+ // sWriteCopyView will not change nor get unmapped once initialized. We take
+ // sLock on paths that could run concurrently with ConvertToReadOnly(). This
+ // method is only called on the main process, and very early, so the only
+ // real risk here should be threads started by third-party products reaching
+ // our patched_NtMapViewOfSection (see bug 1850969).
+ static nt::SRWLock sLock;
+
+ static ULONG NTAPI EnsureWriteCopyViewOnce(PRTL_RUN_ONCE, PVOID, PVOID*);
+ static Layout* EnsureWriteCopyView(bool requireKernel32Exports = false);
+
+ static constexpr size_t kSharedViewSize = 0x1000;
+
+ // For test use only
+ friend class SharedSectionTestHelper;
+
+ public:
+ // Replace |sSectionHandle| with a given handle.
+ static void Reset(HANDLE aNewSectionObject = sSectionHandle);
+
+ static inline nt::AutoSharedLock AutoNoReset() {
+ return nt::AutoSharedLock{sLock};
+ }
+
+ // Replace |sSectionHandle| with a new readonly handle.
+ static void ConvertToReadOnly();
+
+ // Create a new writable section and initialize the Kernel32ExportsSolver
+ // part.
+ static LauncherVoidResult Init();
+
+ // Append a new string to the |sSectionHandle|
+ static LauncherVoidResult AddDependentModule(PCUNICODE_STRING aNtPath);
+ static LauncherVoidResult SetBlocklist(const DynamicBlockList& aBlocklist,
+ bool isDisabled);
+
+ // Map |sSectionHandle| to a copy-on-write page and return a writable pointer
+ // to each structure, or null if Layout failed to resolve exports.
+ Kernel32ExportsSolver* GetKernel32Exports();
+ Span<const wchar_t> GetDependentModules() final override;
+ Span<const DllBlockInfo> GetDynamicBlocklist() final override;
+
+ static bool IsDisabled();
+ static const DllBlockInfo* SearchBlocklist(const UNICODE_STRING& aLeafName);
+
+ // Transfer |sSectionHandle| to a process associated with |aTransferMgr|.
+ static LauncherVoidResult TransferHandle(
+ nt::CrossExecTransferManager& aTransferMgr, DWORD aDesiredAccess,
+ HANDLE* aDestinationAddress = &sSectionHandle);
+};
+
+extern SharedSection gSharedSection;
+
+} // namespace freestanding
+} // namespace mozilla
+
+#endif // mozilla_freestanding_SharedSection_h
diff --git a/browser/app/winlauncher/freestanding/gen_ntdll_freestanding_lib.py b/browser/app/winlauncher/freestanding/gen_ntdll_freestanding_lib.py
new file mode 100644
index 0000000000..d9fe86d78e
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/gen_ntdll_freestanding_lib.py
@@ -0,0 +1,28 @@
+# -*- 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
+import subprocess
+import tempfile
+
+
+def main(output_fd, def_file, llvm_dlltool, *llvm_dlltool_args):
+ # llvm-dlltool can't output to stdout, so we create a temp file, use that
+ # to write out the lib, and then copy it over to output_fd
+ (tmp_fd, tmp_output) = tempfile.mkstemp()
+ os.close(tmp_fd)
+
+ try:
+ cmd = [llvm_dlltool]
+ cmd.extend(llvm_dlltool_args)
+ cmd += ["-d", def_file, "-l", tmp_output]
+
+ subprocess.check_call(cmd)
+
+ with open(tmp_output, "rb") as tmplib:
+ output_fd.write(tmplib.read())
+ finally:
+ os.remove(tmp_output)
diff --git a/browser/app/winlauncher/freestanding/moz.build b/browser/app/winlauncher/freestanding/moz.build
new file mode 100644
index 0000000000..7a43d9a104
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/moz.build
@@ -0,0 +1,56 @@
+# -*- 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("winlauncher-freestanding")
+
+FORCE_STATIC_LIB = True
+
+# Our patched NtMapViewOfSection can be called before the process's import
+# table is populated. Don't let the compiler insert any instrumentation
+# that might call an import.
+NO_PGO = True
+
+UNIFIED_SOURCES += [
+ "DllBlocklist.cpp",
+ "LoaderPrivateAPI.cpp",
+ "ModuleLoadFrame.cpp",
+ "SharedSection.cpp",
+]
+
+# This library must be compiled in a freestanding environment, as its code must
+# not assume that it has access to any runtime libraries.
+if CONFIG["CC_TYPE"] == "clang-cl":
+ CXXFLAGS += ["-Xclang"]
+
+CXXFLAGS += [
+ "-ffreestanding",
+]
+
+# Forcibly include Freestanding.h into all source files in this library.
+if CONFIG["CC_TYPE"] == "clang-cl":
+ CXXFLAGS += ["-FI"]
+else:
+ CXXFLAGS += ["-include"]
+
+CXXFLAGS += [SRCDIR + "/Freestanding.h"]
+
+OS_LIBS += [
+ "ntdll",
+ "ntdll_freestanding",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"] and CONFIG["LLVM_DLLTOOL"]:
+ GeneratedFile(
+ "%sntdll_freestanding.%s" % (CONFIG["LIB_PREFIX"], CONFIG["LIB_SUFFIX"]),
+ script="gen_ntdll_freestanding_lib.py",
+ inputs=["ntdll_freestanding.def"],
+ flags=[CONFIG["LLVM_DLLTOOL"]] + CONFIG["LLVM_DLLTOOL_FLAGS"],
+ )
+
+DisableStlWrapping()
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Launcher Process")
diff --git a/browser/app/winlauncher/freestanding/ntdll_freestanding.def b/browser/app/winlauncher/freestanding/ntdll_freestanding.def
new file mode 100644
index 0000000000..6e5e2685fe
--- /dev/null
+++ b/browser/app/winlauncher/freestanding/ntdll_freestanding.def
@@ -0,0 +1,25 @@
+; 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 ntdll
+
+; When we compile with -freestanding, the compiler still requires implementation
+; of the four functions listed below.
+;
+; We could implement our own naive versions of these functions, but that
+; solution is less than ideal since the implementations must be extern and are
+; thus picked up by the entire firefox.exe binary. This denies the rest of
+; firefox.exe the benefit of optimized implementations. On Windows the
+; sandbox is linked into firefox.exe, so we cannot just shrug and
+; assume that a naive implementation will not have any effect on anything.
+;
+; There are, however, optimized implementations of these functions that are
+; exported by ntdll.dll. OTOH, they are not included in the ntdll.lib
+; import library. This .def file is used to build an import library that "fills
+; in the blanks" and allows us to link into the ntdll implementations.
+EXPORTS
+ memcmp
+ memcpy
+ memmove
+ memset
diff --git a/browser/app/winlauncher/moz.build b/browser/app/winlauncher/moz.build
new file mode 100644
index 0000000000..f2f321ae0c
--- /dev/null
+++ b/browser/app/winlauncher/moz.build
@@ -0,0 +1,61 @@
+# -*- 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("winlauncher")
+
+FORCE_STATIC_LIB = True
+
+UNIFIED_SOURCES += [
+ "/ipc/mscom/COMWrappers.cpp",
+ "/ipc/mscom/ProcessRuntime.cpp",
+ "/toolkit/xre/WinTokenUtils.cpp",
+ "/widget/windows/WindowsConsole.cpp",
+ "DllBlocklistInit.cpp",
+ "ErrorHandler.cpp",
+ "LauncherProcessWin.cpp",
+ "LaunchUnelevated.cpp",
+ "NtLoaderAPI.cpp",
+]
+
+OS_LIBS += [
+ "oleaut32",
+ "ole32",
+ "rpcrt4",
+ "version",
+]
+
+DIRS += [
+ "freestanding",
+]
+
+USE_LIBS += [
+ "winlauncher-freestanding",
+]
+
+TEST_DIRS += [
+ "test",
+]
+
+if CONFIG["MOZ_LAUNCHER_PROCESS"]:
+ LOCAL_INCLUDES += [
+ "/other-licenses/nsis/Contrib/CityHash/cityhash",
+ "/toolkit/mozapps/update/common",
+ ]
+ UNIFIED_SOURCES += [
+ "/other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp",
+ "/toolkit/mozapps/update/common/commonupdatedir.cpp",
+ "/toolkit/xre/LauncherRegistryInfo.cpp",
+ ]
+
+for var in ("MOZ_APP_BASENAME", "MOZ_APP_VENDOR", "MOZ_APP_DISPLAYNAME"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
+
+DEFINES["MOZ_USE_LAUNCHER_ERROR"] = 1
+
+DisableStlWrapping()
+
+with Files("**"):
+ BUG_COMPONENT = ("Firefox", "Launcher Process")
diff --git a/browser/app/winlauncher/test/TestCrossProcessWin.cpp b/browser/app/winlauncher/test/TestCrossProcessWin.cpp
new file mode 100644
index 0000000000..fa212b30c2
--- /dev/null
+++ b/browser/app/winlauncher/test/TestCrossProcessWin.cpp
@@ -0,0 +1,703 @@
+/* -*- 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 <thread>
+#include <winternl.h>
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#include <atomic>
+#include <thread>
+#include "freestanding/SharedSection.cpp"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DynamicBlocklist.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Vector.h"
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) \
+ {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING
+
+#include "mozilla/WindowsDllBlocklistLauncherDefs.h"
+
+const wchar_t kChildArg[] = L"--child";
+const char* kTestDependentModulePaths[] = {
+ "\\Device\\HarddiskVolume4\\Windows\\system32\\A B C",
+ "\\Device\\HarddiskVolume4\\Windows\\system32\\a b c.dll",
+ "\\Device\\HarddiskVolume4\\Windows\\system32\\A B C.exe",
+ "\\Device\\HarddiskVolume4\\Windows\\system32\\X Y Z.dll",
+ "\\Device\\HarddiskVolume1\\a b C",
+ "\\Device\\HarddiskVolume2\\A b c.DLL",
+ "\\Device\\HarddiskVolume3\\A B c.exe",
+ "\\Device\\HarddiskVolume4\\X y Z.dll",
+};
+const wchar_t kExpectedDependentModules[] =
+ L"A B C\0"
+ L"a b c.dll\0"
+ L"A B C.exe\0"
+ L"X Y Z.dll\0";
+
+const UNICODE_STRING kStringNotInBlocklist =
+ MOZ_LITERAL_UNICODE_STRING(L"Test_NotInBlocklist.dll");
+const UNICODE_STRING kTestDependentModuleString =
+ MOZ_LITERAL_UNICODE_STRING(L"Test_DependentModule.dll");
+
+using namespace mozilla;
+using namespace mozilla::freestanding;
+
+// clang-format off
+const DllBlockInfo kDllBlocklistShort[] = {
+ // The entries do not have to be sorted.
+ DLL_BLOCKLIST_ENTRY("X Y Z_Test", MAKE_VERSION(1, 2, 65535, 65535),
+ DllBlockInfoFlags::BLOCK_WIN8_AND_OLDER)
+ DLL_BLOCKLIST_ENTRY("\u30E9\u30FC\u30E1\u30F3_Test")
+ DLL_BLOCKLIST_ENTRY("Avmvirtualsource_Test.ax", MAKE_VERSION(1, 0, 0, 3),
+ DllBlockInfoFlags::BROWSER_PROCESS_ONLY)
+ DLL_BLOCKLIST_ENTRY("1ccelerator_Test.dll", MAKE_VERSION(3, 2, 1, 6))
+ DLL_BLOCKLIST_ENTRY("atkdx11disp_Test.dll", DllBlockInfo::ALL_VERSIONS)
+ {},
+};
+// clang-format on
+
+namespace mozilla::freestanding {
+class SharedSectionTestHelper {
+ public:
+ static constexpr size_t GetModulePathArraySize() {
+ return SharedSection::kSharedViewSize -
+ (offsetof(SharedSection::Layout, mFirstBlockEntry) +
+ sizeof(DllBlockInfo));
+ }
+};
+} // namespace mozilla::freestanding
+
+class TempFile final {
+ wchar_t mFullPath[MAX_PATH + 1];
+
+ public:
+ TempFile() : mFullPath{0} {
+ wchar_t tempDir[MAX_PATH + 1];
+ DWORD len = ::GetTempPathW(ArrayLength(tempDir), tempDir);
+ if (!len) {
+ return;
+ }
+
+ len = ::GetTempFileNameW(tempDir, L"blocklist", 0, mFullPath);
+ if (!len) {
+ return;
+ }
+ }
+
+ operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; }
+};
+
+template <typename T, int N>
+void PrintLauncherError(const LauncherResult<T>& aResult,
+ const char (&aMsg)[N]) {
+ const LauncherError& err = aResult.inspectErr();
+ printf("TEST-FAILED | TestCrossProcessWin | %s - %lx at %s:%d\n", aMsg,
+ err.mError.AsHResult(), err.mFile, err.mLine);
+}
+
+#define VERIFY_FUNCTION_RESOLVED(mod, exports, name) \
+ do { \
+ if (reinterpret_cast<FARPROC>(exports->m##name) != \
+ ::GetProcAddress(mod, #name)) { \
+ printf( \
+ "TEST-FAILED | TestCrossProcessWin | " \
+ "Kernel32ExportsSolver::" #name " did not match.\n"); \
+ return false; \
+ } \
+ } while (0)
+
+static bool VerifySharedSection(SharedSection& aSharedSection) {
+ Kernel32ExportsSolver* k32Exports = aSharedSection.GetKernel32Exports();
+ if (!k32Exports) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | Failed to map a shared section\n");
+ return false;
+ }
+
+ HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll");
+ VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, FlushInstructionCache);
+ VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, GetModuleHandleW);
+ VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, GetSystemInfo);
+ VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, VirtualProtect);
+
+ Span<const wchar_t> modulesArray = aSharedSection.GetDependentModules();
+ bool matched = memcmp(modulesArray.data(), kExpectedDependentModules,
+ sizeof(kExpectedDependentModules)) == 0;
+ if (!matched) {
+ // Print actual strings on error
+ for (const wchar_t* p = modulesArray.data(); *p;) {
+ printf("%p: %S\n", p, p);
+ while (*p) {
+ ++p;
+ }
+ ++p;
+ }
+ return false;
+ }
+
+ for (const DllBlockInfo* info = kDllBlocklistShort; info->mName.Buffer;
+ ++info) {
+ const DllBlockInfo* matched = aSharedSection.SearchBlocklist(info->mName);
+ if (!matched) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | No blocklist entry match for "
+ "entry in blocklist.\n");
+ return false;
+ }
+ }
+
+ if (aSharedSection.SearchBlocklist(kStringNotInBlocklist)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | Found blocklist entry match for "
+ "something not in the blocklist.\n");
+ }
+
+ if (aSharedSection.IsDisabled()) {
+ printf("TEST-FAILED | TestCrossProcessWin | Wrong disabled value.\n");
+ }
+
+ return true;
+}
+
+static bool TestAddString() {
+ wchar_t testBuffer[3] = {0};
+ UNICODE_STRING ustr;
+
+ // This makes |testBuffer| full.
+ ::RtlInitUnicodeString(&ustr, L"a");
+ if (!AddString(testBuffer, ustr)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "AddString failed.\n");
+ return false;
+ }
+
+ // Adding a string to a full buffer should fail.
+ ::RtlInitUnicodeString(&ustr, L"b");
+ if (AddString(testBuffer, ustr)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "AddString caused OOB memory access.\n");
+ return false;
+ }
+
+ bool matched = memcmp(testBuffer, L"a\0", sizeof(testBuffer)) == 0;
+ if (!matched) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "AddString wrote wrong values.\n");
+ return false;
+ }
+
+ return true;
+}
+
+// Convert |aBlockEntries|, which is an array ending with an empty instance
+// of DllBlockInfo, to DynamicBlockList by storing it to a temp file, loading
+// as DynamicBlockList, and deleting the temp file.
+static DynamicBlockList ConvertStaticBlocklistToDynamic(
+ const DllBlockInfo aBlockEntries[]) {
+ size_t originalLength = 0;
+ CheckedUint32 totalStringLen = 0;
+ for (const DllBlockInfo* entry = aBlockEntries; entry->mName.Length;
+ ++entry) {
+ totalStringLen += entry->mName.Length;
+ MOZ_RELEASE_ASSERT(totalStringLen.isValid());
+ ++originalLength;
+ }
+
+ // Pack all strings in this buffer without null characters
+ UniquePtr<uint8_t[]> stringBuffer =
+ MakeUnique<uint8_t[]>(totalStringLen.value());
+
+ // The string buffer is placed immediately after the array of DllBlockInfo
+ const size_t stringBufferOffset = (originalLength + 1) * sizeof(DllBlockInfo);
+
+ // Entries in the dynamic blocklist do have to be sorted,
+ // unlike in the static blocklist.
+ UniquePtr<DllBlockInfo[]> sortedBlockEntries =
+ MakeUnique<DllBlockInfo[]>(originalLength);
+ memcpy(sortedBlockEntries.get(), aBlockEntries,
+ sizeof(DllBlockInfo) * originalLength);
+ std::sort(sortedBlockEntries.get(), sortedBlockEntries.get() + originalLength,
+ [](const DllBlockInfo& a, const DllBlockInfo& b) {
+ return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) < 0;
+ });
+
+ Vector<DllBlockInfo> copied;
+ Unused << copied.resize(originalLength + 1); // aBlockEntries + sentinel
+
+ size_t currentStringOffset = 0;
+ for (size_t i = 0; i < originalLength; ++i) {
+ copied[i].mMaxVersion = sortedBlockEntries[i].mMaxVersion;
+ copied[i].mFlags = sortedBlockEntries[i].mFlags;
+
+ // Copy the module's name to the string buffer and store its offset
+ // in mName.Buffer
+ memcpy(stringBuffer.get() + currentStringOffset,
+ sortedBlockEntries[i].mName.Buffer,
+ sortedBlockEntries[i].mName.Length);
+ copied[i].mName.Buffer =
+ reinterpret_cast<wchar_t*>(stringBufferOffset + currentStringOffset);
+ // Only keep mName.Length and leave mName.MaximumLength to be zero
+ copied[i].mName.Length = sortedBlockEntries[i].mName.Length;
+
+ currentStringOffset += sortedBlockEntries[i].mName.Length;
+ }
+
+ TempFile blocklistFile;
+ nsAutoHandle file(::CreateFileW(blocklistFile, GENERIC_WRITE, FILE_SHARE_READ,
+ nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
+ nullptr));
+ MOZ_RELEASE_ASSERT(file);
+
+ DynamicBlockListBase::FileHeader header;
+ header.mSignature = DynamicBlockListBase::kSignature;
+ header.mFileVersion = DynamicBlockListBase::kCurrentVersion;
+ header.mPayloadSize =
+ sizeof(DllBlockInfo) * copied.length() + totalStringLen.value();
+
+ DWORD written = 0;
+ MOZ_RELEASE_ASSERT(
+ ::WriteFile(file.get(), &header, sizeof(header), &written, nullptr));
+ MOZ_RELEASE_ASSERT(::WriteFile(file.get(), copied.begin(),
+ sizeof(DllBlockInfo) * copied.length(),
+ &written, nullptr));
+ MOZ_RELEASE_ASSERT(::WriteFile(file.get(), stringBuffer.get(),
+ totalStringLen.value(), &written, nullptr));
+
+ DynamicBlockList blockList(blocklistFile);
+ ::DeleteFileW(blocklistFile);
+ return blockList;
+}
+
+const DynamicBlockList gFullList =
+ ConvertStaticBlocklistToDynamic(gWindowsDllBlocklist);
+const DynamicBlockList gShortList =
+ ConvertStaticBlocklistToDynamic(kDllBlocklistShort);
+
+static bool TestDependentModules() {
+ LauncherVoidResult result = gSharedSection.Init();
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::Init failed");
+ return false;
+ }
+
+ constexpr size_t sizeInBytes =
+ SharedSectionTestHelper::GetModulePathArraySize();
+ UniquePtr<uint8_t[]> bufferData = MakeUnique<uint8_t[]>(sizeInBytes);
+ Span<uint8_t> buffer(bufferData, sizeInBytes);
+ memset(buffer.data(), 0x88, buffer.size());
+
+ // Try to add a long string that does not fit in the section,
+ // since there's no room for the NULL character to indicate the final string.
+ UNICODE_STRING ustr;
+ ustr.Buffer = reinterpret_cast<wchar_t*>(buffer.data());
+ ustr.Length = ustr.MaximumLength = buffer.size();
+
+ result = gSharedSection.AddDependentModule(&ustr);
+ if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error(
+ ERROR_INSUFFICIENT_BUFFER)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "Adding a too long string should fail.\n");
+ return false;
+ }
+
+ result = gSharedSection.Init();
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::Init failed");
+ return false;
+ }
+
+ // Keep adding a single-char string until it fails and
+ // make sure no crash.
+ // We want to make sure no strings match any earlier strings so
+ // we can get the expected count. This is a little tricky since
+ // it includes case-insensitivity, so start at the "CJK Unified Ideographs
+ // Extension A" block of Unicode, which has no two characters that compare
+ // equal under a case insensitive comparison.
+ *(reinterpret_cast<wchar_t*>(buffer.data())) = 0x3400;
+ ustr.Length = ustr.MaximumLength = sizeof(wchar_t);
+ wchar_t numberOfStringsAdded = 0;
+ while (gSharedSection.AddDependentModule(&ustr).isOk()) {
+ ++numberOfStringsAdded;
+ // Make sure the string doesn't match any earlier strings
+ wchar_t oldValue = *(reinterpret_cast<wchar_t*>(buffer.data()));
+ *(reinterpret_cast<wchar_t*>(buffer.data())) = oldValue + 1;
+ }
+
+ int numberOfCharactersInBuffer =
+ SharedSectionTestHelper::GetModulePathArraySize() / sizeof(wchar_t);
+ // Each string is two characters long (one "real" character and a null), but
+ // the whole buffer needs an additional null at the end.
+ int expectedNumberOfStringsAdded = (numberOfCharactersInBuffer - 1) / 2;
+ if (numberOfStringsAdded != expectedNumberOfStringsAdded) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "Added %d dependent strings before failing (expected %d).\n",
+ static_cast<int>(numberOfStringsAdded), expectedNumberOfStringsAdded);
+ return false;
+ }
+
+ // SetBlocklist is not allowed after AddDependentModule
+ result = gSharedSection.SetBlocklist(gShortList, false);
+ if (result.isOk() || result.inspectErr() !=
+ WindowsError::FromWin32Error(ERROR_INVALID_STATE)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "SetBlocklist is not allowed after AddDependentModule\n");
+ return false;
+ }
+
+ gSharedSection.Reset();
+ return true;
+}
+
+static bool TestDynamicBlocklist() {
+ if (!gFullList.GetPayloadSize() || !gShortList.GetPayloadSize()) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | DynamicBlockList::LoadFile "
+ "failed\n");
+ return false;
+ }
+
+ LauncherVoidResult result = gSharedSection.Init();
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::Init failed");
+ return false;
+ }
+
+ // Set gShortList, and gShortList
+ // 1. Setting gShortList succeeds
+ // 2. Next try to set gShortList fails
+ result = gSharedSection.SetBlocklist(gShortList, false);
+ if (result.isErr()) {
+ PrintLauncherError(result, "SetBlocklist(gShortList) failed");
+ return false;
+ }
+ result = gSharedSection.SetBlocklist(gShortList, false);
+ if (result.isOk() || result.inspectErr() !=
+ WindowsError::FromWin32Error(ERROR_INVALID_STATE)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "SetBlocklist is allowed only once\n");
+ return false;
+ }
+
+ result = gSharedSection.Init();
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::Init failed");
+ return false;
+ }
+
+ // Add gFullList and gShortList
+ // 1. Adding gFullList always fails because it doesn't fit the section
+ // 2. Adding gShortList succeeds because no entry is added yet
+ MOZ_RELEASE_ASSERT(
+ gFullList.GetPayloadSize() >
+ SharedSectionTestHelper::GetModulePathArraySize(),
+ "Test assumes gFullList is too big to fit in shared section");
+ result = gSharedSection.SetBlocklist(gFullList, false);
+ if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error(
+ ERROR_INSUFFICIENT_BUFFER)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "SetBlocklist(gFullList) should fail\n");
+ return false;
+ }
+ result = gSharedSection.SetBlocklist(gShortList, false);
+ if (result.isErr()) {
+ PrintLauncherError(result, "SetBlocklist(gShortList) failed");
+ return false;
+ }
+
+ // AddDependentModule is allowed after SetBlocklist
+ result = gSharedSection.AddDependentModule(&kTestDependentModuleString);
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::AddDependentModule failed");
+ return false;
+ }
+
+ gSharedSection.Reset();
+ return true;
+}
+
+class ChildProcess final {
+ nsAutoHandle mChildProcess;
+ nsAutoHandle mChildMainThread;
+ DWORD mProcessId;
+
+ public:
+ // The following variables are updated from the parent process via
+ // WriteProcessMemory while the process is suspended as a part of
+ // TestWithChildProcess().
+ //
+ // Having both a non-const and a const is important because a constant
+ // is separately placed in the .rdata section which is read-only, so
+ // the region's attribute needs to be changed before modifying data via
+ // WriteProcessMemory.
+ // The keyword "volatile" is needed for a constant, otherwise the compiler
+ // evaluates a constant as a literal without fetching data from memory.
+ static HMODULE sExecutableImageBase;
+ static volatile const DWORD sReadOnlyProcessId;
+
+ static int Main() {
+ SRWLOCK lock = SRWLOCK_INIT;
+ ::AcquireSRWLockExclusive(&lock);
+
+ Vector<std::thread> threads;
+ std::atomic<bool> success = true;
+ for (int i = 0; i < 10; ++i) {
+ Unused << threads.emplaceBack(
+ [&success](SRWLOCK* aLock) {
+ // All threads call GetKernel32Exports(), but only the first thread
+ // maps a write-copy section and populates it.
+ ::AcquireSRWLockShared(aLock);
+ if (gSharedSection.GetKernel32Exports() == nullptr) {
+ success = false;
+ }
+ ::ReleaseSRWLockShared(aLock);
+ },
+ &lock);
+ }
+
+ // Wait a msec for all threads to be ready and release the lock
+ ::Sleep(1);
+ ::ReleaseSRWLockExclusive(&lock);
+
+ for (auto& thread : threads) {
+ thread.join();
+ }
+
+ if (!success) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "GetKernel32Exports() returned null.\n");
+ return 1;
+ }
+
+ if (sExecutableImageBase != ::GetModuleHandle(nullptr)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "sExecutableImageBase is expected to be %p, but actually was %p.\n",
+ ::GetModuleHandle(nullptr), sExecutableImageBase);
+ return 1;
+ }
+
+ if (sReadOnlyProcessId != ::GetCurrentProcessId()) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "sReadOnlyProcessId is expected to be %lx, but actually was %lx.\n",
+ ::GetCurrentProcessId(), sReadOnlyProcessId);
+ return 1;
+ }
+
+ if (!VerifySharedSection(gSharedSection)) {
+ return 1;
+ }
+
+ // Test a scenario to transfer a transferred section as a readonly handle
+ gSharedSection.ConvertToReadOnly();
+
+ // AddDependentModule fails as the handle is readonly.
+ LauncherVoidResult result =
+ gSharedSection.AddDependentModule(&kTestDependentModuleString);
+ if (result.inspectErr() !=
+ WindowsError::FromWin32Error(ERROR_ACCESS_DENIED)) {
+ PrintLauncherError(result, "The readonly section was writable");
+ return 1;
+ }
+
+ if (!VerifySharedSection(gSharedSection)) {
+ return 1;
+ }
+
+ return 0;
+ }
+
+ ChildProcess(const wchar_t* aExecutable, const wchar_t* aOption)
+ : mProcessId(0) {
+ const wchar_t* childArgv[] = {aExecutable, aOption};
+ auto cmdLine(
+ mozilla::MakeCommandLine(mozilla::ArrayLength(childArgv), childArgv));
+
+ STARTUPINFOW si = {sizeof(si)};
+ PROCESS_INFORMATION pi;
+ BOOL ok =
+ ::CreateProcessW(aExecutable, cmdLine.get(), nullptr, nullptr, FALSE,
+ CREATE_SUSPENDED, nullptr, nullptr, &si, &pi);
+ if (!ok) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "CreateProcessW falied - %08lx.\n",
+ GetLastError());
+ return;
+ }
+
+ mProcessId = pi.dwProcessId;
+
+ mChildProcess.own(pi.hProcess);
+ mChildMainThread.own(pi.hThread);
+ }
+
+ ~ChildProcess() { ::TerminateProcess(mChildProcess, 0); }
+
+ operator HANDLE() const { return mChildProcess; }
+ DWORD GetProcessId() const { return mProcessId; }
+
+ bool ResumeAndWaitUntilExit() {
+ if (::ResumeThread(mChildMainThread) == 0xffffffff) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "ResumeThread failed - %08lx\n",
+ GetLastError());
+ return false;
+ }
+
+ if (::WaitForSingleObject(mChildProcess, 60000) != WAIT_OBJECT_0) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "Unexpected result from WaitForSingleObject\n");
+ return false;
+ }
+
+ DWORD exitCode;
+ if (!::GetExitCodeProcess(mChildProcess, &exitCode)) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "GetExitCodeProcess failed - %08lx\n",
+ GetLastError());
+ return false;
+ }
+
+ return exitCode == 0;
+ }
+};
+
+HMODULE ChildProcess::sExecutableImageBase = 0;
+volatile const DWORD ChildProcess::sReadOnlyProcessId = 0;
+
+int wmain(int argc, wchar_t* argv[]) {
+ printf("Process: %-8lx Base: %p\n", ::GetCurrentProcessId(),
+ ::GetModuleHandle(nullptr));
+
+ if (argc == 2 && wcscmp(argv[1], kChildArg) == 0) {
+ return ChildProcess::Main();
+ }
+
+ ChildProcess childProcess(argv[0], kChildArg);
+ if (!childProcess) {
+ return 1;
+ }
+
+ if (!TestAddString()) {
+ return 1;
+ }
+
+ if (!TestDependentModules()) {
+ return 1;
+ }
+
+ if (!TestDynamicBlocklist()) {
+ return 1;
+ }
+
+ LauncherResult<HMODULE> remoteImageBase =
+ nt::GetProcessExeModule(childProcess);
+ if (remoteImageBase.isErr()) {
+ PrintLauncherError(remoteImageBase, "nt::GetProcessExeModule failed");
+ return 1;
+ }
+
+ nt::CrossExecTransferManager transferMgr(childProcess);
+ if (!transferMgr) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "CrossExecTransferManager instantiation failed.\n");
+ return 1;
+ }
+
+ LauncherVoidResult result =
+ transferMgr.Transfer(&ChildProcess::sExecutableImageBase,
+ &remoteImageBase.inspect(), sizeof(HMODULE));
+ if (result.isErr()) {
+ PrintLauncherError(result, "ChildProcess::WriteData(Imagebase) failed");
+ return 1;
+ }
+
+ DWORD childPid = childProcess.GetProcessId();
+
+ DWORD* readOnlyData = const_cast<DWORD*>(&ChildProcess::sReadOnlyProcessId);
+ result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
+ if (result.isOk()) {
+ printf(
+ "TEST-UNEXPECTED | TestCrossProcessWin | "
+ "A constant was located in a writable section.");
+ return 1;
+ }
+
+ AutoVirtualProtect prot =
+ transferMgr.Protect(readOnlyData, sizeof(uint32_t), PAGE_READWRITE);
+ if (!prot) {
+ printf(
+ "TEST-FAILED | TestCrossProcessWin | "
+ "VirtualProtect failed - %08lx\n",
+ prot.GetError().AsHResult());
+ return 1;
+ }
+
+ result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
+ if (result.isErr()) {
+ PrintLauncherError(result, "ChildProcess::WriteData(PID) failed");
+ return 1;
+ }
+
+ result = gSharedSection.Init();
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::Init failed");
+ return 1;
+ }
+
+ result = gSharedSection.SetBlocklist(gShortList, false);
+ if (result.isErr()) {
+ PrintLauncherError(result, "SetBlocklist(gShortList) failed");
+ return false;
+ }
+
+ for (const char* testString : kTestDependentModulePaths) {
+ // Test AllocatedUnicodeString(const char*) that is used
+ // in IsDependentModule()
+ nt::AllocatedUnicodeString depModule(testString);
+ UNICODE_STRING depModuleLeafName;
+ nt::GetLeafName(&depModuleLeafName, depModule);
+ result = gSharedSection.AddDependentModule(&depModuleLeafName);
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::AddDependentModule failed");
+ return 1;
+ }
+ }
+
+ result =
+ gSharedSection.TransferHandle(transferMgr, GENERIC_READ | GENERIC_WRITE);
+ if (result.isErr()) {
+ PrintLauncherError(result, "SharedSection::TransferHandle failed");
+ return 1;
+ }
+
+ // Close the section in the parent process before resuming the child process
+ gSharedSection.Reset(nullptr);
+
+ if (!childProcess.ResumeAndWaitUntilExit()) {
+ return 1;
+ }
+
+ printf("TEST-PASS | TestCrossProcessWin | All checks passed\n");
+ return 0;
+}
diff --git a/browser/app/winlauncher/test/TestSafeThreadLocal.cpp b/browser/app/winlauncher/test/TestSafeThreadLocal.cpp
new file mode 100644
index 0000000000..31af93c375
--- /dev/null
+++ b/browser/app/winlauncher/test/TestSafeThreadLocal.cpp
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#include "freestanding/SafeThreadLocal.h"
+
+#include "mozilla/NativeNt.h"
+#include "nsWindowsHelpers.h"
+
+#include <process.h>
+#include <stdio.h>
+
+// Need a non-inline function to bypass compiler optimization that the thread
+// local storage pointer is cached in a register before accessing a thread-local
+// variable.
+MOZ_NEVER_INLINE PVOID SwapThreadLocalStoragePointer(PVOID aNewValue) {
+ auto oldValue = mozilla::nt::RtlGetThreadLocalStoragePointer();
+ mozilla::nt::RtlSetThreadLocalStoragePointerForTestingOnly(aNewValue);
+ return oldValue;
+}
+
+static mozilla::freestanding::SafeThreadLocal<int*> gTheStorage;
+
+// Need non-inline functions to bypass compiler optimization that the thread
+// local storage pointer is cached in a register before accessing a thread-local
+// variable. See bug 1803322 for a motivating example.
+MOZ_NEVER_INLINE int* getTheStorage() { return gTheStorage.get(); }
+MOZ_NEVER_INLINE void setTheStorage(int* p) { gTheStorage.set(p); }
+
+static unsigned int __stdcall TestNonMainThread(void* aArg) {
+ for (int i = 0; i < 100; ++i) {
+ setTheStorage(&i);
+ if (getTheStorage() != &i) {
+ printf(
+ "TEST-FAILED | TestSafeThreadLocal | "
+ "A value is not correctly stored in the thread-local storage.\n");
+ return 1;
+ }
+ }
+ return 0;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ int dummy = 0x1234;
+
+ auto origHead = SwapThreadLocalStoragePointer(nullptr);
+ // Setting gTheStorage when TLS is null.
+ setTheStorage(&dummy);
+ SwapThreadLocalStoragePointer(origHead);
+
+ nsAutoHandle handles[8];
+ for (auto& handle : handles) {
+ handle.own(reinterpret_cast<HANDLE>(
+ ::_beginthreadex(nullptr, 0, TestNonMainThread, nullptr, 0, nullptr)));
+ }
+
+ for (int i = 0; i < 100; ++i) {
+ if (getTheStorage() != &dummy) {
+ printf(
+ "TEST-FAILED | TestSafeThreadLocal | "
+ "A value is not correctly stored in the global scope.\n");
+ return 1;
+ }
+ }
+
+ for (auto& handle : handles) {
+ ::WaitForSingleObject(handle, INFINITE);
+
+#if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ DWORD exitCode;
+ if (!::GetExitCodeThread(handle, &exitCode) || exitCode) {
+ return 1;
+ }
+#endif // !defined(MOZ_ASAN)
+ }
+
+ return 0;
+}
diff --git a/browser/app/winlauncher/test/TestSameBinary.cpp b/browser/app/winlauncher/test/TestSameBinary.cpp
new file mode 100644
index 0000000000..2cb45f546f
--- /dev/null
+++ b/browser/app/winlauncher/test/TestSameBinary.cpp
@@ -0,0 +1,255 @@
+/* -*- 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/. */
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <utility>
+
+#include "SameBinary.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsWindowsHelpers.h"
+
+#define EXPECT_SAMEBINARY_IS(expected, option, message) \
+ do { \
+ mozilla::LauncherResult<bool> isSame = \
+ mozilla::IsSameBinaryAsParentProcess(option); \
+ if (isSame.isErr()) { \
+ PrintLauncherError(isSame, \
+ "IsSameBinaryAsParentProcess returned error " \
+ "when we were expecting success."); \
+ return 1; \
+ } \
+ if (isSame.unwrap() != expected) { \
+ PrintErrorMsg(message); \
+ return 1; \
+ } \
+ } while (0)
+
+/**
+ * This test involves three processes:
+ * 1. The "Monitor" process, which is executed by |MonitorMain|. This process
+ * is responsible for integrating with the test harness, so it spawns the
+ * "Parent" process (2), and then waits for the other two processes to
+ * finish.
+ * 2. The "Parent" process, which is executed by |ParentMain|. This process
+ * creates the "Child" process (3) and then waits indefinitely.
+ * 3. The "Child" process, which is executed by |ChildMain| and carries out
+ * the actual test. It terminates the Parent process during its execution,
+ * using the Child PID as the Parent process's exit code. This serves as a
+ * hacky yet effective way to signal to the Monitor process which PID it
+ * should wait on to ensure that the Child process has exited.
+ */
+
+static const char kMsgStart[] = "TEST-FAILED | SameBinary | ";
+
+inline void PrintErrorMsg(const char* aMsg) {
+ printf("%s%s\n", kMsgStart, aMsg);
+}
+
+inline void PrintWinError(const char* aMsg) {
+ mozilla::WindowsError err(mozilla::WindowsError::FromLastError());
+ printf("%s%s: %S\n", kMsgStart, aMsg, err.AsString().get());
+}
+
+template <typename T>
+inline void PrintLauncherError(const mozilla::LauncherResult<T>& aResult,
+ const char* aMsg = nullptr) {
+ const char* const kSep = aMsg ? ": " : "";
+ const char* msg = aMsg ? aMsg : "";
+ const mozilla::LauncherError& err = aResult.inspectErr();
+ printf("%s%s%s%S (%s:%d)\n", kMsgStart, msg, kSep,
+ err.mError.AsString().get(), err.mFile, err.mLine);
+}
+
+static int ChildMain(DWORD aExpectedParentPid) {
+ mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
+ if (parentPid.isErr()) {
+ PrintLauncherError(parentPid);
+ return 1;
+ }
+
+ if (parentPid.inspect() != aExpectedParentPid) {
+ PrintErrorMsg("Unexpected mismatch of parent PIDs");
+ return 1;
+ }
+
+ const DWORD kAccess = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
+ nsAutoHandle parentProcess(
+ ::OpenProcess(kAccess, FALSE, parentPid.inspect()));
+ if (!parentProcess) {
+ PrintWinError("Unexpectedly failed to call OpenProcess on parent");
+ return 1;
+ }
+
+ EXPECT_SAMEBINARY_IS(
+ true, mozilla::ImageFileCompareOption::Default,
+ "IsSameBinaryAsParentProcess returned incorrect result for identical "
+ "binaries");
+ EXPECT_SAMEBINARY_IS(
+ true, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
+ "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
+ "result for identical binaries");
+
+ // Total hack, but who cares? We'll set the parent's exit code as our PID
+ // so that the monitor process knows who to wait for!
+ if (!::TerminateProcess(parentProcess.get(), ::GetCurrentProcessId())) {
+ PrintWinError("Unexpected failure in TerminateProcess");
+ return 1;
+ }
+
+ // Close our handle to the parent process so that no references are held.
+ ::CloseHandle(parentProcess.disown());
+
+ // Querying a pid on a terminated process may still succeed some time after
+ // that process has been terminated. For the purposes of this test, we'll poll
+ // the OS until we cannot succesfully open the parentPid anymore.
+ const uint32_t kMaxAttempts = 100;
+ uint32_t curAttempt = 0;
+ while (HANDLE p = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
+ parentPid.inspect())) {
+ ::CloseHandle(p);
+ ::Sleep(100);
+ ++curAttempt;
+ if (curAttempt >= kMaxAttempts) {
+ PrintErrorMsg(
+ "Exhausted retry attempts waiting for parent pid to become invalid");
+ return 1;
+ }
+ }
+
+ EXPECT_SAMEBINARY_IS(
+ false, mozilla::ImageFileCompareOption::Default,
+ "IsSameBinaryAsParentProcess returned incorrect result for dead parent "
+ "process");
+ EXPECT_SAMEBINARY_IS(
+ false, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
+ "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
+ "result for dead parent process");
+
+ return 0;
+}
+
+static nsReturnRef<HANDLE> CreateSelfProcess(int argc, wchar_t* argv[]) {
+ nsAutoHandle empty;
+
+ DWORD myPid = ::GetCurrentProcessId();
+
+ wchar_t strPid[11] = {};
+#if defined(__MINGW32__)
+ _ultow(myPid, strPid, 16);
+#else
+ if (_ultow_s(myPid, strPid, 16)) {
+ PrintErrorMsg("_ultow_s failed");
+ return empty.out();
+ }
+#endif // defined(__MINGW32__)
+
+ wchar_t* extraArgs[] = {strPid};
+
+ auto cmdLine = mozilla::MakeCommandLine(
+ argc, argv, mozilla::ArrayLength(extraArgs), extraArgs);
+ if (!cmdLine) {
+ PrintErrorMsg("MakeCommandLine failed");
+ return empty.out();
+ }
+
+ STARTUPINFOW si = {sizeof(si)};
+ PROCESS_INFORMATION pi;
+ BOOL ok =
+ ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
+ CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi);
+ if (!ok) {
+ PrintWinError("CreateProcess failed");
+ return empty.out();
+ }
+
+ nsAutoHandle proc(pi.hProcess);
+ nsAutoHandle thd(pi.hThread);
+
+ return proc.out();
+}
+
+static int ParentMain(int argc, wchar_t* argv[]) {
+ nsAutoHandle childProc(CreateSelfProcess(argc, argv));
+ if (!childProc) {
+ return 1;
+ }
+
+ if (::WaitForSingleObject(childProc.get(), INFINITE) != WAIT_OBJECT_0) {
+ PrintWinError(
+ "Unexpected result from WaitForSingleObject on child process");
+ return 1;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("This process should be terminated by now");
+ return 0;
+}
+
+static int MonitorMain(int argc, wchar_t* argv[]) {
+ // In this process, "parent" means the process that will be running
+ // ParentMain, which is our child process (confusing, I know...)
+ nsAutoHandle parentProc(CreateSelfProcess(argc, argv));
+ if (!parentProc) {
+ return 1;
+ }
+
+ if (::WaitForSingleObject(parentProc.get(), 60000) != WAIT_OBJECT_0) {
+ PrintWinError("Unexpected result from WaitForSingleObject on parent");
+ return 1;
+ }
+
+ DWORD childPid;
+ if (!::GetExitCodeProcess(parentProc.get(), &childPid)) {
+ PrintWinError("GetExitCodeProcess failed");
+ return 1;
+ }
+
+ nsAutoHandle childProc(::OpenProcess(SYNCHRONIZE, FALSE, childPid));
+ if (!childProc) {
+ // Nothing to wait on anymore, which is OK.
+ return 0;
+ }
+
+ // We want no more references to parentProc
+ ::CloseHandle(parentProc.disown());
+
+ if (::WaitForSingleObject(childProc.get(), 60000) != WAIT_OBJECT_0) {
+ PrintWinError("Unexpected result from WaitForSingleObject on child");
+ return 1;
+ }
+
+ return 0;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ if (argc == 3) {
+ return ChildMain(wcstoul(argv[2], nullptr, 16));
+ }
+
+ if (!mozilla::SetArgv0ToFullBinaryPath(argv)) {
+ return 1;
+ }
+
+ if (argc == 1) {
+ return MonitorMain(argc, argv);
+ }
+
+ if (argc == 2) {
+ return ParentMain(argc, argv);
+ }
+
+ PrintErrorMsg("Unexpected argc");
+ return 1;
+}
diff --git a/browser/app/winlauncher/test/moz.build b/browser/app/winlauncher/test/moz.build
new file mode 100644
index 0000000000..a8503ddf55
--- /dev/null
+++ b/browser/app/winlauncher/test/moz.build
@@ -0,0 +1,30 @@
+# -*- 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/.
+
+DisableStlWrapping()
+
+GeckoCppUnitTests(
+ [
+ "TestCrossProcessWin",
+ "TestSafeThreadLocal",
+ "TestSameBinary",
+ ],
+ linkage=None,
+)
+
+LOCAL_INCLUDES += [
+ "/browser/app/winlauncher",
+]
+
+OS_LIBS += [
+ "ntdll",
+]
+
+if CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]