diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /mozglue/dllservices | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'mozglue/dllservices')
-rw-r--r-- | mozglue/dllservices/Authenticode.cpp | 432 | ||||
-rw-r--r-- | mozglue/dllservices/Authenticode.h | 32 | ||||
-rw-r--r-- | mozglue/dllservices/LoaderAPIInterfaces.h | 120 | ||||
-rw-r--r-- | mozglue/dllservices/LoaderObserver.cpp | 165 | ||||
-rw-r--r-- | mozglue/dllservices/LoaderObserver.h | 45 | ||||
-rw-r--r-- | mozglue/dllservices/ModuleLoadFrame.cpp | 105 | ||||
-rw-r--r-- | mozglue/dllservices/ModuleLoadFrame.h | 44 | ||||
-rw-r--r-- | mozglue/dllservices/ModuleLoadInfo.h | 173 | ||||
-rw-r--r-- | mozglue/dllservices/NtLoaderAPI.h | 23 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsDllBlocklist.cpp | 782 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsDllBlocklist.h | 57 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsDllBlocklistCommon.h | 118 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsDllBlocklistDefs.in | 284 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsDllServices.h | 211 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsFallbackLoaderAPI.cpp | 86 | ||||
-rw-r--r-- | mozglue/dllservices/WindowsFallbackLoaderAPI.h | 38 | ||||
-rw-r--r-- | mozglue/dllservices/gen_dll_blocklist_defs.py | 744 | ||||
-rw-r--r-- | mozglue/dllservices/moz.build | 60 |
18 files changed, 3519 insertions, 0 deletions
diff --git a/mozglue/dllservices/Authenticode.cpp b/mozglue/dllservices/Authenticode.cpp new file mode 100644 index 0000000000..55ce487f20 --- /dev/null +++ b/mozglue/dllservices/Authenticode.cpp @@ -0,0 +1,432 @@ +/* -*- 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/. */ + +// We need Windows 8 functions and structures to be able to verify SHA-256. +#if defined(_WIN32_WINNT) +# undef _WIN32_WINNT +# define _WIN32_WINNT _WIN32_WINNT_WIN8 +#endif // defined(_WIN32_WINNT) +#if defined(NTDDI_VERSION) +# undef NTDDI_VERSION +# define NTDDI_VERSION NTDDI_WIN8 +#endif // defined(NTDDI_VERSION) + +#include "Authenticode.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/WindowsVersion.h" +#include "nsWindowsHelpers.h" + +#include <windows.h> +#include <softpub.h> +#include <wincrypt.h> +#include <wintrust.h> +#include <mscat.h> + +#include <string.h> + +namespace { + +struct CertStoreDeleter { + typedef HCERTSTORE pointer; + void operator()(pointer aStore) { ::CertCloseStore(aStore, 0); } +}; + +struct CryptMsgDeleter { + typedef HCRYPTMSG pointer; + void operator()(pointer aMsg) { ::CryptMsgClose(aMsg); } +}; + +struct CertContextDeleter { + void operator()(PCCERT_CONTEXT aCertContext) { + ::CertFreeCertificateContext(aCertContext); + } +}; + +struct CATAdminContextDeleter { + typedef HCATADMIN pointer; + void operator()(pointer aCtx) { + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminReleaseContext)> + pCryptCATAdminReleaseContext(L"wintrust.dll", + "CryptCATAdminReleaseContext"); + + MOZ_ASSERT(!!pCryptCATAdminReleaseContext); + if (!pCryptCATAdminReleaseContext) { + return; + } + + pCryptCATAdminReleaseContext(aCtx, 0); + } +}; + +typedef mozilla::UniquePtr<HCERTSTORE, CertStoreDeleter> CertStoreUniquePtr; +typedef mozilla::UniquePtr<HCRYPTMSG, CryptMsgDeleter> CryptMsgUniquePtr; +typedef mozilla::UniquePtr<const CERT_CONTEXT, CertContextDeleter> + CertContextUniquePtr; +typedef mozilla::UniquePtr<HCATADMIN, CATAdminContextDeleter> + CATAdminContextUniquePtr; + +static const DWORD kEncodingTypes = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; + +class SignedBinary final { + public: + SignedBinary(const wchar_t* aFilePath, mozilla::AuthenticodeFlags aFlags); + + explicit operator bool() const { return mCertStore && mCryptMsg && mCertCtx; } + + mozilla::UniquePtr<wchar_t[]> GetOrgName(); + + SignedBinary(const SignedBinary&) = delete; + SignedBinary(SignedBinary&&) = delete; + SignedBinary& operator=(const SignedBinary&) = delete; + SignedBinary& operator=(SignedBinary&&) = delete; + + private: + bool VerifySignature(const wchar_t* aFilePath); + bool QueryObject(const wchar_t* aFilePath); + static bool VerifySignatureInternal(WINTRUST_DATA& aTrustData); + + private: + enum class TrustSource { eNone, eEmbedded, eCatalog }; + + private: + const mozilla::AuthenticodeFlags mFlags; + TrustSource mTrustSource; + CertStoreUniquePtr mCertStore; + CryptMsgUniquePtr mCryptMsg; + CertContextUniquePtr mCertCtx; +}; + +SignedBinary::SignedBinary(const wchar_t* aFilePath, + mozilla::AuthenticodeFlags aFlags) + : mFlags(aFlags), mTrustSource(TrustSource::eNone) { + if (!VerifySignature(aFilePath)) { + return; + } + + DWORD certInfoLen = 0; + BOOL ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0, + nullptr, &certInfoLen); + if (!ok) { + return; + } + + auto certInfoBuf = mozilla::MakeUnique<char[]>(certInfoLen); + + ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0, + certInfoBuf.get(), &certInfoLen); + if (!ok) { + return; + } + + auto certInfo = reinterpret_cast<CERT_INFO*>(certInfoBuf.get()); + + PCCERT_CONTEXT certCtx = + CertFindCertificateInStore(mCertStore.get(), kEncodingTypes, 0, + CERT_FIND_SUBJECT_CERT, certInfo, nullptr); + if (!certCtx) { + return; + } + + mCertCtx.reset(certCtx); +} + +bool SignedBinary::QueryObject(const wchar_t* aFilePath) { + DWORD encodingType, contentType, formatType; + HCERTSTORE rawCertStore; + HCRYPTMSG rawCryptMsg; + BOOL result = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, aFilePath, + CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED, + CERT_QUERY_FORMAT_FLAG_BINARY, 0, + &encodingType, &contentType, &formatType, + &rawCertStore, &rawCryptMsg, nullptr); + if (!result) { + return false; + } + + mCertStore.reset(rawCertStore); + mCryptMsg.reset(rawCryptMsg); + + return true; +} + +/** + * @param aTrustData must be a WINTRUST_DATA structure that has been zeroed out + * and then populated at least with its |cbStruct|, + * |dwUnionChoice|, and appropriate union field. This function + * will then populate the remaining fields as appropriate. + */ +/* static */ +bool SignedBinary::VerifySignatureInternal(WINTRUST_DATA& aTrustData) { + aTrustData.dwUIChoice = WTD_UI_NONE; + aTrustData.fdwRevocationChecks = WTD_REVOKE_NONE; + aTrustData.dwStateAction = WTD_STATEACTION_VERIFY; + aTrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL; + + const HWND hwnd = (HWND)INVALID_HANDLE_VALUE; + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + LONG result = ::WinVerifyTrust(hwnd, &policyGUID, &aTrustData); + + aTrustData.dwStateAction = WTD_STATEACTION_CLOSE; + ::WinVerifyTrust(hwnd, &policyGUID, &aTrustData); + + return result == ERROR_SUCCESS; +} + +bool SignedBinary::VerifySignature(const wchar_t* aFilePath) { + // First, try the binary itself + if (QueryObject(aFilePath)) { + mTrustSource = TrustSource::eEmbedded; + if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) { + return true; + } + + WINTRUST_FILE_INFO fileInfo = {sizeof(fileInfo)}; + fileInfo.pcwszFilePath = aFilePath; + + WINTRUST_DATA trustData = {sizeof(trustData)}; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.pFile = &fileInfo; + + return VerifySignatureInternal(trustData); + } + + // We didn't find anything in the binary, so now try a catalog file. + + // First, we open a catalog admin context. + HCATADMIN rawCatAdmin; + + // Windows 7 also exports the CryptCATAdminAcquireContext2 API, but it does + // *not* sign its binaries with SHA-256, so we use the old API in that case. + if (mozilla::IsWin8OrLater()) { + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminAcquireContext2)> + pCryptCATAdminAcquireContext2(L"wintrust.dll", + "CryptCATAdminAcquireContext2"); + if (!pCryptCATAdminAcquireContext2) { + return false; + } + + CERT_STRONG_SIGN_PARA policy = {sizeof(policy)}; + policy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE; + policy.pszOID = const_cast<char*>( + szOID_CERT_STRONG_SIGN_OS_CURRENT); // -Wwritable-strings + + if (!pCryptCATAdminAcquireContext2(&rawCatAdmin, nullptr, + BCRYPT_SHA256_ALGORITHM, &policy, 0)) { + return false; + } + } else { + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminAcquireContext)> + pCryptCATAdminAcquireContext(L"wintrust.dll", + "CryptCATAdminAcquireContext"); + + if (!pCryptCATAdminAcquireContext || + !pCryptCATAdminAcquireContext(&rawCatAdmin, nullptr, 0)) { + return false; + } + } + + CATAdminContextUniquePtr catAdmin(rawCatAdmin); + + // Now we need to hash the file at aFilePath. + // Since we're hashing this file, let's open it with a sequential scan hint. + HANDLE rawFile = + ::CreateFileW(aFilePath, GENERIC_READ, + FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE, + nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr); + if (rawFile == INVALID_HANDLE_VALUE) { + return false; + } + + nsAutoHandle file(rawFile); + DWORD hashLen = 0; + mozilla::UniquePtr<BYTE[]> hashBuf; + + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminCalcHashFromFileHandle2)> + pCryptCATAdminCalcHashFromFileHandle2( + L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle2"); + if (pCryptCATAdminCalcHashFromFileHandle2) { + if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen, + nullptr, 0) && + ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + + hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen); + + if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen, + hashBuf.get(), 0)) { + return false; + } + } else { + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminCalcHashFromFileHandle)> + pCryptCATAdminCalcHashFromFileHandle( + L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle"); + + if (!pCryptCATAdminCalcHashFromFileHandle) { + return false; + } + + if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, nullptr, 0) && + ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) { + return false; + } + + hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen); + + if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, hashBuf.get(), + 0)) { + return false; + } + } + + // Now that we've hashed the file, query the catalog system to see if any + // catalogs reference a binary with our hash. + + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminEnumCatalogFromHash)> + pCryptCATAdminEnumCatalogFromHash(L"wintrust.dll", + "CryptCATAdminEnumCatalogFromHash"); + if (!pCryptCATAdminEnumCatalogFromHash) { + return false; + } + + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATAdminReleaseCatalogContext)> + pCryptCATAdminReleaseCatalogContext(L"wintrust.dll", + "CryptCATAdminReleaseCatalogContext"); + if (!pCryptCATAdminReleaseCatalogContext) { + return false; + } + + HCATINFO catInfoHdl = pCryptCATAdminEnumCatalogFromHash( + rawCatAdmin, hashBuf.get(), hashLen, 0, nullptr); + if (!catInfoHdl) { + return false; + } + + // We can't use UniquePtr for this because the deleter function requires two + // parameters. + auto cleanCatInfoHdl = + mozilla::MakeScopeExit([rawCatAdmin, catInfoHdl]() -> void { + pCryptCATAdminReleaseCatalogContext(rawCatAdmin, catInfoHdl, 0); + }); + + // We found a catalog! Now query for the path to the catalog file. + + static const mozilla::StaticDynamicallyLinkedFunctionPtr<decltype( + &::CryptCATCatalogInfoFromContext)> + pCryptCATCatalogInfoFromContext(L"wintrust.dll", + "CryptCATCatalogInfoFromContext"); + if (!pCryptCATCatalogInfoFromContext) { + return false; + } + + CATALOG_INFO_ catInfo = {sizeof(catInfo)}; + if (!pCryptCATCatalogInfoFromContext(catInfoHdl, &catInfo, 0)) { + return false; + } + + if (!QueryObject(catInfo.wszCatalogFile)) { + return false; + } + + mTrustSource = TrustSource::eCatalog; + + if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) { + return true; + } + + // WINTRUST_CATALOG_INFO::pcwszMemberTag is commonly set to the string + // representation of the file hash, so we build that here. + + DWORD strHashBufLen = (hashLen * 2) + 1; + auto strHashBuf = mozilla::MakeUnique<wchar_t[]>(strHashBufLen); + if (!::CryptBinaryToStringW(hashBuf.get(), hashLen, + CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF, + strHashBuf.get(), &strHashBufLen)) { + return false; + } + + // Ensure that the tag is uppercase for WinVerifyTrust + // NB: CryptBinaryToStringW overwrites strHashBufLen with the length excluding + // the null terminator, so we need to add it back for this call. + if (_wcsupr_s(strHashBuf.get(), strHashBufLen + 1)) { + return false; + } + + // Now, given the path to the catalog, and the path to the member (ie, the + // binary whose hash we are validating), we may now validate. If the + // validation is successful, we then QueryObject on the *catalog file* + // instead of the binary. + + WINTRUST_CATALOG_INFO wtCatInfo = {sizeof(wtCatInfo)}; + wtCatInfo.pcwszCatalogFilePath = catInfo.wszCatalogFile; + wtCatInfo.pcwszMemberTag = strHashBuf.get(); + wtCatInfo.pcwszMemberFilePath = aFilePath; + wtCatInfo.hMemberFile = rawFile; + if (mozilla::IsWin8OrLater()) { + wtCatInfo.hCatAdmin = rawCatAdmin; + } + + WINTRUST_DATA trustData = {sizeof(trustData)}; + trustData.dwUnionChoice = WTD_CHOICE_CATALOG; + trustData.pCatalog = &wtCatInfo; + + return VerifySignatureInternal(trustData); +} + +mozilla::UniquePtr<wchar_t[]> SignedBinary::GetOrgName() { + DWORD charCount = CertGetNameStringW( + mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0); + if (charCount <= 1) { + // Not found + return nullptr; + } + + auto result = mozilla::MakeUnique<wchar_t[]>(charCount); + charCount = CertGetNameStringW(mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE, + 0, nullptr, result.get(), charCount); + MOZ_ASSERT(charCount > 1); + + return result; +} + +} // anonymous namespace + +namespace mozilla { + +class AuthenticodeImpl : public Authenticode { + public: + virtual UniquePtr<wchar_t[]> GetBinaryOrgName( + const wchar_t* aFilePath, + AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override; +}; + +UniquePtr<wchar_t[]> AuthenticodeImpl::GetBinaryOrgName( + const wchar_t* aFilePath, AuthenticodeFlags aFlags) { + SignedBinary bin(aFilePath, aFlags); + if (!bin) { + return nullptr; + } + + return bin.GetOrgName(); +} + +static AuthenticodeImpl sAuthenticodeImpl; + +Authenticode* GetAuthenticode() { return &sAuthenticodeImpl; } + +} // namespace mozilla diff --git a/mozglue/dllservices/Authenticode.h b/mozglue/dllservices/Authenticode.h new file mode 100644 index 0000000000..182512da2c --- /dev/null +++ b/mozglue/dllservices/Authenticode.h @@ -0,0 +1,32 @@ +/* -*- 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/. */ + +#ifndef mozilla_Authenticode_h +#define mozilla_Authenticode_h + +#include "mozilla/Maybe.h" +#include "mozilla/TypedEnumBits.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +enum class AuthenticodeFlags : uint32_t { + Default = 0, + SkipTrustVerification = 1, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AuthenticodeFlags) + +class Authenticode { + public: + virtual UniquePtr<wchar_t[]> GetBinaryOrgName( + const wchar_t* aFilePath, + AuthenticodeFlags aFlags = AuthenticodeFlags::Default) = 0; +}; + +} // namespace mozilla + +#endif // mozilla_Authenticode_h diff --git a/mozglue/dllservices/LoaderAPIInterfaces.h b/mozglue/dllservices/LoaderAPIInterfaces.h new file mode 100644 index 0000000000..4546cf79bd --- /dev/null +++ b/mozglue/dllservices/LoaderAPIInterfaces.h @@ -0,0 +1,120 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LoaderAPIInterfaces_h +#define mozilla_LoaderAPIInterfaces_h + +#include "nscore.h" +#include "mozilla/ModuleLoadInfo.h" + +namespace mozilla { +namespace nt { + +class NS_NO_VTABLE LoaderObserver { + public: + /** + * Notification that a DLL load has begun. + * + * @param aContext Outparam that allows this observer to store any context + * information pertaining to the current load. + * @param aRequestedDllName The DLL name requested by whatever invoked the + * loader. This name may not match the effective + * name of the DLL once the loader has completed + * its path search. + */ + virtual void OnBeginDllLoad(void** aContext, + PCUNICODE_STRING aRequestedDllName) = 0; + + /** + * Query the observer to determine whether the DLL named |aLSPLeafName| needs + * to be substituted with another module, and substitute the module handle + * when necessary. + * + * @return true when substitution occurs, otherwise false + */ + virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName, + PHANDLE aOutHandle) = 0; + + /** + * Notification that a DLL load has ended. + * + * @param aContext The context that was set by the corresponding call to + * OnBeginDllLoad + * @param aNtStatus The NTSTATUS returned by LdrLoadDll + * @param aModuleLoadInfo Telemetry information that was gathered about the + * load. + */ + virtual void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus, + ModuleLoadInfo&& aModuleLoadInfo) = 0; + + /** + * Called to inform the observer that it is no longer active and, if + * necessary, call aNext->OnForward() with any accumulated telemetry + * information. + */ + virtual void Forward(LoaderObserver* aNext) = 0; + + /** + * Receives a vector of module load telemetry from a previous LoaderObserver. + */ + virtual void OnForward(ModuleLoadInfoVec&& aInfo) = 0; +}; + +class NS_NO_VTABLE LoaderAPI { + public: + /** + * Construct a new ModuleLoadInfo structure and notify the LoaderObserver + * that a library load is beginning. + */ + virtual ModuleLoadInfo ConstructAndNotifyBeginDllLoad( + void** aContext, PCUNICODE_STRING aRequestedDllName) = 0; + + /** + * Query to determine whether the DLL named |aLSPLeafName| needs to be + * substituted with another module, and substitute the module handle when + * necessary. + * + * @return true when substitution occurs, otherwise false + */ + virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName, + PHANDLE aOutHandle) = 0; + + /** + * Notification that a DLL load has ended. + */ + virtual void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus, + ModuleLoadInfo&& aModuleLoadInfo) = 0; + + /** + * Given the address of a mapped section, obtain the name of the file that is + * backing it. + */ + virtual AllocatedUnicodeString GetSectionName(void* aSectionAddr) = 0; + + using InitDllBlocklistOOPFnPtr = LauncherVoidResultWithLineInfo (*)( + const wchar_t*, HANDLE, const IMAGE_THUNK_DATA*); + using HandleLauncherErrorFnPtr = void (*)(const LauncherError&, const char*); + + /** + * Return a pointer to winlauncher's function. + * Used by sandboxBroker::LaunchApp. + */ + virtual InitDllBlocklistOOPFnPtr GetDllBlocklistInitFn() = 0; + virtual HandleLauncherErrorFnPtr GetHandleLauncherErrorFn() = 0; +}; + +struct WinLauncherFunctions final { + nt::LoaderAPI::InitDllBlocklistOOPFnPtr mInitDllBlocklistOOP; + nt::LoaderAPI::HandleLauncherErrorFnPtr mHandleLauncherError; + + WinLauncherFunctions() + : mInitDllBlocklistOOP(nullptr), mHandleLauncherError(nullptr) {} +}; + +} // namespace nt +} // namespace mozilla + +#endif // mozilla_LoaderAPIInterfaces_h diff --git a/mozglue/dllservices/LoaderObserver.cpp b/mozglue/dllservices/LoaderObserver.cpp new file mode 100644 index 0000000000..9c4fa53d3b --- /dev/null +++ b/mozglue/dllservices/LoaderObserver.cpp @@ -0,0 +1,165 @@ +/* -*- 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/. */ + +#include "LoaderObserver.h" + +#include "mozilla/AutoProfilerLabel.h" +#include "mozilla/BaseProfilerMarkers.h" +#include "mozilla/glue/WindowsUnicode.h" +#include "mozilla/StackWalk_windows.h" + +namespace { + +struct LoadContext { + LoadContext(mozilla::ProfilerLabel&& aLabel, + mozilla::UniquePtr<char[]>&& aDynamicStringStorage) + : mProfilerLabel(std::move(aLabel)), + mDynamicStringStorage(std::move(aDynamicStringStorage)), + mStartTime(mozilla::TimeStamp::Now()) {} + mozilla::ProfilerLabel mProfilerLabel; + mozilla::UniquePtr<char[]> mDynamicStringStorage; + mozilla::TimeStamp mStartTime; +}; + +} // anonymous namespace + +namespace mozilla { + +extern glue::Win32SRWLock gDllServicesLock; +extern glue::detail::DllServicesBase* gDllServices; + +namespace glue { + +void LoaderObserver::OnBeginDllLoad(void** aContext, + PCUNICODE_STRING aRequestedDllName) { + MOZ_ASSERT(aContext); + if (IsProfilerPresent()) { + UniquePtr<char[]> utf8RequestedDllName(WideToUTF8(aRequestedDllName)); + const char* dynamicString = utf8RequestedDllName.get(); + *aContext = new LoadContext( + ProfilerLabelBegin("mozilla::glue::LoaderObserver::OnBeginDllLoad", + dynamicString, &aContext), + std::move(utf8RequestedDllName)); + } + +#ifdef _M_AMD64 + // Prevent the stack walker from suspending this thread when LdrLoadDll + // holds the RtlLookupFunctionEntry lock. + SuppressStackWalking(); +#endif +} + +bool LoaderObserver::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName, + PHANDLE aOutHandle) { + // Currently unsupported + return false; +} + +void LoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus, + ModuleLoadInfo&& aModuleLoadInfo) { +#ifdef _M_AMD64 + DesuppressStackWalking(); +#endif + + UniquePtr<LoadContext> loadContext(static_cast<LoadContext*>(aContext)); + if (loadContext && IsValidProfilerLabel(loadContext->mProfilerLabel)) { + ProfilerLabelEnd(loadContext->mProfilerLabel); + BASE_PROFILER_MARKER_TEXT( + "DllLoad", OTHER, + MarkerTiming::IntervalUntilNowFrom(loadContext->mStartTime), + mozilla::ProfilerString8View::WrapNullTerminatedString( + loadContext->mDynamicStringStorage.get())); + } + + // We want to record a denied DLL load regardless of |aNtStatus| because + // |aNtStatus| is set to access-denied when DLL load was blocked. + if ((!NT_SUCCESS(aNtStatus) && !aModuleLoadInfo.WasDenied()) || + !aModuleLoadInfo.WasMapped()) { + return; + } + + { // Scope for lock + AutoSharedLock lock(gDllServicesLock); + if (gDllServices) { + gDllServices->DispatchDllLoadNotification(std::move(aModuleLoadInfo)); + return; + } + } + + // No dll services, save for later + AutoExclusiveLock lock(mLock); + if (!mEnabled) { + return; + } + + if (!mModuleLoads) { + mModuleLoads = new ModuleLoadInfoVec(); + } + + Unused << mModuleLoads->emplaceBack( + std::forward<ModuleLoadInfo>(aModuleLoadInfo)); +} + +void LoaderObserver::Forward(nt::LoaderObserver* aNext) { + MOZ_ASSERT_UNREACHABLE( + "This implementation does not forward to any more " + "nt::LoaderObserver objects"); +} + +void LoaderObserver::Forward(detail::DllServicesBase* aNext) { + MOZ_ASSERT(aNext); + if (!aNext) { + return; + } + + ModuleLoadInfoVec* moduleLoads = nullptr; + + { // Scope for lock + AutoExclusiveLock lock(mLock); + moduleLoads = mModuleLoads; + mModuleLoads = nullptr; + } + + if (!moduleLoads) { + return; + } + + aNext->DispatchModuleLoadBacklogNotification(std::move(*moduleLoads)); + delete moduleLoads; +} + +void LoaderObserver::Disable() { + ModuleLoadInfoVec* moduleLoads = nullptr; + + { // Scope for lock + AutoExclusiveLock lock(mLock); + moduleLoads = mModuleLoads; + mModuleLoads = nullptr; + mEnabled = false; + } + + delete moduleLoads; +} + +void LoaderObserver::OnForward(ModuleLoadInfoVec&& aInfo) { + AutoExclusiveLock lock(mLock); + if (!mModuleLoads) { + mModuleLoads = new ModuleLoadInfoVec(); + } + + MOZ_ASSERT(mModuleLoads->empty()); + if (mModuleLoads->empty()) { + *mModuleLoads = std::move(aInfo); + } else { + // This should not happen, but we can handle it + for (auto&& item : aInfo) { + Unused << mModuleLoads->append(std::move(item)); + } + } +} + +} // namespace glue +} // namespace mozilla diff --git a/mozglue/dllservices/LoaderObserver.h b/mozglue/dllservices/LoaderObserver.h new file mode 100644 index 0000000000..39b13035a3 --- /dev/null +++ b/mozglue/dllservices/LoaderObserver.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef mozilla_glue_LoaderObserver_h +#define mozilla_glue_LoaderObserver_h + +#include "mozilla/Attributes.h" +#include "mozilla/LoaderAPIInterfaces.h" +#include "mozilla/glue/WindowsDllServices.h" +#include "mozilla/glue/WinUtils.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { +namespace glue { + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS LoaderObserver final + : public nt::LoaderObserver { + public: + constexpr LoaderObserver() : mModuleLoads(nullptr), mEnabled(true) {} + + void OnBeginDllLoad(void** aContext, + PCUNICODE_STRING aPreliminaryDllName) final; + bool SubstituteForLSP(PCUNICODE_STRING aLspLeafName, + PHANDLE aOutHandle) final; + void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus, + ModuleLoadInfo&& aModuleLoadInfo) final; + void Forward(nt::LoaderObserver* aNext) final; + void OnForward(ModuleLoadInfoVec&& aInfo) final; + + void Forward(mozilla::glue::detail::DllServicesBase* aSvc); + void Disable(); + + private: + Win32SRWLock mLock; + ModuleLoadInfoVec* mModuleLoads; + bool mEnabled; +}; + +} // namespace glue +} // namespace mozilla + +#endif // mozilla_glue_LoaderObserver_h diff --git a/mozglue/dllservices/ModuleLoadFrame.cpp b/mozglue/dllservices/ModuleLoadFrame.cpp new file mode 100644 index 0000000000..ff972836c3 --- /dev/null +++ b/mozglue/dllservices/ModuleLoadFrame.cpp @@ -0,0 +1,105 @@ +/* -*- 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/. */ + +#include "ModuleLoadFrame.h" +#include "mozilla/NativeNt.h" +#include "mozilla/UniquePtr.h" +#include "NtLoaderAPI.h" + +#include <string.h> + +#include "WindowsFallbackLoaderAPI.h" + +static bool IsNullTerminated(PCUNICODE_STRING aStr) { + return aStr && (aStr->MaximumLength >= (aStr->Length + sizeof(WCHAR))) && + aStr->Buffer && aStr->Buffer[aStr->Length / sizeof(WCHAR)] == 0; +} + +static mozilla::FallbackLoaderAPI gFallbackLoaderAPI; + +namespace mozilla { +namespace glue { + +nt::LoaderAPI* ModuleLoadFrame::sLoaderAPI; + +using GetNtLoaderAPIFn = decltype(&mozilla::GetNtLoaderAPI); + +/* static */ +void ModuleLoadFrame::StaticInit( + nt::LoaderObserver* aNewObserver, + nt::WinLauncherFunctions* aOutWinLauncherFunctions) { + const auto pGetNtLoaderAPI = reinterpret_cast<GetNtLoaderAPIFn>( + ::GetProcAddress(::GetModuleHandleW(nullptr), "GetNtLoaderAPI")); + if (!pGetNtLoaderAPI) { + // This case occurs in processes other than firefox.exe that do not contain + // the launcher process blocklist. + gFallbackLoaderAPI.SetObserver(aNewObserver); + sLoaderAPI = &gFallbackLoaderAPI; + + if (aOutWinLauncherFunctions) { + aOutWinLauncherFunctions->mHandleLauncherError = + [](const mozilla::LauncherError&, const char*) {}; + // We intentionally leave mInitDllBlocklistOOP null to make sure calling + // mInitDllBlocklistOOP in non-Firefox hits MOZ_RELEASE_ASSERT. + } + return; + } + + sLoaderAPI = pGetNtLoaderAPI(aNewObserver); + MOZ_ASSERT(sLoaderAPI); + + if (aOutWinLauncherFunctions) { + aOutWinLauncherFunctions->mInitDllBlocklistOOP = + sLoaderAPI->GetDllBlocklistInitFn(); + aOutWinLauncherFunctions->mHandleLauncherError = + sLoaderAPI->GetHandleLauncherErrorFn(); + } +} + +ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName) + : mAlreadyLoaded(false), + mContext(nullptr), + mDllLoadStatus(STATUS_UNSUCCESSFUL), + mLoadInfo(sLoaderAPI->ConstructAndNotifyBeginDllLoad(&mContext, + aRequestedDllName)) { + if (!aRequestedDllName) { + return; + } + + UniquePtr<WCHAR[]> nameBuf; + const WCHAR* name = nullptr; + + if (IsNullTerminated(aRequestedDllName)) { + name = aRequestedDllName->Buffer; + } else { + USHORT charLenExclNul = aRequestedDllName->Length / sizeof(WCHAR); + USHORT charLenInclNul = charLenExclNul + 1; + nameBuf = MakeUnique<WCHAR[]>(charLenInclNul); + if (!wcsncpy_s(nameBuf.get(), charLenInclNul, aRequestedDllName->Buffer, + charLenExclNul)) { + name = nameBuf.get(); + } + } + + mAlreadyLoaded = name && !!::GetModuleHandleW(name); +} + +ModuleLoadFrame::~ModuleLoadFrame() { + sLoaderAPI->NotifyEndDllLoad(mContext, mDllLoadStatus, std::move(mLoadInfo)); +} + +void ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle) { + mDllLoadStatus = aNtStatus; + void* baseAddr = mozilla::nt::PEHeaders::HModuleToBaseAddr<void*>( + reinterpret_cast<HMODULE>(aHandle)); + mLoadInfo.mBaseAddr = baseAddr; + if (!mAlreadyLoaded) { + mLoadInfo.mSectionName = sLoaderAPI->GetSectionName(baseAddr); + } +} + +} // namespace glue +} // namespace mozilla diff --git a/mozglue/dllservices/ModuleLoadFrame.h b/mozglue/dllservices/ModuleLoadFrame.h new file mode 100644 index 0000000000..6f234f8788 --- /dev/null +++ b/mozglue/dllservices/ModuleLoadFrame.h @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#ifndef mozilla_glue_ModuleLoadFrame_h +#define mozilla_glue_ModuleLoadFrame_h + +#include "mozilla/Attributes.h" +#include "mozilla/LoaderAPIInterfaces.h" + +namespace mozilla { +namespace glue { + +class MOZ_RAII ModuleLoadFrame final { + public: + explicit ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName); + ~ModuleLoadFrame(); + + void SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle); + + ModuleLoadFrame(const ModuleLoadFrame&) = delete; + ModuleLoadFrame(ModuleLoadFrame&&) = delete; + ModuleLoadFrame& operator=(const ModuleLoadFrame&) = delete; + ModuleLoadFrame& operator=(ModuleLoadFrame&&) = delete; + + static void StaticInit(nt::LoaderObserver* aNewObserver, + nt::WinLauncherFunctions* aOutWinLauncherFunctions); + + private: + bool mAlreadyLoaded; + void* mContext; + NTSTATUS mDllLoadStatus; + ModuleLoadInfo mLoadInfo; + + private: + static nt::LoaderAPI* sLoaderAPI; +}; + +} // namespace glue +} // namespace mozilla + +#endif // mozilla_glue_ModuleLoadFrame_h diff --git a/mozglue/dllservices/ModuleLoadInfo.h b/mozglue/dllservices/ModuleLoadInfo.h new file mode 100644 index 0000000000..2d9bdc0cc7 --- /dev/null +++ b/mozglue/dllservices/ModuleLoadInfo.h @@ -0,0 +1,173 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ModuleLoadInfo_h +#define mozilla_ModuleLoadInfo_h + +#include "mozilla/NativeNt.h" +#include "mozilla/Vector.h" +#include "mozilla/Unused.h" + +namespace mozilla { + +struct ModuleLoadInfo final { + // If you add a new value or change the meaning of the values, please + // update createLoadStatusElement in aboutSupport.js accordingly, which + // defines text labels of these enum values displayed on about:support. + enum class Status : uint32_t { + Loaded = 0, + Blocked, + Redirected, + }; + + // We do not provide these methods inside Gecko proper. +#if !defined(MOZILLA_INTERNAL_API) + + /** + * This constructor is for use by the LdrLoadDll hook. + */ + explicit ModuleLoadInfo(PCUNICODE_STRING aRequestedDllName) + : mLoadTimeInfo(), + mThreadId(nt::RtlGetCurrentThreadId()), + mRequestedDllName(aRequestedDllName), + mBaseAddr(nullptr), + mStatus(Status::Loaded) { +# if defined(IMPL_MFBT) + ::QueryPerformanceCounter(&mBeginTimestamp); +# else + ::RtlQueryPerformanceCounter(&mBeginTimestamp); +# endif // defined(IMPL_MFBT) + } + + /** + * This constructor is used by the NtMapViewOfSection hook IF AND ONLY IF + * the LdrLoadDll hook did not already construct a ModuleLoadInfo for the + * current DLL load. This may occur while the loader is loading dependencies + * of another library. + */ + ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName, + const void* aBaseAddr, Status aLoadStatus) + : mLoadTimeInfo(), + mThreadId(nt::RtlGetCurrentThreadId()), + mSectionName(std::move(aSectionName)), + mBaseAddr(aBaseAddr), + mStatus(aLoadStatus) { +# if defined(IMPL_MFBT) + ::QueryPerformanceCounter(&mBeginTimestamp); +# else + ::RtlQueryPerformanceCounter(&mBeginTimestamp); +# endif // defined(IMPL_MFBT) + } + + /** + * Marks the time that LdrLoadDll began loading this library. + */ + void SetBeginLoadTimeStamp() { +# if defined(IMPL_MFBT) + ::QueryPerformanceCounter(&mLoadTimeInfo); +# else + ::RtlQueryPerformanceCounter(&mLoadTimeInfo); +# endif // defined(IMPL_MFBT) + } + + /** + * Marks the time that LdrLoadDll finished loading this library. + */ + void SetEndLoadTimeStamp() { + LARGE_INTEGER endTimeStamp; +# if defined(IMPL_MFBT) + ::QueryPerformanceCounter(&endTimeStamp); +# else + ::RtlQueryPerformanceCounter(&endTimeStamp); +# endif // defined(IMPL_MFBT) + + LONGLONG& timeInfo = mLoadTimeInfo.QuadPart; + if (!timeInfo) { + return; + } + + timeInfo = endTimeStamp.QuadPart - timeInfo; + } + + /** + * Saves the current thread's call stack. + */ + void CaptureBacktrace() { + const DWORD kMaxBacktraceSize = 512; + + if (!mBacktrace.resize(kMaxBacktraceSize)) { + return; + } + + // We don't use a Win32 variant here because Win32's CaptureStackBackTrace + // is just a macro that resolve to this function anyway. + WORD numCaptured = ::RtlCaptureStackBackTrace(2, kMaxBacktraceSize, + mBacktrace.begin(), nullptr); + Unused << mBacktrace.resize(numCaptured); + // These backtraces might stick around for a while, so let's trim any + // excess memory. + mBacktrace.shrinkStorageToFit(); + } + +#endif // !defined(MOZILLA_INTERNAL_API) + + ModuleLoadInfo(ModuleLoadInfo&&) = default; + ModuleLoadInfo& operator=(ModuleLoadInfo&&) = default; + + ModuleLoadInfo() = delete; + ModuleLoadInfo(const ModuleLoadInfo&) = delete; + ModuleLoadInfo& operator=(const ModuleLoadInfo&) = delete; + + /** + * A "bare" module load is one that was mapped without the code passing + * through a call to ntdll!LdrLoadDll. + */ + bool IsBare() const { + // SetBeginLoadTimeStamp() and SetEndLoadTimeStamp() are only called by the + // LdrLoadDll hook, so when mLoadTimeInfo == 0, we know that we are bare. + return !mLoadTimeInfo.QuadPart; + } + + /** + * Returns true for DLL loads where LdrLoadDll was called but + * NtMapViewOfSection was not. This will happen for DLL requests where the DLL + * was already mapped into memory by a previous request. + */ + bool WasMapped() const { return !mSectionName.IsEmpty(); } + + /** + * Returns true for DLL load which was denied by our blocklist. + */ + bool WasDenied() const { + return mStatus == ModuleLoadInfo::Status::Blocked || + mStatus == ModuleLoadInfo::Status::Redirected; + } + + // Timestamp for the creation of this event + LARGE_INTEGER mBeginTimestamp; + // Duration of the LdrLoadDll call + LARGE_INTEGER mLoadTimeInfo; + // Thread ID of this DLL load + DWORD mThreadId; + // The name requested of LdrLoadDll by its caller + nt::AllocatedUnicodeString mRequestedDllName; + // The name of the DLL that backs section that was mapped by the loader. This + // string is the effective name of the DLL that was resolved by the loader's + // path search algorithm. + nt::AllocatedUnicodeString mSectionName; + // The base address of the module's mapped section + const void* mBaseAddr; + // If the module was successfully loaded, stack trace of the DLL load request + Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace; + // The status of DLL load + Status mStatus; +}; + +using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>; + +} // namespace mozilla + +#endif // mozilla_ModuleLoadInfo_h diff --git a/mozglue/dllservices/NtLoaderAPI.h b/mozglue/dllservices/NtLoaderAPI.h new file mode 100644 index 0000000000..628609092b --- /dev/null +++ b/mozglue/dllservices/NtLoaderAPI.h @@ -0,0 +1,23 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_NtLoaderAPI_h +#define mozilla_NtLoaderAPI_h + +#include "mozilla/LoaderAPIInterfaces.h" + +#if !defined(IMPL_MFBT) +# error "This should only be included from mozglue!" +#endif // !defined(IMPL_MFBT) + +namespace mozilla { + +extern "C" MOZ_IMPORT_API nt::LoaderAPI* GetNtLoaderAPI( + nt::LoaderObserver* aNewObserver); + +} // namespace mozilla + +#endif // mozilla_NtLoaderAPI_h diff --git a/mozglue/dllservices/WindowsDllBlocklist.cpp b/mozglue/dllservices/WindowsDllBlocklist.cpp new file mode 100644 index 0000000000..bacd6ad799 --- /dev/null +++ b/mozglue/dllservices/WindowsDllBlocklist.cpp @@ -0,0 +1,782 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include <windows.h> +#include <winternl.h> + +#pragma warning(push) +#pragma warning(disable : 4275 4530) // See msvc-stl-wrapper.template.h +#include <map> +#pragma warning(pop) + +#include "Authenticode.h" +#include "BaseProfiler.h" +#include "nsWindowsDllInterceptor.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/StackWalk_windows.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsWindowsHelpers.h" +#include "WindowsDllBlocklist.h" +#include "mozilla/AutoProfilerLabel.h" +#include "mozilla/glue/Debug.h" +#include "mozilla/glue/WindowsDllServices.h" +#include "mozilla/glue/WinUtils.h" + +// Start new implementation +#include "LoaderObserver.h" +#include "ModuleLoadFrame.h" +#include "mozilla/glue/WindowsUnicode.h" + +namespace mozilla { + +glue::Win32SRWLock gDllServicesLock; +glue::detail::DllServicesBase* gDllServices; + +} // namespace mozilla + +using namespace mozilla; + +using CrashReporter::Annotation; +using CrashReporter::AnnotationWriter; + +#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__}, +#define DLL_BLOCKLIST_STRING_TYPE const char* +#include "mozilla/WindowsDllBlocklistLegacyDefs.h" + +// define this for very verbose dll load debug spew +#undef DEBUG_very_verbose + +static uint32_t sInitFlags; +static bool sBlocklistInitAttempted; +static bool sBlocklistInitFailed; +static bool sUser32BeforeBlocklist; + +typedef MOZ_NORETURN_PTR void(__fastcall* BaseThreadInitThunk_func)( + BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam); +static WindowsDllInterceptor::FuncHookType<BaseThreadInitThunk_func> + stub_BaseThreadInitThunk; + +typedef NTSTATUS(NTAPI* LdrLoadDll_func)(PWCHAR filePath, PULONG flags, + PUNICODE_STRING moduleFileName, + PHANDLE handle); +static WindowsDllInterceptor::FuncHookType<LdrLoadDll_func> stub_LdrLoadDll; + +#ifdef _M_AMD64 +typedef decltype( + RtlInstallFunctionTableCallback)* RtlInstallFunctionTableCallback_func; +static WindowsDllInterceptor::FuncHookType<RtlInstallFunctionTableCallback_func> + stub_RtlInstallFunctionTableCallback; + +extern uint8_t* sMsMpegJitCodeRegionStart; +extern size_t sMsMpegJitCodeRegionSize; + +BOOLEAN WINAPI patched_RtlInstallFunctionTableCallback( + DWORD64 TableIdentifier, DWORD64 BaseAddress, DWORD Length, + PGET_RUNTIME_FUNCTION_CALLBACK Callback, PVOID Context, + PCWSTR OutOfProcessCallbackDll) { + // msmpeg2vdec.dll sets up a function table callback for their JIT code that + // just terminates the process, because their JIT doesn't have unwind info. + // If we see this callback being registered, record the region address, so + // that StackWalk.cpp can avoid unwinding addresses in this region. + // + // To keep things simple I'm not tracking unloads of msmpeg2vdec.dll. + // Worst case the stack walker will needlessly avoid a few pages of memory. + + // Tricky: GetModuleHandleExW adds a ref by default; GetModuleHandleW doesn't. + HMODULE callbackModule = nullptr; + DWORD moduleFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT; + + // These GetModuleHandle calls enter a critical section on Win7. + AutoSuppressStackWalking suppress; + + if (GetModuleHandleExW(moduleFlags, (LPWSTR)Callback, &callbackModule) && + GetModuleHandleW(L"msmpeg2vdec.dll") == callbackModule) { + sMsMpegJitCodeRegionStart = (uint8_t*)BaseAddress; + sMsMpegJitCodeRegionSize = Length; + } + + return stub_RtlInstallFunctionTableCallback(TableIdentifier, BaseAddress, + Length, Callback, Context, + OutOfProcessCallbackDll); +} +#endif + +template <class T> +struct RVAMap { + RVAMap(HANDLE map, DWORD offset) { + SYSTEM_INFO info; + GetSystemInfo(&info); + + DWORD alignedOffset = + (offset / info.dwAllocationGranularity) * info.dwAllocationGranularity; + + MOZ_ASSERT(offset - alignedOffset < info.dwAllocationGranularity, "Wtf"); + + mRealView = ::MapViewOfFile(map, FILE_MAP_READ, 0, alignedOffset, + sizeof(T) + (offset - alignedOffset)); + + mMappedView = + mRealView + ? reinterpret_cast<T*>((char*)mRealView + (offset - alignedOffset)) + : nullptr; + } + ~RVAMap() { + if (mRealView) { + ::UnmapViewOfFile(mRealView); + } + } + operator const T*() const { return mMappedView; } + const T* operator->() const { return mMappedView; } + + private: + const T* mMappedView; + void* mRealView; +}; + +static DWORD GetTimestamp(const wchar_t* path) { + DWORD timestamp = 0; + + HANDLE file = ::CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (file != INVALID_HANDLE_VALUE) { + HANDLE map = + ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr); + if (map) { + RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0); + if (peHeader) { + RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew); + if (ntHeader) { + timestamp = ntHeader->FileHeader.TimeDateStamp; + } + } + ::CloseHandle(map); + } + ::CloseHandle(file); + } + + return timestamp; +} + +// This lock protects both the reentrancy sentinel and the crash reporter +// data structures. +static CRITICAL_SECTION sLock; + +/** + * Some versions of Windows call LoadLibraryEx to get the version information + * for a DLL, which causes our patched LdrLoadDll implementation to re-enter + * itself and cause infinite recursion and a stack-exhaustion crash. We protect + * against reentrancy by allowing recursive loads of the same DLL. + * + * Note that we don't use __declspec(thread) because that doesn't work in DLLs + * loaded via LoadLibrary and there can be a limited number of TLS slots, so + * we roll our own. + */ +class ReentrancySentinel { + public: + explicit ReentrancySentinel(const char* dllName) { + DWORD currentThreadId = GetCurrentThreadId(); + AutoCriticalSection lock(&sLock); + mPreviousDllName = (*sThreadMap)[currentThreadId]; + + // If there is a DLL currently being loaded and it has the same name + // as the current attempt, we're re-entering. + mReentered = mPreviousDllName && !stricmp(mPreviousDllName, dllName); + (*sThreadMap)[currentThreadId] = dllName; + } + + ~ReentrancySentinel() { + DWORD currentThreadId = GetCurrentThreadId(); + AutoCriticalSection lock(&sLock); + (*sThreadMap)[currentThreadId] = mPreviousDllName; + } + + bool BailOut() const { return mReentered; }; + + static void InitializeStatics() { + InitializeCriticalSection(&sLock); + sThreadMap = new std::map<DWORD, const char*>; + } + + private: + static std::map<DWORD, const char*>* sThreadMap; + + const char* mPreviousDllName; + bool mReentered; +}; + +std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap; + +class WritableBuffer { + public: + WritableBuffer() : mBuffer{0}, mLen(0) {} + + void Write(const char* aData, size_t aLen) { + size_t writable_len = std::min(aLen, Available()); + memcpy(mBuffer + mLen, aData, writable_len); + mLen += writable_len; + } + + size_t const Length() { return mLen; } + const char* Data() { return mBuffer; } + + private: + size_t const Available() { return sizeof(mBuffer) - mLen; } + + char mBuffer[1024]; + size_t mLen; +}; + +/** + * This is a linked list of DLLs that have been blocked. It doesn't use + * mozilla::LinkedList because this is an append-only list and doesn't need + * to be doubly linked. + */ +class DllBlockSet { + public: + static void Add(const char* name, unsigned long long version); + + // Write the list of blocked DLLs to a WritableBuffer object. This method is + // run after a crash occurs and must therefore not use the heap, etc. + static void Write(WritableBuffer& buffer); + + private: + DllBlockSet(const char* name, unsigned long long version) + : mName(name), mVersion(version), mNext(nullptr) {} + + const char* mName; // points into the gWindowsDllBlocklist string + unsigned long long mVersion; + DllBlockSet* mNext; + + static DllBlockSet* gFirst; +}; + +DllBlockSet* DllBlockSet::gFirst; + +void DllBlockSet::Add(const char* name, unsigned long long version) { + AutoCriticalSection lock(&sLock); + for (DllBlockSet* b = gFirst; b; b = b->mNext) { + if (0 == strcmp(b->mName, name) && b->mVersion == version) { + return; + } + } + // Not already present + DllBlockSet* n = new DllBlockSet(name, version); + n->mNext = gFirst; + gFirst = n; +} + +void DllBlockSet::Write(WritableBuffer& buffer) { + // It would be nicer to use AutoCriticalSection here. However, its destructor + // might not run if an exception occurs, in which case we would never leave + // the critical section. (MSVC warns about this possibility.) So we + // enter and leave manually. + ::EnterCriticalSection(&sLock); + + // Because this method is called after a crash occurs, and uses heap memory, + // protect this entire block with a structured exception handler. + MOZ_SEH_TRY { + for (DllBlockSet* b = gFirst; b; b = b->mNext) { + // write name[,v.v.v.v]; + buffer.Write(b->mName, strlen(b->mName)); + if (b->mVersion != DllBlockInfo::ALL_VERSIONS) { + buffer.Write(",", 1); + uint16_t parts[4]; + parts[0] = b->mVersion >> 48; + parts[1] = (b->mVersion >> 32) & 0xFFFF; + parts[2] = (b->mVersion >> 16) & 0xFFFF; + parts[3] = b->mVersion & 0xFFFF; + for (int p = 0; p < 4; ++p) { + char buf[32]; + _ltoa_s(parts[p], buf, sizeof(buf), 10); + buffer.Write(buf, strlen(buf)); + if (p != 3) { + buffer.Write(".", 1); + } + } + } + buffer.Write(";", 1); + } + } + MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {} + + ::LeaveCriticalSection(&sLock); +} + +static UniquePtr<wchar_t[]> getFullPath(PWCHAR filePath, wchar_t* fname) { + // In Windows 8, the first parameter seems to be used for more than just the + // path name. For example, its numerical value can be 1. Passing a non-valid + // pointer to SearchPathW will cause a crash, so we need to check to see if we + // are handed a valid pointer, and otherwise just pass nullptr to SearchPathW. + PWCHAR sanitizedFilePath = nullptr; + if ((uintptr_t(filePath) >= 65536) && ((uintptr_t(filePath) & 1) == 0)) { + sanitizedFilePath = filePath; + } + + // figure out the length of the string that we need + DWORD pathlen = + SearchPathW(sanitizedFilePath, fname, L".dll", 0, nullptr, nullptr); + if (pathlen == 0) { + return nullptr; + } + + auto full_fname = MakeUnique<wchar_t[]>(pathlen + 1); + if (!full_fname) { + // couldn't allocate memory? + return nullptr; + } + + // now actually grab it + SearchPathW(sanitizedFilePath, fname, L".dll", pathlen + 1, full_fname.get(), + nullptr); + return full_fname; +} + +// No builtin function to find the last character matching a set +static wchar_t* lastslash(wchar_t* s, int len) { + for (wchar_t* c = s + len - 1; c >= s; --c) { + if (*c == L'\\' || *c == L'/') { + return c; + } + } + return nullptr; +} + +static NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR filePath, PULONG flags, + PUNICODE_STRING moduleFileName, + PHANDLE handle) { + // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename + // portion +#define DLLNAME_MAX 128 + char dllName[DLLNAME_MAX + 1]; + wchar_t* dll_part; + char* dot; + + int len = moduleFileName->Length / 2; + wchar_t* fname = moduleFileName->Buffer; + UniquePtr<wchar_t[]> full_fname; + + // The filename isn't guaranteed to be null terminated, but in practice + // it always will be; ensure that this is so, and bail if not. + // This is done instead of the more robust approach because of bug 527122, + // where lots of weird things were happening when we tried to make a copy. + if (moduleFileName->MaximumLength < moduleFileName->Length + 2 || + fname[len] != 0) { +#ifdef DEBUG + printf_stderr("LdrLoadDll: non-null terminated string found!\n"); +#endif + goto continue_loading; + } + + dll_part = lastslash(fname, len); + if (dll_part) { + dll_part = dll_part + 1; + len -= dll_part - fname; + } else { + dll_part = fname; + } + +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: dll_part '%S' %d\n", dll_part, len); +#endif + + // if it's too long, then, we assume we won't want to block it, + // since DLLNAME_MAX should be at least long enough to hold the longest + // entry in our blocklist. + if (len > DLLNAME_MAX) { +#ifdef DEBUG + printf_stderr("LdrLoadDll: len too long! %d\n", len); +#endif + goto continue_loading; + } + + // copy over to our char byte buffer, lowercasing ASCII as we go + for (int i = 0; i < len; i++) { + wchar_t c = dll_part[i]; + + if (c > 0x7f) { + // welp, it's not ascii; if we need to add non-ascii things to + // our blocklist, we'll have to remove this limitation. + goto continue_loading; + } + + // ensure that dll name is all lowercase + if (c >= 'A' && c <= 'Z') c += 'a' - 'A'; + + dllName[i] = (char)c; + } + + dllName[len] = 0; + +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: dll name '%s'\n", dllName); +#endif + + if (!(sInitFlags & eDllBlocklistInitFlagWasBootstrapped)) { + // Block a suspicious binary that uses various 12-digit hex strings + // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138) + dot = strchr(dllName, '.'); + if (dot && (strchr(dot + 1, '.') == dot + 13)) { + char* end = nullptr; + _strtoui64(dot + 1, &end, 16); + if (end == dot + 13) { + return STATUS_DLL_NOT_FOUND; + } + } + // Block binaries where the filename is at least 16 hex digits + if (dot && ((dot - dllName) >= 16)) { + char* current = dllName; + while (current < dot && isxdigit(*current)) { + current++; + } + if (current == dot) { + return STATUS_DLL_NOT_FOUND; + } + } + + // then compare to everything on the blocklist + DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info); + while (info->mName) { + if (strcmp(info->mName, dllName) == 0) break; + + info++; + } + + if (info->mName) { + bool load_ok = false; + +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: info->mName: '%s'\n", info->mName); +#endif + + if (info->mFlags & DllBlockInfo::REDIRECT_TO_NOOP_ENTRYPOINT) { + printf_stderr( + "LdrLoadDll: " + "Ignoring the REDIRECT_TO_NOOP_ENTRYPOINT flag\n"); + } + + if ((info->mFlags & DllBlockInfo::BLOCK_WIN8_AND_OLDER) && + IsWin8Point1OrLater()) { + goto continue_loading; + } + + if ((info->mFlags & DllBlockInfo::BLOCK_WIN7_AND_OLDER) && + IsWin8OrLater()) { + goto continue_loading; + } + + if ((info->mFlags & DllBlockInfo::CHILD_PROCESSES_ONLY) && + !(sInitFlags & eDllBlocklistInitFlagIsChildProcess)) { + goto continue_loading; + } + + if ((info->mFlags & DllBlockInfo::BROWSER_PROCESS_ONLY) && + (sInitFlags & eDllBlocklistInitFlagIsChildProcess)) { + goto continue_loading; + } + + unsigned long long fVersion = DllBlockInfo::ALL_VERSIONS; + + if (info->mMaxVersion != DllBlockInfo::ALL_VERSIONS) { + ReentrancySentinel sentinel(dllName); + if (sentinel.BailOut()) { + goto continue_loading; + } + + full_fname = getFullPath(filePath, fname); + if (!full_fname) { + // uh, we couldn't find the DLL at all, so... + printf_stderr( + "LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find " + "it?)\n", + dllName); + return STATUS_DLL_NOT_FOUND; + } + + if (info->mFlags & DllBlockInfo::USE_TIMESTAMP) { + fVersion = GetTimestamp(full_fname.get()); + if (fVersion > info->mMaxVersion) { + load_ok = true; + } + } else { + LauncherResult<ModuleVersion> version = + GetModuleVersion(full_fname.get()); + // If we failed to get the version information, we block. + if (version.isOk()) { + load_ok = !info->IsVersionBlocked(version.unwrap()); + } + } + } + + if (!load_ok) { + printf_stderr( + "LdrLoadDll: Blocking load of '%s' -- see " + "http://www.mozilla.com/en-US/blocklist/\n", + dllName); + DllBlockSet::Add(info->mName, fVersion); + return STATUS_DLL_NOT_FOUND; + } + } + } + +continue_loading: +#ifdef DEBUG_very_verbose + printf_stderr("LdrLoadDll: continuing load... ('%S')\n", + moduleFileName->Buffer); +#endif + + glue::ModuleLoadFrame loadFrame(moduleFileName); + + NTSTATUS ret; + HANDLE myHandle; + + ret = stub_LdrLoadDll(filePath, flags, moduleFileName, &myHandle); + + if (handle) { + *handle = myHandle; + } + + loadFrame.SetLoadStatus(ret, myHandle); + + return ret; +} + +#if defined(NIGHTLY_BUILD) +// Map of specific thread proc addresses we should block. In particular, +// LoadLibrary* APIs which indicate DLL injection +static void* gStartAddressesToBlock[4]; +#endif // defined(NIGHTLY_BUILD) + +static bool ShouldBlockThread(void* aStartAddress) { + // Allows crashfirefox.exe to continue to work. Also if your threadproc is + // null, this crash is intentional. + if (aStartAddress == nullptr) return false; + +#if defined(NIGHTLY_BUILD) + for (auto p : gStartAddressesToBlock) { + if (p == aStartAddress) { + return true; + } + } +#endif + + bool shouldBlock = false; + MEMORY_BASIC_INFORMATION startAddressInfo = {0}; + if (VirtualQuery(aStartAddress, &startAddressInfo, + sizeof(startAddressInfo))) { + shouldBlock |= startAddressInfo.State != MEM_COMMIT; + shouldBlock |= startAddressInfo.Protect != PAGE_EXECUTE_READ; + } + + return shouldBlock; +} + +// Allows blocked threads to still run normally through BaseThreadInitThunk, in +// case there's any magic there that we shouldn't skip. +static DWORD WINAPI NopThreadProc(void* /* aThreadParam */) { return 0; } + +static MOZ_NORETURN void __fastcall patched_BaseThreadInitThunk( + BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam) { + if (ShouldBlockThread(aStartAddress)) { + aStartAddress = (void*)NopThreadProc; + } + + stub_BaseThreadInitThunk(aIsInitialThread, aStartAddress, aThreadParam); +} + +static WindowsDllInterceptor NtDllIntercept; +static WindowsDllInterceptor Kernel32Intercept; + +static void GetNativeNtBlockSetWriter(); + +static glue::LoaderObserver gMozglueLoaderObserver; +static nt::WinLauncherFunctions gWinLauncherFunctions; + +MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) { + if (sBlocklistInitAttempted) { + return; + } + sBlocklistInitAttempted = true; + + sInitFlags = aInitFlags; + + glue::ModuleLoadFrame::StaticInit(&gMozglueLoaderObserver, + &gWinLauncherFunctions); + +#ifdef _M_AMD64 + if (!IsWin8OrLater()) { + Kernel32Intercept.Init("kernel32.dll"); + + // The crash that this hook works around is only seen on Win7. + stub_RtlInstallFunctionTableCallback.Set( + Kernel32Intercept, "RtlInstallFunctionTableCallback", + &patched_RtlInstallFunctionTableCallback); + } +#endif + + if (aInitFlags & eDllBlocklistInitFlagWasBootstrapped) { + GetNativeNtBlockSetWriter(); + return; + } + + // There are a couple of exceptional cases where we skip user32.dll check. + // - If the the process was bootstrapped by the launcher process, AppInit + // DLLs will be intercepted by the new DllBlockList. No need to check + // here. + // - The code to initialize the base profiler loads winmm.dll which + // statically links user32.dll on an older Windows. This means if the base + // profiler is active before coming here, we cannot fully intercept AppInit + // DLLs. Given that the base profiler is used outside the typical use + // cases, it's ok not to check user32.dll in this scenario. + const bool skipUser32Check = + (sInitFlags & eDllBlocklistInitFlagWasBootstrapped) +#ifdef MOZ_GECKO_PROFILER + || + (!IsWin10AnniversaryUpdateOrLater() && baseprofiler::profiler_is_active()) +#endif + ; + + // In order to be effective against AppInit DLLs, the blocklist must be + // initialized before user32.dll is loaded into the process (bug 932100). + if (!skipUser32Check && GetModuleHandleW(L"user32.dll")) { + sUser32BeforeBlocklist = true; +#ifdef DEBUG + printf_stderr("DLL blocklist was unable to intercept AppInit DLLs.\n"); +#endif + } + + NtDllIntercept.Init("ntdll.dll"); + + ReentrancySentinel::InitializeStatics(); + + // We specifically use a detour, because there are cases where external + // code also tries to hook LdrLoadDll, and doesn't know how to relocate our + // nop space patches. (Bug 951827) + bool ok = stub_LdrLoadDll.SetDetour(NtDllIntercept, "LdrLoadDll", + &patched_LdrLoadDll); + + if (!ok) { + sBlocklistInitFailed = true; +#ifdef DEBUG + printf_stderr("LdrLoadDll hook failed, no dll blocklisting active\n"); +#endif + } + + // If someone injects a thread early that causes user32.dll to load off the + // main thread this causes issues, so load it as soon as we've initialized + // the block-list. (See bug 1400637) + if (!sUser32BeforeBlocklist) { + ::LoadLibraryW(L"user32.dll"); + } + + Kernel32Intercept.Init("kernel32.dll"); + + // Bug 1361410: WRusr.dll will overwrite our hook and cause a crash. + // Workaround: If we detect WRusr.dll, don't hook. + if (!GetModuleHandleW(L"WRusr.dll")) { + if (!stub_BaseThreadInitThunk.SetDetour(Kernel32Intercept, + "BaseThreadInitThunk", + &patched_BaseThreadInitThunk)) { +#ifdef DEBUG + printf_stderr("BaseThreadInitThunk hook failed\n"); +#endif + } + } + +#if defined(NIGHTLY_BUILD) + // Populate a list of thread start addresses to block. + HMODULE hKernel = GetModuleHandleW(L"kernel32.dll"); + if (hKernel) { + void* pProc; + + pProc = (void*)GetProcAddress(hKernel, "LoadLibraryA"); + gStartAddressesToBlock[0] = pProc; + + pProc = (void*)GetProcAddress(hKernel, "LoadLibraryW"); + gStartAddressesToBlock[1] = pProc; + + pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExA"); + gStartAddressesToBlock[2] = pProc; + + pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExW"); + gStartAddressesToBlock[3] = pProc; + } +#endif +} + +#ifdef DEBUG +MFBT_API void DllBlocklist_Shutdown() {} +#endif // DEBUG + +static void InternalWriteNotes(AnnotationWriter& aWriter) { + WritableBuffer buffer; + DllBlockSet::Write(buffer); + + aWriter.Write(Annotation::BlockedDllList, buffer.Data(), buffer.Length()); + + if (sBlocklistInitFailed) { + aWriter.Write(Annotation::BlocklistInitFailed, "1"); + } + + if (sUser32BeforeBlocklist) { + aWriter.Write(Annotation::User32BeforeBlocklist, "1"); + } +} + +using WriterFn = void (*)(AnnotationWriter&); +static WriterFn gWriterFn = &InternalWriteNotes; + +static void GetNativeNtBlockSetWriter() { + auto nativeWriter = reinterpret_cast<WriterFn>( + ::GetProcAddress(::GetModuleHandleW(nullptr), "NativeNtBlockSet_Write")); + if (nativeWriter) { + gWriterFn = nativeWriter; + } +} + +MFBT_API void DllBlocklist_WriteNotes(AnnotationWriter& aWriter) { + MOZ_ASSERT(gWriterFn); + gWriterFn(aWriter); +} + +MFBT_API bool DllBlocklist_CheckStatus() { + if (sBlocklistInitFailed || sUser32BeforeBlocklist) return false; + return true; +} + +// ============================================================================ +// This section is for DLL Services +// ============================================================================ + +namespace mozilla { +Authenticode* GetAuthenticode(); +} // namespace mozilla + +MFBT_API void DllBlocklist_SetFullDllServices( + mozilla::glue::detail::DllServicesBase* aSvc) { + glue::AutoExclusiveLock lock(gDllServicesLock); + if (aSvc) { + aSvc->SetAuthenticodeImpl(GetAuthenticode()); + aSvc->SetWinLauncherFunctions(gWinLauncherFunctions); + gMozglueLoaderObserver.Forward(aSvc); + } + + gDllServices = aSvc; +} + +MFBT_API void DllBlocklist_SetBasicDllServices( + mozilla::glue::detail::DllServicesBase* aSvc) { + if (!aSvc) { + return; + } + + aSvc->SetAuthenticodeImpl(GetAuthenticode()); + gMozglueLoaderObserver.Disable(); +} diff --git a/mozglue/dllservices/WindowsDllBlocklist.h b/mozglue/dllservices/WindowsDllBlocklist.h new file mode 100644 index 0000000000..475c5a34a5 --- /dev/null +++ b/mozglue/dllservices/WindowsDllBlocklist.h @@ -0,0 +1,57 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifndef mozilla_windowsdllblocklist_h +#define mozilla_windowsdllblocklist_h + +#if (defined(_MSC_VER) || defined(__MINGW32__)) && \ + (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64)) + +# include <windows.h> +# include "CrashAnnotations.h" +# include "mozilla/Attributes.h" +# include "mozilla/Types.h" + +# define HAS_DLL_BLOCKLIST + +enum DllBlocklistInitFlags { + eDllBlocklistInitFlagDefault = 0, + eDllBlocklistInitFlagIsChildProcess = 1, + eDllBlocklistInitFlagWasBootstrapped = 2 +}; + +// Only available from within firefox.exe +# if !defined(IMPL_MFBT) && !defined(MOZILLA_INTERNAL_API) +extern uint32_t gBlocklistInitFlags; +# endif // !defined(IMPL_MFBT) && !defined(MOZILLA_INTERNAL_API) + +MFBT_API void DllBlocklist_Initialize( + uint32_t aInitFlags = eDllBlocklistInitFlagDefault); +MFBT_API void DllBlocklist_WriteNotes(CrashReporter::AnnotationWriter& aWriter); +MFBT_API bool DllBlocklist_CheckStatus(); + +// This export intends to clean up after DllBlocklist_Initialize(). +// It's disabled in release builds for performance and to limit callers' ability +// to interfere with dll blocking. +# ifdef DEBUG +MFBT_API void DllBlocklist_Shutdown(); +# endif // DEBUG + +// Forward declaration +namespace mozilla { +namespace glue { +namespace detail { +class DllServicesBase; +} // namespace detail +} // namespace glue +} // namespace mozilla + +MFBT_API void DllBlocklist_SetFullDllServices( + mozilla::glue::detail::DllServicesBase* aSvc); +MFBT_API void DllBlocklist_SetBasicDllServices( + mozilla::glue::detail::DllServicesBase* aSvc); + +#endif // defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) +#endif // mozilla_windowsdllblocklist_h diff --git a/mozglue/dllservices/WindowsDllBlocklistCommon.h b/mozglue/dllservices/WindowsDllBlocklistCommon.h new file mode 100644 index 0000000000..aa8d65e135 --- /dev/null +++ b/mozglue/dllservices/WindowsDllBlocklistCommon.h @@ -0,0 +1,118 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsDllBlocklistCommon_h +#define mozilla_WindowsDllBlocklistCommon_h + +#include <stdint.h> + +#include "mozilla/ArrayUtils.h" + +namespace mozilla { + +template <typename StrType> +struct DllBlockInfoT { + // The name of the DLL -- in LOWERCASE! It will be compared to + // a lowercase version of the DLL name only. + StrType mName; + + // If mMaxVersion is ALL_VERSIONS, we'll block all versions of this + // dll. Otherwise, we'll block all versions less than or equal to + // the given version, as queried by GetFileVersionInfo and + // VS_FIXEDFILEINFO's dwFileVersionMS and dwFileVersionLS fields. + // + // Note that the version is usually 4 components, which is A.B.C.D + // encoded as 0x AAAA BBBB CCCC DDDD ULL (spaces added for clarity), + // but it's not required to be of that format. + uint64_t mMaxVersion; + + // If the USE_TIMESTAMP flag is set, then we use the timestamp from + // the IMAGE_FILE_HEADER in lieu of a version number. + // + // If the CHILD_PROCESSES_ONLY flag is set, then the dll is blocked + // only when we are a child process. + enum Flags { + FLAGS_DEFAULT = 0, + BLOCK_WIN7_AND_OLDER = 1 << 0, + BLOCK_WIN8_AND_OLDER = 1 << 1, + USE_TIMESTAMP = 1 << 2, + CHILD_PROCESSES_ONLY = 1 << 3, + BROWSER_PROCESS_ONLY = 1 << 4, + REDIRECT_TO_NOOP_ENTRYPOINT = 1 << 5, + } mFlags; + + bool IsVersionBlocked(const uint64_t aOther) const { + if (mMaxVersion == ALL_VERSIONS) { + return true; + } + + return aOther <= mMaxVersion; + } + + static const uint64_t ALL_VERSIONS = (uint64_t)-1LL; + + // DLLs sometimes ship without a version number, particularly early + // releases. Blocking "version <= 0" has the effect of blocking unversioned + // DLLs (since the call to get version info fails), but not blocking + // any versioned instance. + static const uint64_t UNVERSIONED = 0ULL; +}; + +} // namespace mozilla + +// Convert the 4 (decimal) components of a DLL version number into a +// single unsigned long long, as needed by the blocklist +#if defined(_MSC_VER) && !defined(__clang__) + +// MSVC does not properly handle the constexpr MAKE_VERSION, so we use a macro +// instead (ugh). +# define MAKE_VERSION(a, b, c, d) \ + ((a##ULL << 48) + (b##ULL << 32) + (c##ULL << 16) + d##ULL) + +#else + +static inline constexpr uint64_t MAKE_VERSION(uint16_t a, uint16_t b, + uint16_t c, uint16_t d) { + return static_cast<uint64_t>(a) << 48 | static_cast<uint64_t>(b) << 32 | + static_cast<uint64_t>(c) << 16 | static_cast<uint64_t>(d); +} + +#endif + +#if !defined(DLL_BLOCKLIST_STRING_TYPE) +# error "You must define DLL_BLOCKLIST_STRING_TYPE" +#endif // !defined(DLL_BLOCKLIST_STRING_TYPE) + +#define DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(name) \ + using DllBlockInfo = mozilla::DllBlockInfoT<DLL_BLOCKLIST_STRING_TYPE>; \ + static const DllBlockInfo name[] = { +#define DLL_BLOCKLIST_DEFINITIONS_BEGIN \ + DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gWindowsDllBlocklist) + +#define DLL_BLOCKLIST_DEFINITIONS_END \ + {} \ + } \ + ; + +#define DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY_FOR(name, list) \ + const DllBlockInfo* name = &list[0] + +#define DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(name) \ + DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY_FOR(name, gWindowsDllBlocklist) + +#define DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY_FOR(name, list) \ + const DllBlockInfo* name = &list[mozilla::ArrayLength(list) - 1] + +#define DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(name) \ + DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY_FOR(name, gWindowsDllBlocklist) + +#define DECLARE_DLL_BLOCKLIST_NUM_ENTRIES_FOR(name, list) \ + const size_t name = mozilla::ArrayLength(list) - 1 + +#define DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(name) \ + DECLARE_DLL_BLOCKLIST_NUM_ENTRIES_FOR(name, gWindowsDllBlocklist) + +#endif // mozilla_WindowsDllBlocklistCommon_h diff --git a/mozglue/dllservices/WindowsDllBlocklistDefs.in b/mozglue/dllservices/WindowsDllBlocklistDefs.in new file mode 100644 index 0000000000..752a131f9f --- /dev/null +++ b/mozglue/dllservices/WindowsDllBlocklistDefs.in @@ -0,0 +1,284 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +# This file exposes three lists: +# ALL_PROCESSES, BROWSER_PROCESS, and CHILD_PROCESSES +# +# In addition, each of those lists supports a special variant for test-only +# entries: +# ALL_PROCESSES_TESTS, BROWSER_PROCESS_TESTS, and CHILD_PROCESSES_TESTS +# +# Choose the list that is applicable to the applicable process type(s) for your +# DLL block. +# +# The currently supported blocklist entry types are: +# DllBlocklistEntry, A11yBlocklistEntry, LspBlocklistEntry, +# RedirectToNoOpEntryPoint +# (See gen_dll_blocklist_defs.py for their documentation.) +# +# Example: +# ALL_PROCESSES += [ +# DllBlocklistEntry("foo.dll", (1, 2, 3, 4)), +# DllBlocklistEntry("foo.dll", ALL_VERSIONS), +# DllBlocklistEntry("foo.dll", UNVERSIONED), +# DllBlocklistEntry("foo.dll", 0x0000123400000000), +# DllBlocklistEntry("foo.dll", PETimeStamp(0x12345678)), +# ] +# +# The version parameter the "last bad" version, that is, we block anything that +# is less-than or equal to that version. + +ALL_PROCESSES += [ + # NPFFAddon - Known malware + DllBlocklistEntry("npffaddon.dll", ALL_VERSIONS), + + # AVG 8 - Antivirus vendor AVG, old version, plugin already blocklisted + DllBlocklistEntry("avgrsstx.dll", (8,5,0,401)), + + # calc.dll - Suspected malware + DllBlocklistEntry("calc.dll", (1,0,0,1)), + + # hook.dll - Suspected malware + DllBlocklistEntry("hook.dll", ALL_VERSIONS), + + # GoogleDesktopNetwork3.dll - Extremely old, unversioned instances + # of this DLL cause crashes + DllBlocklistEntry("googledesktopnetwork3.dll", UNVERSIONED), + + # rdolib.dll - Suspected malware + DllBlocklistEntry("rdolib.dll", (6,0,88,4)), + + # fgjk4wvb.dll - Suspected malware + DllBlocklistEntry("fgjk4wvb.dll", (8,8,8,8)), + + # radhslib.dll - Naomi internet filter - unmaintained since 2006 + DllBlocklistEntry("radhslib.dll", UNVERSIONED), + + # Music download filter for vkontakte.ru - old instances + # of this DLL cause crashes + DllBlocklistEntry("vksaver.dll", (2,2,2,0)), + + # Topcrash in Firefox 4.0b1 + DllBlocklistEntry("rlxf.dll", (1,2,323,1)), + + # psicon.dll - Topcrashes in Thunderbird, and some crashes in Firefox + # Adobe photoshop library, now redundant in later installations + DllBlocklistEntry("psicon.dll", ALL_VERSIONS), + + # Topcrash in Firefox 4 betas (bug 618899), + DllBlocklistEntry("accelerator.dll", (3,2,1,6)), + + # Topcrash with Roboform in Firefox 8 (bug 699134), + DllBlocklistEntry("rf-firefox.dll", (7,6,1,0)), + DllBlocklistEntry("roboform.dll", (7,6,1,0)), + + # Topcrash with Babylon Toolbar on FF16+ (bug 721264), + DllBlocklistEntry("babyfox.dll", ALL_VERSIONS), + + # sprotector.dll crashes, bug 957258 + DllBlocklistEntry("sprotector.dll", ALL_VERSIONS), + + # Windows Media Foundation FLAC decoder and type sniffer (bug 839031). + DllBlocklistEntry("mfflac.dll", ALL_VERSIONS), + + # Older Relevant Knowledge DLLs cause us to crash (bug 904001). + DllBlocklistEntry("rlnx.dll", (1, 3, 334, 9)), + DllBlocklistEntry("pmnx.dll", (1, 3, 334, 9)), + DllBlocklistEntry("opnx.dll", (1, 3, 334, 9)), + DllBlocklistEntry("prnx.dll", (1, 3, 334, 9)), + + # Older belgian ID card software causes Firefox to crash or hang on + # shutdown, bug 831285 and 918399. + DllBlocklistEntry("beid35cardlayer.dll", (3, 5, 6, 6968)), + + # bug 925459, bitguard crashes + DllBlocklistEntry("bitguard.dll", ALL_VERSIONS), + + # bug 812683 - crashes in Windows library when Asus Gamer OSD is installed + # Software is discontinued/unsupported + DllBlocklistEntry("atkdx11disp.dll", ALL_VERSIONS), + + # Topcrash with Conduit SearchProtect, bug 944542 + DllBlocklistEntry("spvc32.dll", ALL_VERSIONS), + + # Topcrash with V-bates, bug 1002748 and bug 1023239 + DllBlocklistEntry("libinject.dll", UNVERSIONED), + DllBlocklistEntry("libinject2.dll", PETimeStamp(0x537DDC93)), + DllBlocklistEntry("libredir2.dll", PETimeStamp(0x5385B7ED)), + + # Crashes with RoboForm2Go written against old SDK, bug 988311/1196859 + DllBlocklistEntry("rf-firefox-22.dll", ALL_VERSIONS), + DllBlocklistEntry("rf-firefox-40.dll", ALL_VERSIONS), + + # Crashes with DesktopTemperature, bug 1046382 + DllBlocklistEntry("dtwxsvc.dll", PETimeStamp(0x53153234)), + + # Startup crashes with Lenovo Onekey Theater, bug 1123778 + DllBlocklistEntry("activedetect32.dll", UNVERSIONED), + DllBlocklistEntry("activedetect64.dll", UNVERSIONED), + DllBlocklistEntry("windowsapihookdll32.dll", UNVERSIONED), + DllBlocklistEntry("windowsapihookdll64.dll", UNVERSIONED), + + # Flash crashes with RealNetworks RealDownloader, bug 1132663 + DllBlocklistEntry("rndlnpshimswf.dll", ALL_VERSIONS), + DllBlocklistEntry("rndlmainbrowserrecordplugin.dll", ALL_VERSIONS), + + # Startup crashes with RealNetworks Browser Record Plugin, bug 1170141 + DllBlocklistEntry("nprpffbrowserrecordext.dll", ALL_VERSIONS), + DllBlocklistEntry("nprndlffbrowserrecordext.dll", ALL_VERSIONS), + + # Crashes with CyberLink YouCam, bug 1136968 + DllBlocklistEntry("ycwebcamerasource.ax", (2, 0, 0, 1611)), + + # Old version of WebcamMax crashes WebRTC, bug 1130061 + DllBlocklistEntry("vwcsource.ax", (1, 5, 0, 0)), + + # NetOp School, discontinued product, bug 763395 + DllBlocklistEntry("nlsp.dll", (6, 23, 2012, 19)), + + # Orbit Downloader, bug 1222819 + DllBlocklistEntry("grabdll.dll", (2, 6, 1, 0)), + DllBlocklistEntry("grabkernel.dll", (1, 0, 0, 1)), + + # ESET, bug 1229252 + DllBlocklistEntry("eoppmonitor.dll", ALL_VERSIONS), + + # SS2OSD, bug 1262348 + DllBlocklistEntry("ss2osd.dll", ALL_VERSIONS), + DllBlocklistEntry("ss2devprops.dll", ALL_VERSIONS), + + # NHASUSSTRIXOSD.DLL, bug 1269244 + DllBlocklistEntry("nhasusstrixosd.dll", ALL_VERSIONS), + DllBlocklistEntry("nhasusstrixdevprops.dll", ALL_VERSIONS), + + # Crashes with PremierOpinion/RelevantKnowledge, bug 1277846 + DllBlocklistEntry("opls.dll", ALL_VERSIONS), + DllBlocklistEntry("opls64.dll", ALL_VERSIONS), + DllBlocklistEntry("pmls.dll", ALL_VERSIONS), + DllBlocklistEntry("pmls64.dll", ALL_VERSIONS), + DllBlocklistEntry("prls.dll", ALL_VERSIONS), + DllBlocklistEntry("prls64.dll", ALL_VERSIONS), + DllBlocklistEntry("rlls.dll", ALL_VERSIONS), + DllBlocklistEntry("rlls64.dll", ALL_VERSIONS), + + # Vorbis DirectShow filters, bug 1239690. + DllBlocklistEntry("vorbis.acm", (0, 0, 3, 6)), + + # AhnLab Internet Security, bug 1311969 + DllBlocklistEntry("nzbrcom.dll", ALL_VERSIONS), + + # K7TotalSecurity, bug 1339083. + DllBlocklistEntry("k7pswsen.dll", (15, 2, 2, 95)), + + # smci*.dll - goobzo crashware (bug 1339908), + DllBlocklistEntry("smci32.dll", ALL_VERSIONS), + DllBlocklistEntry("smci64.dll", ALL_VERSIONS), + + # Crashes with Internet Download Manager, bug 1333486 + DllBlocklistEntry("idmcchandler7.dll", ALL_VERSIONS), + DllBlocklistEntry("idmcchandler7_64.dll", ALL_VERSIONS), + DllBlocklistEntry("idmcchandler5.dll", ALL_VERSIONS), + DllBlocklistEntry("idmcchandler5_64.dll", ALL_VERSIONS), + + # Nahimic 2 breaks applicaton update (bug 1356637), + DllBlocklistEntry("nahimic2devprops.dll", (2, 5, 19, 0xffff)), + # Nahimic is causing crashes, bug 1233556 + DllBlocklistEntry("nahimicmsiosd.dll", UNVERSIONED), + # Nahimic is causing crashes, bug 1360029 + DllBlocklistEntry("nahimicvrdevprops.dll", UNVERSIONED), + DllBlocklistEntry("nahimic2osd.dll", (2, 5, 19, 0xffff)), + DllBlocklistEntry("nahimicmsidevprops.dll", UNVERSIONED), + + # Bug 1268470 - crashes with Kaspersky Lab on Windows 8 + DllBlocklistEntry("klsihk64.dll", (14, 0, 456, 0xffff), + BLOCK_WIN8_AND_OLDER), + + # Bug 1579758, crashes with OpenSC nightly version 0.19.0.448 and lower + DllBlocklistEntry("onepin-opensc-pkcs11.dll", (0, 19, 0, 448)), + + # Avecto Privilege Guard causes crashes, bug 1385542 + DllBlocklistEntry("pghook.dll", ALL_VERSIONS), + + # Old versions of G DATA BankGuard, bug 1421991 + DllBlocklistEntry("banksafe64.dll", (1, 2, 15299, 65535)), + + # Old versions of G DATA, bug 1043775 + DllBlocklistEntry("gdkbfltdll64.dll", (1, 0, 14141, 240)), + + # Dell Backup and Recovery tool causes crashes, bug 1433408 + DllBlocklistEntry("dbroverlayiconnotbackuped.dll", (1, 8, 0, 9)), + DllBlocklistEntry("dbroverlayiconbackuped.dll", (1, 8, 0, 9)), + + # NVIDIA nView Desktop Management causes crashes, bug 1465787 + DllBlocklistEntry("nviewh64.dll", (6, 14, 10, 14847)), + + # Ivanti Endpoint Security, bug 1553776 + DllBlocklistEntry("sxwmon.dll", ALL_VERSIONS), + DllBlocklistEntry("sxwmon64.dll", ALL_VERSIONS), + + # 360 Safeguard/360 Total Security causes a11y crashes, bug 1536227. + DllBlocklistEntry("safemon64.dll", ALL_VERSIONS), + + # Old versions of Digital Guardian, bug 1318858, bug 1603974, + # and bug 1672367 + RedirectToNoOpEntryPoint("dgapi.dll", (7, 5, 0xffff, 0xffff)), + RedirectToNoOpEntryPoint("dgapi64.dll", (7, 5, 0xffff, 0xffff)), + + # Old versions of COMODO Internet Security, bug 1608048 + DllBlocklistEntry("IseGuard32.dll", (1, 6, 13835, 184)), + DllBlocklistEntry("IseGuard64.dll", (1, 6, 13835, 184)), + + # Old version of COMODO Firewall, bug 1407712 and bug 1624336 + DllBlocklistEntry("guard64.dll", (8, 4, 0, 65535)), + + # Old version of Panda Security, bug 1637984 + DllBlocklistEntry("PavLspHook64.dll", (9, 2, 2, 1), BLOCK_WIN7_AND_OLDER), +] + +ALL_PROCESSES_TESTS += [ + # DLLs used by TestDllBlocklist* gTests + DllBlocklistEntry("testdllblocklist_matchbyname.dll", ALL_VERSIONS), + DllBlocklistEntry("testdllblocklist_matchbyversion.dll", (5, 5, 5, 5)), + DllBlocklistEntry("testdllblocklist_allowbyversion.dll", (5, 5, 5, 5)), + RedirectToNoOpEntryPoint("testdllblocklist_noopentrypoint.dll", + (5, 5, 5, 5)), +] + +BROWSER_PROCESS += [ + # RealPlayer, bug 1418535, bug 1437417 + # Versions before 18.1.11.0 cause severe performance problems. + A11yBlocklistEntry("dtvhooks.dll", (18, 1, 10, 0xffff)), + A11yBlocklistEntry("dtvhooks64.dll", (18, 1, 10, 0xffff)), + + # SolidWorks Windows Explorer integration causes crashes, bug 1566109 + # and bug 1468250 + DllBlocklistEntry("Database.dll", ALL_VERSIONS), + + # Hancom Office shell extension causes crashes when the file picker is + # opened. See bug 1581092. + DllBlocklistEntry("hncshellext64.dll", (1, 0, 0 ,3)), + + # Cambridge Silicon Radio, bug 1634538 + DllBlocklistEntry("BLEtokenCredentialProvider.dll", (2, 1, 63, 0)), + + # FYunZip and PuddingZip, loaded as shell extension, cause crashes + # bug 1576728 + DllBlocklistEntry("oly64.dll", (1, 1, 3, 19920)), + DllBlocklistEntry("oly.dll", (1, 1, 3, 19920)), + DllBlocklistEntry("pdzipmenu64.dll", (1, 4, 4, 20103)), + DllBlocklistEntry("pdzipmenu32.dll", (1, 4, 4, 20103)), + + # McAfee Data Loss Prevention causes crashs with multiple signatures, + # bug 1634090 + DllBlocklistEntry("fcagff.dll", (11, 6, 0xffff, 0xffff)), + DllBlocklistEntry("fcagff64.dll", (11, 6, 0xffff, 0xffff)), +] + +CHILD_PROCESSES += [ + # Causes crashes in the GPU process with WebRender enabled, bug 1544435 + DllBlocklistEntry("wbload.dll", ALL_VERSIONS), +] + diff --git a/mozglue/dllservices/WindowsDllServices.h b/mozglue/dllservices/WindowsDllServices.h new file mode 100644 index 0000000000..e065c8ff6c --- /dev/null +++ b/mozglue/dllservices/WindowsDllServices.h @@ -0,0 +1,211 @@ +/* -*- 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/. */ + +#ifndef mozilla_glue_WindowsDllServices_h +#define mozilla_glue_WindowsDllServices_h + +#include <utility> + +#include "mozilla/Assertions.h" +#include "mozilla/Authenticode.h" +#include "mozilla/LoaderAPIInterfaces.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Vector.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "mozilla/WindowsDllBlocklist.h" +#include "mozilla/mozalloc.h" + +#if defined(MOZILLA_INTERNAL_API) +# include "MainThreadUtils.h" +# include "nsISupportsImpl.h" +# include "nsString.h" +# include "nsThreadUtils.h" +# include "prthread.h" +# include "mozilla/SchedulerGroup.h" +#endif // defined(MOZILLA_INTERNAL_API) + +// For PCUNICODE_STRING +#include <winternl.h> + +namespace mozilla { +namespace glue { +namespace detail { + +class DllServicesBase : public Authenticode { + public: + /** + * WARNING: This method is called from within an unsafe context that holds + * multiple locks inside the Windows loader. The only thing that + * this function should be used for is dispatching the event to our + * event loop so that it may be handled in a safe context. + */ + virtual void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) = 0; + + /** + * This function accepts module load events to be processed later for + * the untrusted modules telemetry ping. + * + * WARNING: This method is run from within the Windows loader and should + * only perform trivial, loader-friendly operations. + */ + virtual void DispatchModuleLoadBacklogNotification( + ModuleLoadInfoVec&& aEvents) = 0; + + void SetAuthenticodeImpl(Authenticode* aAuthenticode) { + mAuthenticode = aAuthenticode; + } + + void SetWinLauncherFunctions(const nt::WinLauncherFunctions& aFunctions) { + mWinLauncherFunctions = aFunctions; + } + + template <typename... Args> + LauncherVoidResultWithLineInfo InitDllBlocklistOOP(Args&&... aArgs) { + MOZ_RELEASE_ASSERT(mWinLauncherFunctions.mInitDllBlocklistOOP); + return mWinLauncherFunctions.mInitDllBlocklistOOP( + std::forward<Args>(aArgs)...); + } + + template <typename... Args> + void HandleLauncherError(Args&&... aArgs) { + MOZ_RELEASE_ASSERT(mWinLauncherFunctions.mHandleLauncherError); + mWinLauncherFunctions.mHandleLauncherError(std::forward<Args>(aArgs)...); + } + + // In debug builds we override GetBinaryOrgName to add a Gecko-specific + // assertion. OTOH, we normally do not want people overriding this function, + // so we'll make it final in the release case, thus covering all bases. +#if defined(DEBUG) + UniquePtr<wchar_t[]> GetBinaryOrgName( + const wchar_t* aFilePath, + AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override +#else + UniquePtr<wchar_t[]> GetBinaryOrgName( + const wchar_t* aFilePath, + AuthenticodeFlags aFlags = AuthenticodeFlags::Default) final +#endif // defined(DEBUG) + { + if (!mAuthenticode) { + return nullptr; + } + + return mAuthenticode->GetBinaryOrgName(aFilePath, aFlags); + } + + virtual void DisableFull() { DllBlocklist_SetFullDllServices(nullptr); } + + DllServicesBase(const DllServicesBase&) = delete; + DllServicesBase(DllServicesBase&&) = delete; + DllServicesBase& operator=(const DllServicesBase&) = delete; + DllServicesBase& operator=(DllServicesBase&&) = delete; + + protected: + DllServicesBase() : mAuthenticode(nullptr) {} + + virtual ~DllServicesBase() = default; + + void EnableFull() { DllBlocklist_SetFullDllServices(this); } + void EnableBasic() { DllBlocklist_SetBasicDllServices(this); } + + private: + Authenticode* mAuthenticode; + nt::WinLauncherFunctions mWinLauncherFunctions; +}; + +} // namespace detail + +#if defined(MOZILLA_INTERNAL_API) + +struct EnhancedModuleLoadInfo final { + explicit EnhancedModuleLoadInfo(ModuleLoadInfo&& aModLoadInfo) + : mNtLoadInfo(std::move(aModLoadInfo)) { + // Only populate mThreadName when we're on the same thread as the event + if (mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) { + mThreadName = PR_GetThreadName(PR_GetCurrentThread()); + } + MOZ_ASSERT(!mNtLoadInfo.mSectionName.IsEmpty()); + } + + EnhancedModuleLoadInfo(EnhancedModuleLoadInfo&&) = default; + EnhancedModuleLoadInfo& operator=(EnhancedModuleLoadInfo&&) = default; + + EnhancedModuleLoadInfo(const EnhancedModuleLoadInfo&) = delete; + EnhancedModuleLoadInfo& operator=(const EnhancedModuleLoadInfo&) = delete; + + nsDependentString GetSectionName() const { + return mNtLoadInfo.mSectionName.AsString(); + } + + using BacktraceType = decltype(ModuleLoadInfo::mBacktrace); + + ModuleLoadInfo mNtLoadInfo; + nsCString mThreadName; +}; + +class DllServices : public detail::DllServicesBase { + public: + void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) final { + nsCOMPtr<nsIRunnable> runnable( + NewRunnableMethod<StoreCopyPassByRRef<EnhancedModuleLoadInfo>>( + "DllServices::NotifyDllLoad", this, &DllServices::NotifyDllLoad, + std::move(aModLoadInfo))); + + SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()); + } + + void DispatchModuleLoadBacklogNotification( + ModuleLoadInfoVec&& aEvents) final { + nsCOMPtr<nsIRunnable> runnable( + NewRunnableMethod<StoreCopyPassByRRef<ModuleLoadInfoVec>>( + "DllServices::NotifyModuleLoadBacklog", this, + &DllServices::NotifyModuleLoadBacklog, std::move(aEvents))); + + SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget()); + } + +# if defined(DEBUG) + UniquePtr<wchar_t[]> GetBinaryOrgName( + const wchar_t* aFilePath, + AuthenticodeFlags aFlags = AuthenticodeFlags::Default) final { + // This function may perform disk I/O, so we should never call it on the + // main thread. + MOZ_ASSERT(!NS_IsMainThread()); + return detail::DllServicesBase::GetBinaryOrgName(aFilePath, aFlags); + } +# endif // defined(DEBUG) + + NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(DllServices) + + protected: + DllServices() = default; + ~DllServices() = default; + + virtual void NotifyDllLoad(EnhancedModuleLoadInfo&& aModLoadInfo) = 0; + virtual void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) = 0; +}; + +#else + +class BasicDllServices final : public detail::DllServicesBase { + public: + BasicDllServices() { EnableBasic(); } + + ~BasicDllServices() = default; + + // Not useful in this class, so provide a default implementation + virtual void DispatchDllLoadNotification( + ModuleLoadInfo&& aModLoadInfo) override {} + + virtual void DispatchModuleLoadBacklogNotification( + ModuleLoadInfoVec&& aEvents) override {} +}; + +#endif // defined(MOZILLA_INTERNAL_API) + +} // namespace glue +} // namespace mozilla + +#endif // mozilla_glue_WindowsDllServices_h diff --git a/mozglue/dllservices/WindowsFallbackLoaderAPI.cpp b/mozglue/dllservices/WindowsFallbackLoaderAPI.cpp new file mode 100644 index 0000000000..e80aa376a7 --- /dev/null +++ b/mozglue/dllservices/WindowsFallbackLoaderAPI.cpp @@ -0,0 +1,86 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "WindowsFallbackLoaderAPI.h" + +namespace mozilla { + +ModuleLoadInfo FallbackLoaderAPI::ConstructAndNotifyBeginDllLoad( + void** aContext, PCUNICODE_STRING aRequestedDllName) { + ModuleLoadInfo loadInfo(aRequestedDllName); + + MOZ_ASSERT(mLoaderObserver); + if (mLoaderObserver) { + mLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName); + } + + return loadInfo; +} + +bool FallbackLoaderAPI::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName, + PHANDLE aOutHandle) { + MOZ_ASSERT(mLoaderObserver); + if (!mLoaderObserver) { + return false; + } + + return mLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle); +} + +void FallbackLoaderAPI::NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus, + ModuleLoadInfo&& aModuleLoadInfo) { + aModuleLoadInfo.SetEndLoadTimeStamp(); + + if (NT_SUCCESS(aLoadNtStatus)) { + aModuleLoadInfo.CaptureBacktrace(); + } + + MOZ_ASSERT(mLoaderObserver); + if (mLoaderObserver) { + mLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus, + std::move(aModuleLoadInfo)); + } +} + +nt::AllocatedUnicodeString FallbackLoaderAPI::GetSectionName( + void* aSectionAddr) { + static const StaticDynamicallyLinkedFunctionPtr<decltype( + &::NtQueryVirtualMemory)> + pNtQueryVirtualMemory(L"ntdll.dll", "NtQueryVirtualMemory"); + MOZ_ASSERT(pNtQueryVirtualMemory); + + if (!pNtQueryVirtualMemory) { + return nt::AllocatedUnicodeString(); + } + + nt::MemorySectionNameBuf buf; + NTSTATUS ntStatus = + pNtQueryVirtualMemory(::GetCurrentProcess(), aSectionAddr, + MemorySectionName, &buf, sizeof(buf), nullptr); + if (!NT_SUCCESS(ntStatus)) { + return nt::AllocatedUnicodeString(); + } + + return nt::AllocatedUnicodeString(&buf.mSectionFileName); +} + +nt::LoaderAPI::InitDllBlocklistOOPFnPtr +FallbackLoaderAPI::GetDllBlocklistInitFn() { + MOZ_ASSERT_UNREACHABLE("This should not be called so soon!"); + return nullptr; +} + +nt::LoaderAPI::HandleLauncherErrorFnPtr +FallbackLoaderAPI::GetHandleLauncherErrorFn() { + MOZ_ASSERT_UNREACHABLE("This should not be called so soon!"); + return nullptr; +} + +void FallbackLoaderAPI::SetObserver(nt::LoaderObserver* aLoaderObserver) { + mLoaderObserver = aLoaderObserver; +} + +} // namespace mozilla diff --git a/mozglue/dllservices/WindowsFallbackLoaderAPI.h b/mozglue/dllservices/WindowsFallbackLoaderAPI.h new file mode 100644 index 0000000000..e0c4236b62 --- /dev/null +++ b/mozglue/dllservices/WindowsFallbackLoaderAPI.h @@ -0,0 +1,38 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WindowsFallbackLoaderAPI_h +#define mozilla_WindowsFallbackLoaderAPI_h + +#include "mozilla/Attributes.h" +#include "NtLoaderAPI.h" + +namespace mozilla { + +class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FallbackLoaderAPI final + : public nt::LoaderAPI { + public: + constexpr FallbackLoaderAPI() : mLoaderObserver(nullptr) {} + + ModuleLoadInfo ConstructAndNotifyBeginDllLoad( + void** aContext, PCUNICODE_STRING aRequestedDllName) final; + bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName, + PHANDLE aOutHandle) final; + void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus, + ModuleLoadInfo&& aModuleLoadInfo) final; + nt::AllocatedUnicodeString GetSectionName(void* aSectionAddr) final; + nt::LoaderAPI::InitDllBlocklistOOPFnPtr GetDllBlocklistInitFn() final; + nt::LoaderAPI::HandleLauncherErrorFnPtr GetHandleLauncherErrorFn() final; + + void SetObserver(nt::LoaderObserver* aLoaderObserver); + + private: + nt::LoaderObserver* mLoaderObserver; +}; + +} // namespace mozilla + +#endif // mozilla_WindowsFallbackLoaderAPI_h diff --git a/mozglue/dllservices/gen_dll_blocklist_defs.py b/mozglue/dllservices/gen_dll_blocklist_defs.py new file mode 100644 index 0000000000..cc71c6b49f --- /dev/null +++ b/mozglue/dllservices/gen_dll_blocklist_defs.py @@ -0,0 +1,744 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +from __future__ import print_function + +from copy import deepcopy +from six import iteritems, PY2 +from struct import unpack +import os +from uuid import UUID + +H_HEADER = """/* -*- 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 file was auto-generated from {0} by gen_dll_blocklist_data.py. */ + +#ifndef mozilla_{1}_h +#define mozilla_{1}_h + +""" + +H_FOOTER = """#endif // mozilla_{1}_h + +""" + +H_DEFS_BEGIN_DEFAULT = """#include "mozilla/WindowsDllBlocklistCommon.h" + +DLL_BLOCKLIST_DEFINITIONS_BEGIN + +""" + +H_DEFS_END_DEFAULT = """ +DLL_BLOCKLIST_DEFINITIONS_END + +""" + +H_BEGIN_LSP = """#include <guiddef.h> + +static const GUID gLayerGuids[] = { + +""" + +H_END_LSP = """ +}; + +""" + +H_BEGIN_A11Y = """#include "mozilla/WindowsDllBlocklistCommon.h" + +DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gBlockedInprocDlls) + +""" + +# These flag names should match the ones defined in WindowsDllBlocklistCommon.h +FLAGS_DEFAULT = "FLAGS_DEFAULT" +BLOCK_WIN8_AND_OLDER = "BLOCK_WIN8_AND_OLDER" +BLOCK_WIN7_AND_OLDER = "BLOCK_WIN7_AND_OLDER" +USE_TIMESTAMP = "USE_TIMESTAMP" +CHILD_PROCESSES_ONLY = "CHILD_PROCESSES_ONLY" +BROWSER_PROCESS_ONLY = "BROWSER_PROCESS_ONLY" +SUBSTITUTE_LSP_PASSTHROUGH = "SUBSTITUTE_LSP_PASSTHROUGH" +REDIRECT_TO_NOOP_ENTRYPOINT = "REDIRECT_TO_NOOP_ENTRYPOINT" + +# Only these flags are available in the input script +INPUT_ONLY_FLAGS = { + BLOCK_WIN8_AND_OLDER, + BLOCK_WIN7_AND_OLDER, +} + + +def FILTER_ALLOW_ALL(entry): + # A11y entries are special, so we always exclude those by default + # (so it's not really allowing 'all', but it is simpler to reason about by + # pretending that it is allowing all.) + return not isinstance(entry, A11yBlocklistEntry) + + +def FILTER_DENY_ALL(entry): + return False + + +def FILTER_ALLOW_ONLY_A11Y(entry): + return isinstance(entry, A11yBlocklistEntry) + + +def FILTER_ALLOW_ONLY_LSP(entry): + return isinstance(entry, LspBlocklistEntry) + + +def FILTER_TESTS_ONLY(entry): + return not isinstance(entry, A11yBlocklistEntry) and entry.is_test() + + +def derive_test_key(key): + return key + "_TESTS" + + +ALL_DEFINITION_LISTS = ("ALL_PROCESSES", "BROWSER_PROCESS", "CHILD_PROCESSES") + + +class BlocklistDescriptor(object): + """This class encapsulates every file that is output from this script. + Each instance has a name, an "input specification", and optional "flag + specification" and "output specification" entries. + """ + + DEFAULT_OUTSPEC = { + "mode": "", + "filter": FILTER_ALLOW_ALL, + "begin": H_DEFS_BEGIN_DEFAULT, + "end": H_DEFS_END_DEFAULT, + } + + FILE_NAME_TPL = "WindowsDllBlocklist{0}Defs" + + OutputDir = None + ExistingFd = None + ExistingFdLeafName = None + + def __init__(self, name, inspec, **kwargs): + """Positional arguments: + + name -- String containing the name of the output list. + + inspec -- One or more members of ALL_DEFINITION_LISTS. The input used + for this blocklist file is the union of all lists specified by this + variable. + + Keyword arguments: + + outspec -- an optional list of dicts that specify how the lists output + will be written out to a file. Each dict may contain the following keys: + + 'mode' -- a string that specifies a mode that is used when writing + out list entries to this particular output. This is passed in as the + mode argument to DllBlocklistEntry's write method. + + 'filter' -- a function that, given a blocklist entry, decides + whether or not that entry shall be included in this output file. + + 'begin' -- a string that is written to the output file after writing + the file's header, but prior to writing out any blocklist entries. + + 'end' -- a string that is written to the output file after writing + out any blocklist entries but before the file's footer. + + Any unspecified keys will be assigned default values. + + flagspec -- an optional dict whose keys consist of one or more of the + list names from inspec. Each corresponding value is a set of flags that + should be applied to each entry from that list. For example, the + flagspec: + + {'CHILD_PROCESSES': {CHILD_PROCESSES_ONLY}} + + causes any blocklist entries from the CHILD_PROCESSES list to be output + with the CHILD_PROCESSES_ONLY flag set. + + """ + + self._name = name + + # inspec's elements must all come from ALL_DEFINITION_LISTS + assert not (set(inspec).difference(set(ALL_DEFINITION_LISTS))) + + # Internally to the descriptor, we store input specifications as a dict + # that maps each input blocklist name to the set of flags to be applied + # to each entry in that blocklist. + self._inspec = {blocklist: set() for blocklist in inspec} + + self._outspecs = kwargs.get("outspec", BlocklistDescriptor.DEFAULT_OUTSPEC) + if isinstance(self._outspecs, dict): + # _outspecs should always be stored as a list of dicts + self._outspecs = [self._outspecs] + + flagspecs = kwargs.get("flagspec", dict()) + # flagspec's keys must all come from ALL_DEFINITION_LISTS + assert not (set(flagspecs.keys()).difference(set(self._inspec.keys()))) + + # Merge the flags from flagspec into _inspec's sets + for blocklist, flagspec in iteritems(flagspecs): + spec = self._inspec[blocklist] + if not isinstance(spec, set): + raise TypeError("Flag spec for list %s must be a set!" % blocklist) + spec.update(flagspec) + + @staticmethod + def set_output_fd(fd): + """The build system has already provided an open file descriptor for + one of our outputs. We save that here so that we may use that fd once + we're ready to process that fd's file. We also obtain the output dir for + use as the base directory for the other output files that we open. + """ + BlocklistDescriptor.ExistingFd = fd + ( + BlocklistDescriptor.OutputDir, + BlocklistDescriptor.ExistingFdLeafName, + ) = os.path.split(fd.name) + + @staticmethod + def ensure_no_dupes(defs): + """Ensure that defs does not contain any dupes. We raise a ValueError + because this is a bug in the input and requires developer intervention. + """ + seen = set() + for e in defs: + name = e.get_name() + if name not in seen: + seen.add(name) + else: + raise ValueError("Duplicate entry found: %s" % name) + + @staticmethod + def get_test_entries(exec_env, blocklist): + """Obtains all test entries for the corresponding blocklist, and also + ensures that each entry has its test flag set. + + Positional arguments: + + exec_env -- dict containing the globals that were passed to exec to + when the input script was run. + + blocklist -- The name of the blocklist to obtain tests from. This + should be one of the members of ALL_DEFINITION_LISTS + """ + test_key = derive_test_key(blocklist) + + def set_is_test(elem): + elem.set_is_test() + return elem + + return map(set_is_test, exec_env[test_key]) + + def gen_list(self, exec_env, filter_func): + """Generates a sorted list of blocklist entries from the input script, + filtered via filter_func. + + Positional arguments: + + exec_env -- dict containing the globals that were passed to exec to + when the input script was run. This function expects exec_env to + contain lists of blocklist entries, keyed using one of the members of + ALL_DEFINITION_LISTS. + + filter_func -- a filter function that evaluates each blocklist entry + to determine whether or not it should be included in the results. + """ + + # This list contains all the entries across all blocklists that we + # potentially want to process + unified_list = [] + + # For each blocklist specified in the _inspec, we query the globals + # for their entries, add any flags, and then add them to the + # unified_list. + for blocklist, listflags in iteritems(self._inspec): + + def add_list_flags(elem): + # We deep copy so that flags set for an entry in one blocklist + # do not interfere with flags set for an entry in a different + # list. + result = deepcopy(elem) + result.add_flags(listflags) + return result + + # We add list flags *before* filtering because the filters might + # want to access flags as part of their operation. + unified_list.extend(map(add_list_flags, exec_env[blocklist])) + + # We also add any test entries for the lists specified by _inspec + unified_list.extend( + map(add_list_flags, self.get_test_entries(exec_env, blocklist)) + ) + + # There should be no dupes in the input. If there are, raise an error. + self.ensure_no_dupes(unified_list) + + # Now we filter out any unwanted list entries + filtered_list = filter(filter_func, unified_list) + + # Sort the list on entry name so that the blocklist code may use + # binary search if it so chooses. + return sorted(filtered_list, key=lambda e: e.get_name()) + + @staticmethod + def get_fd(outspec_leaf_name): + """If BlocklistDescriptor.ExistingFd corresponds to outspec_leaf_name, + then we return that. Otherwise, we construct a new absolute path to + outspec_leaf_name and open a new file descriptor for writing. + """ + if ( + not BlocklistDescriptor.ExistingFd + or BlocklistDescriptor.ExistingFdLeafName != outspec_leaf_name + ): + new_name = os.path.join(BlocklistDescriptor.OutputDir, outspec_leaf_name) + return open(new_name, "w") + + fd = BlocklistDescriptor.ExistingFd + BlocklistDescriptor.ExistingFd = None + return fd + + def write(self, src_file, exec_env): + """Write out all output files that are specified by this descriptor. + + Positional arguments: + + src_file -- name of the input file from which the lists were generated. + + exec_env -- dictionary containing the lists that were parsed from the + input file when it was executed. + """ + + for outspec in self._outspecs: + # Use DEFAULT_OUTSPEC to supply defaults for any unused outspec keys + effective_outspec = BlocklistDescriptor.DEFAULT_OUTSPEC.copy() + effective_outspec.update(outspec) + + entries = self.gen_list(exec_env, effective_outspec["filter"]) + if not entries: + continue + + mode = effective_outspec["mode"] + + # Since each output descriptor may generate output across multiple + # modes, each list is uniquified via the concatenation of our name + # and the mode. + listname = self._name + mode + leafname_no_ext = BlocklistDescriptor.FILE_NAME_TPL.format(listname) + leafname = leafname_no_ext + ".h" + + with self.get_fd(leafname) as output: + print(H_HEADER.format(src_file, leafname_no_ext), file=output, end="") + print(effective_outspec["begin"], file=output, end="") + + for e in entries: + e.write(output, mode) + + print(effective_outspec["end"], file=output, end="") + print(H_FOOTER.format(src_file, leafname_no_ext), file=output, end="") + + +A11Y_OUTPUT_SPEC = { + "filter": FILTER_ALLOW_ONLY_A11Y, + "begin": H_BEGIN_A11Y, +} + +LSP_MODE_GUID = "Guid" + +LSP_OUTPUT_SPEC = [ + { + "mode": LSP_MODE_GUID, + "filter": FILTER_ALLOW_ONLY_LSP, + "begin": H_BEGIN_LSP, + "end": H_END_LSP, + }, +] + +GENERATED_BLOCKLIST_FILES = [ + BlocklistDescriptor("A11y", ["BROWSER_PROCESS"], outspec=A11Y_OUTPUT_SPEC), + BlocklistDescriptor( + "Launcher", + ALL_DEFINITION_LISTS, + flagspec={ + "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY}, + "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY}, + }, + ), + BlocklistDescriptor( + "Legacy", + ALL_DEFINITION_LISTS, + flagspec={ + "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY}, + "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY}, + }, + ), + # Roughed-in for the moment; we'll enable this in bug 1238735 + # BlocklistDescriptor('LSP', ALL_DEFINITION_LISTS, outspec=LSP_OUTPUT_SPEC), + BlocklistDescriptor( + "Test", ALL_DEFINITION_LISTS, outspec={"filter": FILTER_TESTS_ONLY} + ), +] + + +class PETimeStamp(object): + def __init__(self, ts): + max_timestamp = (2 ** 32) - 1 + if ts < 0 or ts > max_timestamp: + raise ValueError("Invalid timestamp value") + self._value = ts + + def __str__(self): + return "0x%08XU" % self._value + + +class Version(object): + """Encapsulates a DLL version.""" + + ALL_VERSIONS = 0xFFFFFFFFFFFFFFFF + UNVERSIONED = 0 + + def __init__(self, *args): + """There are multiple ways to construct a Version: + + As a tuple containing four elements (recommended); + As four integral arguments; + As a PETimeStamp; + As a long integer. + + The tuple and list formats require the value of each element to be + between 0 and 0xFFFF, inclusive. + """ + + self._ver = Version.UNVERSIONED + + if len(args) == 1: + if isinstance(args[0], tuple): + self.validate_iterable(args[0]) + + self._ver = "MAKE_VERSION%r" % (args[0],) + elif isinstance(args[0], PETimeStamp): + self._ver = args[0] + else: + self._ver = int(args[0]) + elif len(args) == 4: + self.validate_iterable(args) + + self._ver = "MAKE_VERSION%r" % (tuple(args),) + else: + raise ValueError("Bad arguments to Version constructor") + + def validate_iterable(self, arg): + if len(arg) != 4: + raise ValueError("Versions must be a 4-tuple") + + for component in arg: + if not isinstance(component, int) or component < 0 or component > 0xFFFF: + raise ValueError( + "Each version component must be a 16-bit " "unsigned integer" + ) + + def build_long(self, args): + self.validate_iterable(args) + return ( + (int(args[0]) << 48) + | (int(args[1]) << 32) + | (int(args[2]) << 16) + | int(args[3]) + ) + + def is_timestamp(self): + return isinstance(self._ver, PETimeStamp) + + def __str__(self): + if isinstance(self._ver, int): + if self._ver == Version.ALL_VERSIONS: + return "DllBlockInfo::ALL_VERSIONS" + + if self._ver == Version.UNVERSIONED: + return "DllBlockInfo::UNVERSIONED" + + return "0x%016XULL" % self._ver + + return str(self._ver) + + +class DllBlocklistEntry(object): + TEST_CONDITION = "defined(ENABLE_TESTS)" + + def __init__(self, name, ver, flags=(), **kwargs): + """Positional arguments: + + name -- The leaf name of the DLL. + + ver -- The maximum version to be blocked. NB: The comparison used by the + blocklist is <=, so you should specify the last bad version, as opposed + to the first good version. + + flags -- iterable containing the flags that should be applicable to + this blocklist entry. + + Keyword arguments: + + condition -- a string containing a C++ preprocessor expression. This + condition is written as a condition for an #if/#endif block that is + generated around the entry during output. + """ + + self.check_ascii(name) + self._name = name.lower() + self._ver = Version(ver) + + self._flags = set() + self.add_flags(flags) + if self._ver.is_timestamp(): + self._flags.add(USE_TIMESTAMP) + + self._cond = kwargs.get("condition", set()) + if isinstance(self._cond, str): + self._cond = {self._cond} + + @staticmethod + def check_ascii(name): + if PY2: + if not all(ord(c) < 128 for c in name): + raise ValueError('DLL name "%s" must be ASCII!' % name) + return + + try: + # Supported in Python 3.7 + if not name.isascii(): + raise ValueError('DLL name "%s" must be ASCII!' % name) + return + except AttributeError: + pass + + try: + name.encode("ascii") + except UnicodeEncodeError: + raise ValueError('DLL name "%s" must be ASCII!' % name) + + def get_name(self): + return self._name + + def set_condition(self, cond): + self._cond.add(cond) + + def get_condition(self): + if len(self._cond) == 1: + fmt = "{0}" + else: + fmt = "({0})" + + return " && ".join([fmt.format(c) for c in self._cond]) + + def set_is_test(self): + self.set_condition(DllBlocklistEntry.TEST_CONDITION) + + def is_test(self): + return self._cond and DllBlocklistEntry.TEST_CONDITION in self._cond + + def add_flags(self, new_flags): + if isinstance(new_flags, str): + self._flags.add(new_flags) + else: + self._flags.update(new_flags) + + @staticmethod + def get_flag_string(flag): + return "DllBlockInfo::" + flag + + def get_flags_list(self): + return self._flags + + def write(self, output, mode): + if self._cond: + print("#if %s" % self.get_condition(), file=output) + + flags_str = "" + + flags = self.get_flags_list() + if flags: + flags_str = ", " + " | ".join(map(self.get_flag_string, flags)) + + entry_str = ' DLL_BLOCKLIST_ENTRY("%s", %s%s)' % ( + self._name, + str(self._ver), + flags_str, + ) + print(entry_str, file=output) + + if self._cond: + print("#endif // %s" % self.get_condition(), file=output) + + +class A11yBlocklistEntry(DllBlocklistEntry): + """Represents a blocklist entry for injected a11y DLLs. This class does + not need to do anything special compared to a DllBlocklistEntry; we just + use this type to distinguish a11y entries from "regular" blocklist entries. + """ + + def __init__(self, name, ver, flags=(), **kwargs): + """These arguments are identical to DllBlocklistEntry.__init__""" + + super(A11yBlocklistEntry, self).__init__(name, ver, flags, **kwargs) + + +class RedirectToNoOpEntryPoint(DllBlocklistEntry): + """Represents a blocklist entry to hook the entrypoint into a function + just returning TRUE to keep a module alive and harmless. + This entry is intended to block a DLL which is injected by IAT patching + which is planted by a kernel callback routine for LoadImage because + blocking such a DLL makes a process fail to launch. + """ + + def __init__(self, name, ver, flags=(), **kwargs): + """These arguments are identical to DllBlocklistEntry.__init__""" + + super(RedirectToNoOpEntryPoint, self).__init__(name, ver, flags, **kwargs) + + def get_flags_list(self): + flags = super(RedirectToNoOpEntryPoint, self).get_flags_list() + # RedirectToNoOpEntryPoint items always include the following flag + flags.add(REDIRECT_TO_NOOP_ENTRYPOINT) + return flags + + +class LspBlocklistEntry(DllBlocklistEntry): + """Represents a blocklist entry for a WinSock Layered Service Provider (LSP).""" + + GUID_UNPACK_FMT_LE = "<IHHBBBBBBBB" + Guids = dict() + + def __init__(self, name, ver, guids, flags=(), **kwargs): + """Positional arguments: + + name -- The leaf name of the DLL. + + ver -- The maximum version to be blocked. NB: The comparison used by the + blocklist is <=, so you should specify the last bad version, as opposed + to the first good version. + + guids -- Either a string or list of strings containing the GUIDs that + uniquely identify the LSP. These GUIDs are generated by the developer of + the LSP and are registered with WinSock alongside the LSP. We record + this GUID as part of the "Winsock_LSP" annotation in our crash reports. + + flags -- iterable containing the flags that should be applicable to + this blocklist entry. + + Keyword arguments: + + condition -- a string containing a C++ preprocessor expression. This + condition is written as a condition for an #if/#endif block that is + generated around the entry during output. + """ + + super(LspBlocklistEntry, self).__init__(name, ver, flags, **kwargs) + if not guids: + raise ValueError("Missing GUID(s)!") + + if isinstance(guids, str): + self.insert(UUID(guids), name) + else: + for guid in guids: + self.insert(UUID(guid), name) + + def insert(self, guid, name): + # Some explanation here: Multiple DLLs (and thus multiple instances of + # LspBlocklistEntry) may share the same GUIDs. To ensure that we do not + # have any duplicates, we store each GUID in a class variable, Guids. + # We include the original DLL name from the blocklist definitions so + # that we may output a comment above each GUID indicating which entries + # correspond to which GUID. + LspBlocklistEntry.Guids.setdefault(guid, []).append(name) + + def get_flags_list(self): + flags = super(LspBlocklistEntry, self).get_flags_list() + # LSP entries always include the following flag + flags.add(SUBSTITUTE_LSP_PASSTHROUGH) + return flags + + @staticmethod + def as_c_struct(guid, names): + parts = unpack(LspBlocklistEntry.GUID_UNPACK_FMT_LE, guid.bytes_le) + str_guid = ( + " // %r\n // {%s}\n { 0x%08x, 0x%04x, 0x%04x, " + "{ 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x }" + " }" + % ( + names, + str(guid), + parts[0], + parts[1], + parts[2], + parts[3], + parts[4], + parts[5], + parts[6], + parts[7], + parts[8], + parts[9], + parts[10], + ) + ) + return str_guid + + def write(self, output, mode): + if mode != LSP_MODE_GUID: + super(LspBlocklistEntry, self).write(output, mode) + return + + # We dump the entire contents of Guids on the first call, and then + # clear it. Remaining invocations of this method are no-ops. + if LspBlocklistEntry.Guids: + result = ",\n".join( + [ + self.as_c_struct(guid, names) + for guid, names in iteritems(LspBlocklistEntry.Guids) + ] + ) + print(result, file=output) + LspBlocklistEntry.Guids.clear() + + +def exec_script_file(script_name, globals): + with open(script_name) as script: + exec(compile(script.read(), script_name, "exec"), globals) + + +def gen_blocklists(first_fd, defs_filename): + + BlocklistDescriptor.set_output_fd(first_fd) + + # exec_env defines the global variables that will be present in the + # execution environment when defs_filename is run by exec. + exec_env = { + # Add the blocklist entry types + "A11yBlocklistEntry": A11yBlocklistEntry, + "DllBlocklistEntry": DllBlocklistEntry, + "LspBlocklistEntry": LspBlocklistEntry, + "RedirectToNoOpEntryPoint": RedirectToNoOpEntryPoint, + # Add the special version types + "ALL_VERSIONS": Version.ALL_VERSIONS, + "UNVERSIONED": Version.UNVERSIONED, + "PETimeStamp": PETimeStamp, + } + + # Import definition lists into exec_env + for defname in ALL_DEFINITION_LISTS: + exec_env[defname] = [] + # For each defname, add a special list for test-only entries + exec_env[derive_test_key(defname)] = [] + + # Import flags into exec_env + exec_env.update({flag: flag for flag in INPUT_ONLY_FLAGS}) + + # Now execute the input script with exec_env providing the globals + exec_script_file(defs_filename, exec_env) + + # Tell the output descriptors to write out the output files. + for desc in GENERATED_BLOCKLIST_FILES: + desc.write(defs_filename, exec_env) diff --git a/mozglue/dllservices/moz.build b/mozglue/dllservices/moz.build new file mode 100644 index 0000000000..b46692fe84 --- /dev/null +++ b/mozglue/dllservices/moz.build @@ -0,0 +1,60 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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 CONFIG["MOZ_WIDGET_TOOLKIT"]: + + SOURCES += [ + # This file contains a |using namespace mozilla;| statement + "WindowsDllBlocklist.cpp", + ] + + UNIFIED_SOURCES += [ + "Authenticode.cpp", + "LoaderObserver.cpp", + "ModuleLoadFrame.cpp", + "WindowsFallbackLoaderAPI.cpp", + ] + +OS_LIBS += [ + "crypt32", + "ntdll", + "version", + "wintrust", +] + +DELAYLOAD_DLLS += [ + "crypt32.dll", + "wintrust.dll", +] + +EXPORTS.mozilla += [ + "Authenticode.h", + "LoaderAPIInterfaces.h", + "ModuleLoadInfo.h", + "WindowsDllBlocklist.h", + "WindowsDllBlocklistCommon.h", +] + +EXPORTS.mozilla.glue += [ + "WindowsDllServices.h", +] + +# Generate DLL Blocklists +blocklist_header_types = ["A11y", "Launcher", "Legacy", "Test"] +blocklist_file_leaf_tpl = "WindowsDllBlocklist{0}Defs.h" +blocklist_files = tuple( + [blocklist_file_leaf_tpl.format(type) for type in blocklist_header_types] +) + +GeneratedFile( + *blocklist_files, + script="gen_dll_blocklist_defs.py", + entry_point="gen_blocklists", + inputs=["WindowsDllBlocklistDefs.in"] +) +EXPORTS.mozilla += ["!" + hdr for hdr in blocklist_files] + +FINAL_LIBRARY = "mozglue" |