From e398eaca799d943c8ff3049cbfd889195ff4bbf4 Mon Sep 17 00:00:00 2001 From: Aaron Klotz Date: Tue, 30 Jan 2018 15:08:03 -0700 Subject: [PATCH] Bug 1430857: Part 2 - Add cert annotations to Windows crash reports; r=mhowell MozReview-Commit-ID: 270iURVhNRu This patch builds upon the existing DLL services functionality: 1) We add code to obtain the name of the subject from the cert used to sign a binary (if present). This code is added inside mozglue because in the future we will be using this code from the DLL blocklist, which is also located there. 2) We add annotation functionality that registers itself for DLL load events and updates crash reporter annotations as new libraries are loaded. It also annotates any existing libraries that are also in memory at the time that the CertAnnotator is first instantiated. This all happens off main thread, with the exception of actually making the annotation when in a child process. --HG-- extra : rebase_source : f86c1a6fd2a44f21a71e7a7418267b3b0d5feeec --- mozglue/build/Authenticode.cpp | 210 +++++++++++++++++++++ mozglue/build/Authenticode.h | 24 +++ mozglue/build/WindowsDllBlocklist.cpp | 27 ++- mozglue/build/WindowsDllServices.h | 26 ++- mozglue/build/moz.build | 4 + toolkit/crashreporter/CertAnnotator.cpp | 240 ++++++++++++++++++++++++ toolkit/crashreporter/CertAnnotator.h | 47 +++++ toolkit/crashreporter/moz.build | 7 + toolkit/xre/nsAppRunner.cpp | 3 + 9 files changed, 577 insertions(+), 11 deletions(-) create mode 100644 mozglue/build/Authenticode.cpp create mode 100644 mozglue/build/Authenticode.h create mode 100644 toolkit/crashreporter/CertAnnotator.cpp create mode 100644 toolkit/crashreporter/CertAnnotator.h diff --git a/mozglue/build/Authenticode.cpp b/mozglue/build/Authenticode.cpp new file mode 100644 index 000000000000..903e6d258229 --- /dev/null +++ b/mozglue/build/Authenticode.cpp @@ -0,0 +1,210 @@ +/* -*- 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/. */ + +#ifdef MOZ_MEMORY +#define MOZ_MEMORY_IMPL +#include "mozmemory_wrap.h" +#define MALLOC_FUNCS MALLOC_FUNCS_MALLOC +// See mozmemory_wrap.h for more details. This file is part of libmozglue, so +// it needs to use _impl suffixes. +#define MALLOC_DECL(name, return_type, ...) \ + extern "C" MOZ_MEMORY_API return_type name ## _impl(__VA_ARGS__); +#include "malloc_decls.h" +#endif + +#include "Authenticode.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/TypeTraits.h" +#include "mozilla/UniquePtr.h" + +#include +#include +#include +#include + +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); + } +}; + +typedef mozilla::UniquePtr CertStoreUniquePtr; +typedef mozilla::UniquePtr CryptMsgUniquePtr; +typedef mozilla::UniquePtr CertContextUniquePtr; + +static const DWORD kEncodingTypes = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING; + +class SignedBinary +{ +public: + explicit SignedBinary(const wchar_t* aFilePath); + + explicit operator bool() const + { + return mCertStore && mCryptMsg && mCertCtx; + } + + mozilla::UniquePtr GetOrgName(); + + SignedBinary(const SignedBinary&) = delete; + SignedBinary(SignedBinary&&) = delete; + SignedBinary& operator=(const SignedBinary&) = delete; + SignedBinary& operator=(SignedBinary&&) = delete; + +private: + bool VerifySignature(const wchar_t* aFilePath); + +private: + CertStoreUniquePtr mCertStore; + CryptMsgUniquePtr mCryptMsg; + CertContextUniquePtr mCertCtx; +}; + +SignedBinary::SignedBinary(const wchar_t* aFilePath) +{ + if (!VerifySignature(aFilePath)) { + return; + } + + 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; + } + + mCertStore.reset(rawCertStore); + mCryptMsg.reset(rawCryptMsg); + + DWORD certInfoLen = 0; + BOOL ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0, + nullptr, &certInfoLen); + if (!ok) { + return; + } + + auto certInfoBuf = mozilla::MakeUnique(certInfoLen); + + ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0, + certInfoBuf.get(), &certInfoLen); + if (!ok) { + return; + } + + auto certInfo = reinterpret_cast(certInfoBuf.get()); + + PCCERT_CONTEXT certCtx = CertFindCertificateInStore(mCertStore.get(), + kEncodingTypes, 0, + CERT_FIND_SUBJECT_CERT, + certInfo, nullptr); + if (!certCtx) { + return; + } + + mCertCtx.reset(certCtx); +} + +bool +SignedBinary::VerifySignature(const wchar_t* aFilePath) +{ + WINTRUST_FILE_INFO fileInfo = {sizeof(fileInfo)}; + fileInfo.pcwszFilePath = aFilePath; + + WINTRUST_DATA trustData = {sizeof(trustData)}; + trustData.dwUIChoice = WTD_UI_NONE; + trustData.fdwRevocationChecks = WTD_REVOKE_NONE; + trustData.dwUnionChoice = WTD_CHOICE_FILE; + trustData.pFile = &fileInfo; + trustData.dwStateAction = WTD_STATEACTION_VERIFY; + + const HWND hwnd = (HWND) INVALID_HANDLE_VALUE; + GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2; + LONG result = WinVerifyTrust(hwnd, &policyGUID, &trustData); + + trustData.dwStateAction = WTD_STATEACTION_CLOSE; + WinVerifyTrust(hwnd, &policyGUID, &trustData); + + return result == ERROR_SUCCESS; +} + +mozilla::UniquePtr +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(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 GetBinaryOrgName(const wchar_t* aFilePath) override; +}; + +UniquePtr +AuthenticodeImpl::GetBinaryOrgName(const wchar_t* aFilePath) +{ + SignedBinary bin(aFilePath); + if (!bin) { + return nullptr; + } + + return bin.GetOrgName(); +} + +static AuthenticodeImpl sAuthenticodeImpl; + +Authenticode* +GetAuthenticode() +{ + return &sAuthenticodeImpl; +} + +} // namespace mozilla + diff --git a/mozglue/build/Authenticode.h b/mozglue/build/Authenticode.h new file mode 100644 index 000000000000..8ec64ca4671a --- /dev/null +++ b/mozglue/build/Authenticode.h @@ -0,0 +1,24 @@ +/* -*- 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/UniquePtr.h" + +namespace mozilla { + +class Authenticode +{ +public: + virtual UniquePtr GetBinaryOrgName(const wchar_t* aFilePath) = 0; +}; + +} // namespace mozilla + +#endif // mozilla_Authenticode_h + diff --git a/mozglue/build/WindowsDllBlocklist.cpp b/mozglue/build/WindowsDllBlocklist.cpp index 157bbccf362d..107a2e7626d1 100644 --- a/mozglue/build/WindowsDllBlocklist.cpp +++ b/mozglue/build/WindowsDllBlocklist.cpp @@ -23,6 +23,7 @@ #include #pragma warning( pop ) +#include "Authenticode.h" #include "nsAutoPtr.h" #include "nsWindowsDllInterceptor.h" #include "mozilla/Sprintf.h" @@ -1046,22 +1047,30 @@ DllLoadNotification(ULONG aReason, PCLDR_DLL_NOTIFICATION_DATA aNotificationData gDllServices->DispatchDllLoadNotification(fullDllName); } +namespace mozilla { +Authenticode* GetAuthenticode(); +} // namespace mozilla + MFBT_API void DllBlocklist_SetDllServices(mozilla::glue::detail::DllServicesBase* aSvc) { AutoExclusiveLock lock(gDllServicesLock); - if (aSvc && !gNotificationCookie) { - auto pLdrRegisterDllNotification = - reinterpret_cast( - ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), - "LdrRegisterDllNotification")); + if (aSvc) { + aSvc->SetAuthenticodeImpl(GetAuthenticode()); - MOZ_DIAGNOSTIC_ASSERT(pLdrRegisterDllNotification); + if (!gNotificationCookie) { + auto pLdrRegisterDllNotification = + reinterpret_cast( + ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), + "LdrRegisterDllNotification")); - NTSTATUS ntStatus = pLdrRegisterDllNotification(0, &DllLoadNotification, - nullptr, &gNotificationCookie); - MOZ_DIAGNOSTIC_ASSERT(NT_SUCCESS(ntStatus)); + MOZ_DIAGNOSTIC_ASSERT(pLdrRegisterDllNotification); + + NTSTATUS ntStatus = pLdrRegisterDllNotification(0, &DllLoadNotification, + nullptr, &gNotificationCookie); + MOZ_DIAGNOSTIC_ASSERT(NT_SUCCESS(ntStatus)); + } } gDllServices = aSvc; diff --git a/mozglue/build/WindowsDllServices.h b/mozglue/build/WindowsDllServices.h index 2f74e421e9e1..3f01dac43276 100644 --- a/mozglue/build/WindowsDllServices.h +++ b/mozglue/build/WindowsDllServices.h @@ -7,6 +7,7 @@ #ifndef mozilla_glue_WindowsDllServices_h #define mozilla_glue_WindowsDllServices_h +#include "mozilla/Authenticode.h" #include "mozilla/WindowsDllBlocklist.h" #if defined(MOZILLA_INTERNAL_API) @@ -25,7 +26,7 @@ namespace mozilla { namespace glue { namespace detail { -class DllServicesBase +class DllServicesBase : public Authenticode { public: /** @@ -36,6 +37,20 @@ public: */ virtual void DispatchDllLoadNotification(PCUNICODE_STRING aDllName) = 0; + void SetAuthenticodeImpl(Authenticode* aAuthenticode) + { + mAuthenticode = aAuthenticode; + } + + virtual UniquePtr GetBinaryOrgName(const wchar_t* aFilePath) override final + { + if (!mAuthenticode) { + return nullptr; + } + + return mAuthenticode->GetBinaryOrgName(aFilePath); + } + void Disable() { DllBlocklist_SetDllServices(nullptr); @@ -47,13 +62,20 @@ public: DllServicesBase& operator=(DllServicesBase&&) = delete; protected: - DllServicesBase() = default; + DllServicesBase() + : mAuthenticode(nullptr) + { + } + virtual ~DllServicesBase() = default; void Enable() { DllBlocklist_SetDllServices(this); } + +private: + Authenticode* mAuthenticode; }; } // namespace detail diff --git a/mozglue/build/moz.build b/mozglue/build/moz.build index 1d6439ea1391..056bb4dc7bef 100644 --- a/mozglue/build/moz.build +++ b/mozglue/build/moz.build @@ -56,14 +56,18 @@ if CONFIG['MOZ_WIDGET_TOOLKIT']: if CONFIG['CC_TYPE'] == "msvc": SOURCES += ['WindowsCFGStatus.cpp'] SOURCES += [ + 'Authenticode.cpp', 'WindowsDllBlocklist.cpp', ] DisableStlWrapping() OS_LIBS += [ + 'crypt32', 'version', + 'wintrust', ] EXPORTS.mozilla += [ + 'Authenticode.h', 'WindowsDllBlocklist.h', ] EXPORTS.mozilla.glue += [ diff --git a/toolkit/crashreporter/CertAnnotator.cpp b/toolkit/crashreporter/CertAnnotator.cpp new file mode 100644 index 000000000000..6aedccf40389 --- /dev/null +++ b/toolkit/crashreporter/CertAnnotator.cpp @@ -0,0 +1,240 @@ +/* -*- 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 "CertAnnotator.h" + +#include "mozilla/JSONWriter.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/Services.h" +#include "mozilla/WinDllServices.h" +#include "nsExceptionHandler.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsXPCOM.h" +#include "nsXULAppAPI.h" +#include "nsWindowsHelpers.h" + +#include + +namespace mozilla { + +NS_IMPL_ISUPPORTS(CertAnnotator, nsIObserver) + +CertAnnotator::~CertAnnotator() +{ + if (mAnnotatorThread) { + mAnnotatorThread->Shutdown(); + } +} + +bool +CertAnnotator::Init() +{ + if (mAnnotatorThread) { + return true; + } + + nsCOMPtr initialEvent = + NewRunnableMethod("mozilla::CertAnnotator::RecordInitialCertInfo", this, + &CertAnnotator::RecordInitialCertInfo); + + nsresult rv = NS_NewNamedThread("Cert Annotator", + getter_AddRefs(mAnnotatorThread), + initialEvent); + return NS_SUCCEEDED(rv); +} + +NS_IMETHODIMP +CertAnnotator::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(!strcmp(aTopic, DllServices::kTopicDllLoadedMainThread) || + !strcmp(aTopic, DllServices::kTopicDllLoadedNonMainThread)); + MOZ_ASSERT(mAnnotatorThread); + + if (!mAnnotatorThread) { + return NS_OK; + } + + nsCOMPtr event = + NewRunnableMethod("mozilla::CertAnnotator::RecordCertInfo", + this, &CertAnnotator::RecordCertInfo, + aData, true); + + mAnnotatorThread->Dispatch(event, NS_DISPATCH_NORMAL); + + return NS_OK; +} + +void +CertAnnotator::RecordInitialCertInfo() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + nsAutoHandle snapshot(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, 0)); + MOZ_ASSERT(snapshot != INVALID_HANDLE_VALUE); + if (snapshot == INVALID_HANDLE_VALUE) { + return; + } + + MODULEENTRY32W moduleEntry = { sizeof(moduleEntry) }; + + if (!::Module32FirstW(snapshot, &moduleEntry)) { + return; + } + + do { + RecordCertInfo(nsDependentString(moduleEntry.szExePath), false); + } while(::Module32NextW(snapshot, &moduleEntry)); + + Serialize(); +} + +void +CertAnnotator::RecordCertInfo(const nsAString& aLibPath, const bool aDoSerialize) +{ + MOZ_ASSERT(!NS_IsMainThread()); + + // (1) Get Lowercase Module Name + nsCOMPtr file; + nsresult rv = NS_NewLocalFile(aLibPath, false, getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return; + } + + nsAutoString key; + rv = file->GetLeafName(key); + if (NS_FAILED(rv)) { + return; + } + + ToLowerCase(key); + + // (2) Get cert subject info + auto flatLibPath = PromiseFlatString(aLibPath); + + RefPtr dllSvc(mozilla::DllServices::Get()); + auto orgName = dllSvc->GetBinaryOrgName(flatLibPath.get()); + if (!orgName) { + return; + } + + // (3) Insert into hash table + auto& modulesForThisSubject = mCertTable.GetOrInsert(nsString(orgName.get())); + + if (modulesForThisSubject.ContainsSorted(key)) { + return; + } + + modulesForThisSubject.InsertElementSorted(Move(key)); + + if (!aDoSerialize) { + return; + } + + // (4) Serialize and annotate + Serialize(); +} + +} // namespace mozilla + +namespace { + +class Writer final : public mozilla::JSONWriteFunc +{ +public: + virtual void Write(const char* aStr) override + { + mStr += aStr; + } + + const nsCString& Get() const + { + return mStr; + } + +private: + nsCString mStr; +}; + +} // anonymous namespace + +namespace mozilla { + +void +CertAnnotator::Serialize() +{ + MOZ_ASSERT(!NS_IsMainThread()); + + JSONWriter json(MakeUnique()); + +#if defined(DEBUG) + const JSONWriter::CollectionStyle style = JSONWriter::MultiLineStyle; +#else + const JSONWriter::CollectionStyle style = JSONWriter::SingleLineStyle; +#endif + + json.Start(style); + + for (auto subjectItr = mCertTable.Iter(); !subjectItr.Done(); subjectItr.Next()) { + json.StartArrayProperty(NS_ConvertUTF16toUTF8(subjectItr.Key()).get()); + auto& modules = subjectItr.Data(); + for (auto&& module : modules) { + json.StringElement(NS_ConvertUTF16toUTF8(module).get()); + } + json.EndArray(); + } + + json.End(); + + const nsCString& serialized = static_cast(json.WriteFunc())->Get(); + + if (XRE_IsParentProcess()) { + // Safe to do off main thread in the parent process + Annotate(serialized); + return; + } + + nsCOMPtr event = + NewRunnableMethod("mozilla::CertAnnotator::Annotate", this, + &CertAnnotator::Annotate, serialized); + NS_DispatchToMainThread(event); +} + +void +CertAnnotator::Annotate(const nsCString& aAnnotation) +{ + nsAutoCString annotationKey; + annotationKey.AppendLiteral("ModuleSignatureInfo"); + if (XRE_IsParentProcess()) { + annotationKey.AppendLiteral("Parent"); + } else { + MOZ_ASSERT(NS_IsMainThread()); + annotationKey.AppendLiteral("Child"); + } + + CrashReporter::AnnotateCrashReport(annotationKey, aAnnotation); +} + +void +CertAnnotator::Register() +{ + RefPtr annotator(new CertAnnotator()); + if (!annotator->Init()) { + return; + } + + nsCOMPtr obsServ(services::GetObserverService()); + obsServ->AddObserver(annotator, DllServices::kTopicDllLoadedMainThread, + false); + obsServ->AddObserver(annotator, DllServices::kTopicDllLoadedNonMainThread, + false); +} + +} // namespace mozilla diff --git a/toolkit/crashreporter/CertAnnotator.h b/toolkit/crashreporter/CertAnnotator.h new file mode 100644 index 000000000000..c55373d5134f --- /dev/null +++ b/toolkit/crashreporter/CertAnnotator.h @@ -0,0 +1,47 @@ +/* -*- 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_CertAnnotator_h +#define mozilla_CertAnnotator_h + +#include "mozilla/Move.h" +#include "mozilla/Mutex.h" +#include "nsDataHashtable.h" +#include "nsIObserver.h" +#include "nsIThread.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { + +class CertAnnotator final : public nsIObserver +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + static void Register(); + +private: + CertAnnotator() = default; + virtual ~CertAnnotator(); + + bool Init(); + + void RecordInitialCertInfo(); + void RecordCertInfo(const nsAString& aLibPath, const bool aDoSerialize); + + void Serialize(); + void Annotate(const nsCString& aAnnotation); + + nsDataHashtable> mCertTable; + nsCOMPtr mAnnotatorThread; +}; + +} // namespace mozilla + +#endif // mozilla_CertAnnotator_h + diff --git a/toolkit/crashreporter/moz.build b/toolkit/crashreporter/moz.build index dafaefa3f9fd..736db498ed20 100644 --- a/toolkit/crashreporter/moz.build +++ b/toolkit/crashreporter/moz.build @@ -35,6 +35,13 @@ if CONFIG['MOZ_CRASHREPORTER']: if CONFIG['MOZ_CRASHREPORTER_INJECTOR']: DIRS += ['breakpad-windows-standalone'] + UNIFIED_SOURCES += [ + 'CertAnnotator.cpp', + ] + EXPORTS.mozilla += [ + 'CertAnnotator.h', + ] + elif CONFIG['OS_ARCH'] == 'Darwin': DIRS += [ 'breakpad-client', diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp index 72ea91271e24..97ffc0bf6123 100644 --- a/toolkit/xre/nsAppRunner.cpp +++ b/toolkit/xre/nsAppRunner.cpp @@ -160,6 +160,7 @@ #ifdef XP_WIN #include #include +#include "mozilla/CertAnnotator.h" #include "mozilla/WinDllServices.h" #include "nsThreadUtils.h" #include @@ -4314,6 +4315,8 @@ XREMain::XRE_mainRun() auto dllServicesDisable = MakeScopeExit([&dllServices]() { dllServices->Disable(); }); + + mozilla::CertAnnotator::Register(); #endif // defined(XP_WIN) #ifdef NS_FUNCTION_TIMER