aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--ios-bootstrap/bundle-loader.m164
-rw-r--r--ios-bootstrap/inject-into-launchd.c10
-rw-r--r--ios-bootstrap/posixspawn-hook.c119
-rw-r--r--ios-bootstrap/unrestrict.c8
-rw-r--r--lib/darwin/interpose.c1
-rw-r--r--lib/darwin/unrestrict.c34
-rwxr-xr-xscript/gen-deb.sh2
8 files changed, 268 insertions, 72 deletions
diff --git a/Makefile b/Makefile
index 7dcc20a..091bf59 100644
--- a/Makefile
+++ b/Makefile
@@ -174,7 +174,7 @@ all: safety-dance
out/posixspawn-hook.dylib: ios-bootstrap/posixspawn-hook.c out/libsubstitute.dylib
$(CC) -dynamiclib -o $@ $< -Lout -lsubstitute
out/bundle-loader.dylib: ios-bootstrap/bundle-loader.m out/libsubstitute.dylib
- $(CC) -dynamiclib -o $@ $< -Lout -lsubstitute
+ $(CC) -dynamiclib -o $@ $< -fobjc-arc -Lout -framework Foundation -framework CoreFoundation
out/unrestrict: ios-bootstrap/unrestrict.c out/libsubstitute.dylib
$(CC) -o $@ $< -Lout -lsubstitute
ldid -Sent.plist $@
diff --git a/ios-bootstrap/bundle-loader.m b/ios-bootstrap/bundle-loader.m
index 532b844..45c37cd 100644
--- a/ios-bootstrap/bundle-loader.m
+++ b/ios-bootstrap/bundle-loader.m
@@ -1,5 +1,165 @@
-#include <syslog.h>
+#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) {
+ NSString *main_identifier = [[NSBundle mainBundle] bundleIdentifier];
+ if (!main_identifier)
+ main_identifier = @"";
+ if (![bundles isKindOfClass:[NSArray class]])
+ return INVALID;
+ for (NSString *identifier in bundles) {
+ if (![identifier isKindOfClass:[NSString class]])
+ return INVALID;
+ if ([identifier isEqualToString:main_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() {
- syslog(LOG_WARNING, "Hi!");
+ NSError *err;
+ NSString *base = @"/Library/Substitute/DynamicLibraries";
+ NSArray *list = [[NSFileManager defaultManager]
+ contentsOfDirectoryAtPath:base
+ error:&err];
+ if (!list)
+ return;
+
+ for (NSString *dylib in list) {
+ NSLog(@"?%@", dylib);
+ 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/ios-bootstrap/inject-into-launchd.c b/ios-bootstrap/inject-into-launchd.c
index 1da4a02..5624cbc 100644
--- a/ios-bootstrap/inject-into-launchd.c
+++ b/ios-bootstrap/inject-into-launchd.c
@@ -74,4 +74,14 @@ int main(UNUSED int argc, char **argv) {
substitute_strerror(ret), error);
return 0;
}
+ /* wait for it to finish */
+ static struct {
+ mach_msg_header_t hdr;
+ mach_msg_trailer_t huh;
+ } msg;
+ kr = mach_msg_overwrite(NULL, MACH_RCV_MSG, 0, sizeof(msg), port,
+ MACH_MSG_TIMEOUT_NONE, MACH_PORT_NULL,
+ &msg.hdr, 0);
+ if (kr)
+ syslog(LOG_EMERG, "mach_msg_overwrite: %x", kr);
}
diff --git a/ios-bootstrap/posixspawn-hook.c b/ios-bootstrap/posixspawn-hook.c
index 9ea9621..f1aff4e 100644
--- a/ios-bootstrap/posixspawn-hook.c
+++ b/ios-bootstrap/posixspawn-hook.c
@@ -8,6 +8,7 @@
#include <malloc/malloc.h>
#include <assert.h>
#include <dispatch/dispatch.h>
+#include <errno.h>
extern char ***_NSGetEnviron(void);
@@ -28,6 +29,7 @@ static void open_logfp_if_necessary() {
}
#define psh_log(fmt, args...) do { \
open_logfp_if_necessary(); \
+ syslog(LOG_EMERG, fmt, ##args); \
fprintf(logfp, fmt "\n", ##args); \
fflush(logfp); \
} while(0)
@@ -46,7 +48,7 @@ static bool advance(char **strp, const char *template) {
return false;
}
-static bool spawn_unrestrict_me(pid_t pid, bool should_resume) {
+static bool spawn_unrestrict(pid_t pid, bool should_resume) {
const char *prog = "/Library/Substitute/unrestrict";
char pid_s[32];
sprintf(pid_s, "%ld", (long) pid);
@@ -59,8 +61,9 @@ static bool spawn_unrestrict_me(pid_t pid, bool should_resume) {
}
int xstat;
/* reap intermediate to avoid zombie - if it doesn't work, not a big deal */
- if (waitpid(prog_pid, &xstat, 0))
+ if (waitpid(prog_pid, &xstat, 0) == -1)
psh_log("posixspawn-hook: couldn't waitpid");
+ psh_log("unrestrict xstat=%x", xstat);
return true;
}
@@ -75,6 +78,29 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old,
char *const *envp_to_use = envp;
char *const *my_envp = envp ? envp : *_NSGetEnviron();
posix_spawnattr_t my_attr = NULL;
+
+ if (attrp) {
+ posix_spawnattr_t attr = *attrp;
+ size_t size = malloc_size(attr);
+ my_attr = malloc(size);
+ if (!my_attr)
+ goto crap;
+ memcpy(my_attr, attr, size);
+ } else {
+ if (posix_spawnattr_init(&my_attr))
+ goto crap;
+ }
+
+ short flags;
+ if (posix_spawnattr_getflags(&my_attr, &flags))
+ goto crap;
+ psh_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++)
+ psh_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
@@ -129,11 +155,12 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old,
if (!safe_mode) {
if (newp != newp_orig)
*newp++ = ':';
- const char *dylib_to_add = !strncmp(path, "/usr/libexec/xpcproxy"))
+ const char *dylib_to_add = !strcmp(path, "/usr/libexec/xpcproxy")
? my_dylib_2
: my_dylib_1;
newp = stpcpy(newp, dylib_to_add);
}
+ psh_log("using %s", new);
/* no libraries? then just get rid of it */
if (newp == newp_orig) {
free(new);
@@ -157,34 +184,26 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old,
/* Deal with the dumb __restrict section. A complication is that this
* could actually be an exec. */
- if (attrp) {
- posix_spawnattr_t attr = *attrp;
- size_t size = malloc_size(attr);
- my_attr = malloc(size);
- if (!my_attr)
- goto crap;
- memcpy(my_attr, attr, size);
- } else {
- if (posix_spawnattr_init(&my_attr))
- goto crap;
- }
- short flags;
- if (posix_spawnattr_getflags(&my_attr, &flags))
- goto crap;
+ /* TODO skip this if Substrate is doing it anyway */
bool was_suspended = flags & POSIX_SPAWN_START_SUSPENDED;
flags |= POSIX_SPAWN_START_SUSPENDED;
if (posix_spawnattr_setflags(&my_attr, flags))
goto crap;
if (flags & POSIX_SPAWN_SETEXEC) {
- if (!spawn_unrestrict_me(getpid(), !was_suspended))
+ /* make the marker fd; hope you weren't using that */
+ if (dup2(2, 255) != 255) {
+ psh_log("dup2 failure - %s", strerror(errno));
+ goto skip;
+ }
+ if (fcntl(255, F_SETFD, FD_CLOEXEC))
+ goto crap;
+ psh_log("spawning unrestrict");
+ if (!spawn_unrestrict(getpid(), !was_suspended))
goto skip;
}
-
- psh_log("calling old <path=%s>", path);
- for (char *const *ap = argv; *ap; ap++)
- psh_log(" %s", *ap);
+ psh_log("**");
int ret = old(pidp, path, file_actions, &my_attr, argv, envp_to_use);
- psh_log("ret=%d", ret);
+ psh_log("ret=%d pid=%ld", ret, (long) *pidp);
if (ret)
goto cleanup;
/* Since it returned, obviously it was not SETEXEC, so we need to
@@ -212,7 +231,7 @@ static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old,
free(error);
});
#else
- spawn_unrestrict_me(pid, !was_suspended);
+ spawn_unrestrict(pid, !was_suspended);
#endif
goto cleanup;
crap:
@@ -227,9 +246,9 @@ cleanup:
}
static void after_wait_generic(pid_t pid, int stat) {
+ /* TODO safety */
(void) pid;
(void) stat;
-
}
int hook_posix_spawn(pid_t *restrict pid, const char *restrict path,
@@ -260,12 +279,28 @@ pid_t hook_waitpid(pid_t pid, int *stat_loc, int options) {
return ret;
}
-void substitute_init(struct shuttle *shuttle, UNUSED size_t nshuttle) {
- /* Don't hook twice - that will just mess things up. */
- static int already_initted;
- if (__atomic_exchange_n(&already_initted, 1, __ATOMIC_RELAXED))
+void substitute_init(struct shuttle *shuttle, size_t nshuttle) {
+ /* Just tell them we're done */
+ if (nshuttle != 1) {
+ psh_log("nshuttle = %zd?", nshuttle);
return;
+ }
+ mach_port_t notify_port = shuttle[0].u.mach.port;
+ mach_msg_header_t done_hdr;
+ done_hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_MOVE_SEND, 0);
+ done_hdr.msgh_size = sizeof(done_hdr);
+ done_hdr.msgh_remote_port = notify_port;
+ done_hdr.msgh_local_port = 0;
+ done_hdr.msgh_voucher_port = 0;
+ done_hdr.msgh_id = 42;
+ kern_return_t kr = mach_msg_send(&done_hdr);
+ if (kr)
+ psh_log("posixspawn-hook: mach_msg_send failed: kr=%x", kr);
+ /* MOVE deallocated the port */
+}
+__attribute__((constructor))
+static void init() {
/* Note: I'm using interposing to minimize the chance of conflict with
* Substrate. This shouldn't actually be necessary, because MSHookProcess,
* at least as of the old version I'm looking at the source code of, blocks
@@ -305,30 +340,4 @@ end:
if (im)
substitute_close_image(im);
- mach_port_t notify_port = shuttle[0].u.mach.port;
- mach_msg_header_t done_hdr;
- done_hdr.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
- done_hdr.msgh_size = sizeof(done_hdr);
- done_hdr.msgh_remote_port = notify_port;
- done_hdr.msgh_local_port = 0;
- done_hdr.msgh_voucher_port = 0;
- done_hdr.msgh_id = 42;
- kern_return_t kr = mach_msg_send(&done_hdr);
- if (kr)
- psh_log("posixspawn-hook: mach_msg_send failed: kr=%x", kr);
- /* MOVE deallocated the port */
-}
-
-__attribute__((constructor))
-static void init() {
- if (getenv("TEST_POSIXSPAWN_HOOK")) {
- mach_port_t port;
- assert(!mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_DEAD_NAME,
- &port));
- struct shuttle shuttle = {
- .type = SUBSTITUTE_SHUTTLE_MACH_PORT,
- .u.mach.port = port
- };
- substitute_init(&shuttle, 1);
- }
}
diff --git a/ios-bootstrap/unrestrict.c b/ios-bootstrap/unrestrict.c
index e9ab4eb..b77bdf5 100644
--- a/ios-bootstrap/unrestrict.c
+++ b/ios-bootstrap/unrestrict.c
@@ -6,6 +6,13 @@
#include <stdio.h>
int main(int argc, char **argv) {
+ char filename[128];
+ sprintf(filename, "/tmp/wtf.%ld",
+ (long) getpid());
+ FILE *fp = fopen(filename, "w");
+ if (!fp)
+ return 1;
+ #define syslog(a, b...) fprintf(fp, b)
if (argc != 3) {
syslog(LOG_EMERG, "unrestrict: wrong number of args");
return 1;
@@ -34,6 +41,7 @@ int main(int argc, char **argv) {
}
char *err = NULL;
+ syslog(LOG_EMERG, "unrestrict: unrestricting %d (%s)", (int) pid, should_resume);
int sret = substitute_ios_unrestrict((pid_t) pid, should_resume[0] == '1', &err);
if (sret) {
syslog(LOG_EMERG, "unrestrict: substitute_ios_unrestrict => %d (%s)",
diff --git a/lib/darwin/interpose.c b/lib/darwin/interpose.c
index bf9ceb4..419fc65 100644
--- a/lib/darwin/interpose.c
+++ b/lib/darwin/interpose.c
@@ -123,6 +123,7 @@ static int try_bind_section(void *bind, size_t size, const struct interpose_stat
}
if (h->old_ptr)
*(uintptr_t *) h->old_ptr = old - addend;
+ offset += stride;
}
break;
}
diff --git a/lib/darwin/unrestrict.c b/lib/darwin/unrestrict.c
index dd852bc..70b089a 100644
--- a/lib/darwin/unrestrict.c
+++ b/lib/darwin/unrestrict.c
@@ -7,6 +7,10 @@
#include <errno.h>
#include <mach/vm_region.h>
+#define PROC_PIDFDVNODEINFO 1
+#define PROC_PIDFDVNODEINFO_SIZE 176
+int proc_pidfdinfo(int, int, int, void *, int);
+
static int unrestrict_macho_header(void *header, size_t size, bool *did_modify_p,
char **error) {
*did_modify_p = false;
@@ -43,7 +47,7 @@ static int unrestrict_macho_header(void *header, size_t size, bool *did_modify_p
for (uint32_t i = 0; i < sc->nsects; i++, sect++) {
if (!strncmp(sect->sectname, "__restrict", 16)) {
strcpy(sect->sectname, "\xf0\x9f\x92\xa9");
- *did_modify_p =true;
+ *did_modify_p = true;
}
}
}
@@ -72,23 +76,25 @@ int substitute_ios_unrestrict(pid_t pid, bool should_resume, char **error) {
int retries = 0;
int wait_us = 1;
-setback:
while (1) {
- /* if calling from unrestrict-me, the process might not have transitioned
- * yet. if it has, then TASK_DYLD_INFO will be filled with 0. */
- task_basic_info_data_t tbi;
- mach_msg_type_number_t cnt = TASK_BASIC_INFO_COUNT;
-
- kern_return_t kr = task_info(task, TASK_BASIC_INFO, (void *) &tbi, &cnt);
- if (kr || cnt != TASK_BASIC_INFO_COUNT) {
- asprintf(error, "task_info: %x", kr);
+ /* If calling from unrestrict-me, the process might not have
+ * transitioned yet. We set up a dummy fd 255 in the parent process
+ * which was marked CLOEXEC, so test if that still exists.
+ * AFAICT, Substrate's equivalent to this is not actually correct.
+ * TODO cleanup
+ */
+ char buf[PROC_PIDFDVNODEINFO_SIZE];
+ int ret = proc_pidfdinfo(pid, 255, PROC_PIDFDVNODEINFO, buf, sizeof(buf));
+ if (ret == -1 && errno == EBADF) {
+ break;
+ } else if (ret == -1) {
+ asprintf(error, "proc_pidfdinfo: %s", strerror(errno));
ret = SUBSTITUTE_ERR_MISC;
goto fail;
}
- if (tbi.user_time.seconds == 0 && tbi.user_time.microseconds == 0)
- break;
+
if (retries++ == 20) {
- asprintf(error, "user_time was not 0 after 20 retries");
+ asprintf(error, "still in parent process after 20 retries");
ret = SUBSTITUTE_ERR_MISC;
goto fail;
}
@@ -101,6 +107,7 @@ setback:
/* alrighty then, let's look at the damage. find the first readable
* segment */
+setback:;
mach_vm_address_t segm_addr = 0;
mach_vm_size_t segm_size = 0;
vm_region_basic_info_data_64_t info;
@@ -111,6 +118,7 @@ setback:
(vm_region_info_t) &info, &info_count, &object_name);
if (kr == KERN_INVALID_ADDRESS) {
/* nothing! maybe it's not there *yet*? this actually is possible */
+ usleep(10);
goto setback;
} else if (kr) {
asprintf(error, "mach_vm_region(%lx): %x", (long) segm_addr, kr);
diff --git a/script/gen-deb.sh b/script/gen-deb.sh
index 3c73fef..81b3efb 100755
--- a/script/gen-deb.sh
+++ b/script/gen-deb.sh
@@ -13,7 +13,7 @@ ln -s libsubstitute.0.dylib $debroot/usr/lib/libsubstitute.dylib
mkdir -p $debroot/usr/include/substitute
cp lib/substitute.h $debroot/usr/include/substitute/
cp substrate/substrate.h $debroot/usr/include/substitute/
-mkdir -p $debroot/Library/Substitute
+mkdir -p $debroot/Library/Substitute/DynamicLibraries
cp out/{posixspawn-hook.dylib,bundle-loader.dylib,unrestrict,inject-into-launchd} $debroot/Library/Substitute/
cp -a DEBIAN $debroot/
sed "s#{VERSION}#$version#g" DEBIAN/control > $debroot/DEBIAN/control