diff options
author | comex | 2015-02-28 21:18:20 -0500 |
---|---|---|
committer | comex | 2015-02-28 21:18:20 -0500 |
commit | bfce3bf03e7bc285bc5bbf3df561a6f4e9169899 (patch) | |
tree | 115e02d7a4d44ca6e0a13c4a02a335dffe9b54e2 /darwin-bootstrap | |
parent | Rename ios-bootstrap to darwin-bootstrap; cleanup posixspawn-hook and unrestr... (diff) | |
download | substitute-bfce3bf03e7bc285bc5bbf3df561a6f4e9169899.tar.gz |
Add substituted and fix things up to use it. Still untested.
Diffstat (limited to 'darwin-bootstrap')
-rw-r--r-- | darwin-bootstrap/bundle-loader.c | 210 | ||||
-rw-r--r-- | darwin-bootstrap/bundle-loader.m | 161 | ||||
-rw-r--r-- | darwin-bootstrap/posixspawn-hook.c | 102 | ||||
-rw-r--r-- | darwin-bootstrap/substituted-messages.h | 37 | ||||
-rw-r--r-- | darwin-bootstrap/substituted-plist-loader.h | 2 | ||||
-rw-r--r-- | darwin-bootstrap/substituted-plist-loader.m | 173 | ||||
-rw-r--r-- | darwin-bootstrap/substituted.c | 127 |
7 files changed, 620 insertions, 192 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 */ +} diff --git a/darwin-bootstrap/bundle-loader.m b/darwin-bootstrap/bundle-loader.m deleted file mode 100644 index fec2513..0000000 --- a/darwin-bootstrap/bundle-loader.m +++ /dev/null @@ -1,161 +0,0 @@ -#import <Foundation/Foundation.h> -#import <CoreFoundation/CoreFoundation.h> -#include <dlfcn.h> -extern char ***_NSGetArgv(void); - -#define PREFIX "Substitute bundle loader: " - -enum test_filters_ret { - PASSED, - FAILED, - INVALID -}; - -static double id_to_double(id o) { - if ([o isKindOfClass:[NSString class]]) { - NSScanner *scan = [NSScanner scannerWithString:o]; - double d; - if (![scan scanDouble:&d] || !scan.atEnd) - return NAN; - return d; - } else if ([o isKindOfClass:[NSNumber class]]) { - return [o doubleValue]; - } else { - return NAN; - } -} - -static enum test_filters_ret test_filters(NSDictionary *plist_dict) { - - NSDictionary *filter = [plist_dict objectForKey:@"Filter"]; - if (!filter) - return PASSED; - if (![filter isKindOfClass:[NSDictionary class]]) - return INVALID; - - for (NSString *key in [filter allKeys]) { - if (!([key isEqualToString:@"CoreFoundationVersion"] || - [key isEqualToString:@"Classes"] || - [key isEqualToString:@"Bundles"] || - [key isEqualToString:@"Executables"])) { - return INVALID; - } - } - NSArray *cfv = [filter objectForKey:@"CoreFoundationVersion"]; - if (cfv) { - if (![cfv isKindOfClass:[NSArray class]] || - [cfv count] == 0 || - [cfv count] > 2) - return INVALID; - double version = kCFCoreFoundationVersionNumber; - double minimum = id_to_double([cfv objectAtIndex:0]); - if (minimum != minimum) - return INVALID; - if (version < minimum) - return FAILED; - id supremum_o = [cfv objectAtIndex:1]; - if (supremum_o) { - double supremum = id_to_double(supremum_o); - if (supremum != supremum) - return INVALID; - if (version >= supremum) - return FAILED; - } - } - - NSArray *classes = [filter objectForKey:@"Classes"]; - if (classes) { - if (![classes isKindOfClass:[NSArray class]]) - return INVALID; - for (NSString *name in classes) { - if (![name isKindOfClass:[NSString class]]) - return INVALID; - if (NSClassFromString(name)) - goto ok1; - } - return FAILED; - ok1:; - } - - NSArray *bundles = [filter objectForKey:@"Bundles"]; - if (bundles) { - if (![bundles isKindOfClass:[NSArray class]]) - return INVALID; - for (NSString *identifier in bundles) { - if (![identifier isKindOfClass:[NSString class]]) - return INVALID; - if ([NSBundle bundleWithIdentifier:identifier]) - goto ok2; - } - return FAILED; - ok2:; - } - - - NSArray *executables = [filter objectForKey:@"Executables"]; - if (executables) { - const char *argv0 = (*_NSGetArgv())[0]; - NSString *exe = nil; - if (argv0) { - NSString *nsargv0 = [NSString stringWithCString:argv0 - encoding:NSUTF8StringEncoding]; - exe = [[nsargv0 pathComponents] lastObject]; - } - if (!exe) - exe = @""; - if (![executables isKindOfClass:[NSArray class]]) - return INVALID; - for (NSString *name in executables) { - if (![name isKindOfClass:[NSString class]]) - return INVALID; - if ([name isEqualToString:exe]) - goto ok3; - } - return FAILED; - ok3:; - } - return PASSED; -} - -/* this is DYLD_INSERT_LIBRARIES'd, not injected. */ -__attribute__((constructor)) -static void init() { - NSError *err; - NSString *base = @"/Library/Substitute/DynamicLibraries"; - NSArray *list = [[NSFileManager defaultManager] - contentsOfDirectoryAtPath:base - error:&err]; - if (!list) - return; - - for (NSString *dylib in list) { - if (![[dylib pathExtension] isEqualToString:@"dylib"]) - continue; - NSString *plist = [[dylib stringByDeletingPathExtension] - stringByAppendingPathExtension:@"plist"]; - NSString *full_plist = [base stringByAppendingPathComponent:plist]; - NSDictionary *plist_dict = [NSDictionary dictionaryWithContentsOfFile: - full_plist]; - if (!plist_dict) { - NSLog(@PREFIX "missing, unreadable, or invalid plist '%@' for dylib '%@'; unlike Substrate, we require plists", full_plist, dylib); - continue; - } - enum test_filters_ret ret = test_filters(plist_dict); - if (ret == FAILED) { - continue; - } else if (ret == INVALID) { - NSLog(@PREFIX "bad data in plist '%@' for dylib '%@'", full_plist, dylib); - continue; - } - NSString *full_dylib = [base stringByAppendingPathComponent:dylib]; - const char *c_dylib = [full_dylib cStringUsingEncoding:NSUTF8StringEncoding]; - if (!c_dylib) { - NSLog(@PREFIX "Not loading weird dylib path %@", full_dylib); - continue; - } - NSLog(@"Substitute loading %@", full_dylib); - if (!dlopen(c_dylib, RTLD_LAZY)) { - NSLog(@PREFIX "Failed to load %@: %s", full_dylib, dlerror()); - } - } -} diff --git a/darwin-bootstrap/posixspawn-hook.c b/darwin-bootstrap/posixspawn-hook.c index 5acb30b..e948b16 100644 --- a/darwin-bootstrap/posixspawn-hook.c +++ b/darwin-bootstrap/posixspawn-hook.c @@ -1,12 +1,20 @@ /* This library is loaded into launchd, and from there into xpcproxy, which - * launchd (always?) uses as an intermediary to exec its processes; its main - * purpose is to ensure that bundle-loader.dylib is specified in - * DYLD_INSERT_LIBRARIES when launching such processes. In the interests of - * not making ssh really weird (and because it's what Substrate does), this is - * separate from bundle-loader itself, so any processes that do their own - * spawning won't get the environment override. + * launchd uses as an intermediary to exec its processes; its main purpose is + * to ensure that bundle-loader.dylib is specified in DYLD_INSERT_LIBRARIES + * when launching such processes. In the interests of not making ssh really + * weird (and because it's what Substrate does), this is separate from + * bundle-loader itself, so any processes that do their own spawning won't get + * the environment override (though D_I_L will be inherited if the environment + * isn't reset). * - * It also handles the sandbox override for substituted. */ + * It also handles the sandbox override for substituted. + * + * Note: Because bundle-loader synchronously contacts substituted, it must not + * be loaded into any synchronous stuff launchd runs before starting jobs + * proper. Therefore, it's only inserted if the spawning process is xpcproxy + * (rather than launchd directly). I don't think iOS 7 does this yet, so this + * needs to be fixed there. + */ #define IB_LOG_NAME "posixspawn-hook" #include "ib-log.h" @@ -20,7 +28,6 @@ #include <sys/wait.h> #include <syslog.h> #include <malloc/malloc.h> -#include <assert.h> #include <errno.h> #include <arpa/inet.h> #include <libkern/OSByteOrder.h> @@ -30,7 +37,10 @@ extern char ***_NSGetEnviron(void); static __typeof__(posix_spawn) *old_posix_spawn, *old_posix_spawnp, hook_posix_spawn, hook_posix_spawnp; static __typeof__(wait4) *old_wait4, hook_wait4; -static __typeof__(waitpid) *old_waitpid, hook_waitpid; +static typeof(waitpid) *old_waitpid, hook_waitpid; +static int (*old_sandbox_check)(pid_t, const char *, int type, ...); + +static bool is_launchd; static bool advance(char **strp, const char *template) { size_t len = strlen(template); @@ -144,6 +154,34 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old, char *const *my_envp = envp ? envp : *_NSGetEnviron(); posix_spawnattr_t my_attr = NULL; + short flags = 0; + if (posix_spawnattr_getflags(attrp, &flags)) + goto crap; + + if (IB_VERBOSE) { + ib_log("hook_posix_spawn_generic: path=%s%s%s", + path, + (flags & POSIX_SPAWN_SETEXEC) ? " (exec)" : "", + (flags & POSIX_SPAWN_START_SUSPENDED) ? " (suspend)" : ""); + for (char *const *ap = argv; *ap; ap++) + ib_log(" %s", *ap); + } + + + static const char bl_dylib[] = + "/Library/Substitute/bundle-loader.dylib"; + static const char psh_dylib[] = + "/Library/Substitute/posixspawn-hook.dylib"; + + /* which dylib should we add, if any? */ + const char *dylib_to_add; + if (!is_launchd) + dylib_to_add = bl_dylib; + else if (!strcmp(path, "/usr/libexec/xpcproxy")) + dylib_to_add = psh_dylib; + else + goto skip; + if (attrp) { posix_spawnattr_t attr = *attrp; size_t size = malloc_size(attr); @@ -155,31 +193,13 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old, if (posix_spawnattr_init(&my_attr)) goto crap; } - - short flags; - if (posix_spawnattr_getflags(&my_attr, &flags)) - goto crap; - if (IB_VERBOSE) { - ib_log("hook_posix_spawn_generic: path=%s%s%s", - path, - (flags & POSIX_SPAWN_SETEXEC) ? " (exec)" : "", - (flags & POSIX_SPAWN_START_SUSPENDED) ? " (suspend)" : ""); - for (char *const *ap = argv; *ap; ap++) - ib_log(" %s", *ap); - } - /* This mirrors Substrate's logic with safe mode. I don't really * understand the point of the difference between its 'safe' (which removes * Substrate from DYLD_INSERT_LIBRARIES) and 'quit' (which just skips * directly to the original spawn), but I guess I'll just do the same for * maximum safety... */ bool safe_mode = false; - const char *orig_dyld_insert = ""; - static const char bl_dylib[] = - "/Library/Substitute/bundle-loader.dylib"; - static const char psh_dylib[] = - "/Library/Substitute/posixspawn-hook.dylib"; size_t env_count = 0; for (char *const *ep = my_envp; *ep; ep++) { env_count++; @@ -223,9 +243,6 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old, if (!safe_mode) { if (newp != newp_orig) *newp++ = ':'; - const char *dylib_to_add = !strcmp(path, "/usr/libexec/xpcproxy") - ? psh_dylib - : bl_dylib; newp = stpcpy(newp, dylib_to_add); } if (IB_VERBOSE) @@ -335,6 +352,26 @@ pid_t hook_waitpid(pid_t pid, int *stat_loc, int options) { return ret; } +int hook_sandbox_check(pid_t pid, const char *op, int type, ...) { + /* Can't easily determine the number of arguments, so just assume there's + * less than 5 pointers' worth. */ + va_list ap; + va_start(ap, type); + long blah[5]; + for (int i = 0; i < 5; i++) + blah[i] = va_arg(ap, long); + va_end(ap); + if (!strcmp(op, "mach-lookup")) { + const char *name = (void *) blah[0]; + if (!strcmp(name, "com.ex.substituted")) { + /* always allow */ + return 0; + } + } + return old_sandbox_check(pid, op, type, + blah[0], blah[1], blah[2], blah[3], blah[4]); +} + void substitute_init(struct shuttle *shuttle, size_t nshuttle) { /* Just tell them we're done */ if (nshuttle != 1) { @@ -372,7 +409,9 @@ static void init() { * disk...) */ - struct substitute_image *im = substitute_open_image(_dyld_get_image_name(0)); + const char *image0 = _dyld_get_image_name(0); + is_launchd = !!strstr(image0, "launchd"); + struct substitute_image *im = substitute_open_image(image0); if (!im) { ib_log("posixspawn-hook: substitute_open_image failed"); goto end; @@ -383,6 +422,7 @@ static void init() { {"_posix_spawnp", hook_posix_spawnp, &old_posix_spawnp}, {"_waitpid", hook_waitpid, &old_waitpid}, {"_wait4", hook_wait4, &old_wait4}, + {"_sandbox_check", hook_sandbox_check, &old_sandbox_check}, }; int err = substitute_interpose_imports(im, hooks, sizeof(hooks)/sizeof(*hooks), 0); diff --git a/darwin-bootstrap/substituted-messages.h b/darwin-bootstrap/substituted-messages.h new file mode 100644 index 0000000..62b92b3 --- /dev/null +++ b/darwin-bootstrap/substituted-messages.h @@ -0,0 +1,37 @@ +#pragma once +#include <mach/mach.h> +#include <sys/param.h> + +enum { + SUBSTITUTED_PROTO_VERSION = 1 +}; + +enum substituted_msg_id { + SUBSTITUTED_MSG_HELLO = 10000, + SUBSTITUTED_MSG_HELLO_RESP = 10001, +}; + +struct substituted_msg_body_hello { + int proto_version; + char argv0[/*0..*/MAXPATHLEN]; +}; + +struct substituted_msg_body_hello_resp { + mach_msg_body_t body; + mach_msg_ool_descriptor_t bundle_list_ool; + int error; +}; + +/* bundle_list: a bunch of substituted_bundle_list_ops */ + +enum substituted_bundle_list_opc { + SUBSTITUTED_TEST_BUNDLE, + SUBSTITUTED_TEST_CLASS, + SUBSTITUTED_USE_DYLIB, +}; + +struct substituted_bundle_list_op { + uint16_t namelen; + uint8_t opc; + /* char name[namelen + 1]; */ +}; diff --git a/darwin-bootstrap/substituted-plist-loader.h b/darwin-bootstrap/substituted-plist-loader.h new file mode 100644 index 0000000..a8bc824 --- /dev/null +++ b/darwin-bootstrap/substituted-plist-loader.h @@ -0,0 +1,2 @@ +void get_bundle_list(const char *exec_name, + const void **bundle_list, size_t *bundle_list_size); diff --git a/darwin-bootstrap/substituted-plist-loader.m b/darwin-bootstrap/substituted-plist-loader.m new file mode 100644 index 0000000..0c8d66f --- /dev/null +++ b/darwin-bootstrap/substituted-plist-loader.m @@ -0,0 +1,173 @@ +#import <Foundation/Foundation.h> +#import <CoreFoundation/CoreFoundation.h> +#include "substituted-messages.h" + +enum convert_filters_ret { + PROVISIONAL_PASS, + FAIL, + INVALID +}; + +static double id_to_double(id o) { + if ([o isKindOfClass:[NSString class]]) { + NSScanner *scan = [NSScanner scannerWithString:o]; + double d; + if (![scan scanDouble:&d] || !scan.atEnd) + return NAN; + return d; + } else if ([o isKindOfClass:[NSNumber class]]) { + return [o doubleValue]; + } else { + return NAN; + } +} + +static enum convert_filters_ret convert_filters(NSDictionary *plist_dict, + const char *exec_name, + NSMutableData *test_out) { + + NSDictionary *filter = [plist_dict objectForKey:@"Filter"]; + if (!filter) + return PROVISIONAL_PASS; + if (![filter isKindOfClass:[NSDictionary class]]) + return INVALID; + + for (NSString *key in [filter allKeys]) { + if (!([key isEqualToString:@"CoreFoundationVersion"] || + [key isEqualToString:@"Classes"] || + [key isEqualToString:@"Bundles"] || + [key isEqualToString:@"Executables"])) { + return INVALID; + } + } + + /* First do the two we can test here. */ + + NSArray *cfv = [filter objectForKey:@"CoreFoundationVersion"]; + if (cfv) { + if (![cfv isKindOfClass:[NSArray class]] || + [cfv count] == 0 || + [cfv count] > 2) + return INVALID; + double version = kCFCoreFoundationVersionNumber; + double minimum = id_to_double([cfv objectAtIndex:0]); + if (minimum != minimum) + return INVALID; + if (version < minimum) + return FAIL; + id supremum_o = [cfv objectAtIndex:1]; + if (supremum_o) { + double supremum = id_to_double(supremum_o); + if (supremum != supremum) + return INVALID; + if (version >= supremum) + return FAIL; + } + } + + NSArray *executables = [filter objectForKey:@"Executables"]; + if (executables) { + NSString *exe = [NSString stringWithCString:exec_name + encoding:NSUTF8StringEncoding]; + if (![executables isKindOfClass:[NSArray class]]) + return INVALID; + for (NSString *name in executables) { + if (![name isKindOfClass:[NSString class]]) + return INVALID; + if ([name isEqualToString:exe]) + goto ok; + } + return FAIL; + ok:; + } + + /* Convert the rest to substituted_bundle_list_ops. */ + + struct { + __unsafe_unretained NSString *key; + uint8_t opc; + } types[2] = { + {@"Classes", SUBSTITUTED_TEST_CLASS}, + {@"Bundles", SUBSTITUTED_TEST_BUNDLE}, + }; + + for (int i = 0; i < 2; i++) { + NSArray *things = [filter objectForKey:types[i].key]; + if (things) { + if (![things isKindOfClass:[NSArray class]]) + return INVALID; + for (NSString *name in things) { + if (![name isKindOfClass:[NSString class]]) + return INVALID; + NSData *name_data = [name dataUsingEncoding:NSUTF8StringEncoding]; + size_t len = [name_data length]; + if (len > 65535) + return INVALID; + struct substituted_bundle_list_op op = {(uint16_t) len, + types[i].opc}; + [test_out appendBytes:&op length:sizeof(op)]; + [test_out appendData:name_data]; + static char zero = '\0'; + [test_out appendBytes:&zero length:1]; + } + } + } + + return PROVISIONAL_PASS; +} + +static NSData *the_bundle_list; + +void get_bundle_list(const char *exec_name, + const void **bundle_list, size_t *bundle_list_size) { + /* TODO cache */ + NSError *err; + NSString *base = @"/Library/Substitute/DynamicLibraries"; + NSArray *list = [[NSFileManager defaultManager] + contentsOfDirectoryAtPath:base + error:&err]; + if (!list) + return; + + NSMutableData *out = [NSMutableData data]; + + for (NSString *dylib in list) { + if (![[dylib pathExtension] isEqualToString:@"dylib"]) + continue; + NSString *plist = [[dylib stringByDeletingPathExtension] + stringByAppendingPathExtension:@"plist"]; + NSString *full_plist = [base stringByAppendingPathComponent:plist]; + NSDictionary *plist_dict = [NSDictionary dictionaryWithContentsOfFile: + full_plist]; + if (!plist_dict) { + NSLog(@"missing, unreadable, or invalid plist '%@' for dylib '%@'; unlike Substrate, we require plists", full_plist, dylib); + continue; + } + + NSMutableData *test = [NSMutableData data]; + enum convert_filters_ret ret = convert_filters(plist_dict, exec_name, test); + if (ret == FAIL) { + continue; + } else if (ret == INVALID) { + NSLog(@"bad data in plist '%@' for dylib '%@'", full_plist, dylib); + continue; + } + NSString *dylib_path = [base stringByAppendingPathComponent:dylib]; + NSData *dylib_path_data = [dylib_path dataUsingEncoding:NSUTF8StringEncoding]; + size_t len = [dylib_path length]; + if (len > 65535) { + NSLog(@"dylib '%@' somehow has an absurdly long path", dylib); + continue; + } + [out appendData:test]; + struct substituted_bundle_list_op op = {(uint16_t) len, + SUBSTITUTED_USE_DYLIB}; + [out appendBytes:&op length:sizeof(op)]; + [out appendData:dylib_path_data]; + static char zero = '\0'; + [out appendBytes:&zero length:1]; + } + the_bundle_list = out; + *bundle_list = [out bytes]; + *bundle_list_size = [out length]; +} diff --git a/darwin-bootstrap/substituted.c b/darwin-bootstrap/substituted.c new file mode 100644 index 0000000..7eade8b --- /dev/null +++ b/darwin-bootstrap/substituted.c @@ -0,0 +1,127 @@ +/* This is a daemon contacted by all processes which can load extensions. It + * currently does the work of reading the plists in + * /Library/Substitute/DynamicLibraries in order to avoid loading objc/CF + * libraries into the target binary (unless actually required by loaded + * libraries). By itself this would not merit the overhead of a separate + * daemon, but in the future this will also coordinate hot loading and + * unloading, for which purpose I think a daemon would be a cleaner solution + * than task_for_pid'ing everything in sight. */ + +#define IB_LOG_NAME "substituted" +#include "darwin/mach-decls.h" +#include "ib-log.h" +#include "substituted-messages.h" +#include "substituted-plist-loader.h" +#include <mach/mach.h> +#include <stddef.h> +//#include <sys/types.h> + +/* libbsm.h */ +struct au_tid; +void audit_token_to_au32(audit_token_t, uid_t *, uid_t *, gid_t *, uid_t *, + gid_t *, pid_t *, pid_t *, struct au_tid *); + +struct msgbuf { + mach_msg_header_t hdr; + union { + struct substituted_msg_body_hello hello; + } u; + mach_msg_audit_trailer_t trail_space; +}; + +static void handle_hello(struct msgbuf *buf, pid_t pid) { + if (buf->hdr.msgh_size < offsetof(struct msgbuf, u.hello.argv0)) { + ib_log("mesage too short"); + return; + } + ((char *) buf)[buf->hdr.msgh_size] = '\0'; /* overwrite trailer or whatever */ + const char *name = buf->u.hello.argv0; + if (IB_VERBOSE) + ib_log("got hello from pid %d [%s]", pid, name); + int error = 0; + const void *bundle_list = NULL; + size_t bundle_list_size = 0; + if (buf->u.hello.proto_version != SUBSTITUTED_PROTO_VERSION) { + error = 1; + } else { + get_bundle_list(name, &bundle_list, &bundle_list_size); + } + struct { + mach_msg_header_t hdr; + struct substituted_msg_body_hello_resp b; + } resp; + resp.hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND_ONCE, 0) | + MACH_MSGH_BITS_COMPLEX; + resp.hdr.msgh_size = sizeof(resp); + resp.hdr.msgh_remote_port = buf->hdr.msgh_remote_port; + resp.hdr.msgh_local_port = MACH_PORT_NULL; + resp.hdr.msgh_reserved = 0; + resp.hdr.msgh_id = SUBSTITUTED_MSG_HELLO_RESP; + resp.b.body.msgh_descriptor_count = 1; + mach_msg_ool_descriptor_t *ool = &resp.b.bundle_list_ool; + ool->pad1 = 0; + ool->type = MACH_MSG_OOL_DESCRIPTOR; + ool->deallocate = 0; + ool->copy = MACH_MSG_PHYSICAL_COPY; + ool->address = (void *) bundle_list; + ool->size = (mach_msg_size_t) bundle_list_size; + resp.b.error = error; + + kern_return_t kr = mach_msg(&resp.hdr, MACH_SEND_MSG, sizeof(resp), 0, + MACH_PORT_NULL, MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (kr) + ib_log("mach_msg(hello resp) -> %x", kr); + else /* don't re-destroy the moved right */ + buf->hdr.msgh_remote_port = MACH_PORT_NULL; +} + +int main() { + mach_port_t service; + kern_return_t kr = bootstrap_check_in(bootstrap_port, + "com.ex.substituted", + &service); + while (1) { + struct msgbuf buf; + mach_msg_option_t option = + MACH_RCV_MSG | + MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); + + kr = mach_msg(&buf.hdr, option, 0, sizeof(buf), service, + MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL); + switch (kr) { + case MACH_MSG_SUCCESS: + break; + case MACH_RCV_BODY_ERROR: + mach_msg_destroy(&buf.hdr); + /* fallthrough */ + case MACH_RCV_TOO_LARGE: + case MACH_RCV_HEADER_ERROR: + case MACH_RCV_SCATTER_SMALL: + ib_log("mach_msg(rcv) -> %x", kr); + continue; + default: + ib_log("mach_msg(rcv) -> %x (fatal)", kr); + return 1; + } + + mach_msg_audit_trailer_t *real_trail = (void *) &buf.hdr + + round_msg(buf.hdr.msgh_size); + + pid_t pid; + audit_token_to_au32(real_trail->msgh_audit, NULL, NULL, NULL, NULL, + NULL, &pid, NULL, NULL); + + switch (buf.hdr.msgh_id) { + case SUBSTITUTED_MSG_HELLO: + handle_hello(&buf, pid); + break; + default: + ib_log("unknown message %d", buf.hdr.msgh_id); + break; + } + + mach_msg_destroy(&buf.hdr); + } +} |