/* -*- 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
