/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #if defined(MOZILLA_INTERNAL_API) # include "MainThreadUtils.h" # include "mozilla/dom/ContentChild.h" #endif #if defined(ACCESSIBILITY) # include "mozilla/mscom/Registration.h" # if defined(MOZILLA_INTERNAL_API) # include "nsTArray.h" # endif #endif #include "mozilla/ArrayUtils.h" #include "mozilla/mscom/COMWrappers.h" #include "mozilla/DebugOnly.h" #include "mozilla/mscom/Objref.h" #include "mozilla/mscom/Utils.h" #include "mozilla/RefPtr.h" #include "mozilla/WindowsVersion.h" #include #include #include #include #if defined(_MSC_VER) extern "C" IMAGE_DOS_HEADER __ImageBase; #endif namespace mozilla { namespace mscom { bool IsCOMInitializedOnCurrentThread() { APTTYPE aptType; APTTYPEQUALIFIER aptTypeQualifier; HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); return hr != CO_E_NOTINITIALIZED; } bool IsCurrentThreadMTA() { APTTYPE aptType; APTTYPEQUALIFIER aptTypeQualifier; HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); if (FAILED(hr)) { return false; } return aptType == APTTYPE_MTA; } bool IsCurrentThreadExplicitMTA() { APTTYPE aptType; APTTYPEQUALIFIER aptTypeQualifier; HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); if (FAILED(hr)) { return false; } return aptType == APTTYPE_MTA && aptTypeQualifier != APTTYPEQUALIFIER_IMPLICIT_MTA; } bool IsCurrentThreadImplicitMTA() { APTTYPE aptType; APTTYPEQUALIFIER aptTypeQualifier; HRESULT hr = wrapped::CoGetApartmentType(&aptType, &aptTypeQualifier); if (FAILED(hr)) { return false; } return aptType == APTTYPE_MTA && aptTypeQualifier == APTTYPEQUALIFIER_IMPLICIT_MTA; } #if defined(MOZILLA_INTERNAL_API) bool IsCurrentThreadNonMainMTA() { if (NS_IsMainThread()) { return false; } return IsCurrentThreadMTA(); } #endif // defined(MOZILLA_INTERNAL_API) bool IsProxy(IUnknown* aUnknown) { if (!aUnknown) { return false; } // Only proxies implement this interface, so if it is present then we must // be dealing with a proxied object. RefPtr clientSecurity; HRESULT hr = aUnknown->QueryInterface(IID_IClientSecurity, (void**)getter_AddRefs(clientSecurity)); if (SUCCEEDED(hr) || hr == RPC_E_WRONG_THREAD) { return true; } return false; } bool IsValidGUID(REFGUID aCheckGuid) { // This function determines whether or not aCheckGuid conforms to RFC4122 // as it applies to Microsoft COM. BYTE variant = aCheckGuid.Data4[0]; if (!(variant & 0x80)) { // NCS Reserved return false; } if ((variant & 0xE0) == 0xE0) { // Reserved for future use return false; } if ((variant & 0xC0) == 0xC0) { // Microsoft Reserved. return true; } BYTE version = HIBYTE(aCheckGuid.Data3) >> 4; // Other versions are specified in RFC4122 but these are the two used by COM. return version == 1 || version == 4; } uintptr_t GetContainingModuleHandle() { HMODULE thisModule = nullptr; #if defined(_MSC_VER) thisModule = reinterpret_cast(&__ImageBase); #else if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, reinterpret_cast(&GetContainingModuleHandle), &thisModule)) { return 0; } #endif return reinterpret_cast(thisModule); } namespace detail { long BuildRegGuidPath(REFGUID aGuid, const GuidType aGuidType, wchar_t* aBuf, const size_t aBufLen) { constexpr wchar_t kClsid[] = L"CLSID\\"; constexpr wchar_t kAppid[] = L"AppID\\"; constexpr wchar_t kSubkeyBase[] = L"SOFTWARE\\Classes\\"; // We exclude null terminators in these length calculations because we include // the stringified GUID's null terminator at the end. Since kClsid and kAppid // have identical lengths, we just choose one to compute this length. constexpr size_t kSubkeyBaseLen = mozilla::ArrayLength(kSubkeyBase) - 1; constexpr size_t kSubkeyLen = kSubkeyBaseLen + mozilla::ArrayLength(kClsid) - 1; // Guid length as formatted for the registry (including curlies and dashes), // but excluding null terminator. constexpr size_t kGuidLen = kGuidRegFormatCharLenInclNul - 1; constexpr size_t kExpectedPathLenInclNul = kSubkeyLen + kGuidLen + 1; if (aBufLen < kExpectedPathLenInclNul) { // Buffer is too short return E_INVALIDARG; } if (wcscpy_s(aBuf, aBufLen, kSubkeyBase)) { return E_INVALIDARG; } const wchar_t* strGuidType = aGuidType == GuidType::CLSID ? kClsid : kAppid; if (wcscat_s(aBuf, aBufLen, strGuidType)) { return E_INVALIDARG; } int guidConversionResult = ::StringFromGUID2(aGuid, &aBuf[kSubkeyLen], aBufLen - kSubkeyLen); if (!guidConversionResult) { return E_INVALIDARG; } return S_OK; } } // namespace detail long CreateStream(const uint8_t* aInitBuf, const uint32_t aInitBufSize, IStream** aOutStream) { if (!aInitBufSize || !aOutStream) { return E_INVALIDARG; } *aOutStream = nullptr; HRESULT hr; RefPtr stream; if (IsWin8OrLater()) { // SHCreateMemStream is not safe for us to use until Windows 8. On older // versions of Windows it is not thread-safe and it creates IStreams that do // not support the full IStream API. // If aInitBuf is null then initSize must be 0. UINT initSize = aInitBuf ? aInitBufSize : 0; stream = already_AddRefed(::SHCreateMemStream(aInitBuf, initSize)); if (!stream) { return E_OUTOFMEMORY; } if (!aInitBuf) { // Now we'll set the required size ULARGE_INTEGER newSize; newSize.QuadPart = aInitBufSize; hr = stream->SetSize(newSize); if (FAILED(hr)) { return hr; } } } else { HGLOBAL hglobal = ::GlobalAlloc(GMEM_MOVEABLE, aInitBufSize); if (!hglobal) { return HRESULT_FROM_WIN32(::GetLastError()); } // stream takes ownership of hglobal if this call is successful hr = ::CreateStreamOnHGlobal(hglobal, TRUE, getter_AddRefs(stream)); if (FAILED(hr)) { ::GlobalFree(hglobal); return hr; } // The default stream size is derived from ::GlobalSize(hglobal), which due // to rounding may be larger than aInitBufSize. We forcibly set the correct // stream size here. ULARGE_INTEGER streamSize; streamSize.QuadPart = aInitBufSize; hr = stream->SetSize(streamSize); if (FAILED(hr)) { return hr; } if (aInitBuf) { ULONG bytesWritten; hr = stream->Write(aInitBuf, aInitBufSize, &bytesWritten); if (FAILED(hr)) { return hr; } if (bytesWritten != aInitBufSize) { return E_UNEXPECTED; } } } // Ensure that the stream is rewound LARGE_INTEGER streamOffset; streamOffset.QuadPart = 0LL; hr = stream->Seek(streamOffset, STREAM_SEEK_SET, nullptr); if (FAILED(hr)) { return hr; } stream.forget(aOutStream); return S_OK; } long CopySerializedProxy(IStream* aInStream, IStream** aOutStream) { if (!aInStream || !aOutStream) { return E_INVALIDARG; } *aOutStream = nullptr; uint32_t desiredStreamSize = GetOBJREFSize(WrapNotNull(aInStream)); if (!desiredStreamSize) { return E_INVALIDARG; } RefPtr stream; HRESULT hr = CreateStream(nullptr, desiredStreamSize, getter_AddRefs(stream)); if (FAILED(hr)) { return hr; } ULARGE_INTEGER numBytesToCopy; numBytesToCopy.QuadPart = desiredStreamSize; hr = aInStream->CopyTo(stream, numBytesToCopy, nullptr, nullptr); if (FAILED(hr)) { return hr; } LARGE_INTEGER seekTo; seekTo.QuadPart = 0LL; hr = stream->Seek(seekTo, STREAM_SEEK_SET, nullptr); if (FAILED(hr)) { return hr; } stream.forget(aOutStream); return S_OK; } #if defined(MOZILLA_INTERNAL_API) void GUIDToString(REFGUID aGuid, nsAString& aOutString) { // This buffer length is long enough to hold a GUID string that is formatted // to include curly braces and dashes. const int kBufLenWithNul = 39; aOutString.SetLength(kBufLenWithNul); int result = StringFromGUID2(aGuid, char16ptr_t(aOutString.BeginWriting()), kBufLenWithNul); MOZ_ASSERT(result); if (result) { // Truncate the terminator aOutString.SetLength(result - 1); } } // Undocumented IIDs that are relevant for diagnostic purposes static const IID IID_ISCMLocalActivator = { 0x00000136, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; static const IID IID_IRundown = { 0x00000134, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; static const IID IID_IRemUnknown = { 0x00000131, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; static const IID IID_IRemUnknown2 = { 0x00000143, 0x0000, 0x0000, {0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}}; struct IIDToLiteralMapEntry { constexpr IIDToLiteralMapEntry(REFIID aIid, nsLiteralCString&& aStr) : mIid(aIid), mStr(std::forward(aStr)) {} REFIID mIid; const nsLiteralCString mStr; }; /** * Given the name of an interface, the IID_ENTRY macro generates a pair * containing a reference to the interface ID and a stringified version of * the interface name. * * For example: * * {IID_ENTRY(IUnknown)} * is expanded to: * {IID_IUnknown, "IUnknown"_ns} * */ // clang-format off # define IID_ENTRY_STRINGIFY(iface) #iface##_ns # define IID_ENTRY(iface) IID_##iface, IID_ENTRY_STRINGIFY(iface) // clang-format on // Mapping of selected IIDs to friendly, human readable descriptions for each // interface. static constexpr IIDToLiteralMapEntry sIidDiagStrs[] = { {IID_ENTRY(IUnknown)}, {IID_IRemUnknown, "cross-apartment IUnknown"_ns}, {IID_IRundown, "cross-apartment object management"_ns}, {IID_ISCMLocalActivator, "out-of-process object instantiation"_ns}, {IID_IRemUnknown2, "cross-apartment IUnknown"_ns}}; # undef IID_ENTRY # undef IID_ENTRY_STRINGIFY void DiagnosticNameForIID(REFIID aIid, nsACString& aOutString) { // If the IID matches something in sIidDiagStrs, output its string. for (const auto& curEntry : sIidDiagStrs) { if (curEntry.mIid == aIid) { aOutString.Assign(curEntry.mStr); return; } } // Otherwise just convert the IID to string form and output that. nsAutoString strIid; GUIDToString(aIid, strIid); aOutString.AssignLiteral("IID "); AppendUTF16toUTF8(strIid, aOutString); } #else void GUIDToString(REFGUID aGuid, wchar_t (&aOutBuf)[kGuidRegFormatCharLenInclNul]) { DebugOnly result = ::StringFromGUID2(aGuid, aOutBuf, ArrayLength(aOutBuf)); MOZ_ASSERT(result); } #endif // defined(MOZILLA_INTERNAL_API) #if defined(ACCESSIBILITY) static bool IsVtableIndexFromParentInterface(TYPEATTR* aTypeAttr, unsigned long aVtableIndex) { MOZ_ASSERT(aTypeAttr); // This is the number of functions declared in this interface (excluding // parent interfaces). unsigned int numExclusiveFuncs = aTypeAttr->cFuncs; // This is the number of vtable entries (which includes parent interfaces). // TYPEATTR::cbSizeVft is the entire vtable size in bytes, so we need to // divide in order to compute the number of entries. unsigned int numVtblEntries = aTypeAttr->cbSizeVft / sizeof(void*); // This is the index of the first entry in the vtable that belongs to this // interface and not a parent. unsigned int firstVtblIndex = numVtblEntries - numExclusiveFuncs; // If aVtableIndex is less than firstVtblIndex, then we're asking for an // index that may belong to a parent interface. return aVtableIndex < firstVtblIndex; } bool IsVtableIndexFromParentInterface(REFIID aInterface, unsigned long aVtableIndex) { RefPtr typeInfo; if (!RegisteredProxy::Find(aInterface, getter_AddRefs(typeInfo))) { return false; } TYPEATTR* typeAttr = nullptr; HRESULT hr = typeInfo->GetTypeAttr(&typeAttr); if (FAILED(hr)) { return false; } bool result = IsVtableIndexFromParentInterface(typeAttr, aVtableIndex); typeInfo->ReleaseTypeAttr(typeAttr); return result; } # if defined(MOZILLA_INTERNAL_API) bool IsCallerExternalProcess() { MOZ_ASSERT(XRE_IsContentProcess()); /** * CoGetCallerTID() gives us the caller's thread ID when that thread resides * in a single-threaded apartment. Since our chrome main thread does live * inside an STA, we will therefore be able to check whether the caller TID * equals our chrome main thread TID. This enables us to distinguish * between our chrome thread vs other out-of-process callers. We check for * S_FALSE to ensure that the caller is a different process from ours, which * is the only scenario that we care about. */ DWORD callerTid; if (::CoGetCallerTID(&callerTid) != S_FALSE) { return false; } // Now check whether the caller is our parent process main thread. const DWORD parentMainTid = dom::ContentChild::GetSingleton()->GetChromeMainThreadId(); return callerTid != parentMainTid; } bool IsInterfaceEqualToOrInheritedFrom(REFIID aInterface, REFIID aFrom, unsigned long aVtableIndexHint) { if (aInterface == aFrom) { return true; } // We expect this array to be length 1 but that is not guaranteed by the API. AutoTArray, 1> typeInfos; // Grab aInterface's ITypeInfo so that we may obtain information about its // inheritance hierarchy. RefPtr typeInfo; if (RegisteredProxy::Find(aInterface, getter_AddRefs(typeInfo))) { typeInfos.AppendElement(std::move(typeInfo)); } /** * The main loop of this function searches the hierarchy of aInterface's * parent interfaces, searching for aFrom. */ while (!typeInfos.IsEmpty()) { RefPtr curTypeInfo(typeInfos.PopLastElement()); TYPEATTR* typeAttr = nullptr; HRESULT hr = curTypeInfo->GetTypeAttr(&typeAttr); if (FAILED(hr)) { break; } bool isFromParentVtable = IsVtableIndexFromParentInterface(typeAttr, aVtableIndexHint); WORD numParentInterfaces = typeAttr->cImplTypes; curTypeInfo->ReleaseTypeAttr(typeAttr); typeAttr = nullptr; if (!isFromParentVtable) { // The vtable index cannot belong to this interface (otherwise the IIDs // would already have matched and we would have returned true). Since we // now also know that the vtable index cannot possibly be contained inside // curTypeInfo's parent interface, there is no point searching any further // up the hierarchy from here. OTOH we still should check any remaining // entries that are still in the typeInfos array, so we continue. continue; } for (WORD i = 0; i < numParentInterfaces; ++i) { HREFTYPE refCookie; hr = curTypeInfo->GetRefTypeOfImplType(i, &refCookie); if (FAILED(hr)) { continue; } RefPtr nextTypeInfo; hr = curTypeInfo->GetRefTypeInfo(refCookie, getter_AddRefs(nextTypeInfo)); if (FAILED(hr)) { continue; } hr = nextTypeInfo->GetTypeAttr(&typeAttr); if (FAILED(hr)) { continue; } IID nextIid = typeAttr->guid; nextTypeInfo->ReleaseTypeAttr(typeAttr); typeAttr = nullptr; if (nextIid == aFrom) { return true; } typeInfos.AppendElement(std::move(nextTypeInfo)); } } return false; } # endif // defined(MOZILLA_INTERNAL_API) #endif // defined(ACCESSIBILITY) #if defined(MOZILLA_INTERNAL_API) bool IsClassThreadAwareInprocServer(REFCLSID aClsid) { nsAutoString strClsid; GUIDToString(aClsid, strClsid); nsAutoString inprocServerSubkey(u"CLSID\\"_ns); inprocServerSubkey.Append(strClsid); inprocServerSubkey.Append(u"\\InprocServer32"_ns); // Of the possible values, "Apartment" is the longest, so we'll make this // buffer large enough to hold that one. wchar_t threadingModelBuf[ArrayLength(L"Apartment")] = {}; DWORD numBytes = sizeof(threadingModelBuf); LONG result = ::RegGetValueW(HKEY_CLASSES_ROOT, inprocServerSubkey.get(), L"ThreadingModel", RRF_RT_REG_SZ, nullptr, threadingModelBuf, &numBytes); if (result != ERROR_SUCCESS) { // This will also handle the case where the CLSID is not an inproc server. return false; } DWORD numChars = numBytes / sizeof(wchar_t); // numChars includes the null terminator if (numChars <= 1) { return false; } nsDependentString threadingModel(threadingModelBuf, numChars - 1); // Ensure that the threading model is one of the known values that indicates // that the class can operate natively (ie, no proxying) inside a MTA. return threadingModel.LowerCaseEqualsLiteral("both") || threadingModel.LowerCaseEqualsLiteral("free") || threadingModel.LowerCaseEqualsLiteral("neutral"); } #endif // defined(MOZILLA_INTERNAL_API) } // namespace mscom } // namespace mozilla