1 : /*
2 : * Copyright (c) 2012 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : #include "native_client/src/trusted/service_runtime/osx/mach_exception_handler.h"
8 :
9 : #include <mach/mach.h>
10 : #include <mach/mach_vm.h>
11 : #include <mach/thread_status.h>
12 : #include <pthread.h>
13 : #include <stddef.h>
14 : #include <stdio.h>
15 : #include <stdlib.h>
16 :
17 : #include "native_client/src/include/nacl_macros.h"
18 : #include "native_client/src/include/portability.h"
19 : #include "native_client/src/shared/platform/nacl_check.h"
20 : #include "native_client/src/shared/platform/nacl_log.h"
21 : #include "native_client/src/trusted/service_runtime/arch/sel_ldr_arch.h"
22 : #include "native_client/src/trusted/service_runtime/nacl_app.h"
23 : #include "native_client/src/trusted/service_runtime/nacl_app_thread.h"
24 : #include "native_client/src/trusted/service_runtime/nacl_config.h"
25 : #include "native_client/src/trusted/service_runtime/nacl_exc.h"
26 : #include "native_client/src/trusted/service_runtime/nacl_exception.h"
27 : #include "native_client/src/trusted/service_runtime/nacl_globals.h"
28 : #include "native_client/src/trusted/service_runtime/nacl_switch_to_app.h"
29 : #include "native_client/src/trusted/service_runtime/nacl_tls.h"
30 : #include "native_client/src/trusted/service_runtime/osx/crash_filter.h"
31 : #include "native_client/src/trusted/service_runtime/osx/mach_thread_map.h"
32 : #include "native_client/src/trusted/service_runtime/sel_ldr.h"
33 : #include "native_client/src/trusted/service_runtime/sel_rt.h"
34 :
35 : #if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86
36 :
37 :
38 : /*
39 : * MIG generated message pump from /usr/include/mach/exc.defs
40 : * Tweaked to place in an isolated namespace.
41 : */
42 : boolean_t nacl_exc_server(
43 : mach_msg_header_t *InHeadP,
44 : mach_msg_header_t *OutHeadP);
45 :
46 :
47 : /* Exception types to intercept. */
48 : #define NACL_MACH_EXCEPTION_MASK \
49 : (EXC_MASK_BAD_ACCESS | \
50 : EXC_MASK_BAD_INSTRUCTION | \
51 : EXC_MASK_ARITHMETIC | \
52 : EXC_MASK_BREAKPOINT)
53 :
54 : /*
55 : * <mach/mach_types.defs> says that the arrays in MachExceptionParameters have
56 : * room for 32 elements. EXC_TYPES_COUNT, a smaller value, has grown over
57 : * time in major SDK releases. task_get_exception_ports expects there to be
58 : * room for all 32 and is not bounded by EXC_TYPES_COUNT or by the initial
59 : * value of its handler_count argument, contrary to its documentation.
60 : * Provide a constant matching its limit to avoid potential buffer overflow.
61 : */
62 : #define NACL_MAX_EXCEPTION_PORTS 32
63 :
64 : struct MachExceptionParameters {
65 : mach_msg_type_number_t count;
66 : exception_mask_t masks[NACL_MAX_EXCEPTION_PORTS];
67 : mach_port_t ports[NACL_MAX_EXCEPTION_PORTS];
68 : exception_behavior_t behaviors[NACL_MAX_EXCEPTION_PORTS];
69 : thread_state_flavor_t flavors[NACL_MAX_EXCEPTION_PORTS];
70 : };
71 :
72 : struct MachExceptionHandlerData {
73 : struct MachExceptionParameters old_ports;
74 : mach_port_t exception_port;
75 : };
76 :
77 : /*
78 : * This is global because MIG does not provide a mechanism to associate
79 : * user data with a handler. We could 'sed' the output, but that might be
80 : * brittle. This is moot as the handler is task wide. Revisit if we switch to
81 : * a per-thread handler.
82 : */
83 : static struct MachExceptionHandlerData *g_MachExceptionHandlerData = 0;
84 :
85 :
86 91 : static int ExceptionCodeToNaClSignalNumber(exception_type_t exception) {
87 91 : switch (exception) {
88 : case EXC_BREAKPOINT:
89 41 : return NACL_ABI_SIGTRAP;
90 : default:
91 50 : return NACL_ABI_SIGSEGV;
92 : }
93 91 : }
94 :
95 91 : static void FireDebugStubEvent(int pipe_fd) {
96 91 : char buf = 0;
97 91 : if (write(pipe_fd, &buf, sizeof(buf)) != sizeof(buf)) {
98 0 : NaClLog(LOG_FATAL, "FireDebugStubEvent: Can't send debug stub event\n");
99 0 : }
100 91 : }
101 :
102 : #if NACL_BUILD_SUBARCH == 32
103 :
104 : #define NATIVE_x86_THREAD_STATE x86_THREAD_STATE32
105 : #define X86_REG_SP(regs) ((regs)->uts.ts32.__esp)
106 : #define X86_REG_IP(regs) ((regs)->uts.ts32.__eip)
107 : #define X86_REG_FLAGS(regs) ((regs)->uts.ts32.__eflags)
108 :
109 : #elif NACL_BUILD_SUBARCH == 64
110 :
111 : #define NATIVE_x86_THREAD_STATE x86_THREAD_STATE64
112 : #define X86_REG_SP(regs) ((regs)->uts.ts64.__rsp)
113 : #define X86_REG_IP(regs) ((regs)->uts.ts64.__rip)
114 : #define X86_REG_FLAGS(regs) ((regs)->uts.ts64.__rflags)
115 :
116 : #endif /* NACL_BUILD_SUBARCH */
117 :
118 : enum HandleExceptionResult {
119 : kHandleExceptionUnhandled = 0,
120 : kHandleExceptionHandled_SetState,
121 : kHandleExceptionHandled_DontSetState
122 : };
123 :
124 15016 : static enum HandleExceptionResult HandleException(mach_port_t thread_port,
125 15016 : exception_type_t exception,
126 15016 : int *is_untrusted,
127 15016 : x86_thread_state_t *regs) {
128 15016 : kern_return_t result;
129 15016 : uint32_t nacl_thread_index;
130 15016 : struct NaClApp *nap;
131 15016 : struct NaClAppThread *natp;
132 15016 : struct NaClExceptionFrame frame;
133 15016 : uint32_t frame_addr_user;
134 15016 : uintptr_t frame_addr_sys;
135 15016 : struct NaClSignalContext sig_context;
136 : #if NACL_BUILD_SUBARCH == 32
137 : uint16_t trusted_cs = NaClGetGlobalCs();
138 : uint16_t trusted_ds = NaClGetGlobalDs();
139 : #endif
140 :
141 45048 : CHECK(regs->tsh.flavor == NATIVE_x86_THREAD_STATE);
142 :
143 : /* Assume untrusted crash until we know otherwise. */
144 15016 : *is_untrusted = TRUE;
145 :
146 : #if NACL_BUILD_SUBARCH == 32
147 : /*
148 : * We can get the thread index from the segment selector used for TLS
149 : * from %gs >> 3.
150 : * TODO(bradnelson): Migrate that knowledge to a single shared location.
151 : */
152 : nacl_thread_index = regs->uts.ts32.__gs >> 3;
153 : #elif NACL_BUILD_SUBARCH == 64
154 15016 : nacl_thread_index = NaClGetThreadIndexForMachThread(thread_port);
155 :
156 15016 : if (nacl_thread_index == NACL_TLS_INDEX_INVALID) {
157 1 : *is_untrusted = FALSE;
158 1 : return kHandleExceptionUnhandled;
159 : }
160 : #endif
161 :
162 15015 : natp = NaClAppThreadGetFromIndex(nacl_thread_index);
163 15015 : if (natp == NULL) {
164 0 : *is_untrusted = FALSE;
165 0 : return kHandleExceptionUnhandled;
166 : }
167 15015 : nap = natp->nap;
168 :
169 : /*
170 : * TODO(bradnelson): For x86_32, this makes the potentially false assumption
171 : * that cs is the last thing to change when switching into untrusted
172 : * code. We need tests to vet this.
173 : */
174 15015 : *is_untrusted = NaClMachThreadStateIsInUntrusted(regs, nacl_thread_index);
175 :
176 : /*
177 : * If trusted code accidentally jumped to untrusted code, don't let the
178 : * untrusted exception handler take over.
179 : */
180 30029 : if (*is_untrusted &&
181 : (natp->suspend_state & NACL_APP_THREAD_UNTRUSTED) == 0) {
182 0 : *is_untrusted = 0;
183 0 : return kHandleExceptionUnhandled;
184 : }
185 :
186 15015 : if (!*is_untrusted) {
187 : #if NACL_BUILD_SUBARCH == 32
188 : /*
189 : * If we are single-stepping, allow NaClSwitchRemainingRegsViaECX()
190 : * to continue in order to restore control to untrusted code.
191 : */
192 : if (exception == EXC_BREAKPOINT &&
193 : (X86_REG_FLAGS(regs) & NACL_X86_TRAP_FLAG) != 0 &&
194 : X86_REG_IP(regs) >= (uintptr_t) NaClSwitchRemainingRegsViaECX &&
195 : X86_REG_IP(regs) < (uintptr_t) NaClSwitchRemainingRegsAsmEnd) {
196 : return kHandleExceptionHandled_DontSetState;
197 : }
198 : #endif
199 1 : return kHandleExceptionUnhandled;
200 : }
201 :
202 15014 : if (nap->enable_faulted_thread_queue) {
203 : #if NACL_BUILD_SUBARCH == 32
204 : /*
205 : * If we are single-stepping, step through until we reach untrusted code.
206 : */
207 : if (exception == EXC_BREAKPOINT &&
208 : (X86_REG_FLAGS(regs) & NACL_X86_TRAP_FLAG) != 0) {
209 : if (X86_REG_IP(regs) >= nap->all_regs_springboard.start_addr &&
210 : X86_REG_IP(regs) < nap->all_regs_springboard.end_addr) {
211 : return kHandleExceptionHandled_DontSetState;
212 : }
213 : /*
214 : * Step through the instruction we have been asked to restore
215 : * control to.
216 : */
217 : if (X86_REG_IP(regs) == natp->user.gs_segment.new_prog_ctr) {
218 : return kHandleExceptionHandled_DontSetState;
219 : }
220 : }
221 : #endif
222 :
223 : /*
224 : * Increment the kernel's thread suspension count so that the
225 : * thread remains suspended after we return.
226 : */
227 91 : result = thread_suspend(thread_port);
228 91 : if (result != KERN_SUCCESS) {
229 0 : NaClLog(LOG_FATAL, "HandleException: thread_suspend() call failed\n");
230 0 : }
231 : /*
232 : * Notify the handler running on another thread. This must happen
233 : * after the thread_suspend() call, otherwise the handler might
234 : * receive the notification and attempt to decrement the thread's
235 : * suspension count before we have incremented it.
236 : */
237 91 : natp->fault_signal = ExceptionCodeToNaClSignalNumber(exception);
238 91 : AtomicIncrement(&nap->faulted_thread_count, 1);
239 91 : FireDebugStubEvent(nap->faulted_thread_fd_write);
240 91 : return kHandleExceptionHandled_DontSetState;
241 : }
242 :
243 14927 : if (exception != EXC_BAD_ACCESS &&
244 : exception != EXC_BAD_INSTRUCTION &&
245 : exception != EXC_ARITHMETIC) {
246 0 : return kHandleExceptionUnhandled;
247 : }
248 :
249 14923 : NaClSignalContextFromMacThreadState(&sig_context, regs);
250 14923 : if (!NaClSignalCheckSandboxInvariants(&sig_context, natp)) {
251 0 : return kHandleExceptionUnhandled;
252 : }
253 :
254 : /* Don't handle if no exception handler is set. */
255 14923 : if (nap->exception_handler == 0) {
256 0 : return kHandleExceptionUnhandled;
257 : }
258 :
259 : /* Don't handle it if the exception flag is set. */
260 14923 : if (natp->exception_flag) {
261 0 : return kHandleExceptionUnhandled;
262 : }
263 : /* Set the flag. */
264 14923 : natp->exception_flag = 1;
265 :
266 : /* Get location of exception stack frame. */
267 14923 : if (natp->exception_stack) {
268 41 : frame_addr_user = natp->exception_stack;
269 41 : } else {
270 : /* If not set default to user stack. */
271 14882 : frame_addr_user = X86_REG_SP(regs) - NACL_STACK_RED_ZONE;
272 : }
273 :
274 : /* Align stack frame properly. */
275 14923 : frame_addr_user -=
276 : sizeof(struct NaClExceptionFrame) - NACL_STACK_PAD_BELOW_ALIGN;
277 14923 : frame_addr_user &= ~NACL_STACK_ALIGN_MASK;
278 14923 : frame_addr_user -= NACL_STACK_PAD_BELOW_ALIGN;
279 :
280 : /* Convert from user to system space. */
281 14923 : frame_addr_sys = NaClUserToSysAddrRange(
282 : nap, frame_addr_user, sizeof(struct NaClExceptionFrame));
283 14923 : if (frame_addr_sys == kNaClBadAddress) {
284 0 : return kHandleExceptionUnhandled;
285 : }
286 :
287 : /* Set up the stack frame for the handler invocation. */
288 14923 : NaClSignalSetUpExceptionFrame(
289 : &frame, &sig_context,
290 : frame_addr_user + offsetof(struct NaClExceptionFrame, context));
291 :
292 : /*
293 : * Write the stack frame into untrusted address space. We do not
294 : * write to the memory directly because that will fault if the
295 : * destination location is not writable. Faulting is OK for NaCl
296 : * syscalls, but here we do not want to trigger an exception while
297 : * in the exception handler. The overhead of using a Mach system
298 : * call to write to memory, 2-3 microseconds on a 2.3GHz 4-core i7,
299 : * is acceptable here.
300 : */
301 14923 : result = mach_vm_write(mach_task_self(), frame_addr_sys,
302 : (uintptr_t) &frame, sizeof(frame));
303 14923 : if (result != KERN_SUCCESS) {
304 0 : return kHandleExceptionUnhandled;
305 : }
306 :
307 : /* Set up thread context to resume at handler. */
308 : /* TODO(bradnelson): put all registers in some default state. */
309 : #if NACL_BUILD_SUBARCH == 32
310 : /*
311 : * Put registers in right place to land at NaClSwitchNoSSEViaECX
312 : * This is required because:
313 : * - For an unknown reason thread_set_state resets %cs to the default
314 : * value, even when set to something else, in current XNU versions.
315 : * - An examination of the XNU sources indicates
316 : * that setting the code which state the thread state resets
317 : * %cs, %ds, %es, %ss to their default values in some early versions.
318 : * (For instance: xnu-792.6.22/osfmk/i386/pcb.c:616)
319 : * This precludes going directly to the untrusted handler.
320 : * Instead we call a variant of NaClSwitchNoSSE which takes a pointer
321 : * to the thread user context in %ecx.
322 : */
323 : natp->user.new_prog_ctr = nap->exception_handler;
324 : natp->user.stack_ptr = frame_addr_user;
325 : X86_REG_IP(regs) = (uint32_t) &NaClSwitchNoSSEViaECX;
326 : regs->uts.ts32.__cs = trusted_cs;
327 : regs->uts.ts32.__ecx = (uint32_t) &natp->user;
328 : regs->uts.ts32.__ds = trusted_ds;
329 : regs->uts.ts32.__es = trusted_ds; /* just for good measure */
330 : regs->uts.ts32.__ss = trusted_ds; /* just for good measure */
331 : #elif NACL_BUILD_SUBARCH == 64
332 14923 : X86_REG_IP(regs) = NaClUserToSys(nap, nap->exception_handler);
333 14923 : X86_REG_SP(regs) = frame_addr_sys;
334 :
335 : /* Argument 1 */
336 14923 : regs->uts.ts64.__rdi = frame_addr_user +
337 : offsetof(struct NaClExceptionFrame, context);
338 : #endif
339 14923 : X86_REG_FLAGS(regs) &= ~NACL_X86_DIRECTION_FLAG;
340 :
341 : /* Return success, and resume the thread. */
342 14923 : return kHandleExceptionHandled_SetState;
343 15016 : }
344 :
345 :
346 : static kern_return_t ForwardException(
347 2 : struct MachExceptionHandlerData *data,
348 2 : mach_port_t thread,
349 2 : mach_port_t task,
350 2 : exception_type_t exception,
351 2 : exception_data_t code,
352 2 : mach_msg_type_number_t code_count) {
353 2 : unsigned int i;
354 2 : mach_port_t target_port;
355 2 : exception_behavior_t target_behavior;
356 2 : kern_return_t kr;
357 :
358 : /* Find a port with a mask matching this exception to pass it on to. */
359 4 : for (i = 0; i < data->old_ports.count; ++i) {
360 2 : if (data->old_ports.masks[i] & (1 << exception)) {
361 2 : break;
362 : }
363 0 : }
364 2 : if (i == data->old_ports.count) {
365 0 : return KERN_FAILURE;
366 : }
367 2 : target_port = data->old_ports.ports[i];
368 2 : target_behavior = data->old_ports.behaviors[i];
369 :
370 : /*
371 : * By default a null exception port is registered.
372 : * As it is unclear how to forward to this, we should just fail.
373 : */
374 2 : if (target_port == MACH_PORT_NULL) {
375 0 : return KERN_FAILURE;
376 : }
377 :
378 : /*
379 : * Only support EXCEPTION_DEFAULT, as we only plan to inter-operate with
380 : * Breakpad for now.
381 : */
382 6 : CHECK(target_behavior == EXCEPTION_DEFAULT);
383 :
384 : /* Forward the exception. */
385 0 : kr = exception_raise(target_port, thread, task, exception, code, code_count);
386 :
387 : /*
388 : * Don't set the thread state. See the comment in
389 : * nacl_catch_exception_raise_state_identity. Transforming KERN_SUCCESS to
390 : * MACH_RCV_PORT_DIED is necessary because the exception was delivered with
391 : * behavior EXCEPTION_STATE_IDENTITY to
392 : * nacl_catch_exception_raise_state_identity, but it was forwarded to a
393 : * handler with behavior EXCEPTION_DEFAULT via exception_raise, and such
394 : * handlers don't provide a new thread state. They can set a new thread
395 : * state on their own by calling thread_set_state. Returning KERN_SUCCESS
396 : * would allow the old state passed to
397 : * nacl_catch_exception_raise_state_identity to overwrite any new state set
398 : * by the handler that the exception was forwarded to via exception_raise.
399 : */
400 0 : return kr == KERN_SUCCESS ? MACH_RCV_PORT_DIED : kr;
401 0 : }
402 :
403 :
404 : kern_return_t nacl_catch_exception_raise(
405 0 : mach_port_t exception_port,
406 0 : mach_port_t thread,
407 0 : mach_port_t task,
408 0 : exception_type_t exception,
409 0 : exception_data_t code,
410 0 : mach_msg_type_number_t code_count) {
411 : /* MIG generated code expects this, but should never be called. */
412 0 : UNREFERENCED_PARAMETER(exception_port);
413 0 : UNREFERENCED_PARAMETER(thread);
414 0 : UNREFERENCED_PARAMETER(task);
415 0 : UNREFERENCED_PARAMETER(exception);
416 0 : UNREFERENCED_PARAMETER(code);
417 0 : UNREFERENCED_PARAMETER(code_count);
418 0 : NaClLog(LOG_FATAL, "nacl_catch_exception_raise: "
419 : "Unrequested message received.\n");
420 0 : return KERN_FAILURE;
421 : }
422 :
423 : kern_return_t nacl_catch_exception_raise_state(
424 0 : mach_port_t exception_port,
425 0 : exception_type_t exception,
426 0 : const exception_data_t code,
427 0 : mach_msg_type_number_t code_count,
428 0 : int *flavor,
429 0 : const thread_state_t old_state,
430 0 : mach_msg_type_number_t old_state_count,
431 0 : thread_state_t new_state,
432 0 : mach_msg_type_number_t *new_state_count) {
433 : /* MIG generated code expects this, but should never be called. */
434 0 : UNREFERENCED_PARAMETER(exception_port);
435 0 : UNREFERENCED_PARAMETER(exception);
436 0 : UNREFERENCED_PARAMETER(code);
437 0 : UNREFERENCED_PARAMETER(code_count);
438 0 : UNREFERENCED_PARAMETER(flavor);
439 0 : UNREFERENCED_PARAMETER(old_state);
440 0 : UNREFERENCED_PARAMETER(old_state_count);
441 0 : UNREFERENCED_PARAMETER(new_state);
442 0 : UNREFERENCED_PARAMETER(new_state_count);
443 0 : NaClLog(LOG_FATAL, "nacl_catch_exception_raise_state: "
444 : "Unrequested message received.\n");
445 0 : return KERN_FAILURE;
446 : }
447 :
448 2 : static void PrintCrashMessage(int exception_type, int is_untrusted,
449 2 : x86_thread_state_t *regs) {
450 2 : char buf[128];
451 2 : int len = snprintf(
452 : buf, sizeof(buf),
453 : "\n** Mach exception %d from %s code: pc=%" NACL_PRIx64 "\n",
454 : exception_type,
455 : is_untrusted ? "untrusted" : "trusted",
456 : (uint64_t) X86_REG_IP(regs));
457 2 : write(2, buf, len);
458 2 : }
459 :
460 : kern_return_t nacl_catch_exception_raise_state_identity (
461 15016 : mach_port_t exception_port,
462 15016 : mach_port_t thread,
463 15016 : mach_port_t task,
464 15016 : exception_type_t exception,
465 15016 : exception_data_t code,
466 15016 : mach_msg_type_number_t code_count,
467 15016 : int *flavor,
468 15016 : thread_state_t old_state,
469 15016 : mach_msg_type_number_t old_state_count,
470 15016 : thread_state_t new_state,
471 15016 : mach_msg_type_number_t *new_state_count) {
472 15016 : int rv;
473 15016 : int is_untrusted;
474 :
475 60064 : DCHECK(exception_port == g_MachExceptionHandlerData->exception_port);
476 :
477 45048 : CHECK(*flavor == x86_THREAD_STATE);
478 45048 : CHECK(old_state_count == x86_THREAD_STATE_COUNT);
479 45048 : CHECK(*new_state_count >= x86_THREAD_STATE_COUNT);
480 15016 : *new_state_count = x86_THREAD_STATE_COUNT;
481 45048 : memcpy(new_state, old_state, x86_THREAD_STATE_COUNT * sizeof(natural_t));
482 :
483 : /* Check if we want to handle this exception. */
484 15016 : rv = HandleException(thread,
485 : exception,
486 : &is_untrusted,
487 : (x86_thread_state_t *) new_state);
488 15016 : if (rv == kHandleExceptionHandled_SetState) {
489 14923 : return KERN_SUCCESS;
490 93 : } else if (rv == kHandleExceptionHandled_DontSetState) {
491 : /*
492 : * To avoid setting the thread state, return MACH_RCV_PORT_DIED. In
493 : * exception_deliver, the kernel will only set the thread state if
494 : * exception_raise_state returns MACH_MSG_SUCCESS (== KERN_SUCCESS), so
495 : * another value is needed to avoid having it set the state. KERN_SUCCESS
496 : * and MACH_RCV_PORT_DIED are the only two values that don't result in task
497 : * termination in exception_triage. See 10.8.2
498 : * xnu-2050.18.24/osfmk/kern/exception.c.
499 : *
500 : * This is done instead of letting the kernel set the new thread state to
501 : * be the same as the old state because the kernel resets %cs to the
502 : * default value when setting a thread's state. This behavior is explained
503 : * in more detail in HandleException.
504 : */
505 91 : return MACH_RCV_PORT_DIED;
506 : }
507 :
508 2 : PrintCrashMessage(exception, is_untrusted, (x86_thread_state_t *) old_state);
509 :
510 : /*
511 : * Don't forward if the crash is untrusted, but unhandled.
512 : * (As we don't want things like Breakpad handling the crash.)
513 : */
514 2 : if (is_untrusted) {
515 0 : return KERN_FAILURE;
516 : }
517 :
518 : /* Forward on the exception to the old set of ports. */
519 2 : return ForwardException(
520 : g_MachExceptionHandlerData, thread, task, exception, code, code_count);
521 15014 : }
522 :
523 31 : static void *MachExceptionHandlerThread(void *arg) {
524 31 : struct MachExceptionHandlerData *data =
525 : (struct MachExceptionHandlerData *) arg;
526 31 : kern_return_t result;
527 : union {
528 : mach_msg_header_t header;
529 : union __RequestUnion__nacl_exc_subsystem nacl_exc_subsystem_request;
530 31 : } request;
531 : union {
532 : mach_msg_header_t header;
533 : union __ReplyUnion__nacl_exc_subsystem nacl_exc_subsystem_reply;
534 31 : } reply;
535 :
536 31 : for (;;) {
537 15016 : result = mach_msg(&request.header, MACH_RCV_MSG | MACH_RCV_LARGE, 0,
538 : sizeof(request), data->exception_port,
539 : MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
540 15016 : if (result != MACH_MSG_SUCCESS) {
541 0 : goto failure;
542 : }
543 15014 : if (!nacl_exc_server(&request.header, &reply.header)) {
544 0 : goto failure;
545 : }
546 15014 : result = mach_msg(&reply.header, MACH_SEND_MSG,
547 : reply.header.msgh_size, 0,
548 : MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
549 15014 : if (result != MACH_MSG_SUCCESS) {
550 0 : goto failure;
551 : }
552 15014 : }
553 :
554 : failure:
555 0 : free(data);
556 :
557 0 : return 0;
558 : }
559 :
560 31 : static int InstallHandler(struct MachExceptionHandlerData *data) {
561 31 : kern_return_t result;
562 31 : mach_port_t current_task = mach_task_self();
563 31 : unsigned int i;
564 :
565 : /* Capture old handler info. */
566 31 : data->old_ports.count = NACL_MAX_EXCEPTION_PORTS;
567 31 : result = task_get_exception_ports(current_task, NACL_MACH_EXCEPTION_MASK,
568 : data->old_ports.masks,
569 : &data->old_ports.count,
570 : data->old_ports.ports,
571 : data->old_ports.behaviors,
572 : data->old_ports.flavors);
573 31 : if (result != KERN_SUCCESS) {
574 0 : return result;
575 : }
576 :
577 : /*
578 : * We only handle forwarding of the EXCEPTION_DEFAULT behavior (all that
579 : * Breakpad needs). Check that all old handlers are either of this behavior
580 : * type or null.
581 : *
582 : * NOTE: Ideally we might also require a particular behavior for null
583 : * exception ports. Unfortunately, testing indicates that while on
584 : * OSX 10.6 / 10.7 the behavior for such a null port is set to 0,
585 : * on OSX 10.5 it is set to 0x803fe956 (on a given run).
586 : * As tasks inherit exception ports from their parents, this may be
587 : * an uninitialized value carried along from a parent.
588 : * http://opensource.apple.com/source/xnu/xnu-1228.0.2/osfmk/kern/ipc_tt.c
589 : * For now, we will ignore the behavior when the port is null.
590 : */
591 124 : for (i = 0; i < data->old_ports.count; ++i) {
592 122 : CHECK(data->old_ports.behaviors[i] == EXCEPTION_DEFAULT ||
593 : data->old_ports.ports[i] == MACH_PORT_NULL);
594 31 : }
595 :
596 : /* TODO(bradnelson): decide if we should set the exception port per thread. */
597 : /* Direct all task exceptions to new exception port. */
598 31 : result = task_set_exception_ports(current_task, NACL_MACH_EXCEPTION_MASK,
599 : data->exception_port,
600 : EXCEPTION_STATE_IDENTITY,
601 : x86_THREAD_STATE);
602 31 : if (result != KERN_SUCCESS) {
603 0 : return result;
604 : }
605 :
606 31 : return KERN_SUCCESS;
607 31 : }
608 :
609 : int NaClInterceptMachExceptions(void) {
610 31 : struct MachExceptionHandlerData *data;
611 31 : kern_return_t result;
612 31 : mach_port_t current_task;
613 31 : pthread_attr_t attr;
614 31 : int thread_result;
615 31 : pthread_t exception_handler_thread;
616 :
617 31 : current_task = mach_task_self();
618 :
619 : /* Allocate structure to share with exception handler thread. */
620 31 : data = (struct MachExceptionHandlerData *) calloc(1, sizeof(*data));
621 31 : if (data == NULL) {
622 0 : goto failure;
623 : }
624 31 : g_MachExceptionHandlerData = data;
625 31 : data->exception_port = MACH_PORT_NULL;
626 :
627 : /* Allocate port to receive exceptions. */
628 31 : result = mach_port_allocate(current_task, MACH_PORT_RIGHT_RECEIVE,
629 : &data->exception_port);
630 31 : if (result != KERN_SUCCESS) {
631 0 : goto failure;
632 : }
633 :
634 : /* Add the right to send. */
635 31 : result = mach_port_insert_right(current_task,
636 : data->exception_port, data->exception_port,
637 : MACH_MSG_TYPE_MAKE_SEND);
638 31 : if (result != KERN_SUCCESS) {
639 0 : goto failure;
640 : }
641 :
642 : /* Install handler. */
643 31 : result = InstallHandler(data);
644 31 : if (result != KERN_SUCCESS) {
645 0 : goto failure;
646 : }
647 :
648 : /* Create handler thread. */
649 31 : pthread_attr_init(&attr);
650 31 : pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
651 31 : thread_result = pthread_create(&exception_handler_thread, &attr,
652 : &MachExceptionHandlerThread, data);
653 31 : pthread_attr_destroy(&attr);
654 31 : if (thread_result) {
655 0 : goto failure;
656 : }
657 :
658 31 : return TRUE;
659 :
660 : failure:
661 0 : if (data) {
662 0 : if (MACH_PORT_NULL != data->exception_port) {
663 0 : mach_port_deallocate(current_task, data->exception_port);
664 0 : }
665 0 : free(data);
666 0 : }
667 0 : return FALSE;
668 31 : }
669 :
670 : #endif /* NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 */
|