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