diff options
Diffstat (limited to 'darwin-bootstrap/bundle-loader.c')
-rw-r--r-- | darwin-bootstrap/bundle-loader.c | 210 |
1 files changed, 210 insertions, 0 deletions
diff --git a/darwin-bootstrap/bundle-loader.c b/darwin-bootstrap/bundle-loader.c new file mode 100644 index 0000000..6de63d9 --- /dev/null +++ b/darwin-bootstrap/bundle-loader.c @@ -0,0 +1,210 @@ +#include "darwin/mach-decls.h" +#include "substituted-messages.h" +#include <dlfcn.h> +#include <mach/mach.h> +#include <mach/mig.h> +#include <objc/runtime.h> +#include <CoreFoundation/CoreFoundation.h> +#include <syslog.h> +extern char ***_NSGetArgv(void); + +static int peek(const void *buf, const void *end) { + if ((size_t) (end - buf) < sizeof(struct substituted_bundle_list_op)) + return -1; + return ((struct substituted_bundle_list_op *) buf)->opc; +} + +static bool pop(const void **buf, const void *end, + int *opc, + const char **name) { + const struct substituted_bundle_list_op *op; + if ((size_t) (end - *buf) < sizeof(*op)) + return false; + op = *buf; + *buf += sizeof(*op); + *opc = op->opc; + if ((size_t) (end - *buf) < op->namelen + 1 || + ((const char *) buf)[op->namelen] != '\0') + return false; + *name = *buf; + *buf += op->namelen + 1; + return true; +} + +static struct { + bool initialized; + typeof(CFBundleGetBundleWithIdentifier) *CFBundleGetBundleWithIdentifier; + typeof(CFStringCreateWithCStringNoCopy) *CFStringCreateWithCStringNoCopy; + typeof(CFRelease) *CFRelease; + typeof(kCFAllocatorNull) *kCFAllocatorNull; + typeof(kCFStringEncodingUTF8) *kCFStringEncodingUTF8; +} cf_funcs; + +static struct { + bool initialized; + typeof(objc_getClass) *objc_getClass; +} objc_funcs; + +#define GET(funcs, handle, name) (funcs)->name = dlsym(handle, "_" #name) + +static bool cf_has_bundle(const char *name) { + if (!cf_funcs.initialized) { + void *handle = dlopen("/System/Library/Frameworks/CoreFoundation.framework", + RTLD_LAZY | RTLD_NOLOAD); + if (handle) { + GET(&cf_funcs, handle, CFBundleGetBundleWithIdentifier); + GET(&cf_funcs, handle, CFStringCreateWithCStringNoCopy); + GET(&cf_funcs, handle, CFRelease); + GET(&cf_funcs, handle, kCFAllocatorNull); + GET(&cf_funcs, handle, kCFStringEncodingUTF8); + } + } + if (!cf_funcs.CFBundleGetBundleWithIdentifier) + return false; + CFStringRef str = cf_funcs.CFStringCreateWithCStringNoCopy( + NULL, name, *cf_funcs.kCFStringEncodingUTF8, *cf_funcs.kCFAllocatorNull); + if (!str) + return false; + bool ret = !!cf_funcs.CFBundleGetBundleWithIdentifier(str); + cf_funcs.CFRelease(str); + return ret; +} + +static bool objc_has_class(const char *name) { + if (!objc_funcs.initialized) { + void *handle = dlopen("/usr/lib/libobjc.A.dylib", + RTLD_LAZY | RTLD_NOLOAD); + if (handle) + GET(&objc_funcs, handle, objc_getClass); + } + if (!objc_funcs.objc_getClass) + return false; + return !!objc_funcs.objc_getClass(name); +} + +static void use_dylib(const char *name) { + syslog(LOG_ERR, "loading dylib %s", name); + // .. +} + +static void load_bundle_list(const void *buf, size_t size) { + int opc; + const char *name; + const void *end = buf + size; + while (buf != end) { + if (!pop(&buf, end, &opc, &name)) + goto invalid; + switch (opc) { + case SUBSTITUTED_TEST_BUNDLE: + if (cf_has_bundle(name)) + goto pass_type; + /* fail, so... */ + if (peek(buf, end) != SUBSTITUTED_TEST_BUNDLE) + goto fail; + break; + case SUBSTITUTED_TEST_CLASS: + if (objc_has_class(name)) + goto pass_type; + if (peek(buf, end) != SUBSTITUTED_TEST_CLASS) + goto fail; + break; + case SUBSTITUTED_USE_DYLIB: + use_dylib(name); + break; + pass_type: + while (peek(buf, end) == opc) { + if (!pop(&buf, end, &opc, &name)) + goto invalid; + } + fail: + do { + if (!pop(&buf, end, &opc, &name)) + goto invalid; + } while (opc != SUBSTITUTED_USE_DYLIB); + break; + } + } + return; +invalid: + syslog(LOG_ERR, "invalid bundle list data"); +} + +static kern_return_t substituted_hello(mach_port_t service, int proto_version, + const char *argv0, void **bundle_list_p, + size_t *bundle_list_size_p) { + struct { + mach_msg_header_t hdr; + union { + struct substituted_msg_body_hello req; + struct substituted_msg_body_hello_resp resp; + } u; + mach_msg_trailer_t trailer_space; + } buf; + mach_port_t reply_port = mig_get_reply_port(); + buf.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, + MACH_MSG_TYPE_MAKE_SEND_ONCE); + buf.hdr.msgh_remote_port = service; + buf.hdr.msgh_local_port = reply_port; + buf.hdr.msgh_reserved = 0; + buf.hdr.msgh_id = SUBSTITUTED_MSG_HELLO; + buf.u.req.proto_version = proto_version; + strlcpy(buf.u.req.argv0, argv0, sizeof(buf.u.req.argv0)); + buf.hdr.msgh_size = sizeof(buf.hdr) + + sizeof(buf.u.req) + + strlen(buf.u.req.argv0); + kern_return_t kr = mach_msg(&buf.hdr, MACH_RCV_MSG | MACH_SEND_MSG, + buf.hdr.msgh_size, sizeof(buf), reply_port, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + if (kr) { + if (kr == MACH_RCV_BODY_ERROR) + mach_msg_destroy(&buf.hdr); + return kr; + } + mach_msg_ool_descriptor_t *ool = &buf.u.resp.bundle_list_ool; + + if (buf.hdr.msgh_size != sizeof(buf.hdr) + sizeof(buf.u.resp) || + !(buf.hdr.msgh_bits & MACH_MSGH_BITS_COMPLEX) || + buf.u.resp.body.msgh_descriptor_count != 1 || + ool->type != MACH_MSG_OOL_DESCRIPTOR) { + kr = KERN_INVALID_ARGUMENT; + goto out; + } + + if (buf.u.resp.error) { + kr = KERN_FAILURE; + goto out; + } + + *bundle_list_p = ool->address; + *bundle_list_size_p = ool->size; + return KERN_SUCCESS; + +out: + mach_msg_destroy(&buf.hdr); + return kr; +} + +/* this is DYLD_INSERT_LIBRARIES'd, not injected. */ +__attribute__((constructor)) +static void init() { + mach_port_t service; + kern_return_t kr = bootstrap_look_up(bootstrap_port, "com.ex.substituted", + &service); + if (kr) { + syslog(LOG_ERR, "bootstrap_look_up com.ex.substituted: %x", kr); + return; + } + const char *argv0 = (*_NSGetArgv())[0]; + void *bundle_list; + size_t bundle_list_size; + kr = substituted_hello(service, SUBSTITUTED_PROTO_VERSION, argv0, + &bundle_list, &bundle_list_size); + if (kr) { + syslog(LOG_ERR, "substituted_hello: %x", kr); + return; + } + load_bundle_list(bundle_list, bundle_list_size); + vm_deallocate(mach_task_self(), (vm_address_t) bundle_list, bundle_list_size); + + /* hang onto the port */ +} |