aboutsummaryrefslogtreecommitdiff
path: root/darwin-bootstrap
diff options
context:
space:
mode:
authorcomex2015-02-28 21:18:20 -0500
committercomex2015-02-28 21:18:20 -0500
commitbfce3bf03e7bc285bc5bbf3df561a6f4e9169899 (patch)
tree115e02d7a4d44ca6e0a13c4a02a335dffe9b54e2 /darwin-bootstrap
parentRename ios-bootstrap to darwin-bootstrap; cleanup posixspawn-hook and unrestr... (diff)
downloadsubstitute-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.c210
-rw-r--r--darwin-bootstrap/bundle-loader.m161
-rw-r--r--darwin-bootstrap/posixspawn-hook.c102
-rw-r--r--darwin-bootstrap/substituted-messages.h37
-rw-r--r--darwin-bootstrap/substituted-plist-loader.h2
-rw-r--r--darwin-bootstrap/substituted-plist-loader.m173
-rw-r--r--darwin-bootstrap/substituted.c127
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);
+ }
+}