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 }