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