tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SharedSection.cpp (12887B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      6 
      7 #include "SharedSection.h"
      8 
      9 #include <algorithm>
     10 #include "CheckForCaller.h"
     11 #include "mozilla/BinarySearch.h"
     12 
     13 namespace {
     14 
     15 bool AddString(mozilla::Span<wchar_t> aBuffer, const UNICODE_STRING& aStr) {
     16  size_t offsetElements = 0;
     17  while (offsetElements < aBuffer.Length()) {
     18    UNICODE_STRING uniStr;
     19    ::RtlInitUnicodeString(&uniStr, aBuffer.data() + offsetElements);
     20 
     21    if (uniStr.Length == 0) {
     22      // Reached to the array's last item.
     23      break;
     24    }
     25 
     26    if (::RtlCompareUnicodeString(&uniStr, &aStr, TRUE) == 0) {
     27      // Already included in the array.
     28      return true;
     29    }
     30 
     31    // Go to the next string.
     32    offsetElements += uniStr.MaximumLength / sizeof(wchar_t);
     33  }
     34 
     35  // Ensure enough space including the last empty string at the end.
     36  if (offsetElements * sizeof(wchar_t) + aStr.Length + sizeof(wchar_t) +
     37          sizeof(wchar_t) >
     38      aBuffer.LengthBytes()) {
     39    return false;
     40  }
     41 
     42  auto newStr = aBuffer.Subspan(offsetElements);
     43  memcpy(newStr.data(), aStr.Buffer, aStr.Length);
     44  memset(newStr.data() + aStr.Length / sizeof(wchar_t), 0, sizeof(wchar_t));
     45  return true;
     46 }
     47 
     48 }  // anonymous namespace
     49 
     50 namespace mozilla {
     51 namespace freestanding {
     52 
     53 SharedSection gSharedSection;
     54 
     55 // Why don't we use ::GetProcAddress?
     56 // If the export table of kernel32.dll is tampered in the current process,
     57 // we cannot transfer an RVA because the function pointed by the RVA may not
     58 // exist in a target process.
     59 // We can use ::GetProcAddress with additional check to detect tampering, but
     60 // FindExportAddressTableEntry fits perfectly here because it returns nullptr
     61 // if the target entry is outside the image, which means it's tampered or
     62 // forwarded to another DLL.
     63 #define INIT_FUNCTION(exports, name)                                 \
     64  do {                                                               \
     65    auto rvaToFunction = exports.FindExportAddressTableEntry(#name); \
     66    if (!rvaToFunction) {                                            \
     67      return;                                                        \
     68    }                                                                \
     69    m##name = reinterpret_cast<decltype(m##name)>(*rvaToFunction);   \
     70  } while (0)
     71 
     72 #define RESOLVE_FUNCTION(base, name)             \
     73  m##name = reinterpret_cast<decltype(m##name)>( \
     74      base + reinterpret_cast<uintptr_t>(m##name))
     75 
     76 void Kernel32ExportsSolver::Init() {
     77  interceptor::MMPolicyInProcess policy;
     78  auto k32Exports = nt::PEExportSection<interceptor::MMPolicyInProcess>::Get(
     79      ::GetModuleHandleW(L"kernel32.dll"), policy);
     80  if (!k32Exports) {
     81    return;
     82  }
     83 
     84  // Please make sure these functions are not forwarded to another DLL.
     85  INIT_FUNCTION(k32Exports, FlushInstructionCache);
     86  INIT_FUNCTION(k32Exports, GetModuleHandleW);
     87  INIT_FUNCTION(k32Exports, GetSystemInfo);
     88  INIT_FUNCTION(k32Exports, VirtualProtect);
     89 }
     90 
     91 bool Kernel32ExportsSolver::Resolve() {
     92  const UNICODE_STRING k32Name = MOZ_LITERAL_UNICODE_STRING(L"kernel32.dll");
     93 
     94  // We cannot use GetModuleHandleW because this code can be called
     95  // before IAT is resolved.
     96  auto k32Module = nt::GetModuleHandleFromLeafName(k32Name);
     97  if (k32Module.isErr()) {
     98    // Probably this is called before kernel32.dll is loaded.
     99    return false;
    100  }
    101 
    102  uintptr_t k32Base =
    103      nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(k32Module.unwrap());
    104 
    105  RESOLVE_FUNCTION(k32Base, FlushInstructionCache);
    106  RESOLVE_FUNCTION(k32Base, GetModuleHandleW);
    107  RESOLVE_FUNCTION(k32Base, GetSystemInfo);
    108  RESOLVE_FUNCTION(k32Base, VirtualProtect);
    109 
    110  return true;
    111 }
    112 
    113 HANDLE SharedSection::sSectionHandle = nullptr;
    114 SharedSection::Layout* SharedSection::sWriteCopyView = nullptr;
    115 RTL_RUN_ONCE SharedSection::sEnsureOnce = RTL_RUN_ONCE_INIT;
    116 nt::SRWLock SharedSection::sLock;
    117 
    118 void SharedSection::Reset(HANDLE aNewSectionObject) {
    119  nt::AutoExclusiveLock{sLock};
    120  if (sWriteCopyView) {
    121    nt::AutoMappedView view(sWriteCopyView);
    122    sWriteCopyView = nullptr;
    123    ::RtlRunOnceInitialize(&sEnsureOnce);
    124  }
    125 
    126  if (sSectionHandle != aNewSectionObject) {
    127    if (sSectionHandle) {
    128      ::CloseHandle(sSectionHandle);
    129    }
    130    sSectionHandle = aNewSectionObject;
    131  }
    132 }
    133 
    134 void SharedSection::ConvertToReadOnly() {
    135  if (!sSectionHandle) {
    136    return;
    137  }
    138 
    139  HANDLE readonlyHandle;
    140  if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
    141                         nt::kCurrentProcess, &readonlyHandle, GENERIC_READ,
    142                         FALSE, 0)) {
    143    return;
    144  }
    145 
    146  Reset(readonlyHandle);
    147 }
    148 
    149 LauncherVoidResult SharedSection::Init() {
    150  static_assert(
    151      kSharedViewSize >= sizeof(Layout),
    152      "kSharedViewSize is too small to represent SharedSection::Layout.");
    153 
    154  HANDLE section =
    155      ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
    156                           kSharedViewSize, nullptr);
    157  if (!section) {
    158    return LAUNCHER_ERROR_FROM_LAST();
    159  }
    160  Reset(section);
    161 
    162  // The initial contents of the pages in a file mapping object backed by
    163  // the operating system paging file are 0 (zero).  No need to zero it out
    164  // ourselves.
    165  // https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw
    166  nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
    167  if (!writableView) {
    168    return LAUNCHER_ERROR_FROM_LAST();
    169  }
    170 
    171  Layout* view = writableView.as<Layout>();
    172  view->mK32Exports.Init();
    173  view->mState = Layout::State::kInitialized;
    174  // Leave view->mDependentModulePathArrayStart to be zero to indicate
    175  // we can add blocklist entries
    176  return Ok();
    177 }
    178 
    179 LauncherVoidResult SharedSection::AddDependentModule(PCUNICODE_STRING aNtPath) {
    180  nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
    181  if (!writableView) {
    182    return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error());
    183  }
    184 
    185  Layout* view = writableView.as<Layout>();
    186  if (!view->mDependentModulePathArrayStart) {
    187    // This is the first time AddDependentModule is called.  We set the initial
    188    // value to mDependentModulePathArrayStart, which *closes* the blocklist.
    189    // After this, AddBlocklist is no longer allowed.
    190    view->mDependentModulePathArrayStart =
    191        FIELD_OFFSET(Layout, mFirstBlockEntry) + sizeof(DllBlockInfo);
    192  }
    193 
    194  if (!AddString(view->GetDependentModules(), *aNtPath)) {
    195    return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
    196  }
    197 
    198  return Ok();
    199 }
    200 
    201 LauncherVoidResult SharedSection::SetBlocklist(
    202    const DynamicBlockList& aBlocklist, bool isDisabled) {
    203  if (!aBlocklist.GetPayloadSize()) {
    204    return Ok();
    205  }
    206 
    207  nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
    208  if (!writableView) {
    209    return LAUNCHER_ERROR_FROM_WIN32(::RtlGetLastWin32Error());
    210  }
    211 
    212  Layout* view = writableView.as<Layout>();
    213  if (view->mDependentModulePathArrayStart > 0) {
    214    // If the dependent module array is already available, we must not update
    215    // the blocklist.
    216    return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_STATE);
    217  }
    218 
    219  view->mBlocklistIsDisabled = isDisabled ? 1 : 0;
    220  uintptr_t bufferEnd = reinterpret_cast<uintptr_t>(view) + kSharedViewSize;
    221  size_t bytesCopied = aBlocklist.CopyTo(
    222      view->mFirstBlockEntry,
    223      bufferEnd - reinterpret_cast<uintptr_t>(view->mFirstBlockEntry));
    224  if (!bytesCopied) {
    225    return LAUNCHER_ERROR_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER);
    226  }
    227 
    228  // Setting mDependentModulePathArrayStart to a non-zero value means
    229  // we no longer accept blocklist entries
    230  // Just to be safe, make sure we don't overwrite mFirstBlockEntry even
    231  // if there are no entries.
    232  view->mDependentModulePathArrayStart =
    233      FIELD_OFFSET(Layout, mFirstBlockEntry) +
    234      std::max(bytesCopied, sizeof(DllBlockInfo));
    235  return Ok();
    236 }
    237 
    238 /* static */
    239 ULONG NTAPI SharedSection::EnsureWriteCopyViewOnce(PRTL_RUN_ONCE, PVOID,
    240                                                   PVOID*) {
    241  if (!sWriteCopyView) {
    242    nt::AutoMappedView view(sSectionHandle, PAGE_WRITECOPY);
    243    if (!view) {
    244      return TRUE;
    245    }
    246    sWriteCopyView = view.as<Layout>();
    247    view.release();
    248  }
    249  return sWriteCopyView->Resolve() ? TRUE : FALSE;
    250 }
    251 
    252 SharedSection::Layout* SharedSection::EnsureWriteCopyView(
    253    bool requireKernel32Exports /*= false */) {
    254  ::RtlRunOnceExecuteOnce(&sEnsureOnce, &EnsureWriteCopyViewOnce, nullptr,
    255                          nullptr);
    256  if (!sWriteCopyView) {
    257    return nullptr;
    258  }
    259  auto requiredState = requireKernel32Exports
    260                           ? Layout::State::kResolved
    261                           : Layout::State::kLoadedDynamicBlocklistEntries;
    262  return sWriteCopyView->mState >= requiredState ? sWriteCopyView : nullptr;
    263 }
    264 
    265 bool SharedSection::Layout::Resolve() {
    266  if (mState == State::kResolved) {
    267    return true;
    268  }
    269  if (mState == State::kUninitialized) {
    270    return false;
    271  }
    272  if (mState == State::kInitialized) {
    273    if (!mNumBlockEntries) {
    274      uintptr_t arrayBase = reinterpret_cast<uintptr_t>(mFirstBlockEntry);
    275      uint32_t numEntries = 0;
    276      for (DllBlockInfo* entry = mFirstBlockEntry;
    277           entry->mName.Length && numEntries < GetMaxNumBlockEntries();
    278           ++entry) {
    279        entry->mName.Buffer = reinterpret_cast<wchar_t*>(
    280            arrayBase + reinterpret_cast<uintptr_t>(entry->mName.Buffer));
    281        ++numEntries;
    282      }
    283      mNumBlockEntries = numEntries;
    284      // Sort by name so that we can binary-search
    285      std::sort(mFirstBlockEntry, mFirstBlockEntry + mNumBlockEntries,
    286                [](const DllBlockInfo& a, const DllBlockInfo& b) {
    287                  return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) <
    288                         0;
    289                });
    290    }
    291    mState = State::kLoadedDynamicBlocklistEntries;
    292  }
    293 
    294  if (!mK32Exports.Resolve()) {
    295    return false;
    296  }
    297 
    298  mState = State::kResolved;
    299  return true;
    300 }
    301 
    302 Span<wchar_t> SharedSection::Layout::GetDependentModules() {
    303  if (!mDependentModulePathArrayStart) {
    304    return nullptr;
    305  }
    306  return Span<wchar_t>(
    307      reinterpret_cast<wchar_t*>(reinterpret_cast<uintptr_t>(this) +
    308                                 mDependentModulePathArrayStart),
    309      (kSharedViewSize - mDependentModulePathArrayStart) / sizeof(wchar_t));
    310 }
    311 
    312 bool SharedSection::Layout::IsDisabled() const {
    313  return !!mBlocklistIsDisabled;
    314 }
    315 
    316 const DllBlockInfo* SharedSection::Layout::SearchBlocklist(
    317    const UNICODE_STRING& aLeafName) const {
    318  MOZ_ASSERT(mState >= State::kLoadedDynamicBlocklistEntries);
    319  DllBlockInfoComparator comp(aLeafName);
    320  size_t match;
    321  if (!BinarySearchIf(mFirstBlockEntry, 0, mNumBlockEntries, comp, &match)) {
    322    return nullptr;
    323  }
    324  return &mFirstBlockEntry[match];
    325 }
    326 
    327 Kernel32ExportsSolver* SharedSection::GetKernel32Exports() {
    328  Layout* writeCopyView = EnsureWriteCopyView(true);
    329  return writeCopyView ? &writeCopyView->mK32Exports : nullptr;
    330 }
    331 
    332 Maybe<Vector<const wchar_t*>> SharedSection::GetDependentModules() {
    333  Layout* writeCopyView = EnsureWriteCopyView();
    334  if (!writeCopyView) {
    335    return Nothing();
    336  }
    337 
    338  mozilla::Span<wchar_t> dependentModules =
    339      writeCopyView->GetDependentModules();
    340  // Convert a null-delimited string set to a string vector.
    341  Vector<const wchar_t*> paths;
    342  for (const wchar_t* p = dependentModules.data();
    343       (p - dependentModules.data() <
    344            static_cast<long long>(dependentModules.size()) &&
    345        *p);) {
    346    if (MOZ_UNLIKELY(!paths.append(p))) {
    347      return Nothing();
    348    }
    349    while (*p) {
    350      ++p;
    351    }
    352    ++p;
    353  }
    354  return Some(std::move(paths));
    355 }
    356 
    357 Span<const DllBlockInfo> SharedSection::GetDynamicBlocklist() {
    358  Layout* writeCopyView = EnsureWriteCopyView();
    359  return writeCopyView ? writeCopyView->GetModulePathArray() : nullptr;
    360 }
    361 
    362 const DllBlockInfo* SharedSection::SearchBlocklist(
    363    const UNICODE_STRING& aLeafName) {
    364  Layout* writeCopyView = EnsureWriteCopyView();
    365  return writeCopyView ? writeCopyView->SearchBlocklist(aLeafName) : nullptr;
    366 }
    367 
    368 bool SharedSection::IsDisabled() {
    369  Layout* writeCopyView = EnsureWriteCopyView();
    370  return writeCopyView ? writeCopyView->IsDisabled() : false;
    371 }
    372 
    373 LauncherVoidResult SharedSection::TransferHandle(
    374    nt::CrossExecTransferManager& aTransferMgr, DWORD aDesiredAccess,
    375    HANDLE* aDestinationAddress) {
    376  HANDLE remoteHandle;
    377  if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
    378                         aTransferMgr.RemoteProcess(), &remoteHandle,
    379                         aDesiredAccess, FALSE, 0)) {
    380    return LAUNCHER_ERROR_FROM_LAST();
    381  }
    382 
    383  return aTransferMgr.Transfer(aDestinationAddress, &remoteHandle,
    384                               sizeof(remoteHandle));
    385 }
    386 
    387 }  // namespace freestanding
    388 }  // namespace mozilla