tor-browser

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

CompatibilityUIA.cpp (16341B)


      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 http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "Compatibility.h"
      8 
      9 #include "mozilla/a11y/Platform.h"
     10 #include "mozilla/UniquePtrExtensions.h"
     11 #include "mozilla/UniquePtr.h"
     12 #include "mozilla/WindowsVersion.h"
     13 #include "nsReadableUtils.h"
     14 #include "nsString.h"
     15 #include "nsTHashSet.h"
     16 #include "nsWindowsHelpers.h"
     17 
     18 #include "NtUndoc.h"
     19 
     20 using namespace mozilla;
     21 
     22 struct ByteArrayDeleter {
     23  void operator()(void* aBuf) { delete[] reinterpret_cast<std::byte*>(aBuf); }
     24 };
     25 
     26 typedef UniquePtr<OBJECT_DIRECTORY_INFORMATION, ByteArrayDeleter> ObjDirInfoPtr;
     27 
     28 // ComparatorFnT returns true to continue searching, or else false to indicate
     29 // search completion.
     30 template <typename ComparatorFnT>
     31 static bool FindNamedObject(const ComparatorFnT& aComparator) {
     32  // We want to enumerate every named kernel object in our session. We do this
     33  // by opening a directory object using a path constructed using the session
     34  // id under which our process resides.
     35  DWORD sessionId;
     36  if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &sessionId)) {
     37    return false;
     38  }
     39 
     40  nsAutoString path;
     41  path.AppendPrintf("\\Sessions\\%lu\\BaseNamedObjects", sessionId);
     42 
     43  UNICODE_STRING baseNamedObjectsName;
     44  ::RtlInitUnicodeString(&baseNamedObjectsName, path.get());
     45 
     46  OBJECT_ATTRIBUTES attributes;
     47  InitializeObjectAttributes(&attributes, &baseNamedObjectsName, 0, nullptr,
     48                             nullptr);
     49 
     50  HANDLE rawBaseNamedObjects;
     51  NTSTATUS ntStatus = ::NtOpenDirectoryObject(
     52      &rawBaseNamedObjects, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &attributes);
     53  if (!NT_SUCCESS(ntStatus)) {
     54    return false;
     55  }
     56 
     57  nsAutoHandle baseNamedObjects(rawBaseNamedObjects);
     58 
     59  ULONG context = 0, returnedLen;
     60 
     61  ULONG objDirInfoBufLen = 1024 * sizeof(OBJECT_DIRECTORY_INFORMATION);
     62  ObjDirInfoPtr objDirInfo(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
     63      new std::byte[objDirInfoBufLen]));
     64 
     65  // Now query that directory object for every named object that it contains.
     66 
     67  BOOL firstCall = TRUE;
     68 
     69  do {
     70    ntStatus = ::NtQueryDirectoryObject(baseNamedObjects, objDirInfo.get(),
     71                                        objDirInfoBufLen, FALSE, firstCall,
     72                                        &context, &returnedLen);
     73 #if defined(HAVE_64BIT_BUILD)
     74    if (!NT_SUCCESS(ntStatus)) {
     75      return false;
     76    }
     77 #else
     78    if (ntStatus == STATUS_BUFFER_TOO_SMALL) {
     79      // This case only occurs on 32-bit builds running atop WOW64.
     80      // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1423999#c3)
     81      objDirInfo.reset(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
     82          new std::byte[returnedLen]));
     83      objDirInfoBufLen = returnedLen;
     84      continue;
     85    } else if (!NT_SUCCESS(ntStatus)) {
     86      return false;
     87    }
     88 #endif
     89 
     90    // NtQueryDirectoryObject gave us an array of OBJECT_DIRECTORY_INFORMATION
     91    // structures whose final entry is zeroed out.
     92    OBJECT_DIRECTORY_INFORMATION* curDir = objDirInfo.get();
     93    while (curDir->mName.Length && curDir->mTypeName.Length) {
     94      // We use nsDependentSubstring here because UNICODE_STRINGs are not
     95      // guaranteed to be null-terminated.
     96      nsDependentSubstring objName(curDir->mName.Buffer,
     97                                   curDir->mName.Length / sizeof(wchar_t));
     98      nsDependentSubstring typeName(curDir->mTypeName.Buffer,
     99                                    curDir->mTypeName.Length / sizeof(wchar_t));
    100 
    101      if (!aComparator(objName, typeName)) {
    102        return true;
    103      }
    104 
    105      ++curDir;
    106    }
    107 
    108    firstCall = FALSE;
    109  } while (ntStatus == STATUS_MORE_ENTRIES);
    110 
    111  return false;
    112 }
    113 
    114 // ComparatorFnT returns true to continue searching, or else false to indicate
    115 // search completion.
    116 template <typename ComparatorFnT>
    117 static bool FindHandle(const ComparatorFnT& aComparator) {
    118  NTSTATUS ntStatus;
    119  // First we must query for a list of all the open handles in the system.
    120  UniquePtr<std::byte[]> handleInfoBuf;
    121  ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) +
    122                           1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX);
    123  // We must query for handle information in a loop, since we are effectively
    124  // asking the kernel to take a snapshot of all the handles on the system;
    125  // the size of the required buffer may fluctuate between successive calls.
    126  while (true) {
    127    // These allocations can be hundreds of megabytes on some computers, so
    128    // we should use fallible new here.
    129    handleInfoBuf = MakeUniqueFallible<std::byte[]>(handleInfoBufLen);
    130    if (!handleInfoBuf) {
    131      return false;
    132    }
    133    ntStatus = ::NtQuerySystemInformation(
    134        (SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation,
    135        handleInfoBuf.get(), handleInfoBufLen, &handleInfoBufLen);
    136    if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) {
    137      continue;
    138    }
    139    if (!NT_SUCCESS(ntStatus)) {
    140      return false;
    141    }
    142    break;
    143  }
    144 
    145  auto handleInfo =
    146      reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(handleInfoBuf.get());
    147  for (ULONG index = 0; index < handleInfo->mHandleCount; ++index) {
    148    SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX& info = handleInfo->mHandles[index];
    149    HANDLE handle = reinterpret_cast<HANDLE>(info.mHandle);
    150    if (!aComparator(info, handle)) {
    151      return true;
    152    }
    153  }
    154  return false;
    155 }
    156 
    157 class GetUiaClientPidsWin11 {
    158 public:
    159  static void Run(nsTArray<DWORD>& aPids);
    160 
    161 private:
    162  struct HandleAndPid {
    163    explicit HandleAndPid(HANDLE aHandle) : mHandle(aHandle) {}
    164    HANDLE mHandle;
    165    ULONG mPid = 0;
    166  };
    167  // Some local testing showed that we get around 40 handles when Firefox has
    168  // been started with a few tabs open for ~30 seconds before starting a UIA
    169  // client. That might increase with a longer duration, more tabs, etc., so
    170  // allow for some extra.
    171  using HandlesAndPids = AutoTArray<HandleAndPid, 128>;
    172 
    173  struct ThreadData {
    174    explicit ThreadData(HandlesAndPids& aHandlesAndPids)
    175        : mHandlesAndPids(aHandlesAndPids) {}
    176    HandlesAndPids& mHandlesAndPids;
    177    // Keeps track of the current index in mHandlesAndPids that is being
    178    // queried. When the thread is (re)started, it starts querying from this
    179    // index.
    180    size_t mCurrentIndex = 0;
    181  };
    182 
    183  static DWORD WINAPI QueryThreadProc(LPVOID aParameter) {
    184    // WARNING! Because this thread may be terminated unexpectedly due to a
    185    // hang, it must not do anything which acquires resources, allocates memory,
    186    // non-atomically modifies state, etc. It may not get a chance to clean up.
    187    auto& data = *(ThreadData*)aParameter;
    188    for (; data.mCurrentIndex < data.mHandlesAndPids.Length();
    189         ++data.mCurrentIndex) {
    190      auto& entry = data.mHandlesAndPids[data.mCurrentIndex];
    191      // Counter-intuitively, for UIA pipes, we're the client and the remote
    192      // process is the server.
    193      ::GetNamedPipeServerProcessId(entry.mHandle, &entry.mPid);
    194    }
    195    return 0;
    196  };
    197 };
    198 
    199 void GetUiaClientPidsWin11::Run(nsTArray<DWORD>& aPids) {
    200  // 1. Get all handles of interest in our process.
    201  HandlesAndPids handlesAndPids;
    202  const DWORD ourPid = ::GetCurrentProcessId();
    203  FindHandle([&](auto aInfo, auto aHandle) {
    204    // UIA pipes always have granted access 0x0012019F. Pipes with this access
    205    // can still hang, but this at least narrows down the handles we need to
    206    // check.
    207    if (aInfo.mPid == ourPid && aInfo.mGrantedAccess == 0x0012019F) {
    208      handlesAndPids.AppendElement(HandleAndPid(aHandle));
    209    }
    210    return true;
    211  });
    212 
    213  // 2. UIA creates a named pipe between the client and server processes. We
    214  // want to find our handle to those pipes (if any). For all named pipes, get
    215  // the process id of the remote end. We must use a background thread to query
    216  // pipes because this can hang on some pipes and there's no way to prevent
    217  // this other than terminating the thread. See bug 1899211 for more details.
    218  ThreadData threadData(handlesAndPids);
    219  while (threadData.mCurrentIndex < handlesAndPids.Length()) {
    220    // We use CreateThread here rather than Gecko's threading support because
    221    // we may terminate this thread and we must be certain it hasn't acquired
    222    // any resources which need to be cleaned up.
    223    nsAutoHandle thread(::CreateThread(nullptr, 0, QueryThreadProc,
    224                                       (LPVOID)&threadData, 0, nullptr));
    225    if (!thread) {
    226      return;
    227    }
    228    if (::WaitForSingleObject(thread, 50) == WAIT_OBJECT_0) {
    229      // We're done querying the handles.
    230      MOZ_ASSERT(threadData.mCurrentIndex == handlesAndPids.Length());
    231      break;
    232    }
    233    // The thread hung. Terminate it.
    234    ::TerminateThread(thread, 1);
    235    // The thread probably hung on threadData.mCurrentIndex, so skip this
    236    // handle. In the next iteration of this loop, we'll create another thread
    237    // and resume from that point. This could result in us skipping a handle if
    238    // the thread didn't actually hang, but took too long and was terminated
    239    // after incrementing but before querying the handle. At worst, we might
    240    // miss a UIA client in this case, but this should be very rare and it's an
    241    // acceptable compromise to avoid a main thread hang.
    242    ++threadData.mCurrentIndex;
    243  }
    244 
    245  // 3. Now that we have pids for all named pipes, get the name of those handles
    246  // and check whether they are UIA pipes. We can't do this in the thread above
    247  // because it allocates memory and that might not get cleaned up if the thread
    248  // is terminated.
    249  for (auto& entry : handlesAndPids) {
    250    if (!entry.mPid) {
    251      continue;  // Not a named pipe.
    252    }
    253    ULONG objNameBufLen;
    254    NTSTATUS ntStatus = ::NtQueryObject(
    255        entry.mHandle, (OBJECT_INFORMATION_CLASS)ObjectNameInformation, nullptr,
    256        0, &objNameBufLen);
    257    if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
    258      continue;
    259    }
    260    auto objNameBuf = MakeUnique<std::byte[]>(objNameBufLen);
    261    ntStatus = ::NtQueryObject(entry.mHandle,
    262                               (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
    263                               objNameBuf.get(), objNameBufLen, &objNameBufLen);
    264    if (!NT_SUCCESS(ntStatus)) {
    265      continue;
    266    }
    267    auto objNameInfo =
    268        reinterpret_cast<OBJECT_NAME_INFORMATION*>(objNameBuf.get());
    269    if (!objNameInfo->Name.Length) {
    270      continue;
    271    }
    272    nsDependentString objName(objNameInfo->Name.Buffer,
    273                              objNameInfo->Name.Length / sizeof(wchar_t));
    274    if (StringBeginsWith(objName, u"\\Device\\NamedPipe\\UIA_PIPE_"_ns)) {
    275      aPids.AppendElement(entry.mPid);
    276    }
    277  }
    278 }
    279 
    280 static DWORD GetUiaClientPidWin10() {
    281  // UIA creates a section of the form "HOOK_SHMEM_%08lx_%08lx_%08lx_%08lx"
    282  constexpr auto kStrHookShmem = u"HOOK_SHMEM_"_ns;
    283  // The second %08lx is the thread id.
    284  nsAutoString sectionThread;
    285  sectionThread.AppendPrintf("_%08lx_", ::GetCurrentThreadId());
    286  // This is the number of characters from the end of the section name where
    287  // the sectionThread substring begins.
    288  constexpr size_t sectionThreadRPos = 27;
    289  // This is the length of sectionThread.
    290  constexpr size_t sectionThreadLen = 10;
    291  // Find any named Section that matches the naming convention of the UIA shared
    292  // memory. There can only be one of these at a time, since this only exists
    293  // while UIA is processing a request and it can only process a single request
    294  // on a single thread.
    295  nsAutoHandle section;
    296  auto objectComparator = [&](const nsDependentSubstring& aName,
    297                              const nsDependentSubstring& aType) -> bool {
    298    if (aType.Equals(u"Section"_ns) && FindInReadable(kStrHookShmem, aName) &&
    299        Substring(aName, aName.Length() - sectionThreadRPos,
    300                  sectionThreadLen) == sectionThread) {
    301      // Get a handle to this section so we can get its kernel object and
    302      // use that to find the handle for this section in the remote process.
    303      section.own(::OpenFileMapping(GENERIC_READ, FALSE,
    304                                    PromiseFlatString(aName).get()));
    305      return false;
    306    }
    307    return true;
    308  };
    309  if (!FindNamedObject(objectComparator) || !section) {
    310    return 0;
    311  }
    312 
    313  // Now, find the kernel object associated with our section, the handle in the
    314  // remote process associated with that kernel object and thus the remote
    315  // process id.
    316  NTSTATUS ntStatus;
    317  const DWORD ourPid = ::GetCurrentProcessId();
    318  Maybe<PVOID> kernelObject;
    319  static Maybe<USHORT> sectionObjTypeIndex;
    320  nsTHashSet<uint32_t> nonSectionObjTypes;
    321  nsTHashMap<nsVoidPtrHashKey, DWORD> objMap;
    322  DWORD remotePid = 0;
    323  FindHandle([&](auto aInfo, auto aHandle) {
    324    // The mapping of the aInfo.mObjectTypeIndex field depends on the
    325    // underlying OS kernel. As we scan through the handle list, we record the
    326    // type indices such that we may use those values to skip over handles that
    327    // refer to non-section objects.
    328    if (sectionObjTypeIndex) {
    329      // If we know the type index for Sections, that's the fastest check...
    330      if (sectionObjTypeIndex.value() != aInfo.mObjectTypeIndex) {
    331        // Not a section
    332        return true;
    333      }
    334    } else if (nonSectionObjTypes.Contains(
    335                   static_cast<uint32_t>(aInfo.mObjectTypeIndex))) {
    336      // Otherwise we check whether or not the object type is definitely _not_
    337      // a Section...
    338      return true;
    339    } else if (ourPid == aInfo.mPid) {
    340      // Otherwise we need to issue some system calls to find out the object
    341      // type corresponding to the current handle's type index.
    342      ULONG objTypeBufLen;
    343      ntStatus = ::NtQueryObject(aHandle, ObjectTypeInformation, nullptr, 0,
    344                                 &objTypeBufLen);
    345      if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
    346        return true;
    347      }
    348      auto objTypeBuf = MakeUnique<std::byte[]>(objTypeBufLen);
    349      ntStatus =
    350          ::NtQueryObject(aHandle, ObjectTypeInformation, objTypeBuf.get(),
    351                          objTypeBufLen, &objTypeBufLen);
    352      if (!NT_SUCCESS(ntStatus)) {
    353        return true;
    354      }
    355      auto objType =
    356          reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get());
    357      // Now we check whether the object's type name matches "Section"
    358      nsDependentSubstring objTypeName(
    359          objType->TypeName.Buffer, objType->TypeName.Length / sizeof(wchar_t));
    360      if (!objTypeName.Equals(u"Section"_ns)) {
    361        nonSectionObjTypes.Insert(
    362            static_cast<uint32_t>(aInfo.mObjectTypeIndex));
    363        return true;
    364      }
    365      sectionObjTypeIndex = Some(aInfo.mObjectTypeIndex);
    366    }
    367 
    368    // At this point we know that aInfo references a Section object.
    369    // Now we can do some actual tests on it.
    370    if (ourPid != aInfo.mPid) {
    371      if (kernelObject && kernelObject.value() == aInfo.mObject) {
    372        // The kernel objects match -- we have found the remote pid!
    373        remotePid = aInfo.mPid;
    374        return false;
    375      }
    376      // An object that is not ours. Since we do not yet know which kernel
    377      // object we're interested in, we'll save the current object for later.
    378      objMap.InsertOrUpdate(aInfo.mObject, aInfo.mPid);
    379    } else if (aHandle == section.get()) {
    380      // This is the file mapping that we opened above. We save this mObject
    381      // in order to compare to Section objects opened by other processes.
    382      kernelObject = Some(aInfo.mObject);
    383    }
    384    return true;
    385  });
    386 
    387  if (remotePid) {
    388    return remotePid;
    389  }
    390  if (!kernelObject) {
    391    return 0;
    392  }
    393 
    394  // If we reach here, we found kernelObject *after* we saw the remote process's
    395  // copy. Now we must look it up in objMap.
    396  if (objMap.Get(kernelObject.value(), &remotePid)) {
    397    return remotePid;
    398  }
    399 
    400  return 0;
    401 }
    402 
    403 namespace mozilla {
    404 namespace a11y {
    405 
    406 void Compatibility::GetUiaClientPids(nsTArray<DWORD>& aPids) {
    407  if (!::GetModuleHandleW(L"uiautomationcore.dll")) {
    408    // UIAutomationCore isn't loaded, so there is no UIA client.
    409    return;
    410  }
    411  if (IsWin11OrLater()) {
    412    GetUiaClientPidsWin11::Run(aPids);
    413  } else {
    414    if (DWORD pid = GetUiaClientPidWin10()) {
    415      aPids.AppendElement(pid);
    416    }
    417  }
    418 }
    419 
    420 }  // namespace a11y
    421 }  // namespace mozilla