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"
14 #include "nspr/prenv.h"
16 #include "nsTHashMap.h"
17 #include "nsTHashSet.h"
18 #include "nsPrintfCString.h"
19 #include "nsReadableUtils.h"
21 #include "nsTHashtable.h"
22 #include "nsUnicharUtils.h"
23 #include "nsWinUtils.h"
27 #if defined(UIA_LOGGING)
29 # define LOG_ERROR(FuncName) \
31 DWORD err = ::GetLastError(); \
32 nsPrintfCString msg(#FuncName " failed with code %u\n", err); \
33 ::OutputDebugStringA(msg.get()); \
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
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.
56 if (!::ProcessIdToSessionId(::GetCurrentProcessId(), &sessionId
)) {
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,
70 HANDLE rawBaseNamedObjects
;
71 NTSTATUS ntStatus
= ::NtOpenDirectoryObject(
72 &rawBaseNamedObjects
, DIRECTORY_QUERY
| DIRECTORY_TRAVERSE
, &attributes
);
73 if (!NT_SUCCESS(ntStatus
)) {
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
;
90 ntStatus
= ::NtQueryDirectoryObject(baseNamedObjects
, objDirInfo
.get(),
91 objDirInfoBufLen
, FALSE
, firstCall
,
92 &context
, &returnedLen
);
93 #if defined(HAVE_64BIT_BUILD)
94 if (!NT_SUCCESS(ntStatus
)) {
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
;
105 } else if (!NT_SUCCESS(ntStatus
)) {
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
)) {
129 } while (ntStatus
== STATUS_MORE_ENTRIES
);
134 static const char* gBlockedUiaClients
[] = {"osk.exe"};
136 static bool ShouldBlockUIAClient(nsIFile
* aClientExe
) {
137 if (PR_GetEnv("MOZ_DISABLE_ACCESSIBLE_BLOCKLIST")) {
141 nsAutoString leafName
;
142 nsresult rv
= aClientExe
->GetLeafName(leafName
);
147 for (size_t index
= 0, len
= ArrayLength(gBlockedUiaClients
); index
< len
;
149 if (leafName
.EqualsIgnoreCase(gBlockedUiaClients
[index
])) {
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
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()));
191 if (!FindNamedObject(comparator
) || !section
) {
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.
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
) {
213 ntStatus
= ::NtQuerySystemInformation(
214 (SYSTEM_INFORMATION_CLASS
)SystemExtendedHandleInformation
,
215 handleInfoBuf
.get(), handleInfoBufLen
, &handleInfoBufLen
);
216 if (ntStatus
== STATUS_INFO_LENGTH_MISMATCH
) {
220 if (!NT_SUCCESS(ntStatus
)) {
227 const DWORD ourPid
= ::GetCurrentProcessId();
228 Maybe
<PVOID
> kernelObject
;
229 static Maybe
<USHORT
> sectionObjTypeIndex
;
230 nsTHashSet
<uint32_t> nonSectionObjTypes
;
231 nsTHashMap
<nsVoidPtrHashKey
, DWORD
> objMap
;
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
) {
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_
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.
260 ntStatus
= ::NtQueryObject(handle
, ObjectTypeInformation
, nullptr, 0,
262 if (ntStatus
!= STATUS_INFO_LENGTH_MISMATCH
) {
266 auto objTypeBuf
= MakeUnique
<char[]>(objTypeBufLen
);
268 ::NtQueryObject(handle
, ObjectTypeInformation
, objTypeBuf
.get(),
269 objTypeBufLen
, &objTypeBufLen
);
270 if (!NT_SUCCESS(ntStatus
)) {
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
));
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
);
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
);
313 if (!sUiaRemotePid
) {
314 // We found kernelObject *after* we saw the remote process's copy. Now we
315 // must look it up in objMap.
317 if (objMap
.Get(kernelObject
.value(), &pid
)) {
318 sUiaRemotePid
= Some(pid
);
322 if (!sUiaRemotePid
) {
326 a11y::SetInstantiator(sUiaRemotePid
.value());
328 // Block if necessary
329 nsCOMPtr
<nsIFile
> instantiator
;
330 if (a11y::GetInstantiator(getter_AddRefs(instantiator
)) &&
331 ShouldBlockUIAClient(instantiator
)) {
339 } // namespace mozilla