browser/app/winlauncher/freestanding/LoaderPrivateAPI.cpp
author Toshihito Kikuchi <tkikuchi@mozilla.com>
Fri, 15 Nov 2019 22:53:49 +0000
changeset 502308 edafd8d2b8c67977bd4658891c1c7063ff9c5b83
parent 494303 d156dc595b6904cf7afbee891571e844eb3e9515
permissions -rw-r--r--
Bug 1587642 - Make the blocklist work when the process heap is not initialized. r=aklotz `patched_NtMapViewOfSection` uses the process default heap to copy a string. However, `patched_NtMapViewOfSection` can be invoked even before the process heap is initialized. One example we found is Windows Defender's EAF, with which "verifier.dll" is loaded before the process heap is initialized. This patch adds a check whether the heap is initialized or not in `patched_NtMapViewOfSection` and `NativeNtBlockSet::Add`. This also minimizes the usage of the heap, i.e. not copying a string when we block a dll. Differential Revision: https://phabricator.services.mozilla.com/D51028

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

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::MemorySectionNameBuf GetSectionNameBuffer(void* aSectionAddr) 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;
};

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::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