760 lines
27 KiB
C++
760 lines
27 KiB
C++
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
|
|
* 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 "ds/MemoryProtectionExceptionHandler.h"
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/Atomics.h"
|
|
|
|
#if defined(XP_WIN)
|
|
# include "jswin.h"
|
|
#elif defined(XP_UNIX) && !defined(XP_DARWIN)
|
|
# include <signal.h>
|
|
# include <sys/types.h>
|
|
# include <unistd.h>
|
|
#elif defined(XP_DARWIN)
|
|
# include <mach/mach.h>
|
|
# include <unistd.h>
|
|
#endif
|
|
|
|
#ifdef ANDROID
|
|
# include <android/log.h>
|
|
#endif
|
|
|
|
#include "ds/SplayTree.h"
|
|
|
|
#include "threading/LockGuard.h"
|
|
#include "threading/Thread.h"
|
|
#include "vm/MutexIDs.h"
|
|
|
|
namespace js {
|
|
|
|
/*
|
|
* A class to store the addresses of the regions recognized as protected
|
|
* by this exception handler. We use a splay tree to store these addresses.
|
|
*/
|
|
class ProtectedRegionTree
|
|
{
|
|
struct Region
|
|
{
|
|
uintptr_t first;
|
|
uintptr_t last;
|
|
|
|
Region(uintptr_t addr, size_t size) : first(addr),
|
|
last(addr + (size - 1)) {}
|
|
|
|
static int compare(const Region& A, const Region& B) {
|
|
if (A.last < B.first)
|
|
return -1;
|
|
if (A.first > B.last)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
Mutex lock;
|
|
LifoAlloc alloc;
|
|
SplayTree<Region, Region> tree;
|
|
|
|
public:
|
|
ProtectedRegionTree() : lock(mutexid::ProtectedRegionTree),
|
|
alloc(4096),
|
|
tree(&alloc) {}
|
|
|
|
~ProtectedRegionTree() { MOZ_ASSERT(tree.empty()); }
|
|
|
|
void insert(uintptr_t addr, size_t size) {
|
|
MOZ_ASSERT(addr && size);
|
|
LockGuard<Mutex> guard(lock);
|
|
AutoEnterOOMUnsafeRegion oomUnsafe;
|
|
if (!tree.insert(Region(addr, size)))
|
|
oomUnsafe.crash("Failed to store allocation ID.");
|
|
}
|
|
|
|
void remove(uintptr_t addr) {
|
|
MOZ_ASSERT(addr);
|
|
LockGuard<Mutex> guard(lock);
|
|
tree.remove(Region(addr, 1));
|
|
}
|
|
|
|
bool isProtected(uintptr_t addr) {
|
|
if (!addr)
|
|
return false;
|
|
LockGuard<Mutex> guard(lock);
|
|
return tree.maybeLookup(Region(addr, 1));
|
|
}
|
|
};
|
|
|
|
static bool sExceptionHandlerInstalled = false;
|
|
|
|
static ProtectedRegionTree sProtectedRegions;
|
|
|
|
bool
|
|
MemoryProtectionExceptionHandler::isDisabled()
|
|
{
|
|
// Disabled everywhere for this release.
|
|
return true;
|
|
}
|
|
|
|
void
|
|
MemoryProtectionExceptionHandler::addRegion(void* addr, size_t size)
|
|
{
|
|
if (sExceptionHandlerInstalled)
|
|
sProtectedRegions.insert(uintptr_t(addr), size);
|
|
}
|
|
|
|
void
|
|
MemoryProtectionExceptionHandler::removeRegion(void* addr)
|
|
{
|
|
if (sExceptionHandlerInstalled)
|
|
sProtectedRegions.remove(uintptr_t(addr));
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* This helper function attempts to replicate the functionality of
|
|
* mozilla::MOZ_ReportCrash() in an async-signal-safe way.
|
|
*/
|
|
static MOZ_COLD MOZ_ALWAYS_INLINE void
|
|
ReportCrashIfDebug(const char* aStr)
|
|
MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS
|
|
{
|
|
#ifdef DEBUG
|
|
# if defined(XP_WIN)
|
|
DWORD bytesWritten;
|
|
BOOL ret = WriteFile(GetStdHandle(STD_ERROR_HANDLE), aStr,
|
|
strlen(aStr) + 1, &bytesWritten, nullptr);
|
|
::__debugbreak();
|
|
# elif defined(ANDROID)
|
|
int ret = __android_log_write(ANDROID_LOG_FATAL, "MOZ_CRASH", aStr);
|
|
# else
|
|
ssize_t ret = write(STDERR_FILENO, aStr, strlen(aStr) + 1);
|
|
# endif
|
|
(void)ret; // Ignore failures; we're already crashing anyway.
|
|
#endif
|
|
}
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
#if defined(XP_WIN)
|
|
|
|
static void* sVectoredExceptionHandler = nullptr;
|
|
|
|
/*
|
|
* We can only handle one exception. To guard against races and reentrancy,
|
|
* we set this value the first time we enter the exception handler and never
|
|
* touch it again.
|
|
*/
|
|
static mozilla::Atomic<bool> sHandlingException(false);
|
|
|
|
static long __stdcall
|
|
VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
|
|
{
|
|
EXCEPTION_RECORD* ExceptionRecord = ExceptionInfo->ExceptionRecord;
|
|
|
|
// We only handle one kind of exception; ignore all others.
|
|
if (ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
|
|
// Make absolutely sure we can only get here once.
|
|
if (sHandlingException.compareExchange(false, true)) {
|
|
// Restore the previous handler. We're going to forward to it
|
|
// anyway, and if we crash while doing so we don't want to hang.
|
|
MOZ_ALWAYS_TRUE(RemoveVectoredExceptionHandler(sVectoredExceptionHandler));
|
|
|
|
// Get the address that the offending code tried to access.
|
|
uintptr_t address = uintptr_t(ExceptionRecord->ExceptionInformation[1]);
|
|
|
|
// If the faulting address is in one of our protected regions, we
|
|
// want to annotate the crash to make it stand out from the crowd.
|
|
if (sProtectedRegions.isProtected(address)) {
|
|
ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
|
|
MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forward to the previous handler which may be a debugger,
|
|
// the crash reporter or something else entirely.
|
|
return EXCEPTION_CONTINUE_SEARCH;
|
|
}
|
|
|
|
bool
|
|
MemoryProtectionExceptionHandler::install()
|
|
{
|
|
MOZ_ASSERT(!sExceptionHandlerInstalled);
|
|
|
|
// If the exception handler is disabled, report success anyway.
|
|
if (MemoryProtectionExceptionHandler::isDisabled())
|
|
return true;
|
|
|
|
// Install our new exception handler.
|
|
sVectoredExceptionHandler = AddVectoredExceptionHandler(/* FirstHandler = */ true,
|
|
VectoredExceptionHandler);
|
|
|
|
sExceptionHandlerInstalled = sVectoredExceptionHandler != nullptr;
|
|
return sExceptionHandlerInstalled;
|
|
}
|
|
|
|
void
|
|
MemoryProtectionExceptionHandler::uninstall()
|
|
{
|
|
if (sExceptionHandlerInstalled) {
|
|
MOZ_ASSERT(!sHandlingException);
|
|
|
|
// Restore the previous exception handler.
|
|
MOZ_ALWAYS_TRUE(RemoveVectoredExceptionHandler(sVectoredExceptionHandler));
|
|
|
|
sExceptionHandlerInstalled = false;
|
|
}
|
|
}
|
|
|
|
#elif defined(XP_UNIX) && !defined(XP_DARWIN)
|
|
|
|
static struct sigaction sPrevSEGVHandler = {};
|
|
|
|
/*
|
|
* We can only handle one exception. To guard against races and reentrancy,
|
|
* we set this value the first time we enter the exception handler and never
|
|
* touch it again.
|
|
*/
|
|
static mozilla::Atomic<bool> sHandlingException(false);
|
|
|
|
static void
|
|
UnixExceptionHandler(int signum, siginfo_t* info, void* context)
|
|
{
|
|
// Make absolutely sure we can only get here once.
|
|
if (sHandlingException.compareExchange(false, true)) {
|
|
// Restore the previous handler. We're going to forward to it
|
|
// anyway, and if we crash while doing so we don't want to hang.
|
|
MOZ_ALWAYS_FALSE(sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr));
|
|
|
|
MOZ_ASSERT(signum == SIGSEGV && info->si_signo == SIGSEGV);
|
|
|
|
if (info->si_code == SEGV_ACCERR) {
|
|
// Get the address that the offending code tried to access.
|
|
uintptr_t address = uintptr_t(info->si_addr);
|
|
|
|
// If the faulting address is in one of our protected regions, we
|
|
// want to annotate the crash to make it stand out from the crowd.
|
|
if (sProtectedRegions.isProtected(address)) {
|
|
ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
|
|
MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Forward to the previous handler which may be a debugger,
|
|
// the crash reporter or something else entirely.
|
|
if (sPrevSEGVHandler.sa_flags & SA_SIGINFO)
|
|
sPrevSEGVHandler.sa_sigaction(signum, info, context);
|
|
else if (sPrevSEGVHandler.sa_handler == SIG_DFL || sPrevSEGVHandler.sa_handler == SIG_IGN)
|
|
sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr);
|
|
else
|
|
sPrevSEGVHandler.sa_handler(signum);
|
|
|
|
// If we reach here, we're returning to let the default signal handler deal
|
|
// with the exception. This is technically undefined behavior, but
|
|
// everything seems to do it, and it removes us from the crash stack.
|
|
}
|
|
|
|
bool
|
|
MemoryProtectionExceptionHandler::install()
|
|
{
|
|
MOZ_ASSERT(!sExceptionHandlerInstalled);
|
|
|
|
// If the exception handler is disabled, report success anyway.
|
|
if (MemoryProtectionExceptionHandler::isDisabled())
|
|
return true;
|
|
|
|
// Install our new exception handler and save the previous one.
|
|
struct sigaction faultHandler = {};
|
|
faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER;
|
|
faultHandler.sa_sigaction = UnixExceptionHandler;
|
|
sigemptyset(&faultHandler.sa_mask);
|
|
sExceptionHandlerInstalled = !sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler);
|
|
|
|
return sExceptionHandlerInstalled;
|
|
}
|
|
|
|
void
|
|
MemoryProtectionExceptionHandler::uninstall()
|
|
{
|
|
if (sExceptionHandlerInstalled) {
|
|
MOZ_ASSERT(!sHandlingException);
|
|
|
|
// Restore the previous exception handler.
|
|
MOZ_ALWAYS_FALSE(sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr));
|
|
|
|
sExceptionHandlerInstalled = false;
|
|
}
|
|
}
|
|
|
|
#elif defined(XP_DARWIN)
|
|
|
|
/*
|
|
* The fact that we need to be able to forward to other exception handlers
|
|
* makes this code much more complicated. The forwarding logic and the
|
|
* structures required to make it work are heavily based on the code at
|
|
* www.ravenbrook.com/project/mps/prototype/2013-06-24/machtest/machtest/main.c
|
|
*/
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* Begin Mach definitions and helper functions */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/*
|
|
* These are the message IDs associated with each exception type.
|
|
* We'll be using sIDRequest64, but we need the others for forwarding.
|
|
*/
|
|
static const mach_msg_id_t sIDRequest32 = 2401;
|
|
static const mach_msg_id_t sIDRequestState32 = 2402;
|
|
static const mach_msg_id_t sIDRequestStateIdentity32 = 2403;
|
|
|
|
static const mach_msg_id_t sIDRequest64 = 2405;
|
|
static const mach_msg_id_t sIDRequestState64 = 2406;
|
|
static const mach_msg_id_t sIDRequestStateIdentity64 = 2407;
|
|
|
|
/*
|
|
* Each message ID has an associated Mach message structure.
|
|
* We use the preprocessor to make defining them a little less arduous.
|
|
*/
|
|
# define REQUEST_HEADER_FIELDS\
|
|
mach_msg_header_t header;
|
|
|
|
# define REQUEST_IDENTITY_FIELDS\
|
|
mach_msg_body_t msgh_body;\
|
|
mach_msg_port_descriptor_t thread;\
|
|
mach_msg_port_descriptor_t task;
|
|
|
|
# define REQUEST_GENERAL_FIELDS(bits)\
|
|
NDR_record_t NDR;\
|
|
exception_type_t exception;\
|
|
mach_msg_type_number_t code_count;\
|
|
int##bits##_t code[2];
|
|
|
|
# define REQUEST_STATE_FIELDS\
|
|
int flavor;\
|
|
mach_msg_type_number_t old_state_count;\
|
|
natural_t old_state[THREAD_STATE_MAX];
|
|
|
|
# define REQUEST_TRAILER_FIELDS\
|
|
mach_msg_trailer_t trailer;
|
|
|
|
# define EXCEPTION_REQUEST(bits)\
|
|
struct ExceptionRequest##bits\
|
|
{\
|
|
REQUEST_HEADER_FIELDS\
|
|
REQUEST_IDENTITY_FIELDS\
|
|
REQUEST_GENERAL_FIELDS(bits)\
|
|
REQUEST_TRAILER_FIELDS\
|
|
};\
|
|
|
|
# define EXCEPTION_REQUEST_STATE(bits)\
|
|
struct ExceptionRequestState##bits\
|
|
{\
|
|
REQUEST_HEADER_FIELDS\
|
|
REQUEST_GENERAL_FIELDS(bits)\
|
|
REQUEST_STATE_FIELDS\
|
|
REQUEST_TRAILER_FIELDS\
|
|
};\
|
|
|
|
# define EXCEPTION_REQUEST_STATE_IDENTITY(bits)\
|
|
struct ExceptionRequestStateIdentity##bits\
|
|
{\
|
|
REQUEST_HEADER_FIELDS\
|
|
REQUEST_IDENTITY_FIELDS\
|
|
REQUEST_GENERAL_FIELDS(bits)\
|
|
REQUEST_STATE_FIELDS\
|
|
REQUEST_TRAILER_FIELDS\
|
|
};\
|
|
|
|
/* This is needed because not all fields are naturally aligned on 64-bit. */
|
|
# ifdef __MigPackStructs
|
|
# pragma pack(4)
|
|
# endif
|
|
|
|
EXCEPTION_REQUEST(32)
|
|
EXCEPTION_REQUEST(64)
|
|
EXCEPTION_REQUEST_STATE(32)
|
|
EXCEPTION_REQUEST_STATE(64)
|
|
EXCEPTION_REQUEST_STATE_IDENTITY(32)
|
|
EXCEPTION_REQUEST_STATE_IDENTITY(64)
|
|
|
|
/* We use this as a common base when forwarding to the previous handler. */
|
|
union ExceptionRequestUnion {
|
|
mach_msg_header_t header;
|
|
ExceptionRequest32 r32;
|
|
ExceptionRequest64 r64;
|
|
ExceptionRequestState32 rs32;
|
|
ExceptionRequestState64 rs64;
|
|
ExceptionRequestStateIdentity32 rsi32;
|
|
ExceptionRequestStateIdentity64 rsi64;
|
|
};
|
|
|
|
/* This isn't really a full Mach message, but it's all we need to send. */
|
|
struct ExceptionReply
|
|
{
|
|
mach_msg_header_t header;
|
|
NDR_record_t NDR;
|
|
kern_return_t RetCode;
|
|
};
|
|
|
|
# ifdef __MigPackStructs
|
|
# pragma pack()
|
|
# endif
|
|
|
|
# undef EXCEPTION_REQUEST_STATE_IDENTITY
|
|
# undef EXCEPTION_REQUEST_STATE
|
|
# undef EXCEPTION_REQUEST
|
|
# undef REQUEST_STATE_FIELDS
|
|
# undef REQUEST_GENERAL_FIELDS
|
|
# undef REQUEST_IDENTITY_FIELDS
|
|
# undef REQUEST_HEADER_FIELDS
|
|
|
|
/*
|
|
* The exception handler we're forwarding to may not have the same behavior
|
|
* or thread state flavor as what we're using. These macros help populate
|
|
* the fields of the message we're about to send to the previous handler.
|
|
*/
|
|
# define COPY_REQUEST_COMMON(bits, id)\
|
|
dst.header = src.header;\
|
|
dst.header.msgh_id = id;\
|
|
dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer));\
|
|
dst.NDR = src.NDR;\
|
|
dst.exception = src.exception;\
|
|
dst.code_count = src.code_count;\
|
|
dst.code[0] = int##bits##_t(src.code[0]);\
|
|
dst.code[1] = int##bits##_t(src.code[1]);
|
|
|
|
# define COPY_REQUEST_IDENTITY\
|
|
dst.msgh_body = src.msgh_body;\
|
|
dst.thread = src.thread;\
|
|
dst.task = src.task;
|
|
|
|
# define COPY_REQUEST_STATE(flavor, stateCount, state)\
|
|
mach_msg_size_t stateSize = stateCount * sizeof(natural_t);\
|
|
dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer) -\
|
|
sizeof(dst.old_state) + stateSize);\
|
|
dst.flavor = flavor;\
|
|
dst.old_state_count = stateCount;\
|
|
memcpy(dst.old_state, state, stateSize);
|
|
|
|
# define COPY_EXCEPTION_REQUEST(bits)\
|
|
static void\
|
|
CopyExceptionRequest##bits(ExceptionRequest64& src,\
|
|
ExceptionRequest##bits& dst)\
|
|
{\
|
|
COPY_REQUEST_COMMON(bits, sIDRequest##bits)\
|
|
COPY_REQUEST_IDENTITY\
|
|
}
|
|
|
|
# define COPY_EXCEPTION_REQUEST_STATE(bits)\
|
|
static void\
|
|
CopyExceptionRequestState##bits(ExceptionRequest64& src,\
|
|
ExceptionRequestState##bits& dst,\
|
|
thread_state_flavor_t flavor,\
|
|
mach_msg_type_number_t stateCount,\
|
|
thread_state_t state)\
|
|
{\
|
|
COPY_REQUEST_COMMON(bits, sIDRequestState##bits)\
|
|
COPY_REQUEST_STATE(flavor, stateCount, state)\
|
|
}
|
|
|
|
# define COPY_EXCEPTION_REQUEST_STATE_IDENTITY(bits)\
|
|
static void\
|
|
CopyExceptionRequestStateIdentity##bits(ExceptionRequest64& src,\
|
|
ExceptionRequestStateIdentity##bits& dst,\
|
|
thread_state_flavor_t flavor,\
|
|
mach_msg_type_number_t stateCount,\
|
|
thread_state_t state)\
|
|
{\
|
|
COPY_REQUEST_COMMON(bits, sIDRequestStateIdentity##bits)\
|
|
COPY_REQUEST_IDENTITY\
|
|
COPY_REQUEST_STATE(flavor, stateCount, state)\
|
|
}
|
|
|
|
COPY_EXCEPTION_REQUEST(32)
|
|
COPY_EXCEPTION_REQUEST_STATE(32)
|
|
COPY_EXCEPTION_REQUEST_STATE_IDENTITY(32)
|
|
COPY_EXCEPTION_REQUEST(64)
|
|
COPY_EXCEPTION_REQUEST_STATE(64)
|
|
COPY_EXCEPTION_REQUEST_STATE_IDENTITY(64)
|
|
|
|
# undef COPY_EXCEPTION_REQUEST_STATE_IDENTITY
|
|
# undef COPY_EXCEPTION_REQUEST_STATE
|
|
# undef COPY_EXCEPTION_REQUEST
|
|
# undef COPY_REQUEST_STATE
|
|
# undef COPY_REQUEST_IDENTITY
|
|
# undef COPY_REQUEST_COMMON
|
|
|
|
/* -------------------------------------------------------------------------- */
|
|
/* End Mach definitions and helper functions */
|
|
/* -------------------------------------------------------------------------- */
|
|
|
|
/* Every Mach exception handler is parameterized by these four properties. */
|
|
struct MachExceptionParameters
|
|
{
|
|
exception_mask_t mask;
|
|
mach_port_t port;
|
|
exception_behavior_t behavior;
|
|
thread_state_flavor_t flavor;
|
|
};
|
|
|
|
struct ExceptionHandlerState
|
|
{
|
|
MachExceptionParameters current;
|
|
MachExceptionParameters previous;
|
|
|
|
/* Each Mach exception handler runs in its own thread. */
|
|
Thread handlerThread;
|
|
};
|
|
|
|
/* This choice of ID is arbitrary, but must not match our exception ID. */
|
|
static const mach_msg_id_t sIDQuit = 42;
|
|
|
|
static ExceptionHandlerState sMachExceptionState;
|
|
|
|
/*
|
|
* The meat of our exception handler. This thread waits for an exception
|
|
* message, annotates the exception if needed, then forwards it to the
|
|
* previously installed handler (which will likely terminate the process).
|
|
*/
|
|
static void
|
|
MachExceptionHandler()
|
|
{
|
|
kern_return_t ret;
|
|
MachExceptionParameters& current = sMachExceptionState.current;
|
|
MachExceptionParameters& previous = sMachExceptionState.previous;
|
|
|
|
// We use the simplest kind of 64-bit exception message here.
|
|
ExceptionRequest64 request = {};
|
|
request.header.msgh_local_port = current.port;
|
|
request.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(request));
|
|
ret = mach_msg(&request.header, MACH_RCV_MSG, 0, request.header.msgh_size,
|
|
current.port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
|
|
|
// Restore the previous handler. We're going to forward to it
|
|
// anyway, and if we crash while doing so we don't want to hang.
|
|
task_set_exception_ports(mach_task_self(), previous.mask, previous.port,
|
|
previous.behavior, previous.flavor);
|
|
|
|
// If we failed even receiving the message, just give up.
|
|
if (ret != MACH_MSG_SUCCESS)
|
|
MOZ_CRASH("MachExceptionHandler: mach_msg failed to receive a message!");
|
|
|
|
// Terminate the thread if we're shutting down.
|
|
if (request.header.msgh_id == sIDQuit)
|
|
return;
|
|
|
|
// The only other valid message ID is the one associated with the
|
|
// EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES behavior we chose.
|
|
if (request.header.msgh_id != sIDRequest64)
|
|
MOZ_CRASH("MachExceptionHandler: Unexpected Message ID!");
|
|
|
|
// Make sure we can understand the exception we received.
|
|
if (request.exception != EXC_BAD_ACCESS || request.code_count != 2)
|
|
MOZ_CRASH("MachExceptionHandler: Unexpected exception type!");
|
|
|
|
// Get the address that the offending code tried to access.
|
|
uintptr_t address = uintptr_t(request.code[1]);
|
|
|
|
// If the faulting address is inside one of our protected regions, we
|
|
// want to annotate the crash to make it stand out from the crowd.
|
|
if (sProtectedRegions.isProtected(address)) {
|
|
ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
|
|
MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
|
|
}
|
|
|
|
// Forward to the previous handler which may be a debugger, the unix
|
|
// signal handler, the crash reporter or something else entirely.
|
|
if (previous.port != MACH_PORT_NULL) {
|
|
mach_msg_type_number_t stateCount;
|
|
thread_state_data_t state;
|
|
if ((uint32_t(previous.behavior) & ~MACH_EXCEPTION_CODES) != EXCEPTION_DEFAULT) {
|
|
// If the previous handler requested thread state, get it here.
|
|
stateCount = THREAD_STATE_MAX;
|
|
ret = thread_get_state(request.thread.name, previous.flavor, state, &stateCount);
|
|
if (ret != KERN_SUCCESS)
|
|
MOZ_CRASH("MachExceptionHandler: Could not get the thread state to forward!");
|
|
}
|
|
|
|
// Depending on the behavior of the previous handler, the forwarded
|
|
// exception message will have a different set of fields.
|
|
// Of particular note is that exception handlers that lack
|
|
// MACH_EXCEPTION_CODES will get 32-bit fields even on 64-bit
|
|
// systems. It appears that OSX simply truncates these fields.
|
|
ExceptionRequestUnion forward;
|
|
switch (uint32_t(previous.behavior)) {
|
|
case EXCEPTION_DEFAULT:
|
|
CopyExceptionRequest32(request, forward.r32);
|
|
break;
|
|
case EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES:
|
|
CopyExceptionRequest64(request, forward.r64);
|
|
break;
|
|
case EXCEPTION_STATE:
|
|
CopyExceptionRequestState32(request, forward.rs32,
|
|
previous.flavor, stateCount, state);
|
|
break;
|
|
case EXCEPTION_STATE | MACH_EXCEPTION_CODES:
|
|
CopyExceptionRequestState64(request, forward.rs64,
|
|
previous.flavor, stateCount, state);
|
|
break;
|
|
case EXCEPTION_STATE_IDENTITY:
|
|
CopyExceptionRequestStateIdentity32(request, forward.rsi32,
|
|
previous.flavor, stateCount, state);
|
|
break;
|
|
case EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES:
|
|
CopyExceptionRequestStateIdentity64(request, forward.rsi64,
|
|
previous.flavor, stateCount, state);
|
|
break;
|
|
default:
|
|
MOZ_CRASH("MachExceptionHandler: Unknown previous handler behavior!");
|
|
}
|
|
|
|
// Forward the generated message to the old port. The local and remote
|
|
// port fields *and their rights* are swapped on arrival, so we need to
|
|
// swap them back first.
|
|
forward.header.msgh_bits = (request.header.msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) |
|
|
MACH_MSGH_BITS(MACH_MSGH_BITS_LOCAL(request.header.msgh_bits),
|
|
MACH_MSGH_BITS_REMOTE(request.header.msgh_bits));
|
|
forward.header.msgh_local_port = forward.header.msgh_remote_port;
|
|
forward.header.msgh_remote_port = previous.port;
|
|
ret = mach_msg(&forward.header, MACH_SEND_MSG, forward.header.msgh_size, 0,
|
|
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
|
if (ret != MACH_MSG_SUCCESS)
|
|
MOZ_CRASH("MachExceptionHandler: Failed to forward to the previous handler!");
|
|
} else {
|
|
// There was no previous task-level exception handler, so defer to the
|
|
// host level one instead. We set the return code to KERN_FAILURE to
|
|
// indicate that we did not handle the exception.
|
|
// The reply message ID is always the request ID + 100.
|
|
ExceptionReply reply = {};
|
|
reply.header.msgh_bits =
|
|
MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0);
|
|
reply.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(reply));
|
|
reply.header.msgh_remote_port = request.header.msgh_remote_port;
|
|
reply.header.msgh_local_port = MACH_PORT_NULL;
|
|
reply.header.msgh_id = request.header.msgh_id + 100;
|
|
reply.NDR = request.NDR;
|
|
reply.RetCode = KERN_FAILURE;
|
|
ret = mach_msg(&reply.header, MACH_SEND_MSG, reply.header.msgh_size, 0,
|
|
MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
|
if (ret != MACH_MSG_SUCCESS)
|
|
MOZ_CRASH("MachExceptionHandler: Failed to forward to the host level!");
|
|
}
|
|
}
|
|
|
|
static void
|
|
TerminateMachExceptionHandlerThread()
|
|
{
|
|
// Send a simple quit message to the exception handler thread.
|
|
mach_msg_header_t msg;
|
|
msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
|
|
msg.msgh_size = static_cast<mach_msg_size_t>(sizeof(msg));
|
|
msg.msgh_remote_port = sMachExceptionState.current.port;
|
|
msg.msgh_local_port = MACH_PORT_NULL;
|
|
msg.msgh_reserved = 0;
|
|
msg.msgh_id = sIDQuit;
|
|
kern_return_t ret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL,
|
|
MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
|
|
|
|
if (ret == MACH_MSG_SUCCESS)
|
|
sMachExceptionState.handlerThread.join();
|
|
else
|
|
MOZ_CRASH("MachExceptionHandler: Handler thread failed to terminate!");
|
|
}
|
|
|
|
bool
|
|
MemoryProtectionExceptionHandler::install()
|
|
{
|
|
MOZ_ASSERT(!sExceptionHandlerInstalled);
|
|
|
|
// If the exception handler is disabled, report success anyway.
|
|
if (MemoryProtectionExceptionHandler::isDisabled())
|
|
return true;
|
|
|
|
kern_return_t ret;
|
|
mach_port_t task = mach_task_self();
|
|
|
|
// Allocate a new exception port with receive rights.
|
|
sMachExceptionState.current = {};
|
|
MachExceptionParameters& current = sMachExceptionState.current;
|
|
ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, ¤t.port);
|
|
if (ret != KERN_SUCCESS)
|
|
return false;
|
|
|
|
// Give the new port send rights as well.
|
|
ret = mach_port_insert_right(task, current.port, current.port, MACH_MSG_TYPE_MAKE_SEND);
|
|
if (ret != KERN_SUCCESS) {
|
|
mach_port_deallocate(task, current.port);
|
|
current = {};
|
|
return false;
|
|
}
|
|
|
|
// Start the thread that will receive the messages from our exception port.
|
|
if (!sMachExceptionState.handlerThread.init(MachExceptionHandler)) {
|
|
mach_port_deallocate(task, current.port);
|
|
current = {};
|
|
return false;
|
|
}
|
|
|
|
// Set the other properties of our new exception handler.
|
|
current.mask = EXC_MASK_BAD_ACCESS;
|
|
current.behavior = exception_behavior_t(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES);
|
|
current.flavor = THREAD_STATE_NONE;
|
|
|
|
// Tell the task to use our exception handler, and save the previous one.
|
|
sMachExceptionState.previous = {};
|
|
MachExceptionParameters& previous = sMachExceptionState.previous;
|
|
mach_msg_type_number_t previousCount = 1;
|
|
ret = task_swap_exception_ports(task, current.mask, current.port, current.behavior,
|
|
current.flavor, &previous.mask, &previousCount,
|
|
&previous.port, &previous.behavior, &previous.flavor);
|
|
if (ret != KERN_SUCCESS) {
|
|
TerminateMachExceptionHandlerThread();
|
|
mach_port_deallocate(task, current.port);
|
|
previous = {};
|
|
current = {};
|
|
return false;
|
|
}
|
|
|
|
// We should have info on the previous exception handler, even if it's null.
|
|
MOZ_ASSERT(previousCount == 1);
|
|
|
|
sExceptionHandlerInstalled = true;
|
|
return sExceptionHandlerInstalled;
|
|
}
|
|
|
|
void
|
|
MemoryProtectionExceptionHandler::uninstall()
|
|
{
|
|
if (sExceptionHandlerInstalled) {
|
|
mach_port_t task = mach_task_self();
|
|
|
|
// Restore the previous exception handler.
|
|
MachExceptionParameters& previous = sMachExceptionState.previous;
|
|
task_set_exception_ports(task, previous.mask, previous.port,
|
|
previous.behavior, previous.flavor);
|
|
|
|
TerminateMachExceptionHandlerThread();
|
|
|
|
// Release the Mach IPC port we used.
|
|
mach_port_deallocate(task, sMachExceptionState.current.port);
|
|
|
|
sMachExceptionState.current = {};
|
|
sMachExceptionState.previous = {};
|
|
|
|
sExceptionHandlerInstalled = false;
|
|
}
|
|
}
|
|
|
|
#else
|
|
|
|
#error "This platform is not supported!"
|
|
|
|
#endif
|
|
|
|
} /* namespace js */
|