forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			237 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			237 lines
		
	
	
	
		
			9.1 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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
 | 
