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 <stdio.h>
14 : #include <stdlib.h>
15 :
16 : #include "gen/native_client/src/trusted/service_runtime/nacl_exc.h"
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_globals.h"
26 : #include "native_client/src/trusted/service_runtime/nacl_switch_to_app.h"
27 : #include "native_client/src/trusted/service_runtime/sel_ldr.h"
28 : #include "native_client/src/trusted/service_runtime/sel_rt.h"
29 :
30 : /* Only handle x86_32 for now. */
31 : #if NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32
32 :
33 :
34 : /*
35 : * MIG generated message pump from /usr/include/mach/exc.defs
36 : * Tweaked to place in an isolated namespace.
37 : */
38 : boolean_t nacl_exc_server(
39 : mach_msg_header_t *InHeadP,
40 : mach_msg_header_t *OutHeadP);
41 :
42 :
43 : /* Exception types to intercept. */
44 : #define NACL_MACH_EXCEPTION_MASK EXC_MASK_BAD_ACCESS
45 : /*
46 : * TODO(bradnelson): eventually consider these too:
47 : * EXC_MASK_BAD_INSTRUCTION
48 : * EXC_MASK_ARITHMETIC
49 : * EXC_MASK_BREAKPOINT
50 : */
51 :
52 : /* TODO(bradnelson): merge this into a common location. */
53 : struct ExceptionFrame {
54 : uint32_t return_addr;
55 : uint32_t prog_ctr;
56 : uint32_t stack_ptr;
57 : };
58 :
59 : struct MachExceptionParameters {
60 : mach_msg_type_number_t count;
61 : exception_mask_t masks[EXC_TYPES_COUNT];
62 : mach_port_t ports[EXC_TYPES_COUNT];
63 : exception_behavior_t behaviors[EXC_TYPES_COUNT];
64 : thread_state_flavor_t flavors[EXC_TYPES_COUNT];
65 : };
66 :
67 : struct MachExceptionHandlerData {
68 : struct MachExceptionParameters old_ports;
69 : mach_port_t exception_port;
70 : };
71 :
72 : /*
73 : * This is global because MIG does not provide a mechanism to associate
74 : * user data with a handler. We could 'sed' the output, but that might be
75 : * brittle. This is moot as the handler is task wide. Revisit if we switch to
76 : * a per-thread handler.
77 : */
78 : static struct MachExceptionHandlerData *g_MachExceptionHandlerData = 0;
79 :
80 :
81 : static int HandleException(mach_port_t thread_port,
82 1 : exception_type_t exception, int *is_untrusted) {
83 : mach_msg_type_number_t size;
84 : x86_thread_state_t regs;
85 : kern_return_t result;
86 1 : uint16_t trusted_cs = NaClGetGlobalCs();
87 1 : uint16_t trusted_ds = NaClGetGlobalDs();
88 : uint32_t nacl_thread_index;
89 : struct NaClApp *nap;
90 : struct NaClAppThread *natp;
91 : struct ExceptionFrame frame;
92 : uintptr_t frame_addr_user;
93 : uintptr_t frame_addr_sys;
94 :
95 : /* Assume untrusted crash until we know otherwise. */
96 1 : *is_untrusted = TRUE;
97 :
98 : /* Capture the register state of the 'excepting' thread. */
99 1 : size = sizeof(regs) / sizeof(natural_t);
100 1 : result = thread_get_state(thread_port, x86_THREAD_STATE,
101 : (void *) ®s, &size);
102 1 : if (result != KERN_SUCCESS) {
103 0 : return 0;
104 : }
105 :
106 : /*
107 : * If trusted_cs is 0 (which is not a usable segment selector), the
108 : * sandbox has not been initialised yet, so there can be no untrusted
109 : * code running.
110 : */
111 1 : if (trusted_cs == 0) {
112 1 : *is_untrusted = FALSE;
113 1 : return 0;
114 : }
115 :
116 : /*
117 : * If the current code segment is the trusted one, we aren't in the
118 : * sandbox.
119 : * TODO(bradnelson): This makes the potentially false assumption that cs is
120 : * the last thing to change when switching into untrusted code. We need
121 : * tests to vet this.
122 : */
123 0 : if (regs.uts.ts32.__cs == trusted_cs) {
124 0 : *is_untrusted = FALSE;
125 0 : return 0;
126 : }
127 :
128 : /* Ignore all but bad accesses for now. */
129 0 : if (exception != EXC_BAD_ACCESS) {
130 0 : return 0;
131 : }
132 :
133 : /*
134 : * We can get the thread index from the segment selector used for TLS
135 : * from %gs >> 3.
136 : * TODO(bradnelson): Migrate that knowledge to a single shared location.
137 : */
138 0 : nacl_thread_index = regs.uts.ts32.__gs >> 3;
139 0 : natp = nacl_thread[nacl_thread_index];
140 0 : nap = natp->nap;
141 :
142 : /* Don't handle it if the exception flag is set. */
143 0 : if (natp->user.exception_flag) {
144 0 : return 0;
145 : }
146 : /* Set the flag. */
147 0 : natp->user.exception_flag = 1;
148 :
149 : /* Don't handle if no exception handler is set. */
150 0 : if (nap->exception_handler == 0) {
151 0 : return 0;
152 : }
153 :
154 : /* Get location of exception stack frame. */
155 0 : if (natp->user.exception_stack) {
156 0 : frame_addr_user = natp->user.exception_stack;
157 : } else {
158 : /* If not set default to user stack. */
159 0 : frame_addr_user = regs.uts.ts32.__esp;
160 : }
161 :
162 : /* Align stack frame properly. */
163 0 : frame_addr_user -= sizeof(struct ExceptionFrame) - NACL_STACK_PAD_BELOW_ALIGN;
164 0 : frame_addr_user &= ~NACL_STACK_ALIGN_MASK;
165 0 : frame_addr_user -= NACL_STACK_PAD_BELOW_ALIGN;
166 :
167 : /* Convert from user to system space. */
168 0 : frame_addr_sys = NaClUserToSysAddrRange(
169 : nap, frame_addr_user, sizeof(struct ExceptionFrame));
170 0 : if (frame_addr_sys == kNaClBadAddress) {
171 0 : return 0;
172 : }
173 :
174 : /* Set up the stack frame for the handler invocation. */
175 0 : frame.return_addr = 0;
176 0 : frame.prog_ctr = regs.uts.ts32.__eip;
177 0 : frame.stack_ptr = regs.uts.ts32.__esp;
178 :
179 : /*
180 : * Write the stack frame into untrusted address space. We do not
181 : * write to the memory directly because that will fault if the
182 : * destination location is not writable. Faulting is OK for NaCl
183 : * syscalls, but here we do not want to trigger an exception while
184 : * in the exception handler. The overhead of using a Mach system
185 : * call to write to memory is acceptable here.
186 : */
187 0 : result = mach_vm_write(mach_task_self(), frame_addr_sys,
188 : (uintptr_t) &frame, sizeof(frame));
189 0 : if (result != KERN_SUCCESS) {
190 0 : return 0;
191 : }
192 :
193 : /* Set up thread context to resume at handler. */
194 0 : natp->user.new_prog_ctr = nap->exception_handler;
195 0 : natp->user.stack_ptr.ptr_32.ptr = frame_addr_user;
196 : /*
197 : * Preserve %ebp for consistency with x86-32 Linux and Windows, and
198 : * because it is needed for doing stack backtraces.
199 : * TODO(mseaborn): Save %ebp in the exception frame rather than
200 : * preserving it.
201 : */
202 0 : natp->user.frame_ptr.ptr_32.ptr = regs.uts.ts32.__ebp;
203 :
204 : /*
205 : * Put registers in right place to land at NaClSwitchNoSSEViaECX
206 : * This is required because:
207 : * - For an unknown reason thread_set_state resets %cs to the default
208 : * value, even when set to something else, in current XNU versions.
209 : * - An examination of the XNU sources indicates
210 : * that setting the code which state the thread state resets
211 : * %cs, %ds, %es, %ss to their default values in some early versions.
212 : * (For instance: xnu-792.6.22/osfmk/i386/pcb.c:616)
213 : * This precludes going directly to the untrusted handler.
214 : * Instead we call a variant of NaClSwitchNoSSE which takes a pointer
215 : * to the thread user context in %ecx.
216 : */
217 0 : regs.uts.ts32.__eip = (uint32_t) &NaClSwitchNoSSEViaECX;
218 0 : regs.uts.ts32.__cs = trusted_cs;
219 0 : regs.uts.ts32.__ecx = (uint32_t) &natp->user;
220 0 : regs.uts.ts32.__ds = trusted_ds;
221 0 : regs.uts.ts32.__es = trusted_ds; /* just for good measure */
222 0 : regs.uts.ts32.__ss = trusted_ds; /* just for good measure */
223 0 : regs.uts.ts32.__eflags &= ~NACL_X86_DIRECTION_FLAG;
224 0 : result = thread_set_state(thread_port, x86_THREAD_STATE,
225 : (void *) ®s, size);
226 0 : if (result != KERN_SUCCESS) {
227 0 : return 0;
228 : }
229 :
230 : /* Return success, and resume the thread. */
231 0 : return 1;
232 : }
233 :
234 :
235 : static kern_return_t ForwardException(
236 : struct MachExceptionHandlerData *data,
237 : mach_port_t thread,
238 : mach_port_t task,
239 : exception_type_t exception,
240 : exception_data_t code,
241 1 : mach_msg_type_number_t code_count) {
242 : unsigned int i;
243 : mach_port_t target_port;
244 : exception_behavior_t target_behavior;
245 :
246 : /* Find a port with a mask matching this exception to pass it on to. */
247 1 : for (i = 0; i < data->old_ports.count; ++i) {
248 1 : if (data->old_ports.masks[i] & (1 << exception)) {
249 1 : break;
250 : }
251 : }
252 1 : if (i == data->old_ports.count) {
253 0 : return KERN_FAILURE;
254 : }
255 1 : target_port = data->old_ports.ports[i];
256 1 : target_behavior = data->old_ports.behaviors[i];
257 :
258 : /*
259 : * By default a null exception port is registered.
260 : * As it is unclear how to forward to this, we should just fail.
261 : */
262 1 : if (target_port == 0) {
263 0 : return KERN_FAILURE;
264 : }
265 :
266 : /*
267 : * Only support EXCEPTION_DEFAULT, as we only plan to inter-operate with
268 : * Breakpad for now.
269 : */
270 1 : CHECK(target_behavior == EXCEPTION_DEFAULT);
271 :
272 : /* Forward the exception. */
273 1 : return exception_raise(target_port, thread, task, exception,
274 : code, code_count);
275 : }
276 :
277 :
278 : kern_return_t nacl_catch_exception_raise(
279 : mach_port_t exception_port,
280 : mach_port_t thread,
281 : mach_port_t task,
282 : exception_type_t exception,
283 : exception_data_t code,
284 1 : mach_msg_type_number_t code_count) {
285 : int is_untrusted;
286 :
287 : UNREFERENCED_PARAMETER(exception_port);
288 :
289 : /* Check if we want to handle this exception. */
290 1 : if (HandleException(thread, exception, &is_untrusted)) {
291 0 : return KERN_SUCCESS;
292 : }
293 :
294 : /*
295 : * Don't forward if the crash is untrusted, but unhandled.
296 : * (As we don't want things like Breakpad handling the crash.)
297 : */
298 1 : if (is_untrusted) {
299 0 : return KERN_FAILURE;
300 : }
301 :
302 : /* Forward on the exception to the old set of ports. */
303 1 : return ForwardException(
304 : g_MachExceptionHandlerData, thread, task, exception, code, code_count);
305 : }
306 :
307 : kern_return_t nacl_catch_exception_raise_state(
308 : mach_port_t exception_port,
309 : exception_type_t exception,
310 : const exception_data_t code,
311 : mach_msg_type_number_t code_count,
312 : int *flavor,
313 : const thread_state_t old_state,
314 : mach_msg_type_number_t old_state_count,
315 : thread_state_t new_state,
316 0 : mach_msg_type_number_t *new_state_count) {
317 : /* MIG generated code expects this, but should never be called. */
318 : UNREFERENCED_PARAMETER(exception_port);
319 : UNREFERENCED_PARAMETER(exception);
320 : UNREFERENCED_PARAMETER(code);
321 : UNREFERENCED_PARAMETER(code_count);
322 : UNREFERENCED_PARAMETER(flavor);
323 : UNREFERENCED_PARAMETER(old_state);
324 : UNREFERENCED_PARAMETER(old_state_count);
325 : UNREFERENCED_PARAMETER(new_state);
326 : UNREFERENCED_PARAMETER(new_state_count);
327 0 : NaClLog(LOG_FATAL, "nacl_catch_exception_raise_state: "
328 : "Unrequested message received.\n");
329 0 : return KERN_FAILURE;
330 : }
331 :
332 : kern_return_t nacl_catch_exception_raise_state_identity (
333 : mach_port_t exception_port,
334 : mach_port_t thread,
335 : mach_port_t task,
336 : exception_type_t exception,
337 : exception_data_t code,
338 : mach_msg_type_number_t code_count,
339 : int *flavor,
340 : thread_state_t old_state,
341 : mach_msg_type_number_t old_state_count,
342 : thread_state_t new_state,
343 0 : mach_msg_type_number_t *new_state_count) {
344 : /* MIG generated code expects this, but should never be called. */
345 : UNREFERENCED_PARAMETER(exception_port);
346 : UNREFERENCED_PARAMETER(thread);
347 : UNREFERENCED_PARAMETER(task);
348 : UNREFERENCED_PARAMETER(exception);
349 : UNREFERENCED_PARAMETER(code);
350 : UNREFERENCED_PARAMETER(code_count);
351 : UNREFERENCED_PARAMETER(flavor);
352 : UNREFERENCED_PARAMETER(old_state);
353 : UNREFERENCED_PARAMETER(old_state_count);
354 : UNREFERENCED_PARAMETER(new_state);
355 : UNREFERENCED_PARAMETER(new_state_count);
356 0 : NaClLog(LOG_FATAL, "nacl_catch_exception_raise_state_identity: "
357 : "Unrequested message received.\n");
358 0 : return KERN_FAILURE;
359 : }
360 :
361 1 : static void *MachExceptionHandlerThread(void *arg) {
362 : struct MachExceptionHandlerData *data =
363 1 : (struct MachExceptionHandlerData *) arg;
364 : kern_return_t result;
365 : /* Have space for a fairly large mach messages. */
366 : struct {
367 : mach_msg_header_t header;
368 : mach_msg_body_t body;
369 : char padding[1024];
370 : } request;
371 : struct {
372 : mach_msg_header_t header;
373 : mach_msg_body_t body;
374 : mig_reply_error_t reply_error;
375 : mach_msg_trailer_t trailer;
376 : } reply;
377 :
378 : for (;;) {
379 1 : result = mach_msg(&request.header, MACH_RCV_MSG | MACH_RCV_LARGE, 0,
380 : sizeof(request), data->exception_port,
381 : MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
382 1 : if (result != MACH_MSG_SUCCESS) {
383 0 : goto failure;
384 : }
385 1 : if (!nacl_exc_server(&request.header, &reply.header)) {
386 0 : goto failure;
387 : }
388 0 : result = mach_msg(&reply.header, MACH_SEND_MSG,
389 : reply.header.msgh_size, 0,
390 : MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL);
391 0 : if (result != MACH_MSG_SUCCESS) {
392 0 : goto failure;
393 : }
394 0 : }
395 :
396 0 : failure:
397 0 : free(data);
398 :
399 0 : return 0;
400 : }
401 :
402 1 : static int InstallHandler(struct MachExceptionHandlerData *data) {
403 : kern_return_t result;
404 1 : mach_port_t current_task = mach_task_self();
405 : unsigned int i;
406 :
407 : /* Capture old handler info. */
408 1 : data->old_ports.count = EXC_TYPES_COUNT;
409 1 : result = task_get_exception_ports(current_task, NACL_MACH_EXCEPTION_MASK,
410 : data->old_ports.masks,
411 : &data->old_ports.count,
412 : data->old_ports.ports,
413 : data->old_ports.behaviors,
414 : data->old_ports.flavors);
415 1 : if (result != KERN_SUCCESS) {
416 0 : return result;
417 : }
418 :
419 : /*
420 : * We only handle forwarding of the EXCEPTION_DEFAULT behavior (all that
421 : * Breakpad needs). Check that all old handlers are either of this behavior
422 : * type or null.
423 : *
424 : * NOTE: Ideally we might also require a particular behavior for null
425 : * exception ports. Unfortunately, testing indicates that while on
426 : * OSX 10.6 / 10.7 the behavior for such a null port is set to 0,
427 : * on OSX 10.5 it is set to 0x803fe956 (on a given run).
428 : * As tasks inherit exception ports from their parents, this may be
429 : * an uninitialized value carried along from a parent.
430 : * http://opensource.apple.com/source/xnu/xnu-1228.0.2/osfmk/kern/ipc_tt.c
431 : * For now, we will ignore the behavior when the port is null.
432 : */
433 2 : for (i = 0; i < data->old_ports.count; ++i) {
434 1 : CHECK(data->old_ports.behaviors[i] == EXCEPTION_DEFAULT ||
435 : data->old_ports.ports[i] == 0);
436 : }
437 :
438 : /* TODO(bradnelson): decide if we should set the exception port per thread. */
439 : /* Direct all task exceptions to new exception port. */
440 1 : result = task_set_exception_ports(current_task, NACL_MACH_EXCEPTION_MASK,
441 : data->exception_port, EXCEPTION_DEFAULT,
442 : THREAD_STATE_NONE);
443 1 : if (result != KERN_SUCCESS) {
444 0 : return result;
445 : }
446 :
447 1 : return KERN_SUCCESS;
448 : }
449 :
450 1 : int NaClInterceptMachExceptions(void) {
451 : struct MachExceptionHandlerData *data;
452 : kern_return_t result;
453 : mach_port_t current_task;
454 : pthread_attr_t attr;
455 : int thread_result;
456 : pthread_t exception_handler_thread;
457 :
458 1 : current_task = mach_task_self();
459 :
460 : /* Allocate structure to share with exception handler thread. */
461 1 : data = (struct MachExceptionHandlerData *) calloc(1, sizeof(*data));
462 1 : if (data == NULL) {
463 0 : goto failure;
464 : }
465 1 : g_MachExceptionHandlerData = data;
466 1 : data->exception_port = MACH_PORT_NULL;
467 :
468 : /* Allocate port to receive exceptions. */
469 1 : result = mach_port_allocate(current_task, MACH_PORT_RIGHT_RECEIVE,
470 : &data->exception_port);
471 1 : if (result != KERN_SUCCESS) {
472 0 : goto failure;
473 : }
474 :
475 : /* Add the right to send. */
476 1 : result = mach_port_insert_right(current_task,
477 : data->exception_port, data->exception_port,
478 : MACH_MSG_TYPE_MAKE_SEND);
479 1 : if (result != KERN_SUCCESS) {
480 0 : goto failure;
481 : }
482 :
483 : /* Install handler. */
484 1 : result = InstallHandler(data);
485 1 : if (result != KERN_SUCCESS) {
486 0 : goto failure;
487 : }
488 :
489 : /* Create handler thread. */
490 1 : pthread_attr_init(&attr);
491 1 : pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
492 1 : thread_result = pthread_create(&exception_handler_thread, &attr,
493 : &MachExceptionHandlerThread, data);
494 1 : pthread_attr_destroy(&attr);
495 1 : if (thread_result) {
496 0 : goto failure;
497 : }
498 :
499 1 : return TRUE;
500 :
501 0 : failure:
502 0 : if (data) {
503 0 : if (MACH_PORT_NULL != data->exception_port) {
504 0 : mach_port_deallocate(current_task, data->exception_port);
505 : }
506 0 : free(data);
507 : }
508 0 : return FALSE;
509 : }
510 :
511 : #else /* NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32 */
512 :
513 : int NaClInterceptMachExceptions(void) {
514 : return FALSE;
515 : }
516 :
517 : #endif /* NACL_ARCH(NACL_BUILD_ARCH) == NACL_x86 && NACL_BUILD_SUBARCH == 32 */
|