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
This commit is contained in:
Aaron Klotz 2018-01-30 15:08:03 -07:00
parent 200eb77750
commit e398eaca79
9 changed files with 577 additions and 11 deletions

View File

@ -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 <windows.h>
#include <softpub.h>
#include <wincrypt.h>
#include <wintrust.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);
}
};
typedef mozilla::UniquePtr<HCERTSTORE, CertStoreDeleter> CertStoreUniquePtr;
typedef mozilla::UniquePtr<HCRYPTMSG, CryptMsgDeleter> CryptMsgUniquePtr;
typedef mozilla::UniquePtr<const CERT_CONTEXT, CertContextDeleter> 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<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);
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<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::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<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) override;
};
UniquePtr<wchar_t[]>
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

View File

@ -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<wchar_t[]> GetBinaryOrgName(const wchar_t* aFilePath) = 0;
};
} // namespace mozilla
#endif // mozilla_Authenticode_h

View File

@ -23,6 +23,7 @@
#include <map>
#pragma warning( pop )
#include "Authenticode.h"
#include "nsAutoPtr.h"
#include "nsWindowsDllInterceptor.h"
#include "mozilla/Sprintf.h"
@ -1046,12 +1047,19 @@ 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) {
if (aSvc) {
aSvc->SetAuthenticodeImpl(GetAuthenticode());
if (!gNotificationCookie) {
auto pLdrRegisterDllNotification =
reinterpret_cast<decltype(&::LdrRegisterDllNotification)>(
::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"),
@ -1063,6 +1071,7 @@ DllBlocklist_SetDllServices(mozilla::glue::detail::DllServicesBase* aSvc)
nullptr, &gNotificationCookie);
MOZ_DIAGNOSTIC_ASSERT(NT_SUCCESS(ntStatus));
}
}
gDllServices = aSvc;
}

View File

@ -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<wchar_t[]> 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

View File

@ -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 += [

View File

@ -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 <Tlhelp32.h>
namespace mozilla {
NS_IMPL_ISUPPORTS(CertAnnotator, nsIObserver)
CertAnnotator::~CertAnnotator()
{
if (mAnnotatorThread) {
mAnnotatorThread->Shutdown();
}
}
bool
CertAnnotator::Init()
{
if (mAnnotatorThread) {
return true;
}
nsCOMPtr<nsIRunnable> 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<nsIRunnable> event =
NewRunnableMethod<nsString, bool>("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<nsIFile> 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<mozilla::DllServices> 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<Writer>());
#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<Writer*>(json.WriteFunc())->Get();
if (XRE_IsParentProcess()) {
// Safe to do off main thread in the parent process
Annotate(serialized);
return;
}
nsCOMPtr<nsIRunnable> event =
NewRunnableMethod<nsCString>("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<CertAnnotator> annotator(new CertAnnotator());
if (!annotator->Init()) {
return;
}
nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService());
obsServ->AddObserver(annotator, DllServices::kTopicDllLoadedMainThread,
false);
obsServ->AddObserver(annotator, DllServices::kTopicDllLoadedNonMainThread,
false);
}
} // namespace mozilla

View File

@ -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<nsStringHashKey, nsTArray<nsString>> mCertTable;
nsCOMPtr<nsIThread> mAnnotatorThread;
};
} // namespace mozilla
#endif // mozilla_CertAnnotator_h

View File

@ -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',

View File

@ -160,6 +160,7 @@
#ifdef XP_WIN
#include <process.h>
#include <shlobj.h>
#include "mozilla/CertAnnotator.h"
#include "mozilla/WinDllServices.h"
#include "nsThreadUtils.h"
#include <comdef.h>
@ -4314,6 +4315,8 @@ XREMain::XRE_mainRun()
auto dllServicesDisable = MakeScopeExit([&dllServices]() {
dllServices->Disable();
});
mozilla::CertAnnotator::Register();
#endif // defined(XP_WIN)
#ifdef NS_FUNCTION_TIMER