diff options
Diffstat (limited to 'lib/darwin/interpose.c')
-rw-r--r-- | lib/darwin/interpose.c | 224 |
1 files changed, 224 insertions, 0 deletions
diff --git a/lib/darwin/interpose.c b/lib/darwin/interpose.c new file mode 100644 index 0000000..06a357f --- /dev/null +++ b/lib/darwin/interpose.c @@ -0,0 +1,224 @@ +#ifdef __APPLE__ + +#include <stdint.h> +#include <stdbool.h> + +#include "substitute.h" +#include "substitute-internal.h" + +struct interpose_state { + size_t nsegments; + segment_command_x **segments; + size_t max_segments; + uintptr_t slide; + const struct substitute_import_hook *hooks; + size_t nhooks; + segment_command_x *stack_segments[32]; +}; + +static uintptr_t read_leb128(void **ptr, void *end, bool is_signed) { + uintptr_t result = 0; + uint8_t *p = *ptr; + uint8_t bit; + unsigned int shift = 0; + do { + if (p >= (uint8_t *) end) + return 0; + bit = *p++; + uintptr_t k = bit & 0x7f; + if (shift < sizeof(uintptr_t) * 8) + result |= k << shift; + shift += 7; + } while (bit & 0x80); + if (is_signed && (bit & 0x40)) + result |= ~((uintptr_t) 0) << shift; + *ptr = p; + return result; +} + +static inline char *read_cstring(void **ptr, void *end) { + char *s = *ptr; + *ptr = s + strnlen(s, (char *) end - s); + return s; +} + +static int try_bind_section(void *bind, size_t size, const struct interpose_state *st, + bool lazy) { + void *ptr = bind, *end = bind + size; + const char *sym = NULL; + uint8_t type = lazy ? BIND_TYPE_POINTER : 0; + intptr_t addend = 0; + size_t offset = 0; + void *segment = NULL; + while (ptr < end) { + uint8_t byte = *(uint8_t *) ptr; + ptr++; + uint8_t immediate = byte & BIND_IMMEDIATE_MASK; + uint8_t opcode = byte & BIND_OPCODE_MASK; + + uintptr_t count, stride; + + switch(opcode) { + case BIND_OPCODE_DONE: + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + read_leb128(&ptr, end, false); + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + sym = read_cstring(&ptr, end); + /* ignoring flags for now */ + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_leb128(&ptr, end, true); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + if (immediate < st->nsegments) + segment = (void *) (st->segments[immediate]->vmaddr + st->slide); + offset = read_leb128(&ptr, end, false); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + offset += read_leb128(&ptr, end, false); + break; + case BIND_OPCODE_DO_BIND: + count = 1; + stride = sizeof(void *); + goto bind; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + count = 1; + stride = read_leb128(&ptr, end, false) + sizeof(void *); + goto bind; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + count = 1; + stride = immediate * sizeof(void *) + sizeof(void *); + goto bind; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_leb128(&ptr, end, false); + stride = read_leb128(&ptr, end, false) + sizeof(void *); + goto bind; + bind: + if (segment && sym) { + const struct substitute_import_hook *h; + size_t i; + for (i = 0; i < st->nhooks; i++) { + h = &st->hooks[i]; + if (!strcmp(sym, h->name)) + break; + } + if (i != st->nhooks) { + while (count--) { + uintptr_t new = (uintptr_t) h->replacement + addend; + uintptr_t old; + void *p = (void *) (segment + offset); + switch (type) { + case BIND_TYPE_POINTER: { + old = __atomic_exchange_n((uintptr_t *) p, new, __ATOMIC_RELAXED); + break; + } + case BIND_TYPE_TEXT_ABSOLUTE32: { + if ((uint32_t) new != new) { + /* text rels should only show up on i386, where + * this is impossible... */ + panic("bad TEXT_ABSOLUTE32 rel\n"); + } + old = __atomic_exchange_n((uint32_t *) p, (uint32_t) new, __ATOMIC_RELAXED); + break; + } + case BIND_TYPE_TEXT_PCREL32: { + uintptr_t pc = (uintptr_t) p + 4; + uintptr_t rel = new - pc; + if ((uint32_t) rel != rel) { + /* ditto */ + panic("bad TEXT_ABSOLUTE32 rel\n"); + } + old = __atomic_exchange_n((uint32_t *) p, (uint32_t) rel, __ATOMIC_RELAXED); + old += pc; + break; + } + default: + panic("unknown relocation type\n"); + break; + } + if (h->old_ptr) + *(uintptr_t *) h->old_ptr = old - addend; + } + break; + } + } + offset += count * stride; + break; + } + } + return SUBSTITUTE_OK; +} + +static void *off_to_addr(const struct interpose_state *st, uint32_t off) { + for (size_t i = 0; i < st->nsegments; i++) { + const segment_command_x *sc = st->segments[i]; + if ((off - sc->fileoff) < sc->filesize) + return (void *) (sc->vmaddr + st->slide + off - sc->fileoff); + } + return NULL; +} + +EXPORT +int substitute_interpose_imports(const struct substitute_image *image, + const struct substitute_import_hook *hooks, + size_t nhooks, int options) { + int ret = SUBSTITUTE_OK; + + if (options != 0) + panic("%s: unrecognized options\n", __func__); + + struct interpose_state st; + st.slide = image->slide; + st.nsegments = 0; + st.hooks = hooks; + st.nhooks = nhooks; + st.segments = st.stack_segments; + st.max_segments = sizeof(st.stack_segments) / sizeof(*st.stack_segments); + + const mach_header_x *mh = image->image_header; + const struct load_command *lc = (void *) (mh + 1); + for (uint32_t i = 0; i < mh->ncmds; i++) { + if (lc->cmd == LC_SEGMENT_X) { + segment_command_x *sc = (void *) lc; + if (st.nsegments == st.max_segments) { + segment_command_x **new = calloc(st.nsegments * 2, sizeof(*st.segments)); + if (!new) + substitute_panic("%s: out of memory\n", __func__); + memcpy(new, st.segments, st.nsegments * sizeof(*st.segments)); + if (st.segments != st.stack_segments) + free(st.segments); + st.segments = new; + } + st.segments[st.nsegments++] = sc; + } + lc = (void *) lc + lc->cmdsize; + } + + lc = (void *) (mh + 1); + for (uint32_t i = 0; i < mh->ncmds; i++) { + if (lc->cmd == LC_DYLD_INFO || lc->cmd == LC_DYLD_INFO_ONLY) { + struct dyld_info_command *dc = (void *) lc; + int ret; + if ((ret = try_bind_section(off_to_addr(&st, dc->bind_off), dc->bind_size, &st, false)) || + (ret = try_bind_section(off_to_addr(&st, dc->weak_bind_off), dc->weak_bind_size, &st, false)) || + (ret = try_bind_section(off_to_addr(&st, dc->lazy_bind_off), dc->lazy_bind_size, &st, true))) + goto fail; + + break; + } + lc = (void *) lc + lc->cmdsize; + } +fail: + if (st.segments != st.stack_segments) + free(st.segments); + return ret; +} + +#endif /* __APPLE__ */ |