diff options
Diffstat (limited to 'js/src/ds/MemoryProtectionExceptionHandler.cpp')
-rw-r--r-- | js/src/ds/MemoryProtectionExceptionHandler.cpp | 760 |
1 files changed, 760 insertions, 0 deletions
diff --git a/js/src/ds/MemoryProtectionExceptionHandler.cpp b/js/src/ds/MemoryProtectionExceptionHandler.cpp new file mode 100644 index 000000000..7c6646a9a --- /dev/null +++ b/js/src/ds/MemoryProtectionExceptionHandler.cpp @@ -0,0 +1,760 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * 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 */ |