LCOV - code coverage report
Current view: top level - js/src/ds - MemoryProtectionExceptionHandler.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 14 72 19.4 %
Date: 2017-07-14 16:53:18 Functions: 3 14 21.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
       2             :  * vim: set ts=8 sts=4 et sw=4 tw=99:
       3             :  * This Source Code Form is subject to the terms of the Mozilla Public
       4             :  * License, v. 2.0. If a copy of the MPL was not distributed with this
       5             :  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
       6             : 
       7             : #include "ds/MemoryProtectionExceptionHandler.h"
       8             : 
       9             : #include "mozilla/Assertions.h"
      10             : #include "mozilla/Atomics.h"
      11             : 
      12             : #if defined(XP_WIN)
      13             : # include "jswin.h"
      14             : #elif defined(XP_UNIX) && !defined(XP_DARWIN)
      15             : # include <signal.h>
      16             : # include <sys/types.h>
      17             : # include <unistd.h>
      18             : #elif defined(XP_DARWIN)
      19             : # include <mach/mach.h>
      20             : # include <unistd.h>
      21             : #endif
      22             : 
      23             : #ifdef ANDROID
      24             : # include <android/log.h>
      25             : #endif
      26             : 
      27             : #include "ds/SplayTree.h"
      28             : 
      29             : #include "threading/LockGuard.h"
      30             : #include "threading/Thread.h"
      31             : #include "vm/MutexIDs.h"
      32             : 
      33             : namespace js {
      34             : 
      35             : /*
      36             :  * A class to store the addresses of the regions recognized as protected
      37             :  * by this exception handler. We use a splay tree to store these addresses.
      38             :  */
      39             : class ProtectedRegionTree
      40             : {
      41             :     struct Region
      42             :     {
      43             :         uintptr_t first;
      44             :         uintptr_t last;
      45             : 
      46           0 :         Region(uintptr_t addr, size_t size) : first(addr),
      47           0 :                                               last(addr + (size - 1)) {}
      48             : 
      49           0 :         static int compare(const Region& A, const Region& B) {
      50           0 :             if (A.last < B.first)
      51           0 :                 return -1;
      52           0 :             if (A.first > B.last)
      53           0 :                 return 1;
      54           0 :             return 0;
      55             :         }
      56             :     };
      57             : 
      58             :     Mutex lock;
      59             :     LifoAlloc alloc;
      60             :     SplayTree<Region, Region> tree;
      61             : 
      62             :   public:
      63           3 :     ProtectedRegionTree() : lock(mutexid::ProtectedRegionTree),
      64             :                             alloc(4096),
      65           3 :                             tree(&alloc) {}
      66             : 
      67           0 :     ~ProtectedRegionTree() { MOZ_ASSERT(tree.empty()); }
      68             : 
      69           0 :     void insert(uintptr_t addr, size_t size) {
      70           0 :         MOZ_ASSERT(addr && size);
      71           0 :         LockGuard<Mutex> guard(lock);
      72           0 :         AutoEnterOOMUnsafeRegion oomUnsafe;
      73           0 :         if (!tree.insert(Region(addr, size)))
      74           0 :             oomUnsafe.crash("Failed to store allocation ID.");
      75           0 :     }
      76             : 
      77           0 :     void remove(uintptr_t addr) {
      78           0 :         MOZ_ASSERT(addr);
      79           0 :         LockGuard<Mutex> guard(lock);
      80           0 :         tree.remove(Region(addr, 1));
      81           0 :     }
      82             : 
      83           0 :     bool isProtected(uintptr_t addr) {
      84           0 :         if (!addr)
      85           0 :             return false;
      86           0 :         LockGuard<Mutex> guard(lock);
      87           0 :         return tree.maybeLookup(Region(addr, 1));
      88             :     }
      89             : };
      90             : 
      91             : static bool sExceptionHandlerInstalled = false;
      92             : 
      93           3 : static ProtectedRegionTree sProtectedRegions;
      94             : 
      95             : bool
      96           3 : MemoryProtectionExceptionHandler::isDisabled()
      97             : {
      98             : #if defined(XP_WIN) && defined(MOZ_ASAN)
      99             :     // Under Windows ASan, WasmFaultHandler registers itself at 'last' priority
     100             :     // in order to let ASan's ShadowExceptionHandler stay at 'first' priority.
     101             :     // Unfortunately that results in spurious wasm faults passing through the
     102             :     // MemoryProtectionExceptionHandler, which breaks its assumption that any
     103             :     // faults it sees are fatal. Just disable this handler in that case, as the
     104             :     // crash annotations provided here are not critical for ASan builds.
     105             :     return true;
     106             : #elif defined(RELEASE_OR_BETA)
     107             :     // Disable the exception handler for Beta and Release builds.
     108             :     return true;
     109             : #else
     110           3 :     return false;
     111             : #endif
     112             : }
     113             : 
     114             : void
     115           0 : MemoryProtectionExceptionHandler::addRegion(void* addr, size_t size)
     116             : {
     117           0 :     if (sExceptionHandlerInstalled)
     118           0 :         sProtectedRegions.insert(uintptr_t(addr), size);
     119           0 : }
     120             : 
     121             : void
     122           0 : MemoryProtectionExceptionHandler::removeRegion(void* addr)
     123             : {
     124           0 :     if (sExceptionHandlerInstalled)
     125           0 :         sProtectedRegions.remove(uintptr_t(addr));
     126           0 : }
     127             : 
     128             : /* -------------------------------------------------------------------------- */
     129             : 
     130             : /*
     131             :  * This helper function attempts to replicate the functionality of
     132             :  * mozilla::MOZ_ReportCrash() in an async-signal-safe way.
     133             :  */
     134             : static MOZ_COLD MOZ_ALWAYS_INLINE void
     135           0 : ReportCrashIfDebug(const char* aStr)
     136             :     MOZ_PRETEND_NORETURN_FOR_STATIC_ANALYSIS
     137             : {
     138             : #ifdef DEBUG
     139             : # if defined(XP_WIN)
     140             :     DWORD bytesWritten;
     141             :     BOOL ret = WriteFile(GetStdHandle(STD_ERROR_HANDLE), aStr,
     142             :                          strlen(aStr) + 1, &bytesWritten, nullptr);
     143             : # elif defined(ANDROID)
     144             :     int ret = __android_log_write(ANDROID_LOG_FATAL, "MOZ_CRASH", aStr);
     145             : # else
     146           0 :     ssize_t ret = write(STDERR_FILENO, aStr, strlen(aStr) + 1);
     147             : # endif
     148             :     (void)ret; // Ignore failures; we're already crashing anyway.
     149             : #endif
     150           0 : }
     151             : 
     152             : /* -------------------------------------------------------------------------- */
     153             : 
     154             : #if defined(XP_WIN)
     155             : 
     156             : static void* sVectoredExceptionHandler = nullptr;
     157             : 
     158             : /*
     159             :  * We can only handle one exception. To guard against races and reentrancy,
     160             :  * we set this value the first time we enter the exception handler and never
     161             :  * touch it again.
     162             :  */
     163             : static mozilla::Atomic<bool> sHandlingException(false);
     164             : 
     165             : static long __stdcall
     166             : VectoredExceptionHandler(EXCEPTION_POINTERS* ExceptionInfo)
     167             : {
     168             :     EXCEPTION_RECORD* ExceptionRecord = ExceptionInfo->ExceptionRecord;
     169             : 
     170             :     // We only handle one kind of exception; ignore all others.
     171             :     if (ExceptionRecord->ExceptionCode == EXCEPTION_ACCESS_VIOLATION) {
     172             :         // Make absolutely sure we can only get here once.
     173             :         if (sHandlingException.compareExchange(false, true)) {
     174             :             // Restore the previous handler. We're going to forward to it
     175             :             // anyway, and if we crash while doing so we don't want to hang.
     176             :             MOZ_ALWAYS_TRUE(RemoveVectoredExceptionHandler(sVectoredExceptionHandler));
     177             : 
     178             :             // Get the address that the offending code tried to access.
     179             :             uintptr_t address = uintptr_t(ExceptionRecord->ExceptionInformation[1]);
     180             : 
     181             :             // If the faulting address is in one of our protected regions, we
     182             :             // want to annotate the crash to make it stand out from the crowd.
     183             :             if (sProtectedRegions.isProtected(address)) {
     184             :                 ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
     185             :                 MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
     186             :             }
     187             :         }
     188             :     }
     189             : 
     190             :     // Forward to the previous handler which may be a debugger,
     191             :     // the crash reporter or something else entirely.
     192             :     return EXCEPTION_CONTINUE_SEARCH;
     193             : }
     194             : 
     195             : bool
     196             : MemoryProtectionExceptionHandler::install()
     197             : {
     198             :     MOZ_ASSERT(!sExceptionHandlerInstalled);
     199             : 
     200             :     // If the exception handler is disabled, report success anyway.
     201             :     if (MemoryProtectionExceptionHandler::isDisabled())
     202             :         return true;
     203             : 
     204             :     // Install our new exception handler.
     205             :     sVectoredExceptionHandler = AddVectoredExceptionHandler(/* FirstHandler = */ true,
     206             :                                                             VectoredExceptionHandler);
     207             : 
     208             :     sExceptionHandlerInstalled = sVectoredExceptionHandler != nullptr;
     209             :     return sExceptionHandlerInstalled;
     210             : }
     211             : 
     212             : void
     213             : MemoryProtectionExceptionHandler::uninstall()
     214             : {
     215             :     if (sExceptionHandlerInstalled) {
     216             :         MOZ_ASSERT(!sHandlingException);
     217             : 
     218             :         // Restore the previous exception handler.
     219             :         MOZ_ALWAYS_TRUE(RemoveVectoredExceptionHandler(sVectoredExceptionHandler));
     220             : 
     221             :         sExceptionHandlerInstalled = false;
     222             :     }
     223             : }
     224             : 
     225             : #elif defined(XP_UNIX) && !defined(XP_DARWIN)
     226             : 
     227             : static struct sigaction sPrevSEGVHandler = {};
     228             : 
     229             : /*
     230             :  * We can only handle one exception. To guard against races and reentrancy,
     231             :  * we set this value the first time we enter the exception handler and never
     232             :  * touch it again.
     233             :  */
     234             : static mozilla::Atomic<bool> sHandlingException(false);
     235             : 
     236             : static void
     237           0 : UnixExceptionHandler(int signum, siginfo_t* info, void* context)
     238             : {
     239             :     // Make absolutely sure we can only get here once.
     240           0 :     if (sHandlingException.compareExchange(false, true)) {
     241             :         // Restore the previous handler. We're going to forward to it
     242             :         // anyway, and if we crash while doing so we don't want to hang.
     243           0 :         MOZ_ALWAYS_FALSE(sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr));
     244             : 
     245           0 :         MOZ_ASSERT(signum == SIGSEGV && info->si_signo == SIGSEGV);
     246             : 
     247           0 :         if (info->si_code == SEGV_ACCERR) {
     248             :             // Get the address that the offending code tried to access.
     249           0 :             uintptr_t address = uintptr_t(info->si_addr);
     250             : 
     251             :             // If the faulting address is in one of our protected regions, we
     252             :             // want to annotate the crash to make it stand out from the crowd.
     253           0 :             if (sProtectedRegions.isProtected(address)) {
     254           0 :                 ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
     255             :                 MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
     256             :             }
     257             :         }
     258             :     }
     259             : 
     260             :     // Forward to the previous handler which may be a debugger,
     261             :     // the crash reporter or something else entirely.
     262           0 :     if (sPrevSEGVHandler.sa_flags & SA_SIGINFO)
     263           0 :         sPrevSEGVHandler.sa_sigaction(signum, info, context);
     264           0 :     else if (sPrevSEGVHandler.sa_handler == SIG_DFL || sPrevSEGVHandler.sa_handler == SIG_IGN)
     265           0 :         sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr);
     266             :     else
     267           0 :         sPrevSEGVHandler.sa_handler(signum);
     268             : 
     269             :     // If we reach here, we're returning to let the default signal handler deal
     270             :     // with the exception. This is technically undefined behavior, but
     271             :     // everything seems to do it, and it removes us from the crash stack.
     272           0 : }
     273             : 
     274             : bool
     275           3 : MemoryProtectionExceptionHandler::install()
     276             : {
     277           3 :     MOZ_ASSERT(!sExceptionHandlerInstalled);
     278             : 
     279             :     // If the exception handler is disabled, report success anyway.
     280           3 :     if (MemoryProtectionExceptionHandler::isDisabled())
     281           0 :         return true;
     282             : 
     283             :     // Install our new exception handler and save the previous one.
     284           3 :     struct sigaction faultHandler = {};
     285           3 :     faultHandler.sa_flags = SA_SIGINFO | SA_NODEFER;
     286           3 :     faultHandler.sa_sigaction = UnixExceptionHandler;
     287           3 :     sigemptyset(&faultHandler.sa_mask);
     288           3 :     sExceptionHandlerInstalled = !sigaction(SIGSEGV, &faultHandler, &sPrevSEGVHandler);
     289             : 
     290           3 :     return sExceptionHandlerInstalled;
     291             : }
     292             : 
     293             : void
     294           0 : MemoryProtectionExceptionHandler::uninstall()
     295             : {
     296           0 :     if (sExceptionHandlerInstalled) {
     297           0 :         MOZ_ASSERT(!sHandlingException);
     298             : 
     299             :         // Restore the previous exception handler.
     300           0 :         MOZ_ALWAYS_FALSE(sigaction(SIGSEGV, &sPrevSEGVHandler, nullptr));
     301             : 
     302           0 :         sExceptionHandlerInstalled = false;
     303             :     }
     304           0 : }
     305             : 
     306             : #elif defined(XP_DARWIN)
     307             : 
     308             : /*
     309             :  * The fact that we need to be able to forward to other exception handlers
     310             :  * makes this code much more complicated. The forwarding logic and the
     311             :  * structures required to make it work are heavily based on the code at
     312             :  * www.ravenbrook.com/project/mps/prototype/2013-06-24/machtest/machtest/main.c
     313             :  */
     314             : 
     315             : /* -------------------------------------------------------------------------- */
     316             : /*                Begin Mach definitions and helper functions                 */
     317             : /* -------------------------------------------------------------------------- */
     318             : 
     319             : /*
     320             :  * These are the message IDs associated with each exception type.
     321             :  * We'll be using sIDRequest64, but we need the others for forwarding.
     322             :  */
     323             : static const mach_msg_id_t sIDRequest32 = 2401;
     324             : static const mach_msg_id_t sIDRequestState32 = 2402;
     325             : static const mach_msg_id_t sIDRequestStateIdentity32 = 2403;
     326             : 
     327             : static const mach_msg_id_t sIDRequest64 = 2405;
     328             : static const mach_msg_id_t sIDRequestState64 = 2406;
     329             : static const mach_msg_id_t sIDRequestStateIdentity64 = 2407;
     330             : 
     331             : /*
     332             :  * Each message ID has an associated Mach message structure.
     333             :  * We use the preprocessor to make defining them a little less arduous.
     334             :  */
     335             : # define REQUEST_HEADER_FIELDS\
     336             :     mach_msg_header_t header;
     337             : 
     338             : # define REQUEST_IDENTITY_FIELDS\
     339             :     mach_msg_body_t msgh_body;\
     340             :     mach_msg_port_descriptor_t thread;\
     341             :     mach_msg_port_descriptor_t task;
     342             : 
     343             : # define REQUEST_GENERAL_FIELDS(bits)\
     344             :     NDR_record_t NDR;\
     345             :     exception_type_t exception;\
     346             :     mach_msg_type_number_t code_count;\
     347             :     int##bits##_t code[2];
     348             : 
     349             : # define REQUEST_STATE_FIELDS\
     350             :     int flavor;\
     351             :     mach_msg_type_number_t old_state_count;\
     352             :     natural_t old_state[THREAD_STATE_MAX];
     353             : 
     354             : # define REQUEST_TRAILER_FIELDS\
     355             :     mach_msg_trailer_t trailer;
     356             : 
     357             : # define EXCEPTION_REQUEST(bits)\
     358             :     struct ExceptionRequest##bits\
     359             :     {\
     360             :         REQUEST_HEADER_FIELDS\
     361             :         REQUEST_IDENTITY_FIELDS\
     362             :         REQUEST_GENERAL_FIELDS(bits)\
     363             :         REQUEST_TRAILER_FIELDS\
     364             :     };\
     365             : 
     366             : # define EXCEPTION_REQUEST_STATE(bits)\
     367             :     struct ExceptionRequestState##bits\
     368             :     {\
     369             :         REQUEST_HEADER_FIELDS\
     370             :         REQUEST_GENERAL_FIELDS(bits)\
     371             :         REQUEST_STATE_FIELDS\
     372             :         REQUEST_TRAILER_FIELDS\
     373             :     };\
     374             : 
     375             : # define EXCEPTION_REQUEST_STATE_IDENTITY(bits)\
     376             :     struct ExceptionRequestStateIdentity##bits\
     377             :     {\
     378             :         REQUEST_HEADER_FIELDS\
     379             :         REQUEST_IDENTITY_FIELDS\
     380             :         REQUEST_GENERAL_FIELDS(bits)\
     381             :         REQUEST_STATE_FIELDS\
     382             :         REQUEST_TRAILER_FIELDS\
     383             :     };\
     384             : 
     385             : /* This is needed because not all fields are naturally aligned on 64-bit. */
     386             : # ifdef  __MigPackStructs
     387             : #  pragma pack(4)
     388             : # endif
     389             : 
     390             : EXCEPTION_REQUEST(32)
     391             : EXCEPTION_REQUEST(64)
     392             : EXCEPTION_REQUEST_STATE(32)
     393             : EXCEPTION_REQUEST_STATE(64)
     394             : EXCEPTION_REQUEST_STATE_IDENTITY(32)
     395             : EXCEPTION_REQUEST_STATE_IDENTITY(64)
     396             : 
     397             : /* We use this as a common base when forwarding to the previous handler. */
     398             : union ExceptionRequestUnion {
     399             :     mach_msg_header_t header;
     400             :     ExceptionRequest32 r32;
     401             :     ExceptionRequest64 r64;
     402             :     ExceptionRequestState32 rs32;
     403             :     ExceptionRequestState64 rs64;
     404             :     ExceptionRequestStateIdentity32 rsi32;
     405             :     ExceptionRequestStateIdentity64 rsi64;
     406             : };
     407             : 
     408             : /* This isn't really a full Mach message, but it's all we need to send. */
     409             : struct ExceptionReply
     410             : {
     411             :     mach_msg_header_t header;
     412             :     NDR_record_t NDR;
     413             :     kern_return_t RetCode;
     414             : };
     415             : 
     416             : # ifdef  __MigPackStructs
     417             : #  pragma pack()
     418             : # endif
     419             : 
     420             : # undef EXCEPTION_REQUEST_STATE_IDENTITY
     421             : # undef EXCEPTION_REQUEST_STATE
     422             : # undef EXCEPTION_REQUEST
     423             : # undef REQUEST_STATE_FIELDS
     424             : # undef REQUEST_GENERAL_FIELDS
     425             : # undef REQUEST_IDENTITY_FIELDS
     426             : # undef REQUEST_HEADER_FIELDS
     427             : 
     428             : /*
     429             :  * The exception handler we're forwarding to may not have the same behavior
     430             :  * or thread state flavor as what we're using. These macros help populate
     431             :  * the fields of the message we're about to send to the previous handler.
     432             :  */
     433             : # define COPY_REQUEST_COMMON(bits, id)\
     434             :     dst.header = src.header;\
     435             :     dst.header.msgh_id = id;\
     436             :     dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer));\
     437             :     dst.NDR = src.NDR;\
     438             :     dst.exception = src.exception;\
     439             :     dst.code_count = src.code_count;\
     440             :     dst.code[0] = int##bits##_t(src.code[0]);\
     441             :     dst.code[1] = int##bits##_t(src.code[1]);
     442             : 
     443             : # define COPY_REQUEST_IDENTITY\
     444             :     dst.msgh_body = src.msgh_body;\
     445             :     dst.thread = src.thread;\
     446             :     dst.task = src.task;
     447             : 
     448             : # define COPY_REQUEST_STATE(flavor, stateCount, state)\
     449             :     mach_msg_size_t stateSize = stateCount * sizeof(natural_t);\
     450             :     dst.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(dst) - sizeof(dst.trailer) -\
     451             :                                                         sizeof(dst.old_state) + stateSize);\
     452             :     dst.flavor = flavor;\
     453             :     dst.old_state_count = stateCount;\
     454             :     memcpy(dst.old_state, state, stateSize);
     455             : 
     456             : # define COPY_EXCEPTION_REQUEST(bits)\
     457             :     static void\
     458             :     CopyExceptionRequest##bits(ExceptionRequest64& src,\
     459             :                                ExceptionRequest##bits& dst)\
     460             :     {\
     461             :         COPY_REQUEST_COMMON(bits, sIDRequest##bits)\
     462             :         COPY_REQUEST_IDENTITY\
     463             :     }
     464             : 
     465             : # define COPY_EXCEPTION_REQUEST_STATE(bits)\
     466             :     static void\
     467             :     CopyExceptionRequestState##bits(ExceptionRequest64& src,\
     468             :                                     ExceptionRequestState##bits& dst,\
     469             :                                     thread_state_flavor_t flavor,\
     470             :                                     mach_msg_type_number_t stateCount,\
     471             :                                     thread_state_t state)\
     472             :     {\
     473             :         COPY_REQUEST_COMMON(bits, sIDRequestState##bits)\
     474             :         COPY_REQUEST_STATE(flavor, stateCount, state)\
     475             :     }
     476             : 
     477             : # define COPY_EXCEPTION_REQUEST_STATE_IDENTITY(bits)\
     478             :     static void\
     479             :     CopyExceptionRequestStateIdentity##bits(ExceptionRequest64& src,\
     480             :                                             ExceptionRequestStateIdentity##bits& dst,\
     481             :                                             thread_state_flavor_t flavor,\
     482             :                                             mach_msg_type_number_t stateCount,\
     483             :                                             thread_state_t state)\
     484             :     {\
     485             :         COPY_REQUEST_COMMON(bits, sIDRequestStateIdentity##bits)\
     486             :         COPY_REQUEST_IDENTITY\
     487             :         COPY_REQUEST_STATE(flavor, stateCount, state)\
     488             :     }
     489             : 
     490             : COPY_EXCEPTION_REQUEST(32)
     491             : COPY_EXCEPTION_REQUEST_STATE(32)
     492             : COPY_EXCEPTION_REQUEST_STATE_IDENTITY(32)
     493             : COPY_EXCEPTION_REQUEST(64)
     494             : COPY_EXCEPTION_REQUEST_STATE(64)
     495             : COPY_EXCEPTION_REQUEST_STATE_IDENTITY(64)
     496             : 
     497             : # undef COPY_EXCEPTION_REQUEST_STATE_IDENTITY
     498             : # undef COPY_EXCEPTION_REQUEST_STATE
     499             : # undef COPY_EXCEPTION_REQUEST
     500             : # undef COPY_REQUEST_STATE
     501             : # undef COPY_REQUEST_IDENTITY
     502             : # undef COPY_REQUEST_COMMON
     503             : 
     504             : /* -------------------------------------------------------------------------- */
     505             : /*                 End Mach definitions and helper functions                  */
     506             : /* -------------------------------------------------------------------------- */
     507             : 
     508             : /* Every Mach exception handler is parameterized by these four properties. */
     509             : struct MachExceptionParameters
     510             : {
     511             :     exception_mask_t mask;
     512             :     mach_port_t port;
     513             :     exception_behavior_t behavior;
     514             :     thread_state_flavor_t flavor;
     515             : };
     516             : 
     517             : struct ExceptionHandlerState
     518             : {
     519             :     MachExceptionParameters current;
     520             :     MachExceptionParameters previous;
     521             : 
     522             :     /* Each Mach exception handler runs in its own thread. */
     523             :     Thread handlerThread;
     524             : 
     525             :     /* Ensure that the exception handler thread is terminated before we quit. */
     526             :     ~ExceptionHandlerState() { MemoryProtectionExceptionHandler::uninstall(); }
     527             : };
     528             : 
     529             : /* This choice of ID is arbitrary, but must not match our exception ID. */
     530             : static const mach_msg_id_t sIDQuit = 42;
     531             : 
     532             : static ExceptionHandlerState sMachExceptionState;
     533             : 
     534             : /*
     535             :  * The meat of our exception handler. This thread waits for an exception
     536             :  * message, annotates the exception if needed, then forwards it to the
     537             :  * previously installed handler (which will likely terminate the process).
     538             :  */
     539             : static void
     540             : MachExceptionHandler()
     541             : {
     542             :     kern_return_t ret;
     543             :     MachExceptionParameters& current = sMachExceptionState.current;
     544             :     MachExceptionParameters& previous = sMachExceptionState.previous;
     545             : 
     546             :     // We use the simplest kind of 64-bit exception message here.
     547             :     ExceptionRequest64 request = {};
     548             :     request.header.msgh_local_port = current.port;
     549             :     request.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(request));
     550             :     ret = mach_msg(&request.header, MACH_RCV_MSG, 0, request.header.msgh_size,
     551             :                    current.port, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
     552             : 
     553             :     // Restore the previous handler. We're going to forward to it
     554             :     // anyway, and if we crash while doing so we don't want to hang.
     555             :     task_set_exception_ports(mach_task_self(), previous.mask, previous.port,
     556             :                              previous.behavior, previous.flavor);
     557             : 
     558             :     // If we failed even receiving the message, just give up.
     559             :     if (ret != MACH_MSG_SUCCESS)
     560             :         MOZ_CRASH("MachExceptionHandler: mach_msg failed to receive a message!");
     561             : 
     562             :     // Terminate the thread if we're shutting down.
     563             :     if (request.header.msgh_id == sIDQuit)
     564             :         return;
     565             : 
     566             :     // The only other valid message ID is the one associated with the
     567             :     // EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES behavior we chose.
     568             :     if (request.header.msgh_id != sIDRequest64)
     569             :         MOZ_CRASH("MachExceptionHandler: Unexpected Message ID!");
     570             : 
     571             :     // Make sure we can understand the exception we received.
     572             :     if (request.exception != EXC_BAD_ACCESS || request.code_count != 2)
     573             :         MOZ_CRASH("MachExceptionHandler: Unexpected exception type!");
     574             : 
     575             :     // Get the address that the offending code tried to access.
     576             :     uintptr_t address = uintptr_t(request.code[1]);
     577             : 
     578             :     // If the faulting address is inside one of our protected regions, we
     579             :     // want to annotate the crash to make it stand out from the crowd.
     580             :     if (sProtectedRegions.isProtected(address)) {
     581             :         ReportCrashIfDebug("Hit MOZ_CRASH(Tried to access a protected region!)\n");
     582             :         MOZ_CRASH_ANNOTATE("MOZ_CRASH(Tried to access a protected region!)");
     583             :     }
     584             : 
     585             :     // Forward to the previous handler which may be a debugger, the unix
     586             :     // signal handler, the crash reporter or something else entirely.
     587             :     if (previous.port != MACH_PORT_NULL) {
     588             :         mach_msg_type_number_t stateCount;
     589             :         thread_state_data_t state;
     590             :         if ((uint32_t(previous.behavior) & ~MACH_EXCEPTION_CODES) != EXCEPTION_DEFAULT) {
     591             :             // If the previous handler requested thread state, get it here.
     592             :             stateCount = THREAD_STATE_MAX;
     593             :             ret = thread_get_state(request.thread.name, previous.flavor, state, &stateCount);
     594             :             if (ret != KERN_SUCCESS)
     595             :                 MOZ_CRASH("MachExceptionHandler: Could not get the thread state to forward!");
     596             :         }
     597             : 
     598             :         // Depending on the behavior of the previous handler, the forwarded
     599             :         // exception message will have a different set of fields.
     600             :         // Of particular note is that exception handlers that lack
     601             :         // MACH_EXCEPTION_CODES will get 32-bit fields even on 64-bit
     602             :         // systems. It appears that OSX simply truncates these fields.
     603             :         ExceptionRequestUnion forward;
     604             :         switch (uint32_t(previous.behavior)) {
     605             :           case EXCEPTION_DEFAULT:
     606             :              CopyExceptionRequest32(request, forward.r32);
     607             :              break;
     608             :           case EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES:
     609             :              CopyExceptionRequest64(request, forward.r64);
     610             :              break;
     611             :           case EXCEPTION_STATE:
     612             :              CopyExceptionRequestState32(request, forward.rs32,
     613             :                                          previous.flavor, stateCount, state);
     614             :              break;
     615             :           case EXCEPTION_STATE | MACH_EXCEPTION_CODES:
     616             :              CopyExceptionRequestState64(request, forward.rs64,
     617             :                                          previous.flavor, stateCount, state);
     618             :              break;
     619             :           case EXCEPTION_STATE_IDENTITY:
     620             :              CopyExceptionRequestStateIdentity32(request, forward.rsi32,
     621             :                                                  previous.flavor, stateCount, state);
     622             :              break;
     623             :           case EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES:
     624             :              CopyExceptionRequestStateIdentity64(request, forward.rsi64,
     625             :                                                  previous.flavor, stateCount, state);
     626             :              break;
     627             :           default:
     628             :              MOZ_CRASH("MachExceptionHandler: Unknown previous handler behavior!");
     629             :         }
     630             : 
     631             :         // Forward the generated message to the old port. The local and remote
     632             :         // port fields *and their rights* are swapped on arrival, so we need to
     633             :         // swap them back first.
     634             :         forward.header.msgh_bits = (request.header.msgh_bits & ~MACH_MSGH_BITS_PORTS_MASK) |
     635             :             MACH_MSGH_BITS(MACH_MSGH_BITS_LOCAL(request.header.msgh_bits),
     636             :                            MACH_MSGH_BITS_REMOTE(request.header.msgh_bits));
     637             :         forward.header.msgh_local_port = forward.header.msgh_remote_port;
     638             :         forward.header.msgh_remote_port = previous.port;
     639             :         ret = mach_msg(&forward.header, MACH_SEND_MSG, forward.header.msgh_size, 0,
     640             :                        MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
     641             :         if (ret != MACH_MSG_SUCCESS)
     642             :             MOZ_CRASH("MachExceptionHandler: Failed to forward to the previous handler!");
     643             :     } else {
     644             :         // There was no previous task-level exception handler, so defer to the
     645             :         // host level one instead. We set the return code to KERN_FAILURE to
     646             :         // indicate that we did not handle the exception.
     647             :         // The reply message ID is always the request ID + 100.
     648             :         ExceptionReply reply = {};
     649             :         reply.header.msgh_bits =
     650             :             MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request.header.msgh_bits), 0);
     651             :         reply.header.msgh_size = static_cast<mach_msg_size_t>(sizeof(reply));
     652             :         reply.header.msgh_remote_port = request.header.msgh_remote_port;
     653             :         reply.header.msgh_local_port = MACH_PORT_NULL;
     654             :         reply.header.msgh_id = request.header.msgh_id + 100;
     655             :         reply.NDR = request.NDR;
     656             :         reply.RetCode = KERN_FAILURE;
     657             :         ret = mach_msg(&reply.header, MACH_SEND_MSG, reply.header.msgh_size, 0,
     658             :                        MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
     659             :         if (ret != MACH_MSG_SUCCESS)
     660             :             MOZ_CRASH("MachExceptionHandler: Failed to forward to the host level!");
     661             :     }
     662             : }
     663             : 
     664             : static void
     665             : TerminateMachExceptionHandlerThread()
     666             : {
     667             :     // Send a simple quit message to the exception handler thread.
     668             :     mach_msg_header_t msg;
     669             :     msg.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
     670             :     msg.msgh_size = static_cast<mach_msg_size_t>(sizeof(msg));
     671             :     msg.msgh_remote_port = sMachExceptionState.current.port;
     672             :     msg.msgh_local_port = MACH_PORT_NULL;
     673             :     msg.msgh_reserved = 0;
     674             :     msg.msgh_id = sIDQuit;
     675             :     kern_return_t ret = mach_msg(&msg, MACH_SEND_MSG, sizeof(msg), 0, MACH_PORT_NULL,
     676             :                                  MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
     677             : 
     678             :     if (ret == MACH_MSG_SUCCESS)
     679             :         sMachExceptionState.handlerThread.join();
     680             :     else
     681             :         MOZ_CRASH("MachExceptionHandler: Handler thread failed to terminate!");
     682             : }
     683             : 
     684             : bool
     685             : MemoryProtectionExceptionHandler::install()
     686             : {
     687             :     MOZ_ASSERT(!sExceptionHandlerInstalled);
     688             : 
     689             :     // If the exception handler is disabled, report success anyway.
     690             :     if (MemoryProtectionExceptionHandler::isDisabled())
     691             :         return true;
     692             : 
     693             :     kern_return_t ret;
     694             :     mach_port_t task = mach_task_self();
     695             : 
     696             :     // Allocate a new exception port with receive rights.
     697             :     sMachExceptionState.current = {};
     698             :     MachExceptionParameters& current = sMachExceptionState.current;
     699             :     ret = mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &current.port);
     700             :     if (ret != KERN_SUCCESS)
     701             :         return false;
     702             : 
     703             :     // Give the new port send rights as well.
     704             :     ret = mach_port_insert_right(task, current.port, current.port, MACH_MSG_TYPE_MAKE_SEND);
     705             :     if (ret != KERN_SUCCESS) {
     706             :         mach_port_deallocate(task, current.port);
     707             :         current = {};
     708             :         return false;
     709             :     }
     710             : 
     711             :     // Start the thread that will receive the messages from our exception port.
     712             :     if (!sMachExceptionState.handlerThread.init(MachExceptionHandler)) {
     713             :         mach_port_deallocate(task, current.port);
     714             :         current = {};
     715             :         return false;
     716             :     }
     717             : 
     718             :     // Set the other properties of our new exception handler.
     719             :     current.mask = EXC_MASK_BAD_ACCESS;
     720             :     current.behavior = exception_behavior_t(EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES);
     721             :     current.flavor = THREAD_STATE_NONE;
     722             : 
     723             :     // Tell the task to use our exception handler, and save the previous one.
     724             :     sMachExceptionState.previous = {};
     725             :     MachExceptionParameters& previous = sMachExceptionState.previous;
     726             :     mach_msg_type_number_t previousCount = 1;
     727             :     ret = task_swap_exception_ports(task, current.mask, current.port, current.behavior,
     728             :                                     current.flavor, &previous.mask, &previousCount,
     729             :                                     &previous.port, &previous.behavior, &previous.flavor);
     730             :     if (ret != KERN_SUCCESS) {
     731             :         TerminateMachExceptionHandlerThread();
     732             :         mach_port_deallocate(task, current.port);
     733             :         previous = {};
     734             :         current = {};
     735             :         return false;
     736             :     }
     737             : 
     738             :     // We should have info on the previous exception handler, even if it's null.
     739             :     MOZ_ASSERT(previousCount == 1);
     740             : 
     741             :     sExceptionHandlerInstalled = true;
     742             :     return sExceptionHandlerInstalled;
     743             : }
     744             : 
     745             : void
     746             : MemoryProtectionExceptionHandler::uninstall()
     747             : {
     748             :     if (sExceptionHandlerInstalled) {
     749             :         mach_port_t task = mach_task_self();
     750             : 
     751             :         // Restore the previous exception handler.
     752             :         MachExceptionParameters& previous = sMachExceptionState.previous;
     753             :         task_set_exception_ports(task, previous.mask, previous.port,
     754             :                                  previous.behavior, previous.flavor);
     755             : 
     756             :         TerminateMachExceptionHandlerThread();
     757             : 
     758             :         // Release the Mach IPC port we used.
     759             :         mach_port_deallocate(task, sMachExceptionState.current.port);
     760             : 
     761             :         sMachExceptionState.current = {};
     762             :         sMachExceptionState.previous = {};
     763             : 
     764             :         sExceptionHandlerInstalled = false;
     765             :     }
     766             : }
     767             : 
     768             : #else
     769             : 
     770             : #error "This platform is not supported!"
     771             : 
     772             : #endif
     773             : 
     774             : } /* namespace js */

Generated by: LCOV version 1.13