Bug 1769547 - Do not MOZ_CRASH() on missing process r=nika
[gecko.git] / accessible / windows / msaa / CompatibilityUIA.cpp
blob012dca650f73a0107a0839c9d1cb2d2388fe2b5a
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/. */
7 #include "Compatibility.h"
9 #include "mozilla/ScopeExit.h"
10 #include "mozilla/Telemetry.h"
11 #include "mozilla/UniquePtrExtensions.h"
12 #include "mozilla/WindowsVersion.h"
13 #include "nsdefs.h"
14 #include "nspr/prenv.h"
16 #include "nsTHashMap.h"
17 #include "nsTHashSet.h"
18 #include "nsPrintfCString.h"
19 #include "nsReadableUtils.h"
20 #include "nsString.h"
21 #include "nsTHashtable.h"
22 #include "nsUnicharUtils.h"
23 #include "nsWinUtils.h"
25 #include "NtUndoc.h"
27 #if defined(UIA_LOGGING)
29 # define LOG_ERROR(FuncName) \
30 { \
31 DWORD err = ::GetLastError(); \
32 nsPrintfCString msg(#FuncName " failed with code %u\n", err); \
33 ::OutputDebugStringA(msg.get()); \
36 #else
38 # define LOG_ERROR(FuncName)
40 #endif // defined(UIA_LOGGING)
42 struct ByteArrayDeleter {
43 void operator()(void* aBuf) { delete[] reinterpret_cast<char*>(aBuf); }
46 typedef UniquePtr<OBJECT_DIRECTORY_INFORMATION, ByteArrayDeleter> ObjDirInfoPtr;
48 // ComparatorFnT returns true to continue searching, or else false to indicate
49 // search completion.
50 template <typename ComparatorFnT>
51 static bool FindNamedObject(const ComparatorFnT& aComparator) {
52 // We want to enumerate every named kernel object in our session. We do this
53 // by opening a directory object using a path constructed using the session
54 // id under which our process resides.
55 DWORD sessionId;
56 if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &sessionId)) {
57 return false;
60 nsAutoString path;
61 path.AppendPrintf("\\Sessions\\%lu\\BaseNamedObjects", sessionId);
63 UNICODE_STRING baseNamedObjectsName;
64 ::RtlInitUnicodeString(&baseNamedObjectsName, path.get());
66 OBJECT_ATTRIBUTES attributes;
67 InitializeObjectAttributes(&attributes, &baseNamedObjectsName, 0, nullptr,
68 nullptr);
70 HANDLE rawBaseNamedObjects;
71 NTSTATUS ntStatus = ::NtOpenDirectoryObject(
72 &rawBaseNamedObjects, DIRECTORY_QUERY | DIRECTORY_TRAVERSE, &attributes);
73 if (!NT_SUCCESS(ntStatus)) {
74 return false;
77 nsAutoHandle baseNamedObjects(rawBaseNamedObjects);
79 ULONG context = 0, returnedLen;
81 ULONG objDirInfoBufLen = 1024 * sizeof(OBJECT_DIRECTORY_INFORMATION);
82 ObjDirInfoPtr objDirInfo(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
83 new char[objDirInfoBufLen]));
85 // Now query that directory object for every named object that it contains.
87 BOOL firstCall = TRUE;
89 do {
90 ntStatus = ::NtQueryDirectoryObject(baseNamedObjects, objDirInfo.get(),
91 objDirInfoBufLen, FALSE, firstCall,
92 &context, &returnedLen);
93 #if defined(HAVE_64BIT_BUILD)
94 if (!NT_SUCCESS(ntStatus)) {
95 return false;
97 #else
98 if (ntStatus == STATUS_BUFFER_TOO_SMALL) {
99 // This case only occurs on 32-bit builds running atop WOW64.
100 // (See https://bugzilla.mozilla.org/show_bug.cgi?id=1423999#c3)
101 objDirInfo.reset(reinterpret_cast<OBJECT_DIRECTORY_INFORMATION*>(
102 new char[returnedLen]));
103 objDirInfoBufLen = returnedLen;
104 continue;
105 } else if (!NT_SUCCESS(ntStatus)) {
106 return false;
108 #endif
110 // NtQueryDirectoryObject gave us an array of OBJECT_DIRECTORY_INFORMATION
111 // structures whose final entry is zeroed out.
112 OBJECT_DIRECTORY_INFORMATION* curDir = objDirInfo.get();
113 while (curDir->mName.Length && curDir->mTypeName.Length) {
114 // We use nsDependentSubstring here because UNICODE_STRINGs are not
115 // guaranteed to be null-terminated.
116 nsDependentSubstring objName(curDir->mName.Buffer,
117 curDir->mName.Length / sizeof(wchar_t));
118 nsDependentSubstring typeName(curDir->mTypeName.Buffer,
119 curDir->mTypeName.Length / sizeof(wchar_t));
121 if (!aComparator(objName, typeName)) {
122 return true;
125 ++curDir;
128 firstCall = FALSE;
129 } while (ntStatus == STATUS_MORE_ENTRIES);
131 return false;
134 static const char* gBlockedUiaClients[] = {"osk.exe"};
136 static bool ShouldBlockUIAClient(nsIFile* aClientExe) {
137 if (PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) {
138 return false;
141 nsAutoString leafName;
142 nsresult rv = aClientExe->GetLeafName(leafName);
143 if (NS_FAILED(rv)) {
144 return false;
147 for (size_t index = 0, len = ArrayLength(gBlockedUiaClients); index < len;
148 ++index) {
149 if (leafName.EqualsIgnoreCase(gBlockedUiaClients[index])) {
150 return true;
154 return false;
157 namespace mozilla {
158 namespace a11y {
160 Maybe<DWORD> Compatibility::sUiaRemotePid;
162 Maybe<bool> Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) {
163 auto clearUiaRemotePid = MakeScopeExit([]() { sUiaRemotePid = Nothing(); });
165 Telemetry::AutoTimer<Telemetry::A11Y_UIA_DETECTION_TIMING_MS> timer;
167 // UIA creates a section containing the substring "HOOK_SHMEM_"
168 constexpr auto kStrHookShmem = u"HOOK_SHMEM_"_ns;
170 // The section name always ends with this suffix, which is derived from the
171 // current thread id and the UIA message's WPARAM and LPARAM.
172 nsAutoString partialSectionSuffix;
173 partialSectionSuffix.AppendPrintf("_%08lx_%08" PRIxLPTR "_%08zx",
174 ::GetCurrentThreadId(), aLParam, aWParam);
176 // Find any named Section that matches the naming convention of the UIA shared
177 // memory.
178 nsAutoHandle section;
179 auto comparator = [&](const nsDependentSubstring& aName,
180 const nsDependentSubstring& aType) -> bool {
181 if (aType.Equals(u"Section"_ns) && FindInReadable(kStrHookShmem, aName) &&
182 StringEndsWith(aName, partialSectionSuffix)) {
183 section.own(::OpenFileMapping(GENERIC_READ, FALSE,
184 PromiseFlatString(aName).get()));
185 return false;
188 return true;
191 if (!FindNamedObject(comparator) || !section) {
192 return Nothing();
195 NTSTATUS ntStatus;
197 // First we must query for a list of all the open handles in the system.
198 UniquePtr<char[]> handleInfoBuf;
199 ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) +
200 1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX);
202 // We must query for handle information in a loop, since we are effectively
203 // asking the kernel to take a snapshot of all the handles on the system;
204 // the size of the required buffer may fluctuate between successive calls.
205 while (true) {
206 // These allocations can be hundreds of megabytes on some computers, so
207 // we should use fallible new here.
208 handleInfoBuf = MakeUniqueFallible<char[]>(handleInfoBufLen);
209 if (!handleInfoBuf) {
210 return Nothing();
213 ntStatus = ::NtQuerySystemInformation(
214 (SYSTEM_INFORMATION_CLASS)SystemExtendedHandleInformation,
215 handleInfoBuf.get(), handleInfoBufLen, &handleInfoBufLen);
216 if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) {
217 continue;
220 if (!NT_SUCCESS(ntStatus)) {
221 return Nothing();
224 break;
227 const DWORD ourPid = ::GetCurrentProcessId();
228 Maybe<PVOID> kernelObject;
229 static Maybe<USHORT> sectionObjTypeIndex;
230 nsTHashSet<uint32_t> nonSectionObjTypes;
231 nsTHashMap<nsVoidPtrHashKey, DWORD> objMap;
233 auto handleInfo =
234 reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(handleInfoBuf.get());
236 for (ULONG index = 0; index < handleInfo->mHandleCount; ++index) {
237 SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX& curHandle = handleInfo->mHandles[index];
239 HANDLE handle = reinterpret_cast<HANDLE>(curHandle.mHandle);
241 // The mapping of the curHandle.mObjectTypeIndex field depends on the
242 // underlying OS kernel. As we scan through the handle list, we record the
243 // type indices such that we may use those values to skip over handles that
244 // refer to non-section objects.
245 if (sectionObjTypeIndex) {
246 // If we know the type index for Sections, that's the fastest check...
247 if (sectionObjTypeIndex.value() != curHandle.mObjectTypeIndex) {
248 // Not a section
249 continue;
251 } else if (nonSectionObjTypes.Contains(
252 static_cast<uint32_t>(curHandle.mObjectTypeIndex))) {
253 // Otherwise we check whether or not the object type is definitely _not_
254 // a Section...
255 continue;
256 } else if (ourPid == curHandle.mPid) {
257 // Otherwise we need to issue some system calls to find out the object
258 // type corresponding to the current handle's type index.
259 ULONG objTypeBufLen;
260 ntStatus = ::NtQueryObject(handle, ObjectTypeInformation, nullptr, 0,
261 &objTypeBufLen);
262 if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) {
263 continue;
266 auto objTypeBuf = MakeUnique<char[]>(objTypeBufLen);
267 ntStatus =
268 ::NtQueryObject(handle, ObjectTypeInformation, objTypeBuf.get(),
269 objTypeBufLen, &objTypeBufLen);
270 if (!NT_SUCCESS(ntStatus)) {
271 continue;
274 auto objType =
275 reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get());
277 // Now we check whether the object's type name matches "Section"
278 nsDependentSubstring objTypeName(
279 objType->TypeName.Buffer, objType->TypeName.Length / sizeof(wchar_t));
280 if (!objTypeName.Equals(u"Section"_ns)) {
281 nonSectionObjTypes.Insert(
282 static_cast<uint32_t>(curHandle.mObjectTypeIndex));
283 continue;
286 sectionObjTypeIndex = Some(curHandle.mObjectTypeIndex);
289 // At this point we know that curHandle references a Section object.
290 // Now we can do some actual tests on it.
292 if (ourPid != curHandle.mPid) {
293 if (kernelObject && kernelObject.value() == curHandle.mObject) {
294 // The kernel objects match -- we have found the remote pid!
295 sUiaRemotePid = Some(curHandle.mPid);
296 break;
299 // An object that is not ours. Since we do not yet know which kernel
300 // object we're interested in, we'll save the current object for later.
301 objMap.InsertOrUpdate(curHandle.mObject, curHandle.mPid);
302 } else if (handle == section.get()) {
303 // This is the file mapping that we opened above. We save this mObject
304 // in order to compare to Section objects opened by other processes.
305 kernelObject = Some(curHandle.mObject);
309 if (!kernelObject) {
310 return Nothing();
313 if (!sUiaRemotePid) {
314 // We found kernelObject *after* we saw the remote process's copy. Now we
315 // must look it up in objMap.
316 DWORD pid;
317 if (objMap.Get(kernelObject.value(), &pid)) {
318 sUiaRemotePid = Some(pid);
322 if (!sUiaRemotePid) {
323 return Nothing();
326 a11y::SetInstantiator(sUiaRemotePid.value());
328 // Block if necessary
329 nsCOMPtr<nsIFile> instantiator;
330 if (a11y::GetInstantiator(getter_AddRefs(instantiator)) &&
331 ShouldBlockUIAClient(instantiator)) {
332 return Some(false);
335 return Some(true);
338 } // namespace a11y
339 } // namespace mozilla