kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

rewrite.c (13619B)


      1 #include "obj/rewrite.h"
      2 
      3 #include <limits.h>
      4 #include <string.h>
      5 
      6 #include "obj/macho/macho.h"
      7 
      8 #define LC_CODE_SIGNATURE 0x1du
      9 #define LC_DYLD_INFO 0x22u
     10 #define LC_DYLD_INFO_ONLY (0x22u | LC_REQ_DYLD)
     11 #define N_STAB 0xe0u
     12 #define N_TYPE 0x0eu
     13 #define N_EXT 0x01u
     14 #define INDIRECT_SYMBOL_LOCAL 0x80000000u
     15 #define INDIRECT_SYMBOL_ABS 0x40000000u
     16 
     17 typedef struct MachoRewriteView {
     18   ObjRewrite rw;
     19   u32 filetype;
     20   u32 ncmds;
     21   u32 sizeofcmds;
     22   int has_export_trie;
     23   u8 section_map[256];
     24 } MachoRewriteView;
     25 
     26 static KitStatus macho_bad(MachoRewriteView* v, const char* detail) {
     27   kit_ctx_diagf(v->rw.ctx, "%.*s: malformed linked Mach-O image (%s)",
     28                 KIT_SLICE_ARG(v->rw.name), detail);
     29   return KIT_MALFORMED;
     30 }
     31 
     32 static int fixed_name_eq(const u8* name, size_t cap, const char* want) {
     33   size_t n = strlen(want);
     34   return n <= cap && memcmp(name, want, n) == 0 &&
     35          (n == cap || name[n] == 0);
     36 }
     37 
     38 static int fixed_name_prefix(const u8* name, size_t cap, const char* prefix) {
     39   size_t n = strlen(prefix);
     40   return n <= cap && memcmp(name, prefix, n) == 0;
     41 }
     42 
     43 static int macho_debug_section(const u8* sec) {
     44   return fixed_name_eq(sec + 16, 16, "__DWARF") ||
     45          fixed_name_prefix(sec, 16, "__debug_") ||
     46          fixed_name_prefix(sec, 16, "__zdebug_") ||
     47          fixed_name_eq(sec, 16, "__apple_names") ||
     48          fixed_name_eq(sec, 16, "__apple_types") ||
     49          fixed_name_eq(sec, 16, "__apple_namespac") ||
     50          fixed_name_eq(sec, 16, "__apple_objc");
     51 }
     52 
     53 static KitStatus macho_validate(MachoRewriteView* v, const KitContext* ctx,
     54                                 KitSlice name, const KitSlice* input) {
     55   KitStatus st;
     56   u64 pos, end;
     57   u32 i;
     58   memset(v, 0, sizeof *v);
     59   st = obj_rewrite_init(&v->rw, ctx, name, input);
     60   if (st != KIT_OK) return st;
     61   if (v->rw.len < MACHO_HDR64_SIZE ||
     62       obj_rw_u32(v->rw.bytes, 0) != MH_MAGIC_64)
     63     return macho_bad(v, "64-bit little-endian header");
     64   v->filetype = obj_rw_u32(v->rw.bytes + 12, 0);
     65   if (v->filetype == MH_OBJECT) {
     66     kit_ctx_diagf(ctx, "%.*s: relocatable object is not a linked image",
     67                   KIT_SLICE_ARG(name));
     68     return KIT_INVALID;
     69   }
     70   if (v->filetype != MH_EXECUTE && v->filetype != MH_DYLIB &&
     71       v->filetype != MH_DYLINKER && v->filetype != MH_BUNDLE)
     72     return macho_bad(v, "unsupported file type");
     73   v->ncmds = obj_rw_u32(v->rw.bytes + 16, 0);
     74   v->sizeofcmds = obj_rw_u32(v->rw.bytes + 20, 0);
     75   pos = MACHO_HDR64_SIZE;
     76   end = pos + v->sizeofcmds;
     77   if (end < pos || !obj_rw_range(v->rw.len, pos, v->sizeofcmds))
     78     return macho_bad(v, "load-command table");
     79   for (i = 0; i < v->ncmds; ++i) {
     80     const u8* cmd;
     81     u32 kind, size;
     82     if (!obj_rw_range(v->rw.len, pos, 8))
     83       return macho_bad(v, "load command");
     84     cmd = v->rw.input + (size_t)pos;
     85     kind = obj_rw_u32(cmd, 0);
     86     size = obj_rw_u32(cmd + 4, 0);
     87     if (size < 8 || pos + size > end)
     88       return macho_bad(v, "load-command size");
     89     if (kind == LC_SEGMENT_64) {
     90       u32 nsects, j;
     91       if (size < MACHO_SEGCMD64_SIZE)
     92         return macho_bad(v, "segment command");
     93       nsects = obj_rw_u32(cmd + 64, 0);
     94       if ((u64)MACHO_SEGCMD64_SIZE + (u64)nsects * MACHO_SECT64_SIZE > size)
     95         return macho_bad(v, "segment sections");
     96       for (j = 0; j < nsects; ++j) {
     97         const u8* sec = cmd + MACHO_SEGCMD64_SIZE +
     98                         (size_t)j * MACHO_SECT64_SIZE;
     99         u64 sec_size = obj_rw_u64(sec + 40, 0);
    100         u32 sec_off = obj_rw_u32(sec + 48, 0);
    101         u32 reloc_off = obj_rw_u32(sec + 56, 0);
    102         u32 nreloc = obj_rw_u32(sec + 60, 0);
    103         u32 sec_type = obj_rw_u32(sec + 64, 0) & SECTION_TYPE;
    104         int zerofill = sec_type == S_ZEROFILL || sec_type == 0x0cu ||
    105                        sec_type == S_THREAD_LOCAL_ZEROFILL;
    106         if (!zerofill && sec_size &&
    107             !obj_rw_range(v->rw.len, sec_off, sec_size))
    108           return macho_bad(v, "section contents");
    109         if (nreloc &&
    110             !obj_rw_range(v->rw.len, reloc_off, (u64)nreloc * 8u))
    111           return macho_bad(v, "section relocations");
    112       }
    113     } else if (kind == LC_SYMTAB) {
    114       u32 symoff, nsyms, stroff, strsize;
    115       if (size < MACHO_SYMTAB_CMD_SIZE)
    116         return macho_bad(v, "symbol command");
    117       symoff = obj_rw_u32(cmd + 8, 0);
    118       nsyms = obj_rw_u32(cmd + 12, 0);
    119       stroff = obj_rw_u32(cmd + 16, 0);
    120       strsize = obj_rw_u32(cmd + 20, 0);
    121       if (!obj_rw_range(v->rw.len, symoff,
    122                         (u64)nsyms * MACHO_NLIST64_SIZE) ||
    123           !obj_rw_range(v->rw.len, stroff, strsize))
    124         return macho_bad(v, "symbol table");
    125     } else if (kind == LC_DYSYMTAB) {
    126       u32 indirectoff, nindirect;
    127       if (size < MACHO_DYSYMTAB_CMD_SIZE)
    128         return macho_bad(v, "dynamic-symbol command");
    129       indirectoff = obj_rw_u32(cmd + 56, 0);
    130       nindirect = obj_rw_u32(cmd + 60, 0);
    131       if (nindirect &&
    132           !obj_rw_range(v->rw.len, indirectoff, (u64)nindirect * 4u))
    133         return macho_bad(v, "indirect symbols");
    134     } else if (kind == LC_CODE_SIGNATURE) {
    135       u32 off, size_data;
    136       if (size < 16) return macho_bad(v, "code signature command");
    137       off = obj_rw_u32(cmd + 8, 0);
    138       size_data = obj_rw_u32(cmd + 12, 0);
    139       if (!obj_rw_range(v->rw.len, off, size_data))
    140         return macho_bad(v, "code signature data");
    141       v->rw.report.had_signature = true;
    142     } else if (kind == LC_DYLD_EXPORTS_TRIE || kind == LC_DYLD_INFO ||
    143                kind == LC_DYLD_INFO_ONLY) {
    144       v->has_export_trie = 1;
    145     }
    146     pos += size;
    147   }
    148   if (pos != end) return macho_bad(v, "load-command count");
    149   return KIT_OK;
    150 }
    151 
    152 static KitStatus macho_compact_commands(MachoRewriteView* v,
    153                                         const KitLinkedRewriteOptions* opts) {
    154   u64 src_pos = MACHO_HDR64_SIZE;
    155   u64 dst_pos = MACHO_HDR64_SIZE;
    156   u32 old_section = 1, new_section = 1;
    157   u32 kept_cmds = 0;
    158   u32 i;
    159   memset(v->section_map, 0, sizeof v->section_map);
    160 
    161   for (i = 0; i < v->ncmds; ++i) {
    162     const u8* src = v->rw.input + (size_t)src_pos;
    163     u32 kind = obj_rw_u32(src, 0);
    164     u32 size = obj_rw_u32(src + 4, 0);
    165     if (kind == LC_CODE_SIGNATURE && opts->remove_signature) {
    166       u32 off = obj_rw_u32(src + 8, 0);
    167       u32 n = obj_rw_u32(src + 12, 0);
    168       if (n) memset(v->rw.bytes + off, 0, n);
    169       v->rw.report.removed_signature = true;
    170       src_pos += size;
    171       continue;
    172     }
    173     if (kind == LC_SEGMENT_64) {
    174       u32 nsects = obj_rw_u32(src + 64, 0);
    175       u32 kept = 0, j;
    176       u8* dst = v->rw.bytes + (size_t)dst_pos;
    177       int dwarf_segment = fixed_name_eq(src + 8, 16, "__DWARF");
    178       memmove(dst, src, MACHO_SEGCMD64_SIZE);
    179       for (j = 0; j < nsects; ++j, ++old_section) {
    180         const u8* sec = src + MACHO_SEGCMD64_SIZE +
    181                         (size_t)j * MACHO_SECT64_SIZE;
    182         int debug = dwarf_segment || macho_debug_section(sec);
    183         if (old_section >= sizeof v->section_map) {
    184           return macho_bad(v, "too many sections");
    185         }
    186         if (debug) {
    187           u64 sec_size = obj_rw_u64(sec + 40, 0);
    188           u32 sec_off = obj_rw_u32(sec + 48, 0);
    189           if (sec_size && sec_off &&
    190               obj_rw_range(v->rw.len, sec_off, sec_size))
    191             memset(v->rw.bytes + sec_off, 0, (size_t)sec_size);
    192           ++v->rw.report.removed_debug_sections;
    193           continue;
    194         }
    195         v->section_map[old_section] = (u8)new_section++;
    196         memmove(dst + MACHO_SEGCMD64_SIZE +
    197                     (size_t)kept * MACHO_SECT64_SIZE,
    198                 sec, MACHO_SECT64_SIZE);
    199         ++kept;
    200       }
    201       if (dwarf_segment && kept == 0) {
    202         src_pos += size;
    203         continue;
    204       }
    205       size = MACHO_SEGCMD64_SIZE + kept * MACHO_SECT64_SIZE;
    206       obj_rw_put_u32(dst + 4, size, 0);
    207       obj_rw_put_u32(dst + 64, kept, 0);
    208       dst_pos += size;
    209       ++kept_cmds;
    210       src_pos += obj_rw_u32(src + 4, 0);
    211       continue;
    212     }
    213     memmove(v->rw.bytes + (size_t)dst_pos, src, size);
    214     dst_pos += size;
    215     src_pos += size;
    216     ++kept_cmds;
    217   }
    218   if (dst_pos > MACHO_HDR64_SIZE + v->sizeofcmds)
    219     return macho_bad(v, "compacted commands");
    220   memset(v->rw.bytes + (size_t)dst_pos, 0,
    221          (size_t)(MACHO_HDR64_SIZE + v->sizeofcmds - dst_pos));
    222   obj_rw_put_u32(v->rw.bytes + 16, kept_cmds, 0);
    223   obj_rw_put_u32(v->rw.bytes + 20, (u32)(dst_pos - MACHO_HDR64_SIZE), 0);
    224   v->ncmds = kept_cmds;
    225   v->sizeofcmds = (u32)(dst_pos - MACHO_HDR64_SIZE);
    226   return KIT_OK;
    227 }
    228 
    229 static u8* macho_find_command(MachoRewriteView* v, u32 want) {
    230   u64 pos = MACHO_HDR64_SIZE;
    231   u32 i;
    232   for (i = 0; i < v->ncmds; ++i) {
    233     u8* cmd = v->rw.bytes + (size_t)pos;
    234     u32 size = obj_rw_u32(cmd + 4, 0);
    235     if (obj_rw_u32(cmd, 0) == want) return cmd;
    236     pos += size;
    237   }
    238   return NULL;
    239 }
    240 
    241 static KitStatus macho_filter_symbols(MachoRewriteView* v,
    242                                       const KitLinkedRewriteOptions* opts) {
    243   u8* symcmd = macho_find_command(v, LC_SYMTAB);
    244   u8* dysym = macho_find_command(v, LC_DYSYMTAB);
    245   u8 *keep = NULL, *src_syms, *dst_syms;
    246   u32* map = NULL;
    247   u32 symoff, nsyms, newn = 0, i;
    248   u32 indirectoff = 0, nindirect = 0;
    249   size_t keep_size, map_size;
    250   if (!symcmd) return KIT_OK;
    251   symoff = obj_rw_u32(symcmd + 8, 0);
    252   nsyms = obj_rw_u32(symcmd + 12, 0);
    253   if (!nsyms) return KIT_OK;
    254   keep_size = nsyms;
    255   map_size = (size_t)nsyms * sizeof(*map);
    256   keep = (u8*)v->rw.ctx->heap->alloc(v->rw.ctx->heap, keep_size, 1u);
    257   map = (u32*)v->rw.ctx->heap->alloc(v->rw.ctx->heap, map_size,
    258                                      _Alignof(u32));
    259   if (!keep || !map) {
    260     if (keep) v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size);
    261     if (map) v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size);
    262     return KIT_NOMEM;
    263   }
    264   memset(keep, 0, keep_size);
    265   for (i = 0; i < nsyms; ++i) map[i] = UINT32_MAX;
    266   src_syms = (u8*)v->rw.input + symoff;
    267   dst_syms = v->rw.bytes + symoff;
    268   for (i = 0; i < nsyms; ++i) {
    269     const u8* sym = src_syms + (size_t)i * MACHO_NLIST64_SIZE;
    270     u8 type = sym[4];
    271     if (type & N_STAB) continue;
    272     if (opts->strip_level == KIT_LINKED_STRIP_DEBUG) {
    273       keep[i] = 1;
    274     } else if ((type & N_TYPE) == N_UNDF ||
    275                ((type & N_EXT) &&
    276                 (v->filetype == MH_DYLIB || !v->has_export_trie))) {
    277       keep[i] = 1;
    278     }
    279   }
    280   if (dysym) {
    281     indirectoff = obj_rw_u32(dysym + 56, 0);
    282     nindirect = obj_rw_u32(dysym + 60, 0);
    283     for (i = 0; i < nindirect; ++i) {
    284       u32 old = obj_rw_u32(v->rw.bytes + indirectoff + (size_t)i * 4u, 0);
    285       if (!(old & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) && old < nsyms)
    286         keep[old] = 1;
    287     }
    288   }
    289   for (i = 0; i < nsyms; ++i) {
    290     u8* dst;
    291     const u8* src;
    292     u8 old_sect;
    293     if (!keep[i]) continue;
    294     src = src_syms + (size_t)i * MACHO_NLIST64_SIZE;
    295     dst = dst_syms + (size_t)newn * MACHO_NLIST64_SIZE;
    296     memmove(dst, src, MACHO_NLIST64_SIZE);
    297     old_sect = dst[5];
    298     if (old_sect) {
    299       if (!v->section_map[old_sect]) {
    300         /* A non-STAB symbol cannot survive when its defining section was debug
    301          * metadata. Drop it instead of creating an invalid n_sect index. */
    302         continue;
    303       }
    304       dst[5] = v->section_map[old_sect];
    305     }
    306     map[i] = newn++;
    307   }
    308   if (newn < nsyms)
    309     memset(dst_syms + (size_t)newn * MACHO_NLIST64_SIZE, 0,
    310            (size_t)(nsyms - newn) * MACHO_NLIST64_SIZE);
    311   obj_rw_put_u32(symcmd + 12, newn, 0);
    312   v->rw.report.removed_symbols += nsyms - newn;
    313 
    314   if (dysym) {
    315     u32 nlocal = 0, nextdef = 0, nundef = 0;
    316     for (i = 0; i < newn; ++i) {
    317       u8 type = dst_syms[(size_t)i * MACHO_NLIST64_SIZE + 4];
    318       if (!(type & N_EXT))
    319         ++nlocal;
    320       else if ((type & N_TYPE) == N_UNDF)
    321         ++nundef;
    322       else
    323         ++nextdef;
    324     }
    325     obj_rw_put_u32(dysym + 8, 0, 0);
    326     obj_rw_put_u32(dysym + 12, nlocal, 0);
    327     obj_rw_put_u32(dysym + 16, nlocal, 0);
    328     obj_rw_put_u32(dysym + 20, nextdef, 0);
    329     obj_rw_put_u32(dysym + 24, nlocal + nextdef, 0);
    330     obj_rw_put_u32(dysym + 28, nundef, 0);
    331     for (i = 0; i < nindirect; ++i) {
    332       u8* entry = v->rw.bytes + indirectoff + (size_t)i * 4u;
    333       u32 old = obj_rw_u32(entry, 0);
    334       if (old & (INDIRECT_SYMBOL_LOCAL | INDIRECT_SYMBOL_ABS)) continue;
    335       if (old >= nsyms || map[old] == UINT32_MAX) {
    336         v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size);
    337         v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size);
    338         return macho_bad(v, "indirect-symbol remap");
    339       }
    340       obj_rw_put_u32(entry, map[old], 0);
    341     }
    342   }
    343   v->rw.ctx->heap->free(v->rw.ctx->heap, map, map_size);
    344   v->rw.ctx->heap->free(v->rw.ctx->heap, keep, keep_size);
    345   return KIT_OK;
    346 }
    347 
    348 KitStatus obj_rewrite_macho(const KitContext* ctx, KitSlice name,
    349                             const KitSlice* input,
    350                             const KitLinkedRewriteOptions* opts,
    351                             KitWriter* out,
    352                             KitLinkedRewriteReport* report_out) {
    353   MachoRewriteView v;
    354   KitStatus st = macho_validate(&v, ctx, name, input);
    355   if (st != KIT_OK) {
    356     obj_rewrite_discard(&v.rw);
    357     return st;
    358   }
    359   if (v.rw.report.had_signature && !opts->remove_signature) {
    360     kit_ctx_diagf(ctx,
    361                   "%.*s: signed Mach-O image is unchanged; pass "
    362                   "--remove-signature and sign the result again",
    363                   KIT_SLICE_ARG(name));
    364     obj_rewrite_discard(&v.rw);
    365     return KIT_UNSUPPORTED;
    366   }
    367   st = macho_compact_commands(&v, opts);
    368   if (st == KIT_OK) st = macho_filter_symbols(&v, opts);
    369   if (st == KIT_OK) st = obj_rewrite_commit(&v.rw, out, report_out);
    370   if (st != KIT_OK) obj_rewrite_discard(&v.rw);
    371   return st;
    372 }