aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xconfigure2
-rw-r--r--darwin-bootstrap/bundle-loader.c293
-rw-r--r--darwin-bootstrap/substituted-messages.h39
-rw-r--r--darwin-bootstrap/substituted.c127
-rw-r--r--darwin-bootstrap/substituted.m (renamed from darwin-bootstrap/substituted-plist-loader.m)153
-rw-r--r--lib/xxpc.h63
6 files changed, 324 insertions, 353 deletions
diff --git a/configure b/configure
index e50683d..f6e51d9 100755
--- a/configure
+++ b/configure
@@ -212,7 +212,7 @@ if settings.enable_ios_bootstrap:
('dylib', '(out)/bundle-loader.dylib', ['(src)/darwin-bootstrap/bundle-loader.c'], [], [], []),
('exec', '(out)/unrestrict', ['(src)/darwin-bootstrap/unrestrict.c'], ls, [], []),
('exec', '(out)/inject-into-launchd', ['(src)/darwin-bootstrap/inject-into-launchd.c'], ls, ['-framework', 'IOKit', '-framework', 'CoreFoundation'], []),
- ('exec', '(out)/substituted', ['(src)/darwin-bootstrap/substituted.c', '(src)/darwin-bootstrap/substituted-plist-loader.m'], [], ['-lbsm', '-framework', 'Foundation', '-framework', 'CoreFoundation'], ['-fobjc-arc']),
+ ('exec', '(out)/substituted', ['(src)/darwin-bootstrap/substituted.m'], [], ['-lbsm', '-framework', 'Foundation', '-framework', 'CoreFoundation'], ['-fobjc-arc']),
]:
mconfig.build_and_link_c_objs(emitter, settings.host_machine(), settings.specialize(override_ldflags=ldf+settings.host.ldflags, override_cflags=cf+settings.host.cflags), ty, out, ins, objs=objs)
diff --git a/darwin-bootstrap/bundle-loader.c b/darwin-bootstrap/bundle-loader.c
index b3c60d5..1369f74 100644
--- a/darwin-bootstrap/bundle-loader.c
+++ b/darwin-bootstrap/bundle-loader.c
@@ -2,38 +2,17 @@
#define IB_LOG_TO_SYSLOG
#include "ib-log.h"
#include "darwin/mach-decls.h"
-#include "substituted-messages.h"
+#include "xxpc.h"
#include <dlfcn.h>
#include <mach/mach.h>
#include <mach/mig.h>
#include <objc/runtime.h>
#include <CoreFoundation/CoreFoundation.h>
#include <syslog.h>
+#include <pthread.h>
+#include <sys/time.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 ||
- ((const char *) *buf)[op->namelen] != '\0')
- return false;
- *name = *buf;
- *buf += op->namelen + 1;
- return true;
-}
-
static struct {
bool initialized;
typeof(CFBundleGetBundleWithIdentifier) *CFBundleGetBundleWithIdentifier;
@@ -47,6 +26,10 @@ static struct {
typeof(objc_getClass) *objc_getClass;
} objc_funcs;
+static pthread_mutex_t hello_reply_mtx = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t hello_reply_cond = PTHREAD_COND_INITIALIZER;
+static xxpc_object_t hello_reply;
+
#define GET(funcs, handle, name) (funcs)->name = dlsym(handle, #name)
static void *dlopen_noload(const char *path) {
@@ -103,142 +86,166 @@ static void use_dylib(const char *name) {
dlopen(name, RTLD_LAZY);
}
-static void load_bundle_list(const void *buf, size_t size) {
- if (IB_VERBOSE) {
- ib_log("load_bundle_list: %p,%zu", buf, size);
- ib_log_hex(buf, size);
+enum bundle_test_result {
+ BUNDLE_TEST_RESULT_INVALID,
+ BUNDLE_TEST_RESULT_FAIL,
+ BUNDLE_TEST_RESULT_PASS,
+ BUNDLE_TEST_RESULT_EMPTY,
+};
+
+static enum bundle_test_result do_bundle_test_type(
+ xxpc_object_t info, const char *key, bool (*test)(const char *)) {
+ xxpc_object_t values = xxpc_dictionary_get_value(info, key);
+ if (!values || xxpc_get_type(values) != XXPC_TYPE_ARRAY)
+ return BUNDLE_TEST_RESULT_INVALID;
+ size_t count = xxpc_array_get_count(values);
+ if (count == 0)
+ return BUNDLE_TEST_RESULT_EMPTY;
+ for (size_t i = 0; i < count; i++) {
+ const char *value = xxpc_array_get_string(values, i);
+ if (!value)
+ return BUNDLE_TEST_RESULT_INVALID;
+ if (test(value))
+ return BUNDLE_TEST_RESULT_PASS;
}
- int opc;
- const char *name;
- const void *end = buf + size;
- while (buf != end) {
- if (!pop(&buf, end, &opc, &name))
- goto invalid;
- bool pass;
- switch (opc) {
- case SUBSTITUTED_TEST_BUNDLE:
- pass = cf_has_bundle(name);
- if (IB_VERBOSE)
- ib_log("cf_has_bundle('%s'): %d", name, pass);
- if (pass)
- goto pass_type;
- /* fail, so... */
- if (peek(buf, end) != SUBSTITUTED_TEST_BUNDLE)
- goto fail;
- break;
- case SUBSTITUTED_TEST_CLASS:
- pass = objc_has_class(name);
- if (IB_VERBOSE)
- ib_log("objc_has_class('%s'): %d", name, pass);
- if (pass)
- 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;
- }
- break;
- fail:
- do {
- if (!pop(&buf, end, &opc, &name))
- goto invalid;
- } while (opc != SUBSTITUTED_USE_DYLIB);
- break;
- }
- }
- return;
-invalid:
- ib_log("invalid bundle list IPC data (substitute bug)");
+ return BUNDLE_TEST_RESULT_FAIL;
}
-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));
- size_t size = sizeof(buf.hdr) +
- offsetof(struct substituted_msg_body_hello, argv0) +
- strlen(buf.u.req.argv0);
- size_t round = round_msg(size);
- memset((char *) &buf + size, 0, round - size);
- buf.hdr.msgh_size = round;
- 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;
+/* return false if the info was invalid */
+static bool load_bundle_with_info(xxpc_object_t info) {
+ if (IB_VERBOSE) {
+ char *desc = xxpc_copy_description(info);
+ ib_log("load_bundle_with_info: %s", desc);
+ free(desc);
}
+ bool any = xxpc_dictionary_get_bool(info, "any");
+ enum bundle_test_result btr =
+ do_bundle_test_type(info, "bundles", cf_has_bundle);
+ if (btr == BUNDLE_TEST_RESULT_INVALID)
+ return false;
+ if (!any && btr == BUNDLE_TEST_RESULT_FAIL)
+ goto no_load;
+ if (any && btr == BUNDLE_TEST_RESULT_PASS)
+ goto do_load;
+ btr = do_bundle_test_type(info, "classes", objc_has_class);
+ if (btr == BUNDLE_TEST_RESULT_INVALID)
+ return false;
+ if (btr == BUNDLE_TEST_RESULT_FAIL)
+ goto no_load;
+ else
+ goto do_load;
- if (buf.u.resp.error) {
- ib_log("substituted_hello returned error %x", buf.u.resp.error);
- kr = KERN_FAILURE;
- goto out;
- }
+no_load:
+ return true;
+do_load:;
+ const char *name = xxpc_dictionary_get_string(info, "dylib");
+ if (!name)
+ return false;
+ use_dylib(name);
+ return true;
+}
- *bundle_list_p = ool->address;
- *bundle_list_size_p = ool->size;
- return KERN_SUCCESS;
+static bool handle_hello_reply(xxpc_object_t dict) {
+ xxpc_object_t bundles = xxpc_dictionary_get_value(dict, "bundles");
+ if (!bundles || xxpc_get_type(bundles) != XXPC_TYPE_ARRAY)
+ return false;
+ for (size_t i = 0, count = xxpc_array_get_count(bundles);
+ i < count; i++) {
+ if (!load_bundle_with_info(xxpc_array_get_value(bundles, i)))
+ return false;
+ }
+ return true;
+}
-out:
- mach_msg_destroy(&buf.hdr);
- return kr;
+static void handle_xxpc_object(xxpc_object_t object, bool is_reply) {
+ const char *msg;
+ xxpc_type_t ty = xxpc_get_type(object);
+ if (ty == XXPC_TYPE_DICTIONARY) {
+ if (is_reply) {
+ pthread_mutex_lock(&hello_reply_mtx);
+ hello_reply = object;
+ pthread_cond_signal(&hello_reply_cond);
+ pthread_mutex_unlock(&hello_reply_mtx);
+ return;
+ }
+ msg = "received extraneous message from substituted";
+ goto complain;
+ } else if (ty == XXPC_TYPE_ERROR) {
+ msg = "XPC error communicating with substituted";
+ goto complain;
+ } else {
+ msg = "unknown object received from XPC";
+ goto complain;
+ }
+complain:;
+ char *desc = xxpc_copy_description(object);
+ ib_log("%s: %s", msg, desc);
+ free(desc);
}
/* 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) {
- ib_log("bootstrap_look_up com.ex.substituted: %x", kr);
+ xxpc_connection_t conn = xxpc_connection_create_mach_service(
+ "com.ex.substituted", NULL, 0);
+ /* it's not supposed to return null, but just in case */
+ if (!conn) {
+ ib_log("xxpc_connection_create_mach_service returned null");
return;
}
+
+ __block xxpc_object_t received_dict = NULL;
+ __block bool did_receive_dict = false;
+
+ xxpc_connection_set_event_handler(conn, ^(xxpc_object_t object) {
+ handle_xxpc_object(object, false);
+ });
+ xxpc_connection_resume(conn);
+
const char *argv0 = (*_NSGetArgv())[0];
if (!argv0)
argv0 = "???";
- void *bundle_list;
- size_t bundle_list_size;
- kr = substituted_hello(service, SUBSTITUTED_PROTO_VERSION, argv0,
- &bundle_list, &bundle_list_size);
- if (kr) {
- ib_log("substituted_hello: %x", kr);
- return;
+
+ xxpc_object_t message = xxpc_dictionary_create(NULL, NULL, 0);
+ xxpc_dictionary_set_string(message, "type", "hello");
+ xxpc_dictionary_set_string(message, "argv0", argv0);
+ xxpc_connection_send_message_with_reply(conn, message, NULL,
+ ^(xxpc_object_t reply) {
+ handle_xxpc_object(reply, true);
+ });
+
+ struct timeval now;
+ struct timespec give_up_time;
+ gettimeofday(&now, NULL);
+ /* Timing out *always* means a bug (or the user manually unloaded
+ * substituted). Therefore, a high timeout is actually a good thing,
+ * because it makes it clear (especially in testing) that something's wrong
+ * rather than more subtly slowing things down. */
+ give_up_time.tv_sec = now.tv_sec + 10;
+ give_up_time.tv_nsec = now.tv_usec * 1000;
+ pthread_mutex_lock(&hello_reply_mtx);
+ while (!hello_reply) {
+ if (pthread_cond_timedwait(&hello_reply_cond, &hello_reply_mtx,
+ &give_up_time)) {
+ if (errno == ETIMEDOUT) {
+ ib_log("ACK - didn't receive a reply from substituted in time!");
+ goto bad;
+ } else {
+ ib_log("pthread_cond_timedwait failed");
+ goto bad;
+ }
+ }
+ }
+ pthread_mutex_unlock(&hello_reply_mtx);
+
+ if (!handle_hello_reply(hello_reply)) {
+ char *desc = xxpc_copy_description(hello_reply);
+ ib_log("received invalid message from substituted: %s", desc);
+ free(desc);
+ goto bad;
}
- 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 */
+ return;
+bad:
+ ib_log("giving up...");
}
diff --git a/darwin-bootstrap/substituted-messages.h b/darwin-bootstrap/substituted-messages.h
deleted file mode 100644
index 04a3378..0000000
--- a/darwin-bootstrap/substituted-messages.h
+++ /dev/null
@@ -1,39 +0,0 @@
-#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
- * this is pretty silly because even low-level bootstrap_lookup uses xpc now -
- * so could have just used xpc structures - but this is more fun */
-
-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]; */
-} __attribute__((packed));
diff --git a/darwin-bootstrap/substituted.c b/darwin-bootstrap/substituted.c
deleted file mode 100644
index 6b1693e..0000000
--- a/darwin-bootstrap/substituted.c
+++ /dev/null
@@ -1,127 +0,0 @@
-/* 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("message 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);
- }
-}
diff --git a/darwin-bootstrap/substituted-plist-loader.m b/darwin-bootstrap/substituted.m
index 0c8d66f..cfa1541 100644
--- a/darwin-bootstrap/substituted-plist-loader.m
+++ b/darwin-bootstrap/substituted.m
@@ -1,6 +1,6 @@
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
-#include "substituted-messages.h"
+#include "xxpc.h"
enum convert_filters_ret {
PROVISIONAL_PASS,
@@ -22,13 +22,18 @@ static double id_to_double(id o) {
}
}
+static xxpc_object_t nsstring_to_xpc(NSString *in) {
+ return xxpc_string_create([in cStringUsingEncoding:NSUTF8StringEncoding]);
+}
+
static enum convert_filters_ret convert_filters(NSDictionary *plist_dict,
const char *exec_name,
- NSMutableData *test_out) {
+ xxpc_object_t out_info) {
NSDictionary *filter = [plist_dict objectForKey:@"Filter"];
if (!filter)
return PROVISIONAL_PASS;
+
if (![filter isKindOfClass:[NSDictionary class]])
return INVALID;
@@ -36,11 +41,22 @@ static enum convert_filters_ret convert_filters(NSDictionary *plist_dict,
if (!([key isEqualToString:@"CoreFoundationVersion"] ||
[key isEqualToString:@"Classes"] ||
[key isEqualToString:@"Bundles"] ||
- [key isEqualToString:@"Executables"])) {
+ [key isEqualToString:@"Executables"] ||
+ [key isEqualToString:@"Mode"])) {
return INVALID;
}
}
+ bool any = false;
+ NSString *mode_str = [filter objectForKey:@"Mode"];
+ if (mode_str) {
+ any = [mode_str isEqual:@"Any"];
+ if (!any && ![mode_str isEqual:@"All"])
+ return INVALID;
+ }
+
+ xxpc_dictionary_set_bool(out_info, "any", any);
+
/* First do the two we can test here. */
NSArray *cfv = [filter objectForKey:@"CoreFoundationVersion"];
@@ -77,59 +93,53 @@ static enum convert_filters_ret convert_filters(NSDictionary *plist_dict,
if ([name isEqualToString:exe])
goto ok;
}
- return FAIL;
+ if (any)
+ return PROVISIONAL_PASS; // without adding other conditions
+ else
+ return FAIL;
ok:;
}
- /* Convert the rest to substituted_bundle_list_ops. */
+ /* Convert the rest to tests for bundle-loader. */
struct {
__unsafe_unretained NSString *key;
- uint8_t opc;
+ const char *okey;
} types[2] = {
- {@"Classes", SUBSTITUTED_TEST_CLASS},
- {@"Bundles", SUBSTITUTED_TEST_BUNDLE},
+ {@"Classes", "classes"},
+ {@"Bundles", "bundles"},
};
for (int i = 0; i < 2; i++) {
NSArray *things = [filter objectForKey:types[i].key];
if (things) {
+ xxpc_object_t out_things = xxpc_array_create(NULL, 0);
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];
+ xxpc_array_append_value(out_things, nsstring_to_xpc(name));
}
+ xxpc_dictionary_set_value(out_info, types[i].okey, out_things);
+ xxpc_release(out_things);
}
}
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) {
+xxpc_object_t get_hello_bundles(const char *exec_name) {
/* TODO cache */
+ xxpc_object_t bundles = xxpc_array_create(NULL, 0);
+
NSError *err;
NSString *base = @"/Library/Substitute/DynamicLibraries";
NSArray *list = [[NSFileManager defaultManager]
contentsOfDirectoryAtPath:base
error:&err];
if (!list)
- return;
-
- NSMutableData *out = [NSMutableData data];
+ return bundles;
for (NSString *dylib in list) {
if (![[dylib pathExtension] isEqualToString:@"dylib"])
@@ -140,34 +150,91 @@ void get_bundle_list(const char *exec_name,
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);
+ 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);
+ xxpc_object_t info = xxpc_dictionary_create(NULL, NULL, 0);
+
+ enum convert_filters_ret ret = convert_filters(plist_dict, exec_name, info);
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;
+ xxpc_dictionary_set_value(info, "dylib", nsstring_to_xpc(dylib_path));
+
+ xxpc_array_append_value(bundles, info);
+ xxpc_release(info);
+ }
+
+ return bundles;
+}
+
+#define PRECISE objc_precise_lifetime
+
+static bool handle_message(xxpc_object_t request, xxpc_object_t reply) {
+ const char *type = xxpc_dictionary_get_string(request, "type");
+ if (!type || strcmp(type, "hello"))
+ return false;
+ const char *argv0 = xxpc_dictionary_get_string(request, "argv0");
+ if (!argv0)
+ return false;
+ xxpc_object_t bundles = get_hello_bundles(argv0);
+ xxpc_dictionary_set_value(reply, "bundles", bundles);
+ xxpc_release(bundles);
+ return true;
+}
+
+static void init_peer(xxpc_object_t peer) {
+ xxpc_connection_set_event_handler(peer, ^(xxpc_object_t event) {
+ xxpc_type_t ty = xxpc_get_type(event);
+ if (ty == XXPC_TYPE_DICTIONARY) {
+ xxpc_object_t reply = xxpc_dictionary_create_reply(event);
+ if (handle_message(event, reply))
+ xxpc_connection_send_message(peer, reply);
+ else
+ xxpc_connection_cancel(peer);
+ xxpc_release(reply);
+ } else if (ty == XXPC_TYPE_ERROR) {
+ if (event == XXPC_ERROR_CONNECTION_INTERRUPTED)
+ return;
+ NSLog(@"XPC error from peer: %@", event);
+ } else {
+ NSLog(@"unknown object received from XPC (peer): %@", event);
}
- [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];
+ });
+ xxpc_connection_resume(peer);
+}
+
+/* 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). In the future it will help with hot loading. */
+
+int main() {
+ xxpc_connection_t listener = xxpc_connection_create_mach_service(
+ "com.ex.substituted", NULL, XXPC_CONNECTION_MACH_SERVICE_LISTENER);
+ if (!listener) {
+ NSLog(@"xxpc_connection_create_mach_service returned null");
+ exit(1);
}
- the_bundle_list = out;
- *bundle_list = [out bytes];
- *bundle_list_size = [out length];
+ xxpc_connection_set_event_handler(listener, ^(xxpc_object_t object) {
+ xxpc_type_t ty = xxpc_get_type(object);
+ if (ty == XXPC_TYPE_CONNECTION) {
+ init_peer(object);
+ } else if (ty == XXPC_TYPE_ERROR) {
+ NSLog(@"XPC error in server: %@", object);
+ exit(1);
+ } else {
+ NSLog(@"unknown object received from XPC: %@", object);
+ }
+ });
+ xxpc_connection_resume(listener);
+ [[NSRunLoop mainRunLoop] run];
}
diff --git a/lib/xxpc.h b/lib/xxpc.h
new file mode 100644
index 0000000..1e2bb5b
--- /dev/null
+++ b/lib/xxpc.h
@@ -0,0 +1,63 @@
+#pragma once
+/* distinct names to avoid incompatibility if <xpc/xpc.h> gets included somehow
+ * on OS X. No ARC support! */
+#include <dispatch/dispatch.h>
+
+typedef struct _xxpc_object {
+ int x;
+} *xxpc_object_t, *xxpc_connection_t, *xxpc_type_t;
+typedef void (^xxpc_handler_t)(xxpc_object_t);
+
+#define DEFINE_CONST(name, sym) \
+ extern struct _xxpc_object name[1] asm("_" #sym)
+
+DEFINE_CONST(XXPC_TYPE_CONNECTION, _xpc_type_connection);
+DEFINE_CONST(XXPC_TYPE_ERROR, _xpc_type_error);
+DEFINE_CONST(XXPC_TYPE_DICTIONARY, _xpc_type_dictionary);
+DEFINE_CONST(XXPC_TYPE_ARRAY, _xpc_type_array);
+DEFINE_CONST(XXPC_TYPE_STRING, _xpc_type_string);
+DEFINE_CONST(XXPC_ERROR_CONNECTION_INTERRUPTED, _xpc_error_connection_interrupted);
+
+
+#define XXPC_ARRAY_APPEND -1
+#define XXPC_CONNECTION_MACH_SERVICE_LISTENER 1
+
+#define WRAP(name, args...) \
+ x##name args asm("_" #name)
+
+void WRAP(xpc_release, (xxpc_object_t));
+xxpc_object_t WRAP(xpc_retain, (xxpc_object_t));
+char *WRAP(xpc_copy_description, (xxpc_object_t));
+
+xxpc_type_t WRAP(xpc_get_type, (xxpc_object_t));
+xxpc_object_t WRAP(xpc_string_create, (const char *));
+void WRAP(xpc_array_append_value, (xxpc_object_t, xxpc_object_t));
+xxpc_object_t WRAP(xpc_array_create, (const xxpc_object_t *, size_t));
+size_t WRAP(xpc_array_get_count, (const xxpc_object_t));
+xxpc_object_t WRAP(xpc_array_get_value, (xxpc_object_t, size_t));
+const char *WRAP(xpc_array_get_string, (xxpc_object_t, size_t));
+void WRAP(xpc_array_set_string, (xxpc_object_t, size_t, const char *));
+xxpc_object_t WRAP(xpc_dictionary_create, (const char *const *,
+ const xxpc_object_t *, size_t));
+
+xxpc_object_t WRAP(xpc_dictionary_create_reply, (xxpc_object_t));
+bool WRAP(xpc_dictionary_get_bool, (xxpc_object_t, const char *));
+const char *WRAP(xpc_dictionary_get_string, (xxpc_object_t, const char *));
+xxpc_object_t WRAP(xpc_dictionary_get_value, (xxpc_object_t, const char *));
+void WRAP(xpc_dictionary_set_bool, (xxpc_object_t, const char *, bool));
+void WRAP(xpc_dictionary_set_string, (xxpc_object_t, const char *, const char *));
+void WRAP(xpc_dictionary_set_value, (xxpc_object_t, const char *, xxpc_object_t));
+
+xxpc_connection_t WRAP(xpc_connection_create_mach_service, (const char *,
+ dispatch_queue_t,
+ uint64_t));
+void WRAP(xpc_connection_resume, (xxpc_connection_t));
+void WRAP(xpc_connection_set_event_handler, (xxpc_connection_t, xxpc_handler_t));
+void WRAP(xpc_connection_send_message_with_reply,
+ (xxpc_connection_t, xxpc_object_t, dispatch_queue_t, xxpc_handler_t));
+void WRAP(xpc_connection_send_message, (xxpc_connection_t, xxpc_object_t));
+void WRAP(xpc_connection_cancel, (xxpc_connection_t));
+
+#undef DEFINE_TYPE
+#undef DEFINE_CONST
+#undef WRAP