Source code

Revision control

Other Tools

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