diff options
author | comex | 2015-02-28 13:16:36 -0500 |
---|---|---|
committer | comex | 2015-02-28 13:16:36 -0500 |
commit | d9a7a8a4d4a23fb65e6319e0e8a435046cc39fea (patch) | |
tree | dd219509fc92e695317b82d5dca43296daaf2ab1 /darwin-bootstrap/unrestrict.c | |
parent | remove deprecated property usage in safety-dance (diff) | |
download | substitute-d9a7a8a4d4a23fb65e6319e0e8a435046cc39fea.tar.gz |
Rename ios-bootstrap to darwin-bootstrap; cleanup posixspawn-hook and unrestrict.
Not tested yet.
Diffstat (limited to 'darwin-bootstrap/unrestrict.c')
-rw-r--r-- | darwin-bootstrap/unrestrict.c | 245 |
1 files changed, 245 insertions, 0 deletions
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; +} |