summaryrefslogtreecommitdiffstats
path: root/browser/app/winlauncher/DllBlocklistInit.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'browser/app/winlauncher/DllBlocklistInit.cpp')
-rw-r--r--browser/app/winlauncher/DllBlocklistInit.cpp237
1 files changed, 237 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