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, ¤t.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 */
|