summaryrefslogtreecommitdiffstats
path: root/accessible/windows/msaa/CompatibilityUIA.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--accessible/windows/msaa/CompatibilityUIA.cpp347
1 files changed, 347 insertions, 0 deletions
diff --git a/accessible/windows/msaa/CompatibilityUIA.cpp b/accessible/windows/msaa/CompatibilityUIA.cpp
new file mode 100644
index 0000000000..1f46a35b20
--- /dev/null
+++ b/accessible/windows/msaa/CompatibilityUIA.cpp
@@ -0,0 +1,347 @@
+/* -*- 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 http://mozilla.org/MPL/2.0/. */
+
+#include "Compatibility.h"
+
+#include "mozilla/a11y/Platform.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsTHashSet.h"
+#include "nsWindowsHelpers.h"
+
+#include "NtUndoc.h"
+
+using namespace mozilla;
+
+struct ByteArrayDeleter {
+ void operator()(void* aBuf) { delete[] reinterpret_cast<std::byte*>(aBuf); }
+};
+
+typedef UniquePtr<OBJECT_DIRECTORY_INFORMATION, ByteArrayDeleter> ObjDirInfoPtr;
+
+// ComparatorFnT returns true to continue searching, or else false to indicate
+// search completion.
+template <typename ComparatorFnT>
+static bool FindNamedObject(const ComparatorFnT& aComparator) {
+ // We want to enumerate every named kernel object in our session. We do this
+ // by opening a directory object using a path constructed using the session
+ // id under which our process resides.
+ DWORD sessionId;
+ if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &sessionId)) {
+ return false;
+ }
+
+ nsAutoString path;
+ path.AppendPrintf("\\Sessions\\%lu\\BaseNamedObjects", sessionId);
+
+ UNICODE_STRING baseNamedObjectsName;
+ ::RtlInitUnicodeString(&baseNamedObjectsName, path.get());
+
+ OBJECT_ATTRIBUTES attributes;
+ InitializeObjectAttributes(&attributes, &baseNamedObjectsName, 0, nullptr,
+ nullptr);
+
+ HANDLE rawBaseNamedObjects;
+ NTSTATUS ntStatus = ::NtOpenDirectoryObject(
+ &rawBaseNamedObjects, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &attributes);
+ if (!NT_SUCCESS(ntStatus)) {
+ return false;
+ }
+
+ nsAutoHandle baseNamedObjects(rawBaseNamedObjects);
+
+ ULONG context = 0, returnedLen;
+
+ ULONG objDirInfoBufLen = 1024 * sizeof(OBJECT_DIRECTORY_INFORMATION);
+ ObjDirInfoPtr objDirInfo(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
+ new std::byte[objDirInfoBufLen]));
+
+ // Now query that directory object for every named object that it contains.
+
+ BOOL firstCall = TRUE;
+
+ do {
+ ntStatus = ::NtQueryDirectoryObject(baseNamedObjects, objDirInfo.get(),
+ objDirInfoBufLen, FALSE, firstCall,
+ &context, &returnedLen);
+#if defined(HAVE_64BIT_BUILD)
+ if (!NT_SUCCESS(ntStatus)) {
+ return false;
+ }
+#else
+ if (ntStatus == STATUS_BUFFER_TOO_SMALL) {
+ // This case only occurs on 32-bit builds running atop WOW64.
+ // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1423999#c3)
+ objDirInfo.reset(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
+ new std::byte[returnedLen]));
+ objDirInfoBufLen = returnedLen;
+ continue;
+ } else if (!NT_SUCCESS(ntStatus)) {
+ return false;
+ }
+#endif
+
+ // NtQueryDirectoryObject gave us an array of OBJECT_DIRECTORY_INFORMATION
+ // structures whose final entry is zeroed out.
+ OBJECT_DIRECTORY_INFORMATION* curDir = objDirInfo.get();
+ while (curDir->mName.Length && curDir->mTypeName.Length) {
+ // We use nsDependentSubstring here because UNICODE_STRINGs are not
+ // guaranteed to be null-terminated.
+ nsDependentSubstring objName(curDir->mName.Buffer,
+ curDir->mName.Length / sizeof(wchar_t));
+ nsDependentSubstring typeName(curDir->mTypeName.Buffer,
+ curDir->mTypeName.Length / sizeof(wchar_t));
+
+ if (!aComparator(objName, typeName)) {
+ return true;
+ }
+
+ ++curDir;
+ }
+
+ firstCall = FALSE;
+ } while (ntStatus == STATUS_MORE_ENTRIES);
+
+ return false;
+}
+
+// ComparatorFnT returns true to continue searching, or else false to indicate
+// search completion.
+template <typename ComparatorFnT>
+static bool FindHandle(const ComparatorFnT& aComparator) {
+ NTSTATUS ntStatus;
+ // First we must query for a list of all the open handles in the system.
+ UniquePtr<std::byte[]> handleInfoBuf;
+ ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) +
+ 1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX);
+ // We must query for handle information in a loop, since we are effectively
+ // asking the kernel to take a snapshot of all the handles on the system;
+ // the size of the required buffer may fluctuate between successive calls.
+ while (true) {
+ // These allocations can be hundreds of megabytes on some computers, so
+ // we should use fallible new here.
+ handleInfoBuf = MakeUniqueFallible<std::byte[]>(handleInfoBufLen);
+ if (!handleInfoBuf) {
+ return false;
+ }
+ ntStatus = ::NtQuerySystemInformation(
+ (SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation,
+ handleInfoBuf.get(), handleInfoBufLen, &handleInfoBufLen);
+ if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) {
+ continue;
+ }
+ if (!NT_SUCCESS(ntStatus)) {
+ return false;
+ }
+ break;
+ }
+
+ auto handleInfo =
+ reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(handleInfoBuf.get());
+ for (ULONG index = 0; index < handleInfo->mHandleCount; ++index) {
+ SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX& info = handleInfo->mHandles[index];
+ HANDLE handle = reinterpret_cast<HANDLE>(info.mHandle);
+ if (!aComparator(info, handle)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void GetUiaClientPidsWin11(nsTArray<DWORD>& aPids) {
+ const DWORD ourPid = ::GetCurrentProcessId();
+ FindHandle([&](auto aInfo, auto aHandle) {
+ if (aInfo.mPid != ourPid) {
+ // We're only interested in handles in our own process.
+ return true;
+ }
+ // UIA creates a named pipe between the client and server processes. We want
+ // to find our handle to that pipe (if any). If this is a named pipe, get
+ // the process id of the remote end. We do this first because querying the
+ // name of the handle might hang in some cases. Counter-intuitively, for UIA
+ // pipes, we're the client and the remote process is the server.
+ ULONG pid = 0;
+ ::GetNamedPipeServerProcessId(aHandle, &pid);
+ if (!pid) {
+ return true;
+ }
+ // We know this is a named pipe and we have the pid. Now, get the name of
+ // the handle and check whether it's a UIA pipe.
+ ULONG objNameBufLen;
+ NTSTATUS ntStatus = ::NtQueryObject(
+ aHandle, (OBJECT_INFORMATION_CLASS)ObjectNameInformation, nullptr, 0,
+ &objNameBufLen);
+ if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
+ return true;
+ }
+ auto objNameBuf = MakeUnique<std::byte[]>(objNameBufLen);
+ ntStatus = ::NtQueryObject(aHandle,
+ (OBJECT_INFORMATION_CLASS)ObjectNameInformation,
+ objNameBuf.get(), objNameBufLen, &objNameBufLen);
+ if (!NT_SUCCESS(ntStatus)) {
+ return true;
+ }
+ auto objNameInfo =
+ reinterpret_cast<OBJECT_NAME_INFORMATION*>(objNameBuf.get());
+ if (!objNameInfo->Name.Length) {
+ return true;
+ }
+ nsDependentString objName(objNameInfo->Name.Buffer,
+ objNameInfo->Name.Length / sizeof(wchar_t));
+ if (StringBeginsWith(objName, u"\\Device\\NamedPipe\\UIA_PIPE_"_ns)) {
+ aPids.AppendElement(pid);
+ }
+ return true;
+ });
+}
+
+static DWORD GetUiaClientPidWin10() {
+ // UIA creates a section of the form "HOOK_SHMEM_%08lx_%08lx_%08lx_%08lx"
+ constexpr auto kStrHookShmem = u"HOOK_SHMEM_"_ns;
+ // The second %08lx is the thread id.
+ nsAutoString sectionThread;
+ sectionThread.AppendPrintf("_%08lx_", ::GetCurrentThreadId());
+ // This is the number of characters from the end of the section name where
+ // the sectionThread substring begins.
+ constexpr size_t sectionThreadRPos = 27;
+ // This is the length of sectionThread.
+ constexpr size_t sectionThreadLen = 10;
+ // Find any named Section that matches the naming convention of the UIA shared
+ // memory. There can only be one of these at a time, since this only exists
+ // while UIA is processing a request and it can only process a single request
+ // on a single thread.
+ nsAutoHandle section;
+ auto objectComparator = [&](const nsDependentSubstring& aName,
+ const nsDependentSubstring& aType) -> bool {
+ if (aType.Equals(u"Section"_ns) && FindInReadable(kStrHookShmem, aName) &&
+ Substring(aName, aName.Length() - sectionThreadRPos,
+ sectionThreadLen) == sectionThread) {
+ // Get a handle to this section so we can get its kernel object and
+ // use that to find the handle for this section in the remote process.
+ section.own(::OpenFileMapping(GENERIC_READ, FALSE,
+ PromiseFlatString(aName).get()));
+ return false;
+ }
+ return true;
+ };
+ if (!FindNamedObject(objectComparator) || !section) {
+ return 0;
+ }
+
+ // Now, find the kernel object associated with our section, the handle in the
+ // remote process associated with that kernel object and thus the remote
+ // process id.
+ NTSTATUS ntStatus;
+ const DWORD ourPid = ::GetCurrentProcessId();
+ Maybe<PVOID> kernelObject;
+ static Maybe<USHORT> sectionObjTypeIndex;
+ nsTHashSet<uint32_t> nonSectionObjTypes;
+ nsTHashMap<nsVoidPtrHashKey, DWORD> objMap;
+ DWORD remotePid = 0;
+ FindHandle([&](auto aInfo, auto aHandle) {
+ // The mapping of the aInfo.mObjectTypeIndex field depends on the
+ // underlying OS kernel. As we scan through the handle list, we record the
+ // type indices such that we may use those values to skip over handles that
+ // refer to non-section objects.
+ if (sectionObjTypeIndex) {
+ // If we know the type index for Sections, that's the fastest check...
+ if (sectionObjTypeIndex.value() != aInfo.mObjectTypeIndex) {
+ // Not a section
+ return true;
+ }
+ } else if (nonSectionObjTypes.Contains(
+ static_cast<uint32_t>(aInfo.mObjectTypeIndex))) {
+ // Otherwise we check whether or not the object type is definitely _not_
+ // a Section...
+ return true;
+ } else if (ourPid == aInfo.mPid) {
+ // Otherwise we need to issue some system calls to find out the object
+ // type corresponding to the current handle's type index.
+ ULONG objTypeBufLen;
+ ntStatus = ::NtQueryObject(aHandle, ObjectTypeInformation, nullptr, 0,
+ &objTypeBufLen);
+ if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
+ return true;
+ }
+ auto objTypeBuf = MakeUnique<std::byte[]>(objTypeBufLen);
+ ntStatus =
+ ::NtQueryObject(aHandle, ObjectTypeInformation, objTypeBuf.get(),
+ objTypeBufLen, &objTypeBufLen);
+ if (!NT_SUCCESS(ntStatus)) {
+ return true;
+ }
+ auto objType =
+ reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get());
+ // Now we check whether the object's type name matches "Section"
+ nsDependentSubstring objTypeName(
+ objType->TypeName.Buffer, objType->TypeName.Length / sizeof(wchar_t));
+ if (!objTypeName.Equals(u"Section"_ns)) {
+ nonSectionObjTypes.Insert(
+ static_cast<uint32_t>(aInfo.mObjectTypeIndex));
+ return true;
+ }
+ sectionObjTypeIndex = Some(aInfo.mObjectTypeIndex);
+ }
+
+ // At this point we know that aInfo references a Section object.
+ // Now we can do some actual tests on it.
+ if (ourPid != aInfo.mPid) {
+ if (kernelObject && kernelObject.value() == aInfo.mObject) {
+ // The kernel objects match -- we have found the remote pid!
+ remotePid = aInfo.mPid;
+ return false;
+ }
+ // An object that is not ours. Since we do not yet know which kernel
+ // object we're interested in, we'll save the current object for later.
+ objMap.InsertOrUpdate(aInfo.mObject, aInfo.mPid);
+ } else if (aHandle == section.get()) {
+ // This is the file mapping that we opened above. We save this mObject
+ // in order to compare to Section objects opened by other processes.
+ kernelObject = Some(aInfo.mObject);
+ }
+ return true;
+ });
+
+ if (remotePid) {
+ return remotePid;
+ }
+ if (!kernelObject) {
+ return 0;
+ }
+
+ // If we reach here, we found kernelObject *after* we saw the remote process's
+ // copy. Now we must look it up in objMap.
+ if (objMap.Get(kernelObject.value(), &remotePid)) {
+ return remotePid;
+ }
+
+ return 0;
+}
+
+namespace mozilla {
+namespace a11y {
+
+void Compatibility::GetUiaClientPids(nsTArray<DWORD>& aPids) {
+ if (!::GetModuleHandleW(L"uiautomationcore.dll")) {
+ // UIAutomationCore isn't loaded, so there is no UIA client.
+ return;
+ }
+ Telemetry::AutoTimer<Telemetry::A11Y_UIA_DETECTION_TIMING_MS> timer;
+ if (IsWin11OrLater()) {
+ GetUiaClientPidsWin11(aPids);
+ } else {
+ if (DWORD pid = GetUiaClientPidWin10()) {
+ aPids.AppendElement(pid);
+ }
+ }
+}
+
+} // namespace a11y
+} // namespace mozilla