aboutsummaryrefslogtreecommitdiff
path: root/darwin-bootstrap
diff options
context:
space:
mode:
Diffstat (limited to 'darwin-bootstrap')
-rw-r--r--darwin-bootstrap/bundle-loader.m161
-rw-r--r--darwin-bootstrap/ib-log.h27
-rw-r--r--darwin-bootstrap/inject-into-launchd.c93
-rw-r--r--darwin-bootstrap/posixspawn-hook.c399
-rw-r--r--darwin-bootstrap/safety-dance/AutoGrid.h17
-rw-r--r--darwin-bootstrap/safety-dance/AutoGrid.m106
-rw-r--r--darwin-bootstrap/safety-dance/Info.plist88
-rw-r--r--darwin-bootstrap/safety-dance/main.m159
-rw-r--r--darwin-bootstrap/safety-dance/white.pngbin0 -> 1047 bytes
-rw-r--r--darwin-bootstrap/unrestrict.c245
10 files changed, 1295 insertions, 0 deletions
diff --git a/darwin-bootstrap/bundle-loader.m b/darwin-bootstrap/bundle-loader.m
new file mode 100644
index 0000000..fec2513
--- /dev/null
+++ b/darwin-bootstrap/bundle-loader.m
@@ -0,0 +1,161 @@
+#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/ib-log.h b/darwin-bootstrap/ib-log.h
new file mode 100644
index 0000000..fee70b5
--- /dev/null
+++ b/darwin-bootstrap/ib-log.h
@@ -0,0 +1,27 @@
+#pragma once
+#include <dispatch/dispatch.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static FILE *logfp;
+static void open_logfp_if_necessary() {
+ /* syslog() doesn't seem to work from launchd... */
+ static dispatch_once_t pred;
+ dispatch_once(&pred, ^{
+ char filename[128];
+ sprintf(filename, "/tmp/substitute-" IB_LOG_NAME "-log.%ld",
+ (long) getpid());
+ logfp = fopen(filename, "w");
+ if (!logfp) {
+ /* Ack... */
+ logfp = stderr;
+ }
+ });
+}
+#define ib_log(fmt, args...) do { \
+ open_logfp_if_necessary(); \
+ fprintf(logfp, fmt "\n", ##args); \
+ fflush(logfp); \
+} while(0)
+
+#define IB_VERBOSE 0
diff --git a/darwin-bootstrap/inject-into-launchd.c b/darwin-bootstrap/inject-into-launchd.c
new file mode 100644
index 0000000..539b1a2
--- /dev/null
+++ b/darwin-bootstrap/inject-into-launchd.c
@@ -0,0 +1,93 @@
+/* This is an iOS executable, placed in /etc/rc.d, that injects
+ * posixspawn-hook.dylib into launchd (pid 1). */
+
+#define IB_LOG_NAME "iil"
+#include "ib-log.h"
+#include "substitute.h"
+#include "substitute-internal.h"
+#include <mach/mach.h>
+#include <mach/mach_time.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <syslog.h>
+#include <CoreFoundation/CoreFoundation.h>
+
+void *IOHIDEventCreateKeyboardEvent(CFAllocatorRef, uint64_t, uint32_t, uint32_t, bool, uint32_t);
+void *IOHIDEventSystemCreate(CFAllocatorRef);
+void *IOHIDEventSystemCopyEvent(void *, uint32_t, void *, uint32_t);
+
+CFIndex IOHIDEventGetIntegerValue(void *, uint32_t);
+enum {
+ kIOHIDEventTypeKeyboard = 3,
+ kIOHIDEventFieldKeyboardDown = 3 << 16 | 2,
+};
+
+static bool button_pressed(void *event_system, uint32_t usage_page, uint32_t usage) {
+ /* This magic comes straight from Substrate... I don't really understand
+ * what it's doing. In particular, where is the equivalent kernel
+ * implementation on OS X? Does it not exist? But I guess Substrate is
+ * emulating backboardd. */
+ void *dummy = IOHIDEventCreateKeyboardEvent(NULL, mach_absolute_time(),
+ usage_page, usage,
+ 0, 0);
+ if (!dummy) {
+ ib_log("couldn't create dummy HID event");
+ return false;
+ }
+ void *event = IOHIDEventSystemCopyEvent(event_system,
+ kIOHIDEventTypeKeyboard,
+ dummy, 0);
+ if (!event)
+ return false;
+ CFIndex ival = IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown);
+ return ival;
+}
+
+int main(UNUSED int argc, char **argv) {
+ pid_t pid = argv[1] ? atoi(argv[1]) : 1; /* for testing */
+
+ void *event_system = IOHIDEventSystemCreate(NULL);
+ if (!event_system) {
+ ib_log("couldn't create HID event system");
+ } else {
+
+ /* consumer page -> Volume Increment */
+ if (button_pressed(event_system, 0x0c, 0xe9) ||
+ /* telephony page -> Flash */
+ button_pressed(event_system, 0x0b, 0x21)) {
+ ib_log("disabling due to button press");
+ return 0;
+ }
+ }
+ mach_port_t port = 0;
+ kern_return_t kr = mach_port_allocate(mach_task_self(),
+ MACH_PORT_RIGHT_RECEIVE,
+ &port);
+ if (kr) {
+ ib_log("mach_port_allocate: %x", kr);
+ return 0;
+ }
+ const char *lib = "/Library/Substitute/posixspawn-hook.dylib";
+ struct shuttle shuttle = {
+ .type = SUBSTITUTE_SHUTTLE_MACH_PORT,
+ .u.mach.right_type = MACH_MSG_TYPE_MAKE_SEND,
+ .u.mach.port = port
+ };
+ char *error;
+ int ret = substitute_dlopen_in_pid(pid, lib, 0, &shuttle, 1, &error);
+ if (ret) {
+ ib_log("substitute_dlopen_in_pid: %s/%s",
+ 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)
+ ib_log("mach_msg_overwrite: %x", kr);
+}
diff --git a/darwin-bootstrap/posixspawn-hook.c b/darwin-bootstrap/posixspawn-hook.c
new file mode 100644
index 0000000..5acb30b
--- /dev/null
+++ b/darwin-bootstrap/posixspawn-hook.c
@@ -0,0 +1,399 @@
+/* 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.
+ *
+ * It also handles the sandbox override for substituted. */
+
+#define IB_LOG_NAME "posixspawn-hook"
+#include "ib-log.h"
+#include "substitute.h"
+#include "substitute-internal.h"
+#include <mach/mach.h>
+#include <mach-o/dyld.h>
+#include <mach-o/loader.h>
+#include <mach-o/fat.h>
+#include <spawn.h>
+#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>
+
+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 bool advance(char **strp, const char *template) {
+ size_t len = strlen(template);
+ if (!strncmp(*strp, template, len)) {
+ *strp += len;
+ return true;
+ }
+ return false;
+}
+
+static bool spawn_unrestrict(pid_t pid, bool should_resume, bool is_exec) {
+ const char *prog = "/Library/Substitute/unrestrict";
+ char pid_s[32];
+ sprintf(pid_s, "%ld", (long) pid);
+ const char *should_resume_s = should_resume ? "1" : "0";
+ const char *is_exec_s = is_exec ? "1" : "0";
+ const char *argv[] = {prog, pid_s, should_resume_s, is_exec_s, NULL};
+ pid_t prog_pid;
+ if (old_posix_spawn(&prog_pid, prog, NULL, NULL, (char **) argv, NULL)) {
+ ib_log("posixspawn-hook: couldn't start unrestrict - oh well...");
+ return false;
+ }
+ int xstat;
+ /* reap intermediate to avoid zombie - if it doesn't work, not a big deal */
+ if (waitpid(prog_pid, &xstat, 0) == -1)
+ ib_log("posixspawn-hook: couldn't waitpid");
+ ib_log("unrestrict xstat=%x", xstat);
+ return true;
+}
+
+static bool looks_restricted(const char *filename) {
+ int fd = open(filename, O_RDONLY);
+ if (fd == -1) {
+ ib_log("open '%s': %s", filename, strerror(errno));
+ return false;
+ }
+ uint32_t offset = 0;
+ union {
+ uint32_t magic;
+ struct {
+ struct fat_header fh;
+ struct fat_arch fa1;
+ };
+ struct mach_header mh;
+ } u;
+ if (read(fd, &u, sizeof(u)) != sizeof(u)) {
+ ib_log("read header for '%s': %s", filename, strerror(errno));
+ return false;
+ }
+ if (ntohl(u.magic) == FAT_MAGIC) {
+ /* Fat binary - to avoid needing to replicate grade_binary in the
+ * kernel, we assume all architectures have the same restrict-ness. */
+ if (u.fh.nfat_arch == 0)
+ return false;
+ offset = ntohl(u.fa1.offset);
+ if (pread(fd, &u, sizeof(u), offset) != sizeof(u)) {
+ ib_log("read header (inside fat) for '%s': %s",
+ filename, strerror(errno));
+ return false;
+ }
+ }
+ bool swap, is64;
+ switch (u.magic) {
+ case MH_MAGIC:
+ swap = false;
+ is64 = false;
+ break;
+ case MH_MAGIC_64:
+ swap = false;
+ is64 = true;
+ break;
+ case MH_CIGAM:
+ swap = true;
+ is64 = false;
+ break;
+ case MH_CIGAM_64:
+ swap = true;
+ is64 = true;
+ break;
+ default:
+ ib_log("bad mach-o magic for '%s'", filename);
+ return false;
+ }
+ uint32_t sizeofcmds = u.mh.sizeofcmds;
+ if (swap)
+ sizeofcmds = OSSwapInt32(sizeofcmds);
+ offset += is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header);
+ char *cmds_buf = malloc(sizeofcmds);
+ ssize_t actual = pread(fd, cmds_buf, sizeofcmds, offset);
+ if (actual < 0 || (uint32_t) actual != sizeofcmds) {
+ ib_log("read load cmds for '%s': %s", filename, strerror(errno));
+ free(cmds_buf);
+ return false;
+ }
+ /* overestimation is fine here */
+ const char sectname[] = "__restrict";
+ bool ret = !!memmem(cmds_buf, sizeofcmds, sectname, sizeof(sectname));
+ free(cmds_buf);
+ return ret;
+}
+
+static int hook_posix_spawn_generic(__typeof__(posix_spawn) *old,
+ pid_t *restrict pidp, const char *restrict path,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *restrict attrp,
+ char *const argv[restrict],
+ char *const envp[restrict]) {
+ char *new = NULL;
+ char **new_envp = NULL;
+ 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;
+ 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++;
+ char *env = *ep;
+ if (advance(&env, "_MSSafeMode=") || advance(&env, "_SubstituteSafeMode=")) {
+ if (!strcmp(env, "0") || !strcmp(env, "NO"))
+ continue;
+ else if (!strcmp(env, "1") || !strcmp(env, "YES"))
+ safe_mode = true;
+ else
+ goto skip;
+ } else if (advance(&env, "DYLD_INSERT_LIBRARIES=")) {
+ orig_dyld_insert = env;
+ }
+ }
+ new = malloc(sizeof("DYLD_INSERT_LIBRARIES=") - 1 +
+ sizeof(psh_dylib) /* not - 1, because : */ +
+ strlen(orig_dyld_insert) + 1);
+ char *newp_orig = stpcpy(new, "DYLD_INSERT_LIBRARIES=");
+ char *newp = newp_orig;
+ const char *p = orig_dyld_insert;
+ while (*p) { /* W.N.H. */
+ const char *next = strchr(p, ':') ?: (p + strlen(p));
+ /* append if it isn't one of ours */
+ bool is_substitute =
+ (next - p == sizeof(bl_dylib) - 1 &&
+ !memcmp(next, bl_dylib, sizeof(bl_dylib) - 1)) ||
+ (next - p == sizeof(psh_dylib) - 1 &&
+ !memcmp(next, psh_dylib, sizeof(psh_dylib) - 1));
+ if (!is_substitute) {
+ if (newp != newp_orig)
+ *newp++ = ':';
+ memcpy(newp, p, next - p);
+ newp += next - p;
+ }
+ if (!*next)
+ break;
+ p = next + 1;
+ }
+ /* append ours if necessary */
+ 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)
+ ib_log("using %s", new);
+ /* no libraries? then just get rid of it */
+ if (newp == newp_orig) {
+ free(new);
+ new = NULL;
+ }
+ new_envp = malloc(sizeof(char *) * (env_count + 2));
+ envp_to_use = new_envp;
+ char **outp = new_envp;
+ for (size_t idx = 0; idx < env_count; idx++) {
+ char *env = my_envp[idx];
+ /* remove *all* D_I_L, including duplicates */
+ if (!advance(&env, "DYLD_INSERT_LIBRARIES="))
+ *outp++ = env;
+ }
+ if (new)
+ *outp++ = new;
+ *outp++ = NULL;
+
+ if (safe_mode)
+ goto skip;
+
+
+ /* Deal with the dumb __restrict section. A complication is that this
+ * could actually be an exec. */
+ bool need_unrestrict = looks_restricted(path);
+
+ /* TODO skip this if Substrate is doing it anyway */
+ bool was_suspended;
+ if (need_unrestrict) {
+ 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) {
+ /* make the marker fd; hope you weren't using that */
+ if (dup2(2, 255) != 255) {
+ ib_log("dup2 failure - %s", strerror(errno));
+ goto skip;
+ }
+ if (fcntl(255, F_SETFD, FD_CLOEXEC))
+ goto crap;
+ if (IB_VERBOSE)
+ ib_log("spawning unrestrict");
+ if (!spawn_unrestrict(getpid(), !was_suspended, true))
+ goto skip;
+ }
+ }
+ if (IB_VERBOSE)
+ ib_log("**");
+ int ret = old(pidp, path, file_actions, &my_attr, argv, envp_to_use);
+ if (IB_VERBOSE)
+ ib_log("ret=%d pid=%ld", ret, (long) *pidp);
+ if (ret)
+ goto cleanup;
+ /* Since it returned, obviously it was not SETEXEC, so we need to
+ * unrestrict it ourself. */
+ pid_t pid = *pidp;
+ if (need_unrestrict)
+ spawn_unrestrict(pid, !was_suspended, false);
+ goto cleanup;
+crap:
+ ib_log("posixspawn-hook: weird error - OOM? skipping our stuff");
+skip:
+ ret = old(pidp, path, file_actions, attrp, argv, envp);
+cleanup:
+ free(new_envp);
+ free(new);
+ free(my_attr);
+ return ret;
+}
+
+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,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *restrict attrp,
+ char *const argv[restrict], char *const envp[restrict]) {
+ return hook_posix_spawn_generic(old_posix_spawn, pid, path, file_actions,
+ attrp, argv, envp);
+}
+
+int hook_posix_spawnp(pid_t *restrict pid, const char *restrict path,
+ const posix_spawn_file_actions_t *file_actions,
+ const posix_spawnattr_t *restrict attrp,
+ char *const argv[restrict], char *const envp[restrict]) {
+ return hook_posix_spawn_generic(old_posix_spawnp, pid, path, file_actions,
+ attrp, argv, envp);
+}
+
+pid_t hook_wait4(pid_t pid, int *stat_loc, int options, struct rusage *rusage) {
+ pid_t ret = old_wait4(pid, stat_loc, options, rusage);
+ after_wait_generic(ret, *stat_loc);
+ return ret;
+}
+
+pid_t hook_waitpid(pid_t pid, int *stat_loc, int options) {
+ pid_t ret = old_waitpid(pid, stat_loc, options);
+ after_wait_generic(ret, *stat_loc);
+ return ret;
+}
+
+void substitute_init(struct shuttle *shuttle, size_t nshuttle) {
+ /* Just tell them we're done */
+ if (nshuttle != 1) {
+ ib_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)
+ ib_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
+ * until the thread it remotely creates exits, and that thread does
+ * pthread_join on the 'real' pthread it creates to do the dlopen (unlike
+ * the equivalent in Substitute - the difference is to decrease dependence
+ * on pthread internals). substitute_dlopen_in_pid does not, but that's
+ * what the notify port is for. Meanwhile, the jailbreak I have installed
+ * properly runs rc.d sequentially, so the injection tools won't do their
+ * thing at the same time. But just in case any of that doesn't hold up...
+ *
+ * (it also decreases the amount of library code necessary to load from
+ * disk...)
+ */
+
+ struct substitute_image *im = substitute_open_image(_dyld_get_image_name(0));
+ if (!im) {
+ ib_log("posixspawn-hook: substitute_open_image failed");
+ goto end;
+ }
+
+ static const struct substitute_import_hook hooks[] = {
+ {"_posix_spawn", hook_posix_spawn, &old_posix_spawn},
+ {"_posix_spawnp", hook_posix_spawnp, &old_posix_spawnp},
+ {"_waitpid", hook_waitpid, &old_waitpid},
+ {"_wait4", hook_wait4, &old_wait4},
+ };
+
+ int err = substitute_interpose_imports(im, hooks, sizeof(hooks)/sizeof(*hooks), 0);
+ if (err) {
+ ib_log("posixspawn-hook: substitute_interpose_imports failed: %s",
+ substitute_strerror(err));
+ goto end;
+ }
+
+end:
+ if (im)
+ substitute_close_image(im);
+
+}
diff --git a/darwin-bootstrap/safety-dance/AutoGrid.h b/darwin-bootstrap/safety-dance/AutoGrid.h
new file mode 100644
index 0000000..335381a
--- /dev/null
+++ b/darwin-bootstrap/safety-dance/AutoGrid.h
@@ -0,0 +1,17 @@
+//
+// AutoGrid.h
+// SafetyDance
+//
+// Created by Nicholas Allegra on 1/26/15.
+// Copyright (c) 2015 Nicholas Allegra. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+
+@interface AutoGrid : UIView {
+ NSArray *views;
+ UIScrollView *scrollView;
+}
+- (void)setViews:(NSArray *)views;
+
+@end
diff --git a/darwin-bootstrap/safety-dance/AutoGrid.m b/darwin-bootstrap/safety-dance/AutoGrid.m
new file mode 100644
index 0000000..2a8d3ed
--- /dev/null
+++ b/darwin-bootstrap/safety-dance/AutoGrid.m
@@ -0,0 +1,106 @@
+//
+// AutoGrid.m
+// SafetyDance
+//
+// Created by Nicholas Allegra on 1/26/15.
+// Copyright (c) 2015 Nicholas Allegra. All rights reserved.
+//
+
+#import "AutoGrid.h"
+
+@implementation AutoGrid
+- (void)setViews:(NSArray *)_views {
+ views = _views;
+ [scrollView removeFromSuperview];
+ scrollView = [[UIScrollView alloc] init];
+ [self addSubview:scrollView];
+ for (UIView *view in views)
+ [scrollView addSubview:view];
+ [self setNeedsLayout];
+}
+
+- (void)layoutSubviews {
+ scrollView.frame = self.bounds;
+ CGFloat paddingX = 22, paddingY = 10;
+ NSUInteger nviews = [views count];
+ CGSize *sizes = malloc(sizeof(*sizes) * nviews);
+
+ for (NSUInteger i = 0; i < nviews; i++)
+ sizes[i] = [[views objectAtIndex:i] intrinsicContentSize];
+
+ CGFloat availableWidth = self.bounds.size.width;
+ /* try to lay out using an increasing number of columns */
+ NSUInteger cols;
+ CGSize contentSize;
+ CGFloat *colWidths = NULL;
+ for (cols = 1; ; cols++) {
+ free(colWidths);
+ colWidths = malloc(sizeof(*colWidths) * cols);
+ for (NSUInteger col = 0; col < cols; col++)
+ colWidths[col] = 0;
+ CGFloat tentativeHeight = 0;
+ CGFloat tentativeWidth = 0;
+ for (NSUInteger row = 0; row < nviews / cols; row++) {
+ CGFloat totalWidth = 0;
+ CGFloat maxHeight = 0;
+ for (NSUInteger col = 0; col < cols; col++) {
+ NSUInteger i = row * cols + col;
+ if (i >= nviews)
+ goto done1;
+ CGSize size = sizes[i];
+ if (size.width > colWidths[col])
+ colWidths[col] = size.width;
+ if (col != 0)
+ totalWidth += paddingX;
+ totalWidth += size.width;
+ if (size.height > maxHeight)
+ maxHeight = size.height;
+ }
+ if (totalWidth > tentativeWidth)
+ tentativeWidth = totalWidth;
+ tentativeHeight += maxHeight + paddingY;
+ }
+ done1:
+ if (cols > 1 && tentativeWidth > availableWidth) {
+ cols--;
+ break;
+ }
+ contentSize = CGSizeMake(tentativeWidth, tentativeHeight);
+ NSLog(@"%f", contentSize.height);
+ if (contentSize.width == 0)
+ break;
+
+ }
+ scrollView.contentSize = contentSize;
+ CGFloat y = 0;
+ for (NSUInteger row = 0; ; row++) {
+ CGFloat x = 0;
+ CGFloat maxHeight = 0;
+ for (NSUInteger col = 0; col < cols; col++) {
+ NSUInteger i = row * cols + col;
+ if (i >= nviews)
+ goto done2;
+ CGSize size = sizes[i];
+ UIView *view = [views objectAtIndex:i];
+ if (col != 0)
+ x += paddingX;
+ view.frame = CGRectMake(x, y, size.width, size.height);
+ x += colWidths[col];
+ if (size.height > maxHeight)
+ maxHeight = size.height;
+ }
+ y += maxHeight + paddingY;
+ }
+done2:
+ free(sizes);
+ free(colWidths);
+}
+/*
+// Only override drawRect: if you perform custom drawing.
+// An empty implementation adversely affects performance during animation.
+- (void)drawRect:(CGRect)rect {
+ // Drawing code
+}
+*/
+
+@end
diff --git a/darwin-bootstrap/safety-dance/Info.plist b/darwin-bootstrap/safety-dance/Info.plist
new file mode 100644
index 0000000..f2fa520
--- /dev/null
+++ b/darwin-bootstrap/safety-dance/Info.plist
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>SafetyDance</string>
+ <key>CFBundleIdentifier</key>
+ <string>com.ex.SafetyDance</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>SafetyDance</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <false/>
+ <key>UILaunchImages</key>
+ <array>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>7.0</string>
+ <key>UILaunchImageName</key>
+ <string>Default</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{320, 480}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>7.0</string>
+ <key>UILaunchImageName</key>
+ <string>Default</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{320, 568}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>8.0</string>
+ <key>UILaunchImageName</key>
+ <string>Default</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{375, 667}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>8.0</string>
+ <key>UILaunchImageName</key>
+ <string>Default</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{414, 736}</string>
+ </dict>
+ <dict>
+ <key>UILaunchImageMinimumOSVersion</key>
+ <string>7.0</string>
+ <key>UILaunchImageName</key>
+ <string>Default</string>
+ <key>UILaunchImageOrientation</key>
+ <string>Portrait</string>
+ <key>UILaunchImageSize</key>
+ <string>{768, 1024}</string>
+ </dict>
+ </array>
+ <key>UIStatusBarStyle</key>
+ <string>UIStatusBarStyleLightContent</string>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+</dict>
+</plist>
diff --git a/darwin-bootstrap/safety-dance/main.m b/darwin-bootstrap/safety-dance/main.m
new file mode 100644
index 0000000..0d05e00
--- /dev/null
+++ b/darwin-bootstrap/safety-dance/main.m
@@ -0,0 +1,159 @@
+#import <UIKit/UIKit.h>
+#import "AutoGrid.h"
+
+@interface ViewController : UIViewController {
+ AutoGrid *autoGrid;
+}
+
+@end
+
+@implementation ViewController
+
+- (void)viewDidLoad {
+ [super viewDidLoad];
+ [self loadStuff];
+ NSMutableArray *names = [NSMutableArray array];
+ for (int i = 0; i < 100; i++)
+ [names addObject:[NSString stringWithFormat:@"Some Dylib %d", i]];
+ NSMutableArray *views = [NSMutableArray array];
+ for (NSString *name in names) {
+ UILabel *label = [[UILabel alloc] init];
+ label.text = name;
+ [views addObject:label];
+ }
+ [autoGrid setViews:views];
+}
+
+#define EXPLANATION \
+ @"SpringBoard seems to have crashed. The cause might be a Substitute jailbreak extension, or unrelated. Just to be safe, extensions in SpringBoard have been temporarily disabled. You can continue in this mode, or restart SpringBoard normally.\n\nThe following extensions were running:"
+
+static void hugging(UIView *view, UILayoutPriority pri) {
+ [view setContentHuggingPriority:pri forAxis:UILayoutConstraintAxisHorizontal];
+ [view setContentHuggingPriority:pri forAxis:UILayoutConstraintAxisVertical];
+}
+static void compression(UIView *view, UILayoutPriority pri) {
+ [view setContentCompressionResistancePriority:pri forAxis:UILayoutConstraintAxisHorizontal];
+ [view setContentCompressionResistancePriority:pri forAxis:UILayoutConstraintAxisVertical];
+}
+
+- (void)loadStuff {
+ self.view.backgroundColor = [UIColor whiteColor];
+
+ UILabel *top = [[UILabel alloc] init];
+ top.translatesAutoresizingMaskIntoConstraints = NO;
+ top.textAlignment = NSTextAlignmentCenter;
+ hugging(top, 251);
+ top.text = @"libsubstitute";
+ top.font = [UIFont systemFontOfSize:23];
+ [self.view addSubview:top];
+
+ UILabel *big = [[UILabel alloc] init];
+ big.translatesAutoresizingMaskIntoConstraints = NO;
+ big.textAlignment = NSTextAlignmentCenter;
+ hugging(big, 251);
+ [big setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisHorizontal];
+ [big setContentHuggingPriority:251 forAxis:UILayoutConstraintAxisVertical];
+ big.text = @"Safe Mode";
+ big.font = [UIFont systemFontOfSize:32];
+ [self.view addSubview:big];
+
+ UILabel *explain = [[UILabel alloc] init];
+ explain.translatesAutoresizingMaskIntoConstraints = NO;
+ explain.textAlignment = NSTextAlignmentCenter;
+ hugging(explain, 251);
+ compression(explain, 999);
+ explain.text = EXPLANATION;
+ explain.font = [UIFont systemFontOfSize:14];
+ explain.minimumScaleFactor = 0.5; /* test */
+ explain.numberOfLines = 0;
+ [self.view addSubview:explain];
+
+ UIButton *returnButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ returnButton.translatesAutoresizingMaskIntoConstraints = NO;
+ returnButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
+ returnButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
+ returnButton.titleLabel.font = [UIFont systemFontOfSize:17];
+ [returnButton setTitle:@"Return to Normal" forState:UIControlStateNormal];
+ [self.view addSubview:returnButton];
+
+ UIButton *continueButton = [UIButton buttonWithType:UIButtonTypeSystem];
+ continueButton.translatesAutoresizingMaskIntoConstraints = NO;
+ hugging(continueButton, 999);
+ compression(continueButton, 300);
+ continueButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
+ continueButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
+ continueButton.titleLabel.font = [UIFont systemFontOfSize:17];
+ [continueButton setTitle:@"Continue in Safe Mode" forState:UIControlStateNormal];
+ [self.view addSubview:continueButton];
+
+ autoGrid = [[AutoGrid alloc] init];
+ autoGrid.translatesAutoresizingMaskIntoConstraints = NO;
+ [self.view addSubview:autoGrid];
+
+ NSDictionary *viewsDictionary = @{
+ @"top": top,
+ @"big": big,
+ @"explain": explain,
+ @"returnButton": returnButton,
+ @"continueButton": continueButton,
+ @"grid": autoGrid,
+ @"topGuide": self.topLayoutGuide,
+ @"bottomGuide": self.bottomLayoutGuide,
+ };
+ NSMutableArray *constraints = [[NSMutableArray alloc] init];
+ [constraints addObjectsFromArray:
+ [NSLayoutConstraint constraintsWithVisualFormat:
+ @"V:[topGuide]-10-[top]-0@100-[big]-0@100-[explain]-18@200-[grid]-18-[continueButton]-8-[returnButton]-20@100-[bottomGuide]"
+ options:NSLayoutFormatAlignAllCenterX metrics:nil views:viewsDictionary]];
+ NSArray *additional = @[
+ @"[explain(<=650)]",
+ @"|-10-[explain]-10-|",
+ @"|-20-[grid]-20-|",
+ ];
+ for (NSString *fmt in additional) {
+ [constraints addObjectsFromArray:
+ [NSLayoutConstraint constraintsWithVisualFormat:fmt options:0 metrics:nil views:viewsDictionary]];
+ }
+ [self.view addConstraints:constraints];
+}
+
+
+- (NSUInteger)supportedInterfaceOrientations
+{
+ if ([UIDevice currentDevice].userInterfaceIdiom == UIUserInterfaceIdiomPad)
+ return UIInterfaceOrientationMaskAll;
+ else if ([UIScreen mainScreen].bounds.size.width >= 414)
+ return UIInterfaceOrientationMaskAllButUpsideDown;
+ else
+ return UIInterfaceOrientationMaskPortrait;
+}
+
+@end
+
+@interface AppDelegate : UIResponder <UIApplicationDelegate> {
+}
+
+@property (strong, nonatomic) UIWindow *window;
+
+
+@end
+
+@implementation AppDelegate
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+ NSLog(@"dflwo");
+ self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
+ ViewController *viewController = [[ViewController alloc] init];
+ self.window.rootViewController = viewController;
+ [self.window makeKeyAndVisible];
+ return YES;
+}
+
+@end
+
+int main(int argc, char *argv[]) {
+ NSLog(@"main");
+ @autoreleasepool {
+ return UIApplicationMain(argc, argv, nil, @"AppDelegate");
+ }
+}
+
diff --git a/darwin-bootstrap/safety-dance/white.png b/darwin-bootstrap/safety-dance/white.png
new file mode 100644
index 0000000..eab5a56
--- /dev/null
+++ b/darwin-bootstrap/safety-dance/white.png
Binary files differ
diff --git a/darwin-bootstrap/unrestrict.c b/darwin-bootstrap/unrestrict.c
new file mode 100644
index 0000000..c2f13bb
--- /dev/null
+++ b/darwin-bootstrap/unrestrict.c
@@ -0,0 +1,245 @@
+/* This is an iOS executable spawned from posixspawn-hook.dylib, which accesses
+ * a process and changes the name of any __RESTRICT,__restrict sections in the
+ * main executable in memory. Doing so prevents dyld from refusing to honor
+ * DYLD_INSERT_LIBRARIES (which is a pretty dumb protection mechanism in the
+ * first place, even on OS X).
+ *
+ * It exists as a separate executable because (a) such processes may be
+ * launched with POSIX_SPAWN_SETEXEC, which makes posix_spawn act like exec and
+ * replace the current process, and (b) if they're not, launchd (into which
+ * posixspawn-hook is injected) still can't task_for_pid the child process
+ * itself, because it doesn't have the right entitlements.
+*/
+
+#define IB_LOG_NAME "unrestrict"
+#include "ib-log.h"
+#include "darwin/mach-decls.h"
+#include "substitute.h"
+#include "substitute-internal.h"
+#include <stdlib.h>
+#include <syslog.h>
+#include <signal.h>
+#include <errno.h>
+#include <stdio.h>
+#include <mach/mach.h>
+#include <mach-o/loader.h>
+
+#define PROC_PIDFDVNODEINFO 1
+#define PROC_PIDFDVNODEINFO_SIZE 176
+int proc_pidfdinfo(int, int, int, void *, int);
+
+static bool unrestrict_macho_header(void *header, size_t size) {
+ struct mach_header *mh = header;
+ if (mh->magic != MH_MAGIC && mh->magic != MH_MAGIC_64) {
+ ib_log("bad mach-o magic");
+ return false;
+ }
+ bool did_modify = false;
+ size_t off = mh->magic == MH_MAGIC_64 ? sizeof(struct mach_header_64)
+ : sizeof(struct mach_header);
+ for (uint32_t i = 0; i < mh->ncmds; i++) {
+ if (off > size || size - off < sizeof(struct load_command))
+ break; /* whatever */
+ struct load_command *lc = header + off;
+ if (lc->cmdsize > size - off)
+ break;
+ #define CASES(code...) \
+ if (lc->cmd == LC_SEGMENT) { \
+ typedef struct segment_command segment_command_y; \
+ typedef struct section section_y; \
+ code \
+ } else if (lc->cmd == LC_SEGMENT_64) { \
+ typedef struct segment_command_64 segment_command_y; \
+ typedef struct section_64 section_y; \
+ code \
+ }
+ CASES(
+ segment_command_y *sc = (void *) lc;
+ if (lc->cmdsize < sizeof(*sc) ||
+ sc->nsects > (lc->cmdsize - sizeof(*sc)) / sizeof(struct section)) {
+ ib_log("bad segment_command");
+ return false;
+ }
+ if (!strncmp(sc->segname, "__RESTRICT", 16)) {
+ section_y *sect = (void *) (sc + 1);
+ 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 = true;
+ }
+ }
+ }
+ )
+ #undef CASES
+
+ if (off + lc->cmdsize < off) {
+ ib_log("overflowing lc->cmdsize");
+ return false;
+ }
+ off += lc->cmdsize;
+ }
+ return did_modify;
+}
+
+static void unrestrict(task_t task) {
+ vm_address_t header_addr = 0;
+ kern_return_t kr;
+
+ /* 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;
+ mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
+ mach_port_t object_name;
+ while (1) {
+ kr = mach_vm_region(task, &segm_addr, &segm_size, VM_REGION_BASIC_INFO_64,
+ (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) {
+ ib_log("mach_vm_region(%lx): %x", (long) segm_addr, kr);
+ return;
+ }
+ if (info.protection)
+ break;
+
+ segm_addr++;
+ }
+
+ size_t toread = 0x4000;
+ if (segm_size < toread)
+ toread = segm_size;
+ if ((kr = vm_allocate(mach_task_self(), &header_addr, toread,
+ VM_FLAGS_ANYWHERE))) {
+ ib_log("vm_allocate(%zx): %x", toread, kr);
+ return;
+ }
+ mach_vm_size_t actual = toread;
+ kr = mach_vm_read_overwrite(task, segm_addr, toread, header_addr,
+ &actual);
+ if (kr || actual != toread) {
+ ib_log("mach_vm_read_overwrite: %x", kr);
+ return;
+ }
+
+ bool did_modify = unrestrict_macho_header((void *) header_addr, toread);
+
+ if (did_modify) {
+ if ((kr = vm_protect(mach_task_self(), header_addr, toread,
+ FALSE, info.protection))) {
+ ib_log("vm_protect(%lx=>%d): %x",
+ (long) header_addr, info.protection, kr);
+ return;
+ }
+ vm_prot_t cur, max;
+ if ((kr = mach_vm_remap(task, &segm_addr, toread, 0,
+ VM_FLAGS_OVERWRITE,
+ mach_task_self(), header_addr, FALSE,
+ &cur, &max, info.inheritance))) {
+ ib_log("mach_vm_remap(%lx=>%lx size=%zx): %x",
+ (long) header_addr, (long) segm_addr, toread, kr);
+ return;
+ }
+ }
+}
+
+
+
+int main(int argc, char **argv) {
+ if (argc != 4) {
+ ib_log("wrong number of args");
+ return 1;
+ }
+
+ const char *pids = argv[1];
+ char *end;
+ long pid = strtol(pids, &end, 10);
+ if (!pids[0] || *end) {
+ ib_log("pid not an integer");
+ return 1;
+ }
+
+ const char *should_resume = argv[2];
+ if (strcmp(should_resume, "0") && strcmp(should_resume, "1")) {
+ ib_log("should_resume not 0 or 1");
+ return 1;
+ }
+
+ const char *is_exec = argv[3];
+ if (strcmp(is_exec, "0") && strcmp(is_exec, "1")) {
+ ib_log("is_exec not 0 or 1");
+ return 1;
+ }
+
+ /* double fork to avoid zombies */
+ int ret = fork();
+ if (ret == -1) {
+ ib_log("fork: %s", strerror(errno));
+ return 1;
+ } else if (ret) {
+ return 0;
+ }
+
+ if (IB_VERBOSE) {
+ ib_log("unrestricting %ld (sr=%s, ie=%s)", pid,
+ should_resume, is_exec);
+ }
+
+ int ec = 1;
+
+ mach_port_t task;
+ kern_return_t kr = task_for_pid(mach_task_self(), (pid_t) pid, &task);
+ if (kr) {
+ ib_log("TFP fail: %d", kr);
+ goto fail;
+ }
+
+ if (is_exec[0] == '1') {
+ int retries = 0;
+ int wait_us = 1;
+ while (1) {
+ /* 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];
+ /* A bug in proc_pidfdinfo makes it never return -1. Yuck. */
+ errno = 0;
+ proc_pidfdinfo(pid, 255, PROC_PIDFDVNODEINFO, buf, sizeof(buf));
+ if (errno == EBADF) {
+ break;
+ } else if (errno) {
+ ib_log("proc_pidfdinfo: %s", strerror(errno));
+ goto fail;
+ }
+
+ if (retries++ == 20) {
+ ib_log("still in parent process after 20 retries");
+ goto fail;
+ }
+ wait_us *= 2;
+ if (wait_us > 200000)
+ wait_us = 200000;
+ while (usleep(wait_us))
+ ;
+ }
+ }
+
+ unrestrict(task);
+
+fail:
+ if (should_resume[0] == '1') {
+ if ((kill(pid, SIGCONT))) {
+ ib_log("kill SIGCONT: %s", strerror(errno));
+ return 1;
+ }
+ }
+
+ return ec;
+}