forked from mirrors/gecko-dev
		
	This patch is to improve the way to detect an injected dependent module for automatic DLL blocking (bug 1659438). In the previous version, we created a list of dependent modules in the launcher process and shared it with other processes via the shared section. However, it was not compatible with third-party applications who tamper the Import Table and revert it in the injected module's DllMain (bug 1682834) because we parsed the Import Table in the launcher process after it was reverted. With this patch, we check the Import Table in `patched_NtMapViewOfSection`, so we can see tampering before it's reverted. More specifically, we create a list of dependent modules in the browser process as below. 1. The launcher process creates a section object and initializes the kernel32.dll's functions in it. 2. The launcher process transfers a writable handle of the shared section to the browser process. 3. In the browser process, if an injected dependent module is being mapped by `NtMapViewOfSection`, we add its NT path to the shared section and block it with `REDIRECT_TO_NOOP_ENTRYPOINT`. 4. The `main` function of the browser process converts the writable handle of the shared section into a readonly handle. 5. The browser process transfers a readonly handle of the shared section to a sandbox process. Since automatic DLL blocking may still cause a compat issue like bug 1682304, we activate it only in Nightly for now. Differential Revision: https://phabricator.services.mozilla.com/D101460
		
			
				
	
	
		
			365 lines
		
	
	
	
		
			11 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			365 lines
		
	
	
	
		
			11 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/. */
 | 
						|
 | 
						|
#define MOZ_USE_LAUNCHER_ERROR
 | 
						|
 | 
						|
#include "freestanding/SharedSection.cpp"
 | 
						|
#include "mozilla/CmdLineAndEnvUtils.h"
 | 
						|
#include "mozilla/NativeNt.h"
 | 
						|
 | 
						|
const wchar_t kChildArg[] = L"--child";
 | 
						|
const char kTestStrings[][6] = {
 | 
						|
    "a b c", "A B C", "a b c", "A B C", "X Y Z",
 | 
						|
};
 | 
						|
const wchar_t kTestStringsMerged[] =
 | 
						|
    L"a b c"
 | 
						|
    L"\0"
 | 
						|
    L"X Y Z"
 | 
						|
    L"\0";
 | 
						|
 | 
						|
using namespace mozilla;
 | 
						|
using namespace mozilla::freestanding;
 | 
						|
 | 
						|
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) {
 | 
						|
  LauncherResult<SharedSection::Layout*> resultView = aSharedSection.GetView();
 | 
						|
  if (resultView.isErr()) {
 | 
						|
    PrintLauncherError(resultView, "Failed to map a shared section");
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  SharedSection::Layout* view = resultView.unwrap();
 | 
						|
 | 
						|
  // Use a local variable of RTL_RUN_ONCE to resolve Kernel32Exports every time
 | 
						|
  RTL_RUN_ONCE sRunEveryTime = RTL_RUN_ONCE_INIT;
 | 
						|
  view->mK32Exports.Resolve(sRunEveryTime);
 | 
						|
 | 
						|
  HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll");
 | 
						|
  VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, FlushInstructionCache);
 | 
						|
  VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, GetModuleHandleW);
 | 
						|
  VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, GetSystemInfo);
 | 
						|
  VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, VirtualProtect);
 | 
						|
 | 
						|
  bool matched = memcmp(view->mModulePathArray, kTestStringsMerged,
 | 
						|
                        sizeof(kTestStringsMerged)) == 0;
 | 
						|
  if (!matched) {
 | 
						|
    // Print actual strings on error
 | 
						|
    for (const wchar_t* p = view->mModulePathArray; *p;) {
 | 
						|
      printf("%p: %ls\n", p, p);
 | 
						|
      while (*p) {
 | 
						|
        ++p;
 | 
						|
      }
 | 
						|
      ++p;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  return true;
 | 
						|
}
 | 
						|
 | 
						|
static bool TestAddString() {
 | 
						|
  wchar_t testBuffer[3] = {0};
 | 
						|
  UNICODE_STRING ustr;
 | 
						|
 | 
						|
  // This makes |testBuffer| full.
 | 
						|
  ::RtlInitUnicodeString(&ustr, L"a");
 | 
						|
  if (!AddString(testBuffer, sizeof(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, sizeof(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;
 | 
						|
}
 | 
						|
 | 
						|
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() {
 | 
						|
    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;
 | 
						|
    }
 | 
						|
 | 
						|
    auto getDependentModulePaths =
 | 
						|
        reinterpret_cast<const wchar_t (*)()>(::GetProcAddress(
 | 
						|
            ::GetModuleHandleW(nullptr), "GetDependentModulePaths"));
 | 
						|
    if (!getDependentModulePaths) {
 | 
						|
      printf(
 | 
						|
          "TEST-FAILED | TestCrossProcessWin | "
 | 
						|
          "Failed to get a pointer to GetDependentModulePaths - %08lx.\n",
 | 
						|
          ::GetLastError());
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
 | 
						|
#if !defined(DEBUG)
 | 
						|
    // GetDependentModulePaths does not allow a caller other than xul.dll.
 | 
						|
    // Skip on Debug build because it hits MOZ_ASSERT.
 | 
						|
    if (getDependentModulePaths()) {
 | 
						|
      printf(
 | 
						|
          "TEST-FAILED | TestCrossProcessWin | "
 | 
						|
          "GetDependentModulePaths should return zero if the caller is "
 | 
						|
          "not xul.dll.\n");
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
#endif  // !defined(DEBUG)
 | 
						|
 | 
						|
    if (!VerifySharedSection(gSharedSection)) {
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    // Test a scenario to transfer a transferred section as a readonly handle
 | 
						|
    static HANDLE copiedHandle = nullptr;
 | 
						|
    nt::CrossExecTransferManager tansferToSelf(::GetCurrentProcess());
 | 
						|
    LauncherVoidResult result = gSharedSection.TransferHandle(
 | 
						|
        tansferToSelf, GENERIC_READ, &copiedHandle);
 | 
						|
    if (result.isErr()) {
 | 
						|
      PrintLauncherError(result, "SharedSection::TransferHandle(self) failed");
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    gSharedSection.Reset(copiedHandle);
 | 
						|
 | 
						|
    UNICODE_STRING ustr;
 | 
						|
    ::RtlInitUnicodeString(&ustr, L"test");
 | 
						|
    result = gSharedSection.AddDepenentModule(&ustr);
 | 
						|
    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;
 | 
						|
  }
 | 
						|
 | 
						|
  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;
 | 
						|
  }
 | 
						|
 | 
						|
  {
 | 
						|
    // Define a scope for |sharedSection| to resume the child process after
 | 
						|
    // the section is deleted in the parent process.
 | 
						|
    result = gSharedSection.Init(transferMgr.LocalPEHeaders());
 | 
						|
    if (result.isErr()) {
 | 
						|
      PrintLauncherError(result, "SharedSection::Init failed");
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    for (const auto& testString : kTestStrings) {
 | 
						|
      nt::AllocatedUnicodeString ustr(testString);
 | 
						|
      result = gSharedSection.AddDepenentModule(ustr);
 | 
						|
      if (result.isErr()) {
 | 
						|
        PrintLauncherError(result, "SharedSection::AddDepenentModule failed");
 | 
						|
        return 1;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    result = gSharedSection.TransferHandle(transferMgr,
 | 
						|
                                           GENERIC_READ | GENERIC_WRITE);
 | 
						|
    if (result.isErr()) {
 | 
						|
      PrintLauncherError(result, "SharedSection::TransferHandle failed");
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  if (!childProcess.ResumeAndWaitUntilExit()) {
 | 
						|
    return 1;
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 |