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