diff options
Diffstat (limited to 'ipc/mscom/Utils.cpp')
-rw-r--r-- | ipc/mscom/Utils.cpp | 600 |
1 files changed, 600 insertions, 0 deletions
diff --git a/ipc/mscom/Utils.cpp b/ipc/mscom/Utils.cpp new file mode 100644 index 0000000000..377bbeba5c --- /dev/null +++ b/ipc/mscom/Utils.cpp @@ -0,0 +1,600 @@ +/* -*- 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 <objidl.h> +#include <shlwapi.h> +#include <winnt.h> + +#include <utility> + +#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<IClientSecurity> 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<HMODULE>(&__ImageBase); +#else + if (!GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, + reinterpret_cast<LPCTSTR>(&GetContainingModuleHandle), + &thisModule)) { + return 0; + } +#endif + return reinterpret_cast<uintptr_t>(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<IStream> 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<IStream>(::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<IStream> 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<nsLiteralCString>(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<int> 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<ITypeInfo> 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<RefPtr<ITypeInfo>, 1> typeInfos; + + // Grab aInterface's ITypeInfo so that we may obtain information about its + // inheritance hierarchy. + RefPtr<ITypeInfo> 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<ITypeInfo> 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<ITypeInfo> 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 |