Bug 1523276 - Implement PHC, a probabilistic heap checker. r=glandium,gsvelto

Differential Revision: https://phabricator.services.mozilla.com/D25021

--HG--
extra : rebase_source : 86e94499f746b18a596130341692c6a9992d4867
This commit is contained in:
Nicholas Nethercote 2019-07-03 09:26:11 +10:00
parent 7c88ba1685
commit 38dbbfcffc
32 changed files with 2165 additions and 31 deletions

View File

@ -45,3 +45,33 @@ def replace_malloc_static(build_project):
set_config('MOZ_REPLACE_MALLOC_STATIC', replace_malloc_static)
# PHC (Probabilistic Heap Checker)
# ==============================================================
# In general, it only makes sense for PHC to run on the platforms that have a
# crash reporter. Currently it only runs on Linux, but not on 32-bit Linux
# because stack tracing frequently crashes (for unclear reasons). In the
# future, we want it to run on Win64 and Mac as well.
@depends(milestone, target, replace_malloc_default, '--enable-replace-malloc',
when='--enable-jemalloc')
def phc_default(milestone, target, replace_malloc_default, replace_malloc):
if not replace_malloc_default or \
(replace_malloc.origin != 'default' and not replace_malloc):
return False
if not milestone.is_nightly:
return False
return (target.os == 'GNU' and target.kernel == 'Linux' and
target.bitness == 64)
option('--enable-phc', env='MOZ_PHC', default=phc_default,
when='--enable-jemalloc',
help='{Enable|Disable} PHC (Probabilistic Memory Checker). '
'Also enables replace-malloc and frame pointers')
imply_option('--enable-replace-malloc', True, when='--enable-phc')
imply_option('--enable-frame-pointers', True, when='--enable-phc')
set_config('MOZ_PHC', True, when='--enable-phc')

View File

@ -20,6 +20,9 @@ if CONFIG['MOZ_REPLACE_MALLOC']:
'replace_malloc_bridge.h',
]
if CONFIG['MOZ_PHC']:
DEFINES['MOZ_PHC'] = True
if CONFIG['MOZ_MEMORY']:
UNIFIED_SOURCES += [
'mozjemalloc.cpp',

View File

@ -4511,6 +4511,8 @@ static void replace_malloc_init_funcs(malloc_table_t*);
extern "C" void logalloc_init(malloc_table_t*, ReplaceMallocBridge**);
extern "C" void dmd_init(malloc_table_t*, ReplaceMallocBridge**);
extern "C" void phc_init(malloc_table_t*, ReplaceMallocBridge**);
# endif
bool Equals(const malloc_table_t& aTable1, const malloc_table_t& aTable2) {
@ -4549,6 +4551,11 @@ static void init() {
dmd_init(&tempTable, &gReplaceMallocBridge);
}
# endif
# ifdef MOZ_PHC
if (Equals(tempTable, gDefaultMallocTable)) {
phc_init(&tempTable, &gReplaceMallocBridge);
}
# endif
# endif
if (!Equals(tempTable, gDefaultMallocTable)) {
replace_malloc_init_funcs(&tempTable);

View File

@ -115,6 +115,10 @@ namespace dmd {
struct DMDFuncs;
} // namespace dmd
namespace phc {
class AddrInfo;
} // namespace phc
// Callbacks to register debug file handles for Poison IO interpose.
// See Mozilla(|Un)RegisterDebugHandle in xpcom/build/PoisonIOInterposer.h
struct DebugFdRegistry {
@ -126,7 +130,7 @@ struct DebugFdRegistry {
} // namespace mozilla
struct ReplaceMallocBridge {
ReplaceMallocBridge() : mVersion(3) {}
ReplaceMallocBridge() : mVersion(4) {}
// This method was added in version 1 of the bridge.
virtual mozilla::dmd::DMDFuncs* GetDMDFuncs() { return nullptr; }
@ -156,6 +160,28 @@ struct ReplaceMallocBridge {
return nullptr;
}
// If this is a PHC-handled address, return true, and if an AddrInfo is
// provided, fill in all of its fields. Otherwise, return false and leave
// AddrInfo unchanged.
// This method was added in version 4 of the bridge.
virtual bool IsPHCAllocation(const void*, mozilla::phc::AddrInfo*) {
return false;
}
// Disable PHC allocations on the current thread. Only useful for tests. Note
// that PHC deallocations will still occur as needed.
// This method was added in version 4 of the bridge.
virtual void DisablePHCOnCurrentThread() {}
// Re-enable PHC allocations on the current thread. Only useful for tests.
// This method was added in version 4 of the bridge.
virtual void ReenablePHCOnCurrentThread() {}
// Test whether PHC allocations are enabled on the current thread. Only
// useful for tests.
// This method was added in version 4 of the bridge.
virtual bool IsPHCEnabledOnCurrentThread() { return false; }
# ifndef REPLACE_MALLOC_IMPL
// Returns the replace-malloc bridge if its version is at least the
// requested one.
@ -199,6 +225,30 @@ struct ReplaceMalloc {
return singleton ? singleton->RegisterHook(aName, aTable, aHookTable)
: nullptr;
}
static bool IsPHCAllocation(const void* aPtr, mozilla::phc::AddrInfo* aOut) {
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
return singleton ? singleton->IsPHCAllocation(aPtr, aOut) : false;
}
static void DisablePHCOnCurrentThread() {
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
if (singleton) {
singleton->DisablePHCOnCurrentThread();
}
}
static void ReenablePHCOnCurrentThread() {
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
if (singleton) {
singleton->ReenablePHCOnCurrentThread();
}
}
static bool IsPHCEnabledOnCurrentThread() {
auto singleton = ReplaceMallocBridge::Get(/* minimumVersion */ 4);
return singleton ? singleton->IsPHCEnabledOnCurrentThread() : false;
}
};
# endif

View File

@ -16,6 +16,10 @@
#include "gtest/gtest.h"
#ifdef MOZ_PHC
# include "replace_malloc_bridge.h"
#endif
#if defined(DEBUG) && !defined(XP_WIN) && !defined(ANDROID)
# define HAS_GDB_SLEEP_DURATION 1
extern unsigned int _gdb_sleep_duration;
@ -47,6 +51,21 @@ static void DisableCrashReporter() {
using namespace mozilla;
class AutoDisablePHCOnCurrentThread {
public:
AutoDisablePHCOnCurrentThread() {
#ifdef MOZ_PHC
ReplaceMalloc::DisablePHCOnCurrentThread();
#endif
}
~AutoDisablePHCOnCurrentThread() {
#ifdef MOZ_PHC
ReplaceMalloc::ReenablePHCOnCurrentThread();
#endif
}
};
static inline void TestOne(size_t size) {
size_t req = size;
size_t adv = malloc_good_size(req);
@ -378,6 +397,12 @@ static bool IsSameRoundedHugeClass(size_t aSize1, size_t aSize2,
static bool CanReallocInPlace(size_t aFromSize, size_t aToSize,
jemalloc_stats_t& aStats) {
// PHC allocations must be disabled because PHC reallocs differently to
// mozjemalloc.
#ifdef MOZ_PHC
MOZ_RELEASE_ASSERT(!ReplaceMalloc::IsPHCEnabledOnCurrentThread());
#endif
if (aFromSize == malloc_good_size(aToSize)) {
// Same size class: in-place.
return true;
@ -397,6 +422,10 @@ static bool CanReallocInPlace(size_t aFromSize, size_t aToSize,
TEST(Jemalloc, InPlace)
{
// Disable PHC allocations for this test, because CanReallocInPlace() isn't
// valid for PHC allocations.
AutoDisablePHCOnCurrentThread disable;
jemalloc_stats_t stats;
jemalloc_stats(&stats);
@ -430,6 +459,10 @@ TEST(Jemalloc, InPlace)
#if !defined(XP_WIN) || !defined(MOZ_CODE_COVERAGE)
TEST(Jemalloc, JunkPoison)
{
// Disable PHC allocations for this test, because CanReallocInPlace() isn't
// valid for PHC allocations, and the testing UAFs aren't valid.
AutoDisablePHCOnCurrentThread disable;
jemalloc_stats_t stats;
jemalloc_stats(&stats);
@ -631,6 +664,10 @@ TEST(Jemalloc, JunkPoison)
TEST(Jemalloc, GuardRegion)
{
// Disable PHC allocations for this test, because even a single PHC
// allocation occurring can throw it off.
AutoDisablePHCOnCurrentThread disable;
jemalloc_stats_t stats;
jemalloc_stats(&stats);

View File

@ -10,6 +10,9 @@ if CONFIG['OS_TARGET'] != 'Android' and not(CONFIG['OS_TARGET'] == 'WINNT' and C
'TestJemalloc.cpp',
]
if CONFIG['MOZ_PHC']:
DEFINES['MOZ_PHC'] = True
FINAL_LIBRARY = 'xul-gtest'
LOCAL_INCLUDES += [

View File

@ -15,12 +15,17 @@ SOURCES += [
'Replay.cpp',
]
if CONFIG['MOZ_REPLACE_MALLOC_STATIC'] and CONFIG['MOZ_DMD']:
if CONFIG['MOZ_REPLACE_MALLOC_STATIC'] and \
(CONFIG['MOZ_DMD'] or CONFIG['MOZ_PHC']):
UNIFIED_SOURCES += [
'/mfbt/HashFunctions.cpp',
'/mfbt/JSONWriter.cpp',
'/mozglue/misc/StackWalk.cpp',
]
if CONFIG['MOZ_BUILD_APP'] == 'memory':
EXPORTS.mozilla += [
'/mozglue/misc/StackWalk.h',
]
if not CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
SOURCES += [

View File

@ -18,3 +18,6 @@ DIRS += [
if CONFIG['MOZ_DMD']:
DIRS += ['dmd']
if CONFIG['MOZ_PHC']:
DIRS += ['phc']

1352
memory/replace/phc/PHC.cpp Normal file

File diff suppressed because it is too large Load Diff

88
memory/replace/phc/PHC.h Normal file
View File

@ -0,0 +1,88 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=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 PHC_h
#define PHC_h
#include "mozilla/Assertions.h"
#include <stdint.h>
#include <stdlib.h>
namespace mozilla {
namespace phc {
// Note: a more compact stack trace representation could be achieved with
// some effort.
struct StackTrace {
public:
static const size_t kMaxFrames = 16;
// The number of PCs in the stack trace.
size_t mLength;
// The PCs in the stack trace. Only the first mLength are initialized.
const void* mPcs[kMaxFrames];
public:
StackTrace() : mLength(0) {}
};
// Info from PHC about an address in memory.
class AddrInfo {
public:
enum class Kind {
// The address is not in PHC-managed memory.
Unknown = 0,
// The address is within a PHC page that has never been allocated. A crash
// involving such an address is unlikely in practice, because it would
// require the crash to happen quite early.
NeverAllocatedPage = 1,
// The address is within a PHC page that is in use.
InUsePage = 2,
// The address is within a PHC page that has been allocated and then freed.
// A crash involving such an address most likely indicates a
// use-after-free. (A sufficiently wild write -- e.g. a large buffer
// overflow -- could also trigger it, but this is less likely.)
FreedPage = 3,
// The address is within a PHC guard page. A crash involving such an
// address most likely indicates a buffer overflow. (Again, a sufficiently
// wild write could unluckily trigger it, but this is less likely.)
//
// NOTE: guard pages are not yet implemented. This value is present so they
// can be added easily in the future.
GuardPage = 4,
};
Kind mKind;
// The base address of the containing PHC allocation, if there is one.
const void* mBaseAddr;
// The usable size of the containing PHC allocation, if there is one.
size_t mUsableSize;
// The allocation and free stack traces of the containing PHC allocation, if
// there is one.
StackTrace mAllocStack;
StackTrace mFreeStack;
// Default to no PHC info.
AddrInfo()
: mKind(Kind::Unknown),
mBaseAddr(nullptr),
mUsableSize(0),
mAllocStack(),
mFreeStack() {}
};
} // namespace phc
} // namespace mozilla
#endif /* PHC_h */

View File

@ -0,0 +1,32 @@
# -*- 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/.
ReplaceMalloc('phc')
DEFINES['MOZ_NO_MOZALLOC'] = True
LOCAL_INCLUDES += [
'../logalloc',
'/memory/build',
]
EXPORTS += [
'PHC.h',
]
UNIFIED_SOURCES += [
'PHC.cpp',
]
if not CONFIG['MOZ_REPLACE_MALLOC_STATIC']:
SOURCES += [
'../logalloc/FdPrintf.cpp',
'/mozglue/misc/StackWalk.cpp',
]
TEST_DIRS += ['test']
DisableStlWrapping()

View File

@ -0,0 +1,155 @@
/* 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 "gtest/gtest.h"
#include "mozmemory.h"
#include "replace_malloc_bridge.h"
#include "mozilla/Assertions.h"
#include "../../PHC.h"
using namespace mozilla;
bool PHCInfoEq(phc::AddrInfo& aInfo, phc::AddrInfo::Kind aKind, void* aBaseAddr,
size_t aUsableSize, bool aHasAllocStack, bool aHasFreeStack) {
return aInfo.mKind == aKind && aInfo.mBaseAddr == aBaseAddr &&
aInfo.mUsableSize == aUsableSize &&
// Proper stack traces will have at least 3 elements.
(aHasAllocStack ? (aInfo.mAllocStack.mLength > 2)
: (aInfo.mAllocStack.mLength == 0)) &&
(aHasFreeStack ? (aInfo.mFreeStack.mLength > 2)
: (aInfo.mFreeStack.mLength == 0));
}
bool JeInfoEq(jemalloc_ptr_info_t& aInfo, PtrInfoTag aTag, void* aAddr,
size_t aSize, arena_id_t arenaId) {
return aInfo.tag == aTag && aInfo.addr == aAddr && aInfo.size == aSize
#ifdef MOZ_DEBUG
&& aInfo.arenaId == arenaId
#endif
;
}
char* GetPHCAllocation(size_t aSize) {
// A crude but effective way to get a PHC allocation.
for (int i = 0; i < 2000000; i++) {
char* p = (char*)malloc(aSize);
if (ReplaceMalloc::IsPHCAllocation(p, nullptr)) {
return p;
}
free(p);
}
return nullptr;
}
TEST(PHC, TestPHCBasics)
{
int stackVar;
phc::AddrInfo phcInfo;
jemalloc_ptr_info_t jeInfo;
// Test a default AddrInfo.
ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0ul,
false, false));
// Test some non-PHC allocation addresses.
ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(nullptr, &phcInfo));
ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
false, false));
ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(&stackVar, &phcInfo));
ASSERT_TRUE(PHCInfoEq(phcInfo, phc::AddrInfo::Kind::Unknown, nullptr, 0,
false, false));
char* p = GetPHCAllocation(32);
if (!p) {
MOZ_CRASH("failed to get a PHC allocation");
}
// Test an in-use PHC allocation, via its base address.
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
ASSERT_TRUE(
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
ASSERT_EQ(malloc_usable_size(p), 32ul);
jemalloc_ptr_info(p, &jeInfo);
ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
// Test an in-use PHC allocation, via an address in its middle.
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 10, &phcInfo));
ASSERT_TRUE(
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
ASSERT_EQ(malloc_usable_size(p), 32ul);
jemalloc_ptr_info(p + 10, &jeInfo);
ASSERT_TRUE(JeInfoEq(jeInfo, TagLiveAlloc, p, 32, 0));
// Test an in-use PHC allocation, via an address past its end. The results
// for phcInfo should be the same, but be different for jeInfo.
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 64, &phcInfo));
ASSERT_TRUE(
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::InUsePage, p, 32ul, true, false));
jemalloc_ptr_info(p + 64, &jeInfo);
ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
free(p);
// Test a freed PHC allocation, via its base address.
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p, &phcInfo));
ASSERT_TRUE(
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
jemalloc_ptr_info(p, &jeInfo);
ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
// Test a freed PHC allocation, via an address in its middle.
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 10, &phcInfo));
ASSERT_TRUE(
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
jemalloc_ptr_info(p + 10, &jeInfo);
ASSERT_TRUE(JeInfoEq(jeInfo, TagFreedAlloc, p, 32, 0));
// Test a freed PHC allocation, via an address past its end.
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p + 10, &phcInfo));
ASSERT_TRUE(
PHCInfoEq(phcInfo, phc::AddrInfo::Kind::FreedPage, p, 32ul, true, true));
jemalloc_ptr_info(p + 64, &jeInfo);
ASSERT_TRUE(JeInfoEq(jeInfo, TagUnknown, nullptr, 0, 0));
// There are no tests for `mKind == NeverAllocatedPage` because it's not
// possible to reliably get ahold of such a page.
// There are not tests for `mKind == GuardPage` because it's currently not
// implemented.
}
TEST(PHC, TestPHCDisabling)
{
char* p = GetPHCAllocation(32);
char* q = GetPHCAllocation(32);
if (!p || !q) {
MOZ_CRASH("failed to get a PHC allocation");
}
ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
ReplaceMalloc::DisablePHCOnCurrentThread();
ASSERT_FALSE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
// Test realloc() on a PHC allocation while PHC is disabled on the thread.
char* p2 = (char*)realloc(p, 128);
// The small realloc is in-place.
ASSERT_TRUE(p2 == p);
ASSERT_TRUE(ReplaceMalloc::IsPHCAllocation(p2, nullptr));
char* p3 = (char*)realloc(p2, 8192);
// The big realloc is not in-place, and the result is not a PHC allocation.
ASSERT_TRUE(p3 != p2);
ASSERT_FALSE(ReplaceMalloc::IsPHCAllocation(p3, nullptr));
free(p3);
// Test free() on a PHC allocation while PHC is disabled on the thread.
free(q);
// These must not be PHC allocations.
char* r = GetPHCAllocation(32); // This will fail.
ASSERT_FALSE(!!r);
ReplaceMalloc::ReenablePHCOnCurrentThread();
ASSERT_TRUE(ReplaceMalloc::IsPHCEnabledOnCurrentThread());
}

View File

@ -0,0 +1,15 @@
# -*- 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/.
UNIFIED_SOURCES += [
'TestPHC.cpp',
]
LOCAL_INCLUDES += [
'../../',
]
FINAL_LIBRARY = 'xul-gtest'

View File

@ -0,0 +1,9 @@
# -*- 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/.
# The gtests won't work in a SpiderMonkey-only build.
if CONFIG['MOZ_WIDGET_TOOLKIT']:
TEST_DIRS += ['gtest']

View File

@ -350,6 +350,40 @@ GraphicsStartupTest:
Set to 1 by the graphics driver crash guard when it's activated.
type: boolean
PHCKind:
description: >
The allocation kind, if the crash involved a bad access of a special PHC
allocation.
type: string
PHCBaseAddress:
description: >
The allocation's base address, if the crash involved a bad access of a
special PHC allocation. Encoded as a decimal address.
type: string
PHCUsableSize:
description: >
The allocation's usable size, if the crash involved a bad access of a
special PHC allocation.
# A 32-bit integer is enough because the maximum usable size of a special PHC
# allocation is far less than 2 GiB.
type: integer
PHCAllocStack:
description: >
The allocation's allocation stack trace, if the crash involved a bad access
of a special PHC allocation. Encoded as a comma-separated list of decimal
addresses.
type: string
PHCFreeStack:
description: >
The allocation's free stack trace, if the crash involved a bad access
of a special PHC allocation. Encoded as a comma-separated list of decimal
addresses.
type: string
HangMonitorDescription:
description: >
Name of the hang monitor that generated the crash.

View File

@ -98,6 +98,10 @@
#include "third_party/lss/linux_syscall_support.h"
#include "prenv.h"
#ifdef MOZ_PHC
#include "replace_malloc_bridge.h"
#endif
#if defined(__ANDROID__)
#include "linux/sched.h"
#endif
@ -446,10 +450,24 @@ int ExceptionHandler::ThreadEntry(void *arg) {
thread_arg->context_size) == false;
}
#ifdef MOZ_PHC
void GetPHCAddrInfo(siginfo_t* siginfo, mozilla::phc::AddrInfo* addr_info) {
// Is this a crash involving a PHC allocation?
if (siginfo->si_signo == SIGSEGV || siginfo->si_signo == SIGBUS) {
ReplaceMalloc::IsPHCAllocation(siginfo->si_addr, addr_info);
}
}
#endif
// This function runs in a compromised context: see the top of the file.
// Runs on the crashing thread.
bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
if (filter_ && !filter_(callback_context_))
mozilla::phc::AddrInfo addr_info;
#ifdef MOZ_PHC
GetPHCAddrInfo(info, &addr_info);
#endif
if (filter_ && !filter_(callback_context_, &addr_info))
return false;
// Allow ourselves to be dumped if the signal is trusted.
@ -489,7 +507,8 @@ bool ExceptionHandler::HandleSignal(int /*sig*/, siginfo_t* info, void* uc) {
return true;
}
}
return GenerateDump(&g_crash_context_);
return GenerateDump(&g_crash_context_, &addr_info);
}
// This is a public interface to HandleSignal that allows the client to
@ -506,7 +525,8 @@ bool ExceptionHandler::SimulateSignalDelivery(int sig) {
}
// This function may run in a compromised context: see the top of the file.
bool ExceptionHandler::GenerateDump(CrashContext *context) {
bool ExceptionHandler::GenerateDump(
CrashContext *context, const mozilla::phc::AddrInfo* addr_info) {
if (IsOutOfProcess())
return crash_generation_client_->RequestDump(context, sizeof(*context));
@ -591,7 +611,8 @@ bool ExceptionHandler::GenerateDump(CrashContext *context) {
bool success = r != -1 && WIFEXITED(status) && WEXITSTATUS(status) == 0;
if (callback_)
success = callback_(minidump_descriptor_, callback_context_, success);
success =
callback_(minidump_descriptor_, callback_context_, addr_info, success);
return success;
}
@ -765,7 +786,8 @@ bool ExceptionHandler::WriteMinidump() {
#error "This code has not been ported to your platform yet."
#endif
return GenerateDump(&context);
// nullptr here for phc::AddrInfo* is ok because this is not a crash.
return GenerateDump(&context, nullptr);
}
void ExceptionHandler::AddMappingInfo(const string& name,
@ -822,7 +844,9 @@ bool ExceptionHandler::WriteMinidumpForChild(pid_t child,
child_blamed_thread))
return false;
return callback ? callback(descriptor, callback_context, true) : true;
// nullptr here for phc::AddrInfo* is ok because this is not a crash.
return callback ? callback(descriptor, callback_context, nullptr, true)
: true;
}
void SetFirstChanceExceptionHandler(FirstChanceHandler callback) {

View File

@ -44,6 +44,12 @@
#include "common/using_std_string.h"
#include "google_breakpad/common/minidump_format.h"
#ifdef MOZ_PHC
#include "PHC.h"
#else
namespace mozilla { namespace phc { class AddrInfo {}; } }
#endif
namespace google_breakpad {
// ExceptionHandler
@ -82,7 +88,8 @@ class ExceptionHandler {
// attempting to write a minidump. If a FilterCallback returns false,
// Breakpad will immediately report the exception as unhandled without
// writing a minidump, allowing another handler the opportunity to handle it.
typedef bool (*FilterCallback)(void *context);
typedef bool (*FilterCallback)(void *context,
const mozilla::phc::AddrInfo* addr_info);
// A callback function to run after the minidump has been written.
// |descriptor| contains the file descriptor or file path containing the
@ -102,6 +109,7 @@ class ExceptionHandler {
// return true directly (unless |succeeded| is true).
typedef bool (*MinidumpCallback)(const MinidumpDescriptor& descriptor,
void* context,
const mozilla::phc::AddrInfo* addr_info,
bool succeeded);
// In certain cases, a user may wish to handle the generation of the minidump
@ -234,7 +242,8 @@ class ExceptionHandler {
static void RestoreHandlersLocked();
void PreresolveSymbols();
bool GenerateDump(CrashContext *context);
bool GenerateDump(CrashContext *context,
const mozilla::phc::AddrInfo* addr_info);
void SendContinueSignalToChild();
void WaitForContinueSignal();

View File

@ -33,3 +33,6 @@ if CONFIG['OS_TARGET'] == 'Android':
FINAL_LIBRARY = 'breakpad_client'
include('/toolkit/crashreporter/crashreporter.mozbuild')
if CONFIG['MOZ_PHC']:
DEFINES['MOZ_PHC'] = True

View File

@ -343,7 +343,7 @@ bool ExceptionHandler::WriteMinidumpForChild(mach_port_t child,
if (callback) {
return callback(dump_path.c_str(), dump_id.c_str(),
callback_context, result);
callback_context, nullptr, result);
}
return result;
}
@ -377,7 +377,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
if (exception_type && exception_code) {
// If this is a real exception, give the filter (if any) a chance to
// decide if this should be sent.
if (filter_ && !filter_(callback_context_))
if (filter_ && !filter_(callback_context_, nullptr))
return false;
result = crash_generation_client_->RequestDumpForException(
exception_type,
@ -402,7 +402,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
if (exception_type && exception_code) {
// If this is a real exception, give the filter (if any) a chance to
// decide if this should be sent.
if (filter_ && !filter_(callback_context_))
if (filter_ && !filter_(callback_context_, nullptr))
return false;
md.SetExceptionInformation(exception_type, exception_code,
@ -418,7 +418,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
// (rather than just writing out the file), then we should exit without
// forwarding the exception to the next handler.
if (callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
result)) {
nullptr, result)) {
if (exit_after_write)
_exit(exception_type);
}

View File

@ -48,6 +48,12 @@
#include "mac/crash_generation/crash_generation_client.h"
#endif
#ifdef MOZ_PHC
#include "PHC.h"
#else
namespace mozilla { namespace phc { class AddrInfo {}; } }
#endif
namespace google_breakpad {
using std::string;
@ -75,7 +81,8 @@ class ExceptionHandler {
// attempting to write a minidump. If a FilterCallback returns false, Breakpad
// will immediately report the exception as unhandled without writing a
// minidump, allowing another handler the opportunity to handle it.
typedef bool (*FilterCallback)(void *context);
typedef bool (*FilterCallback)(void *context,
const mozilla::phc::AddrInfo* addr_info);
// A callback function to run after the minidump has been written.
// |minidump_id| is a unique id for the dump, so the minidump
@ -87,7 +94,9 @@ class ExceptionHandler {
// exception.
typedef bool (*MinidumpCallback)(const char *dump_dir,
const char *minidump_id,
void *context, bool succeeded);
void *context,
const mozilla::phc::AddrInfo* addr_info,
bool succeeded);
// A callback function which will be called directly if an exception occurs.
// This bypasses the minidump file writing and simply gives the client

View File

@ -824,7 +824,7 @@ bool ExceptionHandler::WriteMinidumpForChild(HANDLE child,
if (callback) {
success = callback(handler.dump_path_c_, handler.next_minidump_id_c_,
callback_context, NULL, NULL, success);
callback_context, NULL, NULL, nullptr, success);
}
return success;
@ -840,7 +840,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
// HandleException to call any previous handler or return
// EXCEPTION_CONTINUE_SEARCH on the exception thread, allowing it to appear
// as though this handler were not present at all.
if (filter_ && !filter_(callback_context_, exinfo, assertion)) {
if (filter_ && !filter_(callback_context_, exinfo, nullptr, assertion)) {
return false;
}
@ -861,7 +861,7 @@ bool ExceptionHandler::WriteMinidumpWithException(
// scenario, the server process ends up creating the dump path and dump
// id so they are not known to the client.
success = callback_(dump_path_c_, next_minidump_id_c_, callback_context_,
exinfo, assertion, success);
exinfo, assertion, nullptr, success);
}
return success;

View File

@ -75,6 +75,12 @@
#include "common/scoped_ptr.h"
#include "google_breakpad/common/minidump_format.h"
#ifdef MOZ_PHC
#include "PHC.h"
#else
namespace mozilla { namespace phc { class AddrInfo {}; } }
#endif
namespace google_breakpad {
using std::vector;
@ -94,6 +100,7 @@ class ExceptionHandler {
// Breakpad will immediately report the exception as unhandled without
// writing a minidump, allowing another handler the opportunity to handle it.
typedef bool (*FilterCallback)(void* context, EXCEPTION_POINTERS* exinfo,
const mozilla::phc::AddrInfo* addr_info,
MDRawAssertionInfo* assertion);
// A callback function to run after the minidump has been written.
@ -125,6 +132,7 @@ class ExceptionHandler {
void* context,
EXCEPTION_POINTERS* exinfo,
MDRawAssertionInfo* assertion,
const mozilla::phc::AddrInfo* addr_info,
bool succeeded);
// HandlerType specifies which types of handlers should be installed, if

View File

@ -115,6 +115,9 @@ if CONFIG['MOZ_CRASHREPORTER']:
DEFINES['UNICODE'] = True
DEFINES['_UNICODE'] = True
if CONFIG['MOZ_PHC']:
DEFINES['MOZ_PHC'] = True
LOCAL_INCLUDES += [
'google-breakpad/src',
]

View File

@ -668,6 +668,72 @@ class BinaryAnnotationWriter : public AnnotationWriter {
PlatformWriter& mPlatformWriter;
};
#ifdef MOZ_PHC
// The stack traces are encoded as a comma-separated list of decimal
// (not hexadecimal!) addresses, e.g. "12345678,12345679,12345680".
static void WritePHCStackTrace(AnnotationWriter& aWriter,
const Annotation aName,
const phc::StackTrace* aStack) {
// 21 is the max length of a 64-bit decimal address entry, including the
// trailing comma or '\0'. And then we add another 32 just to be safe.
char addrsString[mozilla::phc::StackTrace::kMaxFrames * 21 + 32];
char addrString[32];
char* p = addrsString;
*p = 0;
for (size_t i = 0; i < aStack->mLength; i++) {
if (i != 0) {
strcat(addrsString, ",");
p++;
}
XP_STOA(uintptr_t(aStack->mPcs[i]), addrString);
strcat(addrsString, addrString);
}
aWriter.Write(aName, addrsString);
}
static void WritePHCAddrInfo(AnnotationWriter& writer,
const phc::AddrInfo* aAddrInfo) {
// Is this a PHC allocation needing special treatment?
if (aAddrInfo && aAddrInfo->mKind != phc::AddrInfo::Kind::Unknown) {
const char* kindString;
switch (aAddrInfo->mKind) {
case phc::AddrInfo::Kind::Unknown:
kindString = "Unknown(?!)";
break;
case phc::AddrInfo::Kind::NeverAllocatedPage:
kindString = "NeverAllocatedPage";
break;
case phc::AddrInfo::Kind::InUsePage:
kindString = "InUsePage(?!)";
break;
case phc::AddrInfo::Kind::FreedPage:
kindString = "FreedPage";
break;
case phc::AddrInfo::Kind::GuardPage:
kindString = "GuardPage";
break;
default:
kindString = "Unmatched(?!)";
break;
}
writer.Write(Annotation::PHCKind, kindString);
char baseAddrString[32];
XP_STOA(uintptr_t(aAddrInfo->mBaseAddr), baseAddrString);
writer.Write(Annotation::PHCBaseAddress, baseAddrString);
char usableSizeString[32];
XP_TTOA(aAddrInfo->mUsableSize, usableSizeString);
writer.Write(Annotation::PHCUsableSize, usableSizeString);
WritePHCStackTrace(writer, Annotation::PHCAllocStack,
&aAddrInfo->mAllocStack);
WritePHCStackTrace(writer, Annotation::PHCFreeStack,
&aAddrInfo->mFreeStack);
}
}
#endif
/**
* If minidump_id is null, we assume that dump_path contains the full
* dump file path.
@ -885,6 +951,7 @@ static void WriteMozCrashReason(AnnotationWriter& aWriter) {
}
static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
const phc::AddrInfo* addrInfo,
time_t crashTime) {
INIAnnotationWriter writer(pw);
for (auto key : MakeEnumeratedRange(Annotation::Count)) {
@ -961,6 +1028,10 @@ static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
writer.Write(Annotation::ContainsMemoryReport, "1");
}
#ifdef MOZ_PHC
WritePHCAddrInfo(writer, addrInfo);
#endif
std::function<void(const char*)> getThreadAnnotationCB =
[&](const char* aValue) -> void {
if (aValue) {
@ -971,6 +1042,7 @@ static void WriteAnnotationsForMainProcessCrash(PlatformWriter& pw,
}
static void WriteCrashEventFile(time_t crashTime, const char* crashTimeString,
const phc::AddrInfo* addrInfo,
#ifdef XP_LINUX
const MinidumpDescriptor& descriptor
#else
@ -1013,7 +1085,7 @@ static void WriteCrashEventFile(time_t crashTime, const char* crashTimeString,
WriteLiteral(eventFile, "\n");
WriteString(eventFile, id_ascii);
WriteLiteral(eventFile, "\n");
WriteAnnotationsForMainProcessCrash(eventFile, crashTime);
WriteAnnotationsForMainProcessCrash(eventFile, addrInfo, crashTime);
}
}
@ -1033,7 +1105,7 @@ bool MinidumpCallback(
#ifdef XP_WIN
EXCEPTION_POINTERS* exinfo, MDRawAssertionInfo* assertion,
#endif
bool succeeded) {
const phc::AddrInfo* addrInfo, bool succeeded) {
bool returnValue = showOSCrashReporter ? false : succeeded;
static XP_CHAR minidumpPath[XP_PATH_MAX];
@ -1086,7 +1158,7 @@ bool MinidumpCallback(
WriteString(lastCrashFile, crashTimeString);
}
WriteCrashEventFile(crashTime, crashTimeString,
WriteCrashEventFile(crashTime, crashTimeString, addrInfo,
#ifdef XP_LINUX
descriptor
#else
@ -1100,7 +1172,7 @@ bool MinidumpCallback(
#else
OpenAPIData(apiData, dump_path, minidump_id);
#endif
WriteAnnotationsForMainProcessCrash(apiData, crashTime);
WriteAnnotationsForMainProcessCrash(apiData, addrInfo, crashTime);
if (!doReport) {
#ifdef XP_WIN
@ -1220,7 +1292,8 @@ static bool BuildTempPath(PathStringT& aResult) {
return true;
}
static void PrepareChildExceptionTimeAnnotations(void* context) {
static void PrepareChildExceptionTimeAnnotations(
void* context, const phc::AddrInfo* addrInfo) {
MOZ_ASSERT(!XRE_IsParentProcess());
FileHandle f;
@ -1245,6 +1318,10 @@ static void PrepareChildExceptionTimeAnnotations(void* context) {
WriteMozCrashReason(writer);
#ifdef MOZ_PHC
WritePHCAddrInfo(writer, addrInfo);
#endif
std::function<void(const char*)> getThreadAnnotationCB =
[&](const char* aValue) -> void {
if (aValue) {
@ -1275,6 +1352,7 @@ static void FreeBreakpadVM() {
* Also calls FreeBreakpadVM if appropriate.
*/
static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
const phc::AddrInfo* addr_info,
MDRawAssertionInfo* assertion) {
if (!exinfo) {
mozilla::IOInterposer::Disable();
@ -1307,10 +1385,11 @@ static bool FPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
}
static bool ChildFPEFilter(void* context, EXCEPTION_POINTERS* exinfo,
const phc::AddrInfo* addrInfo,
MDRawAssertionInfo* assertion) {
bool result = FPEFilter(context, exinfo, assertion);
bool result = FPEFilter(context, exinfo, addrInfo, assertion);
if (result) {
PrepareChildExceptionTimeAnnotations(context);
PrepareChildExceptionTimeAnnotations(context, addrInfo);
}
return result;
}
@ -1364,14 +1443,14 @@ static bool ShouldReport() {
#if !defined(XP_WIN)
static bool Filter(void* context) {
static bool Filter(void* context, const phc::AddrInfo* addrInfo) {
mozilla::IOInterposer::Disable();
return true;
}
static bool ChildFilter(void* context) {
static bool ChildFilter(void* context, const phc::AddrInfo* addrInfo) {
mozilla::IOInterposer::Disable();
PrepareChildExceptionTimeAnnotations(context);
PrepareChildExceptionTimeAnnotations(context, addrInfo);
return true;
}
@ -3310,7 +3389,7 @@ static bool PairedDumpCallback(
#ifdef XP_WIN
EXCEPTION_POINTERS* /*unused*/, MDRawAssertionInfo* /*unused*/,
#endif
bool succeeded) {
const phc::AddrInfo* addrInfo, bool succeeded) {
nsCOMPtr<nsIFile>& minidump = *static_cast<nsCOMPtr<nsIFile>*>(context);
xpstring path;

View File

@ -31,6 +31,8 @@ var CrashTestUtils = {
CRASH_X64CFI_SAVE_XMM128_FAR: 18,
CRASH_X64CFI_EPILOG: 19,
CRASH_X64CFI_EOF: 20,
CRASH_PHC_USE_AFTER_FREE: 21,
CRASH_PHC_DOUBLE_FREE: 22,
// Constants for dumpHasStream()
// From google_breakpad/common/minidump_format.h

View File

@ -5,7 +5,16 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
FINAL_TARGET = '_tests/xpcshell/toolkit/crashreporter/test'
XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini', 'unit_ipc/xpcshell.ini']
XPCSHELL_TESTS_MANIFESTS += [
'unit/xpcshell.ini',
'unit_ipc/xpcshell.ini'
]
if CONFIG['MOZ_PHC']:
XPCSHELL_TESTS_MANIFESTS += [
'unit/xpcshell-phc.ini',
'unit_ipc/xpcshell-phc.ini'
]
BROWSER_CHROME_MANIFESTS += ['browser/browser.ini']
UNIFIED_SOURCES += [
@ -41,6 +50,9 @@ else:
'-fexceptions',
]
if CONFIG['MOZ_PHC']:
DEFINES['MOZ_PHC'] = True
GeckoSharedLibrary('testcrasher')
DEFINES['SHARED_LIBRARY'] = '%s%s%s' % (

View File

@ -12,6 +12,10 @@
# include <windows.h>
#endif
#ifdef MOZ_PHC
# include "replace_malloc_bridge.h"
#endif
/*
* This pure virtual call example is from MSDN
*/
@ -77,6 +81,8 @@ const int16_t CRASH_X64CFI_SAVE_XMM128 = 17;
const int16_t CRASH_X64CFI_SAVE_XMM128_FAR = 18;
const int16_t CRASH_X64CFI_EPILOG = 19;
const int16_t CRASH_X64CFI_EOF = 20;
const int16_t CRASH_PHC_USE_AFTER_FREE = 21;
const int16_t CRASH_PHC_DOUBLE_FREE = 22;
#if XP_WIN && HAVE_64BIT_BUILD && defined(_M_X64) && !defined(__MINGW32__)
@ -118,6 +124,21 @@ void MOZ_NEVER_INLINE ReserveStack() {
#endif // XP_WIN && HAVE_64BIT_BUILD
#ifdef MOZ_PHC
char* GetPHCAllocation(size_t aSize) {
// A crude but effective way to get a PHC allocation.
for (int i = 0; i < 2000000; i++) {
char* p = (char*)malloc(aSize);
if (ReplaceMalloc::IsPHCAllocation(p, nullptr)) {
return p;
}
free(p);
}
// This failure doesn't seem to occur in practice...
MOZ_CRASH("failed to get a PHC allocation");
}
#endif
extern "C" NS_EXPORT void Crash(int16_t how) {
switch (how) {
case CRASH_INVALID_POINTER_DEREF: {
@ -170,6 +191,22 @@ extern "C" NS_EXPORT void Crash(int16_t how) {
break;
}
#endif // XP_WIN && HAVE_64BIT_BUILD && !defined(__MINGW32__)
#ifdef MOZ_PHC
case CRASH_PHC_USE_AFTER_FREE: {
// Do a UAF, triggering a crash.
char* p = GetPHCAllocation(32);
free(p);
*p = 0;
// not reached
}
case CRASH_PHC_DOUBLE_FREE: {
// Do a double free, triggering a crash.
char* p = GetPHCAllocation(64);
free(p);
free(p);
// not reached
}
#endif
default:
break;
}

View File

@ -0,0 +1,43 @@
function check(extra, size) {
Assert.equal(extra.PHCKind, "FreedPage");
// This is a string holding a decimal address.
Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
Assert.equal(extra.PHCUsableSize, size);
// These are strings holding comma-separated lists of decimal addresses.
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCAllocStack));
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCFreeStack));
}
function run_test() {
if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
dump(
"INFO | test_crash_phc.js | Can't test crashreporter in a non-libxul build.\n"
);
return;
}
do_crash(
function() {
crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE;
},
function(mdump, extra) {
// CRASH_PHC_USE_AFTER_FREE uses 32 for the size.
check(extra, 32);
},
true
);
do_crash(
function() {
crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE;
},
function(mdump, extra) {
// CRASH_PHC_DOUBLE_FREE uses 64 for the size.
check(extra, 64);
},
true
);
}

View File

@ -0,0 +1,9 @@
[DEFAULT]
head = head_crashreporter.js
skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # 1536217
support-files =
crasher_subprocess_head.js
crasher_subprocess_tail.js
[test_crash_phc.js]

View File

@ -0,0 +1,30 @@
/* import-globals-from ../unit/head_crashreporter.js */
load("../unit/head_crashreporter.js");
function run_test() {
if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
dump(
"INFO | test_content_phc.js | Can't test crashreporter in a non-libxul build.\n"
);
return;
}
do_content_crash(
function() {
crashType = CrashTestUtils.CRASH_PHC_USE_AFTER_FREE;
},
function(mdump, extra) {
Assert.equal(extra.PHCKind, "FreedPage");
// This is a string holding a decimal address.
Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
// CRASH_PHC_USE_AFTER_FREE uses 32 for the size.
Assert.equal(extra.PHCUsableSize, 32);
// These are strings holding comma-separated lists of decimal addresses.
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCAllocStack));
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCFreeStack));
}
);
}

View File

@ -0,0 +1,33 @@
/* import-globals-from ../unit/head_crashreporter.js */
load("../unit/head_crashreporter.js");
function run_test() {
if (!("@mozilla.org/toolkit/crash-reporter;1" in Cc)) {
dump(
"INFO | test_content_phc.js | Can't test crashreporter in a non-libxul build.\n"
);
return;
}
// For some unknown reason, having two do_content_crash() calls in a single
// test doesn't work. That explains why this test exists separately from
// test_content_phc.js.
do_content_crash(
function() {
crashType = CrashTestUtils.CRASH_PHC_DOUBLE_FREE;
},
function(mdump, extra) {
Assert.equal(extra.PHCKind, "FreedPage");
// This is a string holding a decimal address.
Assert.ok(/^\d+$/.test(extra.PHCBaseAddress));
// CRASH_PHC_DOUBLE_FREE uses 64 for the size.
Assert.equal(extra.PHCUsableSize, 64);
// These are strings holding comma-separated lists of decimal addresses.
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCAllocStack));
Assert.ok(/^(\d+,)+\d+$/.test(extra.PHCFreeStack));
}
);
}

View File

@ -0,0 +1,10 @@
[DEFAULT]
head =
skip-if = toolkit == 'android' || (os == "win" && processor == "aarch64") # 1536217
support-files =
!/toolkit/crashreporter/test/unit/crasher_subprocess_head.js
!/toolkit/crashreporter/test/unit/crasher_subprocess_tail.js
!/toolkit/crashreporter/test/unit/head_crashreporter.js
[test_content_phc.js]
[test_content_phc2.js]