1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-25 06:50:46 -08:00
emacs/mps/code/protxc.c
Richard Brooksby 96448d34a9 Initial implementation of mac os x protection exception handling using mach.
Some threading tests get quite a long way.

Copied from Perforce
 Change: 182872
 ServerID: perforce.ravenbrook.com
2013-06-29 01:22:42 +01:00

596 lines
20 KiB
C

/* protxc.c: PROTECTION EXCPETION HANDLER FOR OS X MACH
*
* $Id$
* Copyright (c) 2013 Ravenbrook Limited. See end of file for license.
*
* SOURCES
*
* .source.man: <http://felinemenace.org/~nemo/mach/manpages/>
*
* REFERENCES
* http://www.mikeash.com/pyblog/friday-qa-2013-01-11-mach-exception-handlers.html
*/
#include "mpm.h"
#include "prmcxc.h"
#include <stdlib.h>
#include <pthread.h>
#include <mach/mach_port.h>
#include <mach/mach_init.h>
#include <mach/task.h>
#include <mach/thread_act.h>
#include <mach/thread_status.h>
#include <mach/mach_error.h>
#include <mach/i386/thread_status.h>
#include <mach/exc.h>
#if !defined(MPS_OS_XC)
#error "protxc.c is OS X specific"
#endif
#ifndef PROTECTION
#error "protxc.c implements protection, but PROTECTION is not set"
#endif
SRCID(protxc, "$Id$");
/* The Mach headers in /usr/include/mach and the code generated by the Mach
Interface Generator (mig) program are not the truth. In particular, if
you specify your exception behaviour with the MACH_EXCEPTION_CODES flag
then you get a different layout with 64-bit wide exception code and
subcode fields. We want those (so we can get the faulting address on
x86_64) but also we must cope with passing them onwards to other
exception handlers in the debugger or whatever. So we're forced to
define our own copies of these structures. Makes a bit of a nonsense of
having them in /usr/include or mig at all, really. We definitely don't
want to complicate the MPS build process with the mig. */
#define REQUEST_RAISE_STRUCT(name, code_type) \
struct name { \
mach_msg_header_t Head; \
mach_msg_body_t msgh_body; \
mach_msg_port_descriptor_t thread; \
mach_msg_port_descriptor_t task; \
NDR_record_t NDR; \
exception_type_t exception; \
mach_msg_type_number_t codeCnt; \
code_type code[2]; \
}
#define REQUEST_RAISE_STATE_STRUCT(name, code_type) \
struct name { \
mach_msg_header_t Head; \
NDR_record_t NDR; \
exception_type_t exception; \
mach_msg_type_number_t codeCnt; \
code_type code[2]; \
int flavor; \
mach_msg_type_number_t old_stateCnt; \
natural_t old_state[224]; \
}
#define REQUEST_RAISE_STATE_IDENTITY_STRUCT(name, code_type) \
struct name { \
mach_msg_header_t Head; \
mach_msg_body_t msgh_body; \
mach_msg_port_descriptor_t thread; \
mach_msg_port_descriptor_t task; \
NDR_record_t NDR; \
exception_type_t exception; \
mach_msg_type_number_t codeCnt; \
code_type code[2]; \
int flavor; \
mach_msg_type_number_t old_stateCnt; \
natural_t old_state[224]; \
}
/* Structure packing really makes a difference because the code field is
not naturally aligned on 64-bit. */
#ifdef __MigPackStructs
#pragma pack(4)
#endif
typedef REQUEST_RAISE_STRUCT(request_32_s, __int32_t) request_32_s;
typedef REQUEST_RAISE_STRUCT(request_64_s, __int64_t) request_64_s;
typedef REQUEST_RAISE_STATE_STRUCT(request_s32_s, __int32_t) request_s32_s;
typedef REQUEST_RAISE_STATE_STRUCT(request_s64_s, __int64_t) request_s64_s;
typedef REQUEST_RAISE_STATE_IDENTITY_STRUCT(request_si32_s, __int32_t) request_si32_s;
typedef REQUEST_RAISE_STATE_IDENTITY_STRUCT(request_si64_s, __int64_t) request_si64_s;
#ifdef __MigPackStructs
#pragma pack()
#endif
/* These are the message IDs that appear in request messages, determined
by experimentation. They can also be extracted by running mig on
/usr/include/mach/exc.defs and /usr/include/mach/mach_exc.defs.
The replies to these messages are these + 100. */
#define MSG_ID_REQUEST_32 2401
#define MSG_ID_REQUEST_STATE_32 2402
#define MSG_ID_REQUEST_STATE_IDENTITY_32 2403
#define MSG_ID_REQUEST_64 2405
#define MSG_ID_REQUEST_STATE_64 2406
#define MSG_ID_REQUEST_STATE_IDENTITY_64 2407
/* Convert request messages between 32- and 64-bit code code layouts.
May truncate 64-bit codes, of course, but that appears to be what
Mac OS X does. */
#define COPY_COMMON(dst, src, id, code_type) \
do { \
(dst)->Head = (src)->Head; \
(dst)->Head.msgh_id = (id); \
(dst)->Head.msgh_size = sizeof(*dst); \
(dst)->NDR = (src)->NDR; \
(dst)->exception = (src)->exception; \
(dst)->codeCnt = (src)->codeCnt; \
(dst)->code[0] = (code_type)(src)->code[0]; \
(dst)->code[1] = (code_type)(src)->code[1]; \
} while(0)
#define COPY_IDENTITY(dst, src) \
do { \
(dst)->thread = (src)->thread; \
(dst)->task = (src)->task; \
} while(0)
#define COPY_STATE(dst, src, state_flavor, state, state_count) \
do { \
mach_msg_size_t _s; \
(dst)->flavor = (state_flavor); \
(dst)->old_stateCnt = (state_count); \
_s = (dst)->old_stateCnt * sizeof(natural_t); \
AVER(_s <= sizeof((dst)->old_state)); \
memcpy((dst)->old_state, (state), _s); \
(dst)->Head.msgh_size = \
offsetof(request_s32_s, old_state) + _s; \
} while(0)
#define COPY_REQUEST(dst, src, width) \
do { \
COPY_COMMON(dst, src, MSG_ID_REQUEST_##width, __int##width##_t); \
COPY_IDENTITY(dst, src); \
} while(0);
#define COPY_REQUEST_STATE(dst, src, width, state_flavor, state, state_count) \
do { \
COPY_COMMON(dst, src, MSG_ID_REQUEST_STATE_##width, __int##width##_t); \
COPY_STATE(dst, src, state_flavor, state, state_count); \
} while(0)
#define COPY_REQUEST_STATE_IDENTITY(dst, src, width, state_flavor, state, state_count) \
do { \
COPY_COMMON(dst, src, MSG_ID_REQUEST_STATE_IDENTITY_##width, __int##width##_t); \
COPY_IDENTITY(dst, src); \
COPY_STATE(dst, src, state_flavor, state, state_count); \
} while(0)
/* Guard used to ensure we only set up the exception handler once. */
static pthread_once_t prot_setup_once = PTHREAD_ONCE_INIT;
/* The exception handling thread. */
static pthread_t exc_thread;
/* This will be the port that will receive messages for our
exception handler. */
static mach_port_name_t exc_port;
/* This is a record of the previously-installed exception handler. The
behaviour says which format of messages it wants to receive, and the
flavour says which kind of thread state should be attached to those
messages.
When we forward exceptions that we don't want to handle, we have to cope
with translating our message for the other handler. That's a real pain.
Note that the other handler might well be an attached debugger or the
BSD subsystem of Mac OS X. The latter does the work of translating
Mach exceptions into Unix signals.
TODO: Determine what happens if you attach a debugger to a process that
has already set up the exception handler. */
static exception_handler_t old_port;
static exception_behavior_t old_behaviour;
static thread_state_flavor_t old_flavor;
/* This corresponds to the portable part of the MPS that would resolve
the access, doing some scanning, lowering the shield, or single-stepping
the mutator in the case of a weak access. */
static int catch_bad_access(void *address, THREAD_STATE_T *state)
{
MutatorFaultContextStruct mfcStruct;
fprintf(stderr, "catch_bad_access(address = %p)\n", address);
mfcStruct.address = (Addr)address;
AVER(sizeof(mfcStruct.thread_state) == sizeof(THREAD_STATE_T));
memcpy(&mfcStruct.thread_state, state, sizeof(mfcStruct.thread_state));
return ArenaAccess(mfcStruct.address,
AccessREAD | AccessWRITE,
&mfcStruct);
}
/* Build a reply message based on a request. */
static void build_reply(__Reply__exception_raise_state_identity_t *reply,
request_si64_s *request,
kern_return_t ret_code)
{
mach_msg_size_t state_size;
reply->Head.msgh_bits = MACH_MSGH_BITS(MACH_MSGH_BITS_REMOTE(request->Head.msgh_bits), 0);
reply->Head.msgh_remote_port = request->Head.msgh_remote_port;
reply->Head.msgh_local_port = MACH_PORT_NULL;
reply->Head.msgh_reserved = 0;
reply->Head.msgh_id = request->Head.msgh_id + 100;
reply->NDR = request->NDR;
reply->RetCode = ret_code;
reply->flavor = request->flavor;
reply->new_stateCnt = request->old_stateCnt;
state_size = reply->new_stateCnt * sizeof(natural_t);
AVER(sizeof(reply->new_state) >= state_size);
memcpy(reply->new_state, request->old_state, state_size);
/* If you use sizeof(reply) for reply->Head.msgh_size then the state
gets ignored. */
reply->Head.msgh_size = offsetof(__Reply__exception_raise_state_identity_t, new_state) + state_size;
}
static void must_send(mach_msg_header_t *head)
{
kern_return_t kr;
kr = mach_msg(head,
MACH_SEND_MSG,
head->msgh_size,
/* recv_size */ 0,
MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
MACH_PORT_NULL);
if (kr != KERN_SUCCESS) {
mach_error("mach_msg send", kr);
exit(1);
}
}
/* Handle (or forward) one EXC_BAD_ACCESS exception message. */
/* Derived from reverse-engineering and running "mig /usr/include/exc.defs" */
/* Mac OS X provides a function exc_server (in
/usr/lib/system/libsystem_kernel.dylib) that's documented in the XNU
sources and generated by the Mach Interface Generator (mig). It unpacks
an exception message structure and calls one of several handler functions.
We can't use it because:
1. It's hard-wired to call certain functions by name. The MPS can't
steal those names in case the client program is using them too.
2. It fails anyway in Xcode's default "Release" build with hidden
symbols, because it uses dlsym to find those handler functins, and
dlsym can't find them.
So instead this function duplicates the work of exc_server, and is shorter
because it's specialised for protection exceptions of a single behaviour
and flavour. It is also more flexible and can dispatch to any function
we want. The downside is that it depends on various unpublished stuff
like the code numbers for certain messages. */
static void handle_one(void)
{
request_si64_s request;
union {
mach_msg_header_t Head;
request_32_s r32;
request_64_s r64;
request_s32_s rs32;
request_s64_s rs64;
request_si32_s rsi32;
request_si64_s rsi64;
} forward;
mach_msg_return_t mr;
kern_return_t kr;
thread_state_data_t thread_state;
mach_msg_type_number_t thread_state_count;
__Reply__exception_raise_state_identity_t reply;
/* TODO: This timeout loop probably isn't necessary, but it might help
with killing or interrupting the process. */
do {
mr = mach_msg(&request.Head,
MACH_RCV_MSG | MACH_RCV_TIMEOUT,
/* send_size */ 0,
/* receive_size */ sizeof(request),
exc_port,
/* timeout */ 10000,
/* notify */ MACH_PORT_NULL);
} while (mr == MACH_RCV_TIMED_OUT);
if (mr != MACH_MSG_SUCCESS) {
mach_error("mach_msg recv\n", mr);
exit(1);
}
AVER(request.Head.msgh_id == MSG_ID_REQUEST_STATE_IDENTITY_64);
AVER(request.Head.msgh_local_port == exc_port);
AVER(request.task.name == mach_task_self());
AVER(request.exception == EXC_BAD_ACCESS);
AVER(request.codeCnt == 2);
AVER(request.old_stateCnt == THREAD_STATE_COUNT);
AVER(request.flavor == THREAD_STATE_FLAVOR);
fprintf(stderr,
"message from %u, exception %u, code %llu\n",
request.Head.msgh_remote_port,
request.exception,
request.code[0]);
if (request.code[0] == KERN_PROTECTION_FAILURE)
if (catch_bad_access((void *)request.code[1],
(THREAD_STATE_T *)request.old_state)) {
/* Send a reply that will cause the thread to continue. */
build_reply(&reply, &request, KERN_SUCCESS);
must_send(&reply.Head);
return;
}
/* We didn't handle the exception -- it wasn't one of ours. */
/* If there was no previously installed exception port, send a reply
that will cause the system to pass the message on to the next kind
of handler -- presumably the host handler. */
if (old_port == MACH_PORT_NULL) {
build_reply(&reply, &request, KERN_FAILURE);
must_send(&reply.Head);
}
/* Forward the exception message to the previously installed exception
port. That port might've been expecting a different behavior and
flavour, unfortunately, so we have to cope. Note that we leave the
reply port intact, so we don't have to handle and forward replies. */
switch (old_behaviour) {
case EXCEPTION_DEFAULT:
COPY_REQUEST(&forward.r32, &request, 32);
break;
case EXCEPTION_DEFAULT | MACH_EXCEPTION_CODES:
COPY_REQUEST(&forward.r64, &request, 64);
break;
default:
/* Get the flavour of thread state that the old handler expects. */
thread_state_count = THREAD_STATE_MAX;
kr = thread_get_state(request.thread.name,
old_flavor,
thread_state,
&thread_state_count);
if (kr != KERN_SUCCESS) {
mach_error("thread_get_state", kr);
exit(1);
}
switch (old_behaviour) {
case EXCEPTION_STATE:
COPY_REQUEST_STATE(&forward.rs32,
&request,
32,
old_flavor,
thread_state,
thread_state_count);
break;
case EXCEPTION_STATE | MACH_EXCEPTION_CODES:
COPY_REQUEST_STATE(&forward.rs64,
&request,
64,
old_flavor,
thread_state,
thread_state_count);
break;
case EXCEPTION_STATE_IDENTITY:
COPY_REQUEST_STATE_IDENTITY(&forward.rsi32,
&request,
32,
old_flavor,
thread_state,
thread_state_count);
break;
case EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES:
COPY_REQUEST_STATE_IDENTITY(&forward.rsi64,
&request,
64,
old_flavor,
thread_state,
thread_state_count);
break;
default:
fprintf(stderr, "unknown old behaviour %x\n", old_behaviour);
NOTREACHED;
abort();
}
break;
}
fprintf(stderr, "forwarding to %u\n", old_port);
/* Forward the message to the old port. */
forward.Head.msgh_local_port = old_port;
mr = mach_msg(&forward.Head,
MACH_SEND_MSG,
forward.Head.msgh_size,
/* rcv_size */ 0,
/* rcv_name */ MACH_PORT_NULL,
MACH_MSG_TIMEOUT_NONE,
/* notify */ MACH_PORT_NULL);
if (mr != MACH_MSG_SUCCESS) {
mach_error("mach_msg forward", mr);
/* Fall back to sending a reply. This might bypass a
handler but may avoid exiting the process entirely. */
build_reply(&reply, &request, KERN_FAILURE);
must_send(&reply.Head);
}
}
/* The exception handler thread loop. */
static void *handler(void *p) {
UNUSED(p);
for (;;)
handle_one();
return NULL;
}
static void protSetup(void)
{
kern_return_t kr;
mach_msg_type_number_t old_cnt;
exception_mask_t old_mask;
exception_behavior_t behaviour;
int pr;
/* Create a port right to send and receive exceptions */
kr = mach_port_allocate(mach_task_self(),
MACH_PORT_RIGHT_RECEIVE,
&exc_port);
if (kr != KERN_SUCCESS) {
mach_error("mach_port_allocate", kr);
exit(1);
}
/* Allow me to send exceptions on this port right. */
/* FIXME: Why? */
kr = mach_port_insert_right(mach_task_self(),
exc_port, exc_port,
MACH_MSG_TYPE_MAKE_SEND);
if (kr != KERN_SUCCESS) {
mach_error("mach_port_insert_right", kr);
exit(1);
}
/* Ask to receive EXC_BAD_ACCESS exceptions on our new port, complete
with thread state and identity information in the message.
The MACH_EXCEPTION_CODES flag causes the code fields to be
passed 64-bits wide, matching request_si64_s. */
behaviour = (exception_behavior_t)(EXCEPTION_STATE_IDENTITY | MACH_EXCEPTION_CODES);
kr = task_swap_exception_ports(mach_task_self(),
EXC_MASK_BAD_ACCESS,
exc_port,
behaviour,
THREAD_STATE_FLAVOR,
&old_mask,
&old_cnt,
&old_port,
&old_behaviour,
&old_flavor);
if (kr != KERN_SUCCESS) {
mach_error("task_swap_exception_ports", kr);
exit(1);
}
AVER(old_mask == EXC_MASK_BAD_ACCESS);
AVER(old_cnt == 1);
fprintf(stderr,
"old_port = %u, old_behaviour = %x, old_flavour = %d\n",
old_port, old_behaviour, old_flavor);
/* Launch the exception handling thread. */
pr = pthread_create(&exc_thread, NULL, handler, NULL);
if (pr != 0) {
fprintf(stderr, "pthread_create: %d\n", pr);
exit(1);
}
}
void ProtSetup(void)
{
pthread_once(&prot_setup_once, protSetup);
}
#if 0
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, const char * argv[])
{
ProtSetup();
protected_block = mmap(0, getpagesize(), PROT_NONE, MAP_ANON | MAP_PRIVATE, -1, 0);
if (protected_block == MAP_FAILED) {
perror("mmap");
exit(1);
}
*protected_block = 'x';
puts("Hello, world!");
*(volatile char *)0xDEADBEEFC0DE = 'x';
puts("Goodbye, moon!");
return 0;
}
#endif
/* C. COPYRIGHT AND LICENSE
*
* Copyright (C) 2013 Ravenbrook Limited <http://www.ravenbrook.com/>.
* All rights reserved. This is an open source license. Contact
* Ravenbrook for commercial licensing options.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* 3. Redistributions in any form must be accompanied by information on how
* to obtain complete source code for this software and any accompanying
* software that uses this software. The source code must either be
* included in the distribution or be available for no more than the cost
* of distribution plus a nominal fee, and must be freely redistributable
* under reasonable conditions. For an executable file, complete source
* code means the source code for all modules it contains. It does not
* include source code for modules or files that typically accompany the
* major components of the operating system on which the executable file
* runs.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/