|
| 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/Telemetry.h" |
| 10 | + |
| 11 | +#include "nsPrintfCString.h" |
| 12 | +#include "nsReadableUtils.h" |
| 13 | +#include "nsString.h" |
| 14 | +#include "nsUnicharUtils.h" |
| 15 | +#include "nsWinUtils.h" |
| 16 | + |
| 17 | +#include "NtUndoc.h" |
| 18 | + |
| 19 | +#if defined(UIA_LOGGING) |
| 20 | + |
| 21 | +#define LOG_ERROR(FuncName) \ |
| 22 | + { \ |
| 23 | + DWORD err = ::GetLastError(); \ |
| 24 | + nsPrintfCString msg(#FuncName " failed with code %u\n", err); \ |
| 25 | + ::OutputDebugStringA(msg.get()); \ |
| 26 | + } |
| 27 | + |
| 28 | +#else |
| 29 | + |
| 30 | +#define LOG_ERROR(FuncName) |
| 31 | + |
| 32 | +#endif // defined(UIA_LOGGING) |
| 33 | + |
| 34 | +static bool |
| 35 | +GetLocalObjectHandle(DWORD aSrcPid, HANDLE aSrcHandle, nsAutoHandle& aProcess, |
| 36 | + nsAutoHandle& aLocal) |
| 37 | +{ |
| 38 | + aLocal.reset(); |
| 39 | + |
| 40 | + if (!aProcess) { |
| 41 | + HANDLE rawProcess = ::OpenProcess(PROCESS_DUP_HANDLE, FALSE, aSrcPid); |
| 42 | + if (!rawProcess) { |
| 43 | + LOG_ERROR(OpenProcess); |
| 44 | + return false; |
| 45 | + } |
| 46 | + |
| 47 | + aProcess.own(rawProcess); |
| 48 | + } |
| 49 | + |
| 50 | + HANDLE rawDuped; |
| 51 | + if (!::DuplicateHandle(aProcess.get(), aSrcHandle, ::GetCurrentProcess(), |
| 52 | + &rawDuped, GENERIC_READ, FALSE, 0)) { |
| 53 | + LOG_ERROR(DuplicateHandle); |
| 54 | + return false; |
| 55 | + } |
| 56 | + |
| 57 | + aLocal.own(rawDuped); |
| 58 | + |
| 59 | + return true; |
| 60 | +} |
| 61 | + |
| 62 | +namespace mozilla { |
| 63 | +namespace a11y { |
| 64 | + |
| 65 | +Maybe<DWORD> Compatibility::sUiaRemotePid; |
| 66 | + |
| 67 | +Maybe<bool> |
| 68 | +Compatibility::OnUIAMessage(WPARAM aWParam, LPARAM aLParam) |
| 69 | +{ |
| 70 | + Maybe<DWORD>& remotePid = sUiaRemotePid; |
| 71 | + auto clearUiaRemotePid = MakeScopeExit([&remotePid]() { |
| 72 | + remotePid = Nothing(); |
| 73 | + }); |
| 74 | + |
| 75 | + static auto pNtQuerySystemInformation = |
| 76 | + reinterpret_cast<decltype(&::NtQuerySystemInformation)>( |
| 77 | + ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), |
| 78 | + "NtQuerySystemInformation")); |
| 79 | + |
| 80 | + static auto pNtQueryObject = |
| 81 | + reinterpret_cast<decltype(&::NtQueryObject)>( |
| 82 | + ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtQueryObject")); |
| 83 | + |
| 84 | + // UIA creates a section containing the substring "HOOK_SHMEM_" |
| 85 | + NS_NAMED_LITERAL_STRING(kStrHookShmem, "HOOK_SHMEM_"); |
| 86 | + |
| 87 | + // The section name always ends with this suffix, which is derived from the |
| 88 | + // current thread id and the UIA message's WPARAM and LPARAM. |
| 89 | + nsAutoString partialSectionSuffix; |
| 90 | + partialSectionSuffix.AppendPrintf("_%08x_%08x_%08x", ::GetCurrentThreadId(), |
| 91 | + aLParam, aWParam); |
| 92 | + |
| 93 | + NTSTATUS ntStatus; |
| 94 | + |
| 95 | + // First we must query for a list of all the open handles in the system. |
| 96 | + UniquePtr<char[]> handleInfoBuf; |
| 97 | + ULONG handleInfoBufLen = sizeof(SYSTEM_HANDLE_INFORMATION_EX) + |
| 98 | + 1024 * sizeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX); |
| 99 | + |
| 100 | + // We must query for handle information in a loop, since we are effectively |
| 101 | + // asking the kernel to take a snapshot of all the handles on the system; |
| 102 | + // the size of the required buffer may fluctuate between successive calls. |
| 103 | + while (true) { |
| 104 | + handleInfoBuf = MakeUnique<char[]>(handleInfoBufLen); |
| 105 | + |
| 106 | + ntStatus = pNtQuerySystemInformation( |
| 107 | + (SYSTEM_INFORMATION_CLASS) SystemExtendedHandleInformation, |
| 108 | + handleInfoBuf.get(), handleInfoBufLen, &handleInfoBufLen); |
| 109 | + if (ntStatus == STATUS_INFO_LENGTH_MISMATCH) { |
| 110 | + continue; |
| 111 | + } |
| 112 | + |
| 113 | + if (!NT_SUCCESS(ntStatus)) { |
| 114 | + return Nothing(); |
| 115 | + } |
| 116 | + |
| 117 | + break; |
| 118 | + } |
| 119 | + |
| 120 | + // Now we iterate through the system handle list, searching for a section |
| 121 | + // handle whose name matches the section name used by UIA. |
| 122 | + |
| 123 | + static Maybe<USHORT> sSectionObjTypeIndex; |
| 124 | + |
| 125 | + const DWORD ourPid = ::GetCurrentProcessId(); |
| 126 | + |
| 127 | + Maybe<PVOID> kernelObject; |
| 128 | + |
| 129 | + ULONG lastPid = 0; |
| 130 | + nsAutoHandle process; |
| 131 | + |
| 132 | + auto handleInfo = reinterpret_cast<SYSTEM_HANDLE_INFORMATION_EX*>(handleInfoBuf.get()); |
| 133 | + |
| 134 | + for (ULONG index = 0; index < handleInfo->mHandleCount; ++index) { |
| 135 | + SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX& curHandle = handleInfo->mHandles[index]; |
| 136 | + |
| 137 | + if (lastPid && lastPid == curHandle.mPid && !process) { |
| 138 | + // During the previous iteration, we could not obtain a handle for this |
| 139 | + // pid. Skip any remaining handles belonging to that pid. |
| 140 | + continue; |
| 141 | + } |
| 142 | + |
| 143 | + // As a perf optimization, we reuse the same process handle as long as we're |
| 144 | + // still looking at the same pid. Once the pid changes, we need to reset |
| 145 | + // process so that we open a new handle to the newly-seen process. |
| 146 | + if (lastPid != curHandle.mPid) { |
| 147 | + process.reset(); |
| 148 | + } |
| 149 | + |
| 150 | + nsAutoHandle handle; |
| 151 | + |
| 152 | + if (kernelObject.isSome() && kernelObject.value() == curHandle.mObject) { |
| 153 | + // If we know the value of the underlying kernel object, we can immediately |
| 154 | + // check for equality by comparing against curHandle.mObject |
| 155 | + remotePid = Some(static_cast<DWORD>(curHandle.mPid)); |
| 156 | + break; |
| 157 | + } else if (sSectionObjTypeIndex.isSome()) { |
| 158 | + // Otherwise, if we know which object type value corresponds to a Section |
| 159 | + // object, we can use that to eliminate any handles that are not sections. |
| 160 | + if (curHandle.mObjectTypeIndex != sSectionObjTypeIndex.value()) { |
| 161 | + // Not a section handle |
| 162 | + continue; |
| 163 | + } |
| 164 | + } else { |
| 165 | + // Otherwise we need to query the handle to determine its type. Note that |
| 166 | + // each handle in the system list is relative to its owning process, so |
| 167 | + // we cannot do anything with it until we duplicate the handle into our |
| 168 | + // own process. |
| 169 | + |
| 170 | + lastPid = curHandle.mPid; |
| 171 | + |
| 172 | + if (!GetLocalObjectHandle((DWORD) curHandle.mPid, |
| 173 | + (HANDLE) curHandle.mHandle, |
| 174 | + process, handle)) { |
| 175 | + // We don't have access to do this, assume this handle isn't relevant |
| 176 | + continue; |
| 177 | + } |
| 178 | + |
| 179 | + // Now we have our own handle to the object, lets find out what type of |
| 180 | + // object this handle represents. Any handle whose type is not "Section" |
| 181 | + // is of no interest to us. |
| 182 | + ULONG objTypeBufLen; |
| 183 | + ntStatus = pNtQueryObject(handle, ObjectTypeInformation, nullptr, |
| 184 | + 0, &objTypeBufLen); |
| 185 | + if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) { |
| 186 | + continue; |
| 187 | + } |
| 188 | + |
| 189 | + auto objTypeBuf = MakeUnique<char[]>(objTypeBufLen); |
| 190 | + ntStatus = pNtQueryObject(handle, ObjectTypeInformation, objTypeBuf.get(), |
| 191 | + objTypeBufLen, &objTypeBufLen); |
| 192 | + if (!NT_SUCCESS(ntStatus)) { |
| 193 | + // We don't have access to do this, assume this handle isn't relevant |
| 194 | + continue; |
| 195 | + } |
| 196 | + |
| 197 | + auto objType = |
| 198 | + reinterpret_cast<PUBLIC_OBJECT_TYPE_INFORMATION*>(objTypeBuf.get()); |
| 199 | + |
| 200 | + nsDependentString objTypeName(objType->TypeName.Buffer, |
| 201 | + objType->TypeName.Length / sizeof(wchar_t)); |
| 202 | + if (!objTypeName.Equals(NS_LITERAL_STRING("Section"))) { |
| 203 | + // Not a section, so we don't care about this handle anymore. |
| 204 | + continue; |
| 205 | + } |
| 206 | + |
| 207 | + // We have a section, save this handle's type code so that we can go |
| 208 | + // faster in future iterations. |
| 209 | + sSectionObjTypeIndex = Some(curHandle.mObjectTypeIndex); |
| 210 | + } |
| 211 | + |
| 212 | + // If we reached this point without needing to query the handle, then we |
| 213 | + // need to open it here so that we can query its name. |
| 214 | + lastPid = curHandle.mPid; |
| 215 | + |
| 216 | + if ((!process || !handle) && |
| 217 | + !GetLocalObjectHandle((DWORD) curHandle.mPid, (HANDLE) curHandle.mHandle, |
| 218 | + process, handle)) { |
| 219 | + // We don't have access to do this, assume this handle isn't relevant |
| 220 | + continue; |
| 221 | + } |
| 222 | + |
| 223 | + // At this point, |handle| is a valid section handle. Let's try to find |
| 224 | + // out the name of its underlying object. |
| 225 | + ULONG objNameBufLen; |
| 226 | + ntStatus = pNtQueryObject(handle, |
| 227 | + (OBJECT_INFORMATION_CLASS)ObjectNameInformation, |
| 228 | + nullptr, 0, &objNameBufLen); |
| 229 | + if (ntStatus != STATUS_INFO_LENGTH_MISMATCH) { |
| 230 | + continue; |
| 231 | + } |
| 232 | + |
| 233 | + auto objNameBuf = MakeUnique<char[]>(objNameBufLen); |
| 234 | + ntStatus = pNtQueryObject(handle, |
| 235 | + (OBJECT_INFORMATION_CLASS)ObjectNameInformation, |
| 236 | + objNameBuf.get(), objNameBufLen, &objNameBufLen); |
| 237 | + if (!NT_SUCCESS(ntStatus)) { |
| 238 | + continue; |
| 239 | + } |
| 240 | + |
| 241 | + auto objNameInfo = reinterpret_cast<OBJECT_NAME_INFORMATION*>(objNameBuf.get()); |
| 242 | + if (!objNameInfo->mName.Length) { |
| 243 | + // This section is unnamed. We don't care about those. |
| 244 | + continue; |
| 245 | + } |
| 246 | + |
| 247 | + nsDependentString objName(objNameInfo->mName.Buffer, |
| 248 | + objNameInfo->mName.Length / sizeof(wchar_t)); |
| 249 | + |
| 250 | + // Check to see if the section's name matches our expected name. |
| 251 | + if (!FindInReadable(kStrHookShmem, objName) || |
| 252 | + !StringEndsWith(objName, partialSectionSuffix)) { |
| 253 | + // The names don't match, continue searching. |
| 254 | + continue; |
| 255 | + } |
| 256 | + |
| 257 | + // At this point we have a section handle whose name matches the one that |
| 258 | + // we're looking for. |
| 259 | + |
| 260 | + if (curHandle.mPid == ourPid) { |
| 261 | + // Our own process also has a handle to the section of interest. While we |
| 262 | + // don't want our own pid, this *does* give us an opportunity to speed up |
| 263 | + // future iterations by examining each handle for its kernel object (which |
| 264 | + // is the same for all processes) instead of searching by name. |
| 265 | + kernelObject = Some(curHandle.mObject); |
| 266 | + continue; |
| 267 | + } |
| 268 | + |
| 269 | + // Bingo! We want this pid! |
| 270 | + remotePid = Some(static_cast<DWORD>(curHandle.mPid)); |
| 271 | + |
| 272 | + break; |
| 273 | + } |
| 274 | + |
| 275 | + if (!remotePid) { |
| 276 | + return Nothing(); |
| 277 | + } |
| 278 | + |
| 279 | + a11y::SetInstantiator(remotePid.value()); |
| 280 | + |
| 281 | + /* This is where we could block UIA stuff |
| 282 | + nsCOMPtr<nsIFile> instantiator; |
| 283 | + if (a11y::GetInstantiator(getter_AddRefs(instantiator)) && |
| 284 | + ShouldBlockUIAClient(instantiator)) { |
| 285 | + return Some(false); |
| 286 | + } |
| 287 | + */ |
| 288 | + |
| 289 | + return Some(true); |
| 290 | +} |
| 291 | + |
| 292 | +} // namespace a11y |
| 293 | +} // namespace mozilla |
0 commit comments