| 1 | /* |
| 2 | * Copyright (c) 2019 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ |
| 5 | * |
| 6 | * This file contains Original Code and/or Modifications of Original Code |
| 7 | * as defined in and that are subject to the Apple Public Source License |
| 8 | * Version 2.0 (the 'License'). You may not use this file except in |
| 9 | * compliance with the License. The rights granted to you under the License |
| 10 | * may not be used to create, or enable the creation or redistribution of, |
| 11 | * unlawful or unlicensed copies of an Apple operating system, or to |
| 12 | * circumvent, violate, or enable the circumvention or violation of, any |
| 13 | * terms of an Apple operating system software license agreement. |
| 14 | * |
| 15 | * Please obtain a copy of the License at |
| 16 | * http://www.opensource.apple.com/apsl/ and read it before using this file. |
| 17 | * |
| 18 | * The Original Code and all software distributed under the License are |
| 19 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER |
| 20 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, |
| 21 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, |
| 22 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. |
| 23 | * Please see the License for the specific language governing rights and |
| 24 | * limitations under the License. |
| 25 | * |
| 26 | * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ |
| 27 | */ |
| 28 | |
| 29 | /* |
| 30 | * This file contains static dyld helper functions for |
| 31 | * exclusive use in platform startup code. |
| 32 | */ |
| 33 | |
| 34 | #include <mach-o/fixup-chains.h> |
| 35 | #include <mach-o/loader.h> |
| 36 | |
| 37 | #if defined(HAS_APPLE_PAC) |
| 38 | #include <ptrauth.h> |
| 39 | #endif /* defined(HAS_APPLE_PAC) */ |
| 40 | |
| 41 | #ifndef dyldLogFunc |
| 42 | #define dyldLogFunc(msg, ...) kprintf(msg, ## __VA_ARGS__) |
| 43 | #endif |
| 44 | |
| 45 | #if 0 |
| 46 | #define dyldLogFunc(msg, ...) ({int _wait = 0; do { asm volatile ("yield" : "+r"(_wait) : ); } while(!_wait); }) |
| 47 | #endif |
| 48 | #define LogFixups 0 |
| 49 | |
| 50 | // cannot safely callout out to functions like strcmp before initial fixup |
| 51 | static inline int |
| 52 | strings_are_equal(const char* a, const char* b) |
| 53 | { |
| 54 | while (*a && *b) { |
| 55 | if (*a != *b) { |
| 56 | return 0; |
| 57 | } |
| 58 | ++a; |
| 59 | ++b; |
| 60 | } |
| 61 | return *a == *b; |
| 62 | } |
| 63 | |
| 64 | /* |
| 65 | * Functions from dyld to rebase, fixup and sign the contents of MH_FILESET |
| 66 | * kernel collections. |
| 67 | */ |
| 68 | |
| 69 | union ChainedFixupPointerOnDisk { |
| 70 | uint64_t raw64; |
| 71 | struct dyld_chained_ptr_64_kernel_cache_rebase fixup64; |
| 72 | }; |
| 73 | |
| 74 | static uint64_t __unused |
| 75 | sign_pointer(struct dyld_chained_ptr_64_kernel_cache_rebase pointer __unused, |
| 76 | void *loc __unused, |
| 77 | uint64_t target __unused) |
| 78 | { |
| 79 | #if HAS_APPLE_PAC |
| 80 | uint64_t discriminator = pointer.diversity; |
| 81 | if (pointer.addrDiv) { |
| 82 | if (discriminator) { |
| 83 | discriminator = __builtin_ptrauth_blend_discriminator(loc, discriminator); |
| 84 | } else { |
| 85 | discriminator = (uint64_t)(uintptr_t)loc; |
| 86 | } |
| 87 | } |
| 88 | switch (pointer.key) { |
| 89 | case 0: // IA |
| 90 | return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 0, discriminator); |
| 91 | case 1: // IB |
| 92 | return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 1, discriminator); |
| 93 | case 2: // DA |
| 94 | return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 2, discriminator); |
| 95 | case 3: // DB |
| 96 | return (uint64_t)__builtin_ptrauth_sign_unauthenticated((void*)target, 3, discriminator); |
| 97 | } |
| 98 | #endif |
| 99 | return target; |
| 100 | } |
| 101 | |
| 102 | static inline __attribute__((__always_inline__)) void |
| 103 | fixup_value(union ChainedFixupPointerOnDisk* fixupLoc __unused, |
| 104 | const struct dyld_chained_starts_in_segment* segInfo, |
| 105 | uintptr_t slide __unused, |
| 106 | const void* basePointers[KCNumKinds] __unused, |
| 107 | int* stop) |
| 108 | { |
| 109 | if (LogFixups) { |
| 110 | dyldLogFunc("[LOG] kernel-fixups: fixup_value %p\n" , fixupLoc); |
| 111 | } |
| 112 | switch (segInfo->pointer_format) { |
| 113 | #if __LP64__ |
| 114 | case DYLD_CHAINED_PTR_64_KERNEL_CACHE: |
| 115 | case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: { |
| 116 | const void* baseAddress = basePointers[fixupLoc->fixup64.cacheLevel]; |
| 117 | if (baseAddress == 0) { |
| 118 | dyldLogFunc("Invalid cache level: %d\n" , fixupLoc->fixup64.cacheLevel); |
| 119 | *stop = 1; |
| 120 | return; |
| 121 | } |
| 122 | uintptr_t slidValue = (uintptr_t)baseAddress + fixupLoc->fixup64.target; |
| 123 | if (LogFixups) { |
| 124 | dyldLogFunc("[LOG] kernel-fixups: slidValue %p (base=%p, target=%p)\n" , (void*)slidValue, |
| 125 | (const void *)baseAddress, (void *)(uintptr_t)fixupLoc->fixup64.target); |
| 126 | } |
| 127 | #if HAS_APPLE_PAC |
| 128 | if (fixupLoc->fixup64.isAuth) { |
| 129 | slidValue = sign_pointer(pointer: fixupLoc->fixup64, loc: fixupLoc, target: slidValue); |
| 130 | } |
| 131 | #else |
| 132 | if (fixupLoc->fixup64.isAuth) { |
| 133 | dyldLogFunc("Unexpected authenticated fixup\n" ); |
| 134 | *stop = 1; |
| 135 | return; |
| 136 | } |
| 137 | #endif // HAS_APPLE_PAC |
| 138 | fixupLoc->raw64 = slidValue; |
| 139 | break; |
| 140 | } |
| 141 | #endif // __LP64__ |
| 142 | default: |
| 143 | dyldLogFunc("unsupported pointer chain format: 0x%04X" , segInfo->pointer_format); |
| 144 | *stop = 1; |
| 145 | break; |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | static inline __attribute__((__always_inline__)) int |
| 150 | walk_chain(const struct mach_header_64* mh, |
| 151 | const struct dyld_chained_starts_in_segment* segInfo, |
| 152 | uint32_t pageIndex, |
| 153 | uint16_t offsetInPage, |
| 154 | uintptr_t slide __unused, |
| 155 | const void* basePointers[KCNumKinds]) |
| 156 | { |
| 157 | if (LogFixups) { |
| 158 | dyldLogFunc("[LOG] kernel-fixups: walk_chain page[%d]\n" , pageIndex); |
| 159 | } |
| 160 | int stop = 0; |
| 161 | uintptr_t pageContentStart = (uintptr_t)mh + (uintptr_t)segInfo->segment_offset |
| 162 | + (pageIndex * segInfo->page_size); |
| 163 | union ChainedFixupPointerOnDisk* chain = (union ChainedFixupPointerOnDisk*)(pageContentStart + offsetInPage); |
| 164 | int chainEnd = 0; |
| 165 | if (LogFixups) { |
| 166 | dyldLogFunc("[LOG] kernel-fixups: segInfo->segment_offset 0x%llx\n" , segInfo->segment_offset); |
| 167 | dyldLogFunc("[LOG] kernel-fixups: segInfo->segment_pagesize %d\n" , segInfo->page_size); |
| 168 | dyldLogFunc("[LOG] kernel-fixups: segInfo pointer format %d\n" , segInfo->pointer_format); |
| 169 | } |
| 170 | while (!stop && !chainEnd) { |
| 171 | // copy chain content, in case handler modifies location to final value |
| 172 | if (LogFixups) { |
| 173 | dyldLogFunc("[LOG] kernel-fixups: value of chain %p" , chain); |
| 174 | } |
| 175 | union ChainedFixupPointerOnDisk chainContent __unused = *chain; |
| 176 | fixup_value(fixupLoc: chain, segInfo, slide, basePointers, stop: &stop); |
| 177 | if (!stop) { |
| 178 | switch (segInfo->pointer_format) { |
| 179 | #if __LP64__ |
| 180 | case DYLD_CHAINED_PTR_64_KERNEL_CACHE: |
| 181 | if (chainContent.fixup64.next == 0) { |
| 182 | chainEnd = 1; |
| 183 | } else { |
| 184 | if (LogFixups) { |
| 185 | dyldLogFunc("[LOG] kernel-fixups: chainContent fixup 64.next %d\n" , chainContent.fixup64.next); |
| 186 | } |
| 187 | chain = (union ChainedFixupPointerOnDisk*)((uintptr_t)chain + chainContent.fixup64.next * 4); |
| 188 | } |
| 189 | break; |
| 190 | case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: |
| 191 | if (chainContent.fixup64.next == 0) { |
| 192 | chainEnd = 1; |
| 193 | } else { |
| 194 | if (LogFixups) { |
| 195 | dyldLogFunc("[LOG] kernel-fixups: chainContent fixup x86 64.next %d\n" , chainContent.fixup64.next); |
| 196 | } |
| 197 | chain = (union ChainedFixupPointerOnDisk*)((uintptr_t)chain + chainContent.fixup64.next); |
| 198 | } |
| 199 | break; |
| 200 | #endif // __LP64__ |
| 201 | default: |
| 202 | dyldLogFunc("unknown pointer format 0x%04X" , segInfo->pointer_format); |
| 203 | stop = 1; |
| 204 | } |
| 205 | } |
| 206 | } |
| 207 | return stop; |
| 208 | } |
| 209 | |
| 210 | static inline __attribute__((__always_inline__)) int |
| 211 | kernel_collection_slide(const struct mach_header_64* mh, const void* basePointers[KCNumKinds]) |
| 212 | { |
| 213 | // First find the slide and chained fixups load command |
| 214 | uint64_t textVMAddr = 0; |
| 215 | const struct linkedit_data_command* chainedFixups = 0; |
| 216 | uint64_t linkeditVMAddr = 0; |
| 217 | uint64_t linkeditFileOffset = 0; |
| 218 | |
| 219 | if (LogFixups) { |
| 220 | dyldLogFunc("[LOG] kernel-fixups: parsing load commands\n" ); |
| 221 | } |
| 222 | |
| 223 | const struct load_command* startCmds = 0; |
| 224 | if (mh->magic == MH_MAGIC_64) { |
| 225 | startCmds = (struct load_command*)((uintptr_t)mh + sizeof(struct mach_header_64)); |
| 226 | } else if (mh->magic == MH_MAGIC) { |
| 227 | startCmds = (struct load_command*)((uintptr_t)mh + sizeof(struct mach_header)); |
| 228 | } else { |
| 229 | //const uint32_t* h = (uint32_t*)mh; |
| 230 | //diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]); |
| 231 | return 1; // not a mach-o file |
| 232 | } |
| 233 | const struct load_command* const cmdsEnd = (struct load_command*)((uintptr_t)startCmds + mh->sizeofcmds); |
| 234 | const struct load_command* cmd = startCmds; |
| 235 | for (uint32_t i = 0; i < mh->ncmds; ++i) { |
| 236 | if (LogFixups) { |
| 237 | dyldLogFunc("[LOG] kernel-fixups: parsing load command %d with cmd=0x%x\n" , i, cmd->cmd); |
| 238 | } |
| 239 | const struct load_command* nextCmd = (struct load_command*)((uintptr_t)cmd + cmd->cmdsize); |
| 240 | if (cmd->cmdsize < 8) { |
| 241 | //diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, this->ncmds, cmd, this, cmd->cmdsize); |
| 242 | return 1; |
| 243 | } |
| 244 | if ((nextCmd > cmdsEnd) || (nextCmd < startCmds)) { |
| 245 | //diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, this->ncmds, cmd, this, cmd->cmdsize, cmdsEnd); |
| 246 | return 1; |
| 247 | } |
| 248 | if (cmd->cmd == LC_DYLD_CHAINED_FIXUPS) { |
| 249 | chainedFixups = (const struct linkedit_data_command*)cmd; |
| 250 | } else if (cmd->cmd == LC_SEGMENT_64) { |
| 251 | const struct segment_command_64* seg = (const struct segment_command_64*)(uintptr_t)cmd; |
| 252 | |
| 253 | if (LogFixups) { |
| 254 | dyldLogFunc("[LOG] kernel-fixups: segment name vm start and size: %s 0x%llx 0x%llx\n" , |
| 255 | seg->segname, seg->vmaddr, seg->vmsize); |
| 256 | } |
| 257 | if (strings_are_equal(a: seg->segname, b: "__TEXT" )) { |
| 258 | textVMAddr = seg->vmaddr; |
| 259 | } else if (strings_are_equal(a: seg->segname, b: "__LINKEDIT" )) { |
| 260 | linkeditVMAddr = seg->vmaddr; |
| 261 | linkeditFileOffset = seg->fileoff; |
| 262 | } |
| 263 | } |
| 264 | cmd = nextCmd; |
| 265 | } |
| 266 | |
| 267 | uintptr_t slide = (uintptr_t)mh - (uintptr_t)textVMAddr; |
| 268 | |
| 269 | if (LogFixups) { |
| 270 | dyldLogFunc("[LOG] kernel-fixups: slide %lx\n" , slide); |
| 271 | } |
| 272 | |
| 273 | if (chainedFixups == 0) { |
| 274 | return 0; |
| 275 | } |
| 276 | |
| 277 | if (LogFixups) { |
| 278 | dyldLogFunc("[LOG] kernel-fixups: found chained fixups %p\n" , chainedFixups); |
| 279 | dyldLogFunc("[LOG] kernel-fixups: found linkeditVMAddr %p\n" , (void*)linkeditVMAddr); |
| 280 | dyldLogFunc("[LOG] kernel-fixups: found linkeditFileOffset %p\n" , (void*)linkeditFileOffset); |
| 281 | } |
| 282 | |
| 283 | // Now we have the chained fixups, walk it to apply all the rebases |
| 284 | uint64_t offsetInLinkedit = chainedFixups->dataoff - linkeditFileOffset; |
| 285 | uintptr_t linkeditStartAddr = (uintptr_t)linkeditVMAddr + slide; |
| 286 | if (LogFixups) { |
| 287 | dyldLogFunc("[LOG] kernel-fixups: offsetInLinkedit %llx\n" , offsetInLinkedit); |
| 288 | dyldLogFunc("[LOG] kernel-fixups: linkeditStartAddr %p\n" , (void*)linkeditStartAddr); |
| 289 | } |
| 290 | |
| 291 | const struct dyld_chained_fixups_header* = (const struct dyld_chained_fixups_header*)(linkeditStartAddr + offsetInLinkedit); |
| 292 | const struct dyld_chained_starts_in_image* fixupStarts = (const struct dyld_chained_starts_in_image*)((uintptr_t)fixupsHeader + fixupsHeader->starts_offset); |
| 293 | if (LogFixups) { |
| 294 | dyldLogFunc("[LOG] kernel-fixups: fixupsHeader %p\n" , fixupsHeader); |
| 295 | dyldLogFunc("[LOG] kernel-fixups: fixupStarts %p\n" , fixupStarts); |
| 296 | } |
| 297 | |
| 298 | int stopped = 0; |
| 299 | for (uint32_t segIndex = 0; segIndex < fixupStarts->seg_count && !stopped; ++segIndex) { |
| 300 | if (LogFixups) { |
| 301 | dyldLogFunc("[LOG] kernel-fixups: segment %d\n" , segIndex); |
| 302 | } |
| 303 | if (fixupStarts->seg_info_offset[segIndex] == 0) { |
| 304 | continue; |
| 305 | } |
| 306 | const struct dyld_chained_starts_in_segment* segInfo = (const struct dyld_chained_starts_in_segment*)((uintptr_t)fixupStarts + fixupStarts->seg_info_offset[segIndex]); |
| 307 | for (uint32_t pageIndex = 0; pageIndex < segInfo->page_count && !stopped; ++pageIndex) { |
| 308 | uint16_t offsetInPage = segInfo->page_start[pageIndex]; |
| 309 | if (offsetInPage == DYLD_CHAINED_PTR_START_NONE) { |
| 310 | continue; |
| 311 | } |
| 312 | if (offsetInPage & DYLD_CHAINED_PTR_START_MULTI) { |
| 313 | // FIXME: Implement this |
| 314 | return 1; |
| 315 | } else { |
| 316 | // one chain per page |
| 317 | if (walk_chain(mh, segInfo, pageIndex, offsetInPage, slide, basePointers)) { |
| 318 | stopped = 1; |
| 319 | } |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | |
| 324 | return stopped; |
| 325 | } |
| 326 | |
| 327 | /* |
| 328 | * Utility functions to adjust the load command vmaddrs in constituent MachO's |
| 329 | * of an MH_FILESET kernel collection. |
| 330 | */ |
| 331 | |
| 332 | MARK_AS_FIXUP_TEXT static void |
| 333 | kernel_collection_adjust_fileset_entry_addrs(struct mach_header_64 *mh, uintptr_t adj) |
| 334 | { |
| 335 | struct load_command *lc; |
| 336 | struct segment_command_64 *seg, *linkedit_cmd = NULL; |
| 337 | struct symtab_command *symtab_cmd = NULL; |
| 338 | struct section_64 *sec; |
| 339 | uint32_t i, j; |
| 340 | |
| 341 | lc = (struct load_command *)((uintptr_t)mh + sizeof(*mh)); |
| 342 | for (i = 0; i < mh->ncmds; i++, |
| 343 | lc = (struct load_command *)((uintptr_t)lc + lc->cmdsize)) { |
| 344 | if (lc->cmd == LC_SYMTAB) { |
| 345 | symtab_cmd = (struct symtab_command *)lc; |
| 346 | continue; |
| 347 | } |
| 348 | if (lc->cmd != LC_SEGMENT_64) { |
| 349 | continue; |
| 350 | } |
| 351 | if (strings_are_equal(a: ((struct segment_command_64 *)(uintptr_t)lc)->segname, SEG_LINKEDIT)) { |
| 352 | linkedit_cmd = ((struct segment_command_64 *)(uintptr_t)lc); |
| 353 | } |
| 354 | |
| 355 | seg = (struct segment_command_64 *)(uintptr_t)lc; |
| 356 | seg->vmaddr += adj; |
| 357 | /* slide/adjust every section in the segment */ |
| 358 | sec = (struct section_64 *)((uintptr_t)seg + sizeof(*seg)); |
| 359 | for (j = 0; j < seg->nsects; j++, sec++) { |
| 360 | sec->addr += adj; |
| 361 | } |
| 362 | } |
| 363 | |
| 364 | |
| 365 | if (symtab_cmd != NULL && linkedit_cmd != NULL) { |
| 366 | struct nlist_64 *sym; |
| 367 | uint32_t cnt = 0; |
| 368 | |
| 369 | if (LogFixups) { |
| 370 | dyldLogFunc("[LOG] Symbols:\n" ); |
| 371 | dyldLogFunc("[LOG] nsyms: %d, symoff: 0x%x\n" , symtab_cmd->nsyms, symtab_cmd->symoff); |
| 372 | } |
| 373 | |
| 374 | if (symtab_cmd->nsyms == 0) { |
| 375 | dyldLogFunc("[LOG] No symbols to relocate\n" ); |
| 376 | } |
| 377 | |
| 378 | sym = (struct nlist_64 *)(linkedit_cmd->vmaddr + symtab_cmd->symoff - linkedit_cmd->fileoff); |
| 379 | |
| 380 | for (i = 0; i < symtab_cmd->nsyms; i++) { |
| 381 | if (sym[i].n_type & N_STAB) { |
| 382 | continue; |
| 383 | } |
| 384 | sym[i].n_value += adj; |
| 385 | cnt++; |
| 386 | } |
| 387 | if (LogFixups) { |
| 388 | dyldLogFunc("[LOG] KASLR: Relocated %d symbols\n" , cnt); |
| 389 | } |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | MARK_AS_FIXUP_TEXT static void |
| 394 | kernel_collection_adjust_mh_addrs(struct mach_header_64 *kc_mh, uintptr_t adj, |
| 395 | bool pageable, uintptr_t *kc_lowest_vmaddr, uintptr_t *kc_highest_vmaddr, |
| 396 | uintptr_t *kc_lowest_ro_vmaddr, uintptr_t *kc_highest_ro_vmaddr, |
| 397 | uintptr_t *kc_lowest_rx_vmaddr, uintptr_t *kc_highest_rx_vmaddr, |
| 398 | uintptr_t *kc_highest_nle_vmaddr) |
| 399 | { |
| 400 | assert(kc_mh->filetype == MH_FILESET); |
| 401 | |
| 402 | struct load_command *lc; |
| 403 | struct fileset_entry_command *fse; |
| 404 | struct segment_command_64 *seg; |
| 405 | struct section_64 *sec; |
| 406 | struct mach_header_64 *mh; |
| 407 | uintptr_t lowest_vmaddr = UINTPTR_MAX, highest_vmaddr = 0, highest_nle_vmaddr = 0; |
| 408 | uintptr_t lowest_ro_vmaddr = UINTPTR_MAX, highest_ro_vmaddr = 0; |
| 409 | uintptr_t lowest_rx_vmaddr = UINTPTR_MAX, highest_rx_vmaddr = 0; |
| 410 | uint32_t i, j; |
| 411 | int is_linkedit = 0; |
| 412 | |
| 413 | /* |
| 414 | * Slide (offset/adjust) every segment/section of every kext contained |
| 415 | * in this MH_FILESET mach-o. |
| 416 | */ |
| 417 | lc = (struct load_command *)((uintptr_t)kc_mh + sizeof(*kc_mh)); |
| 418 | for (i = 0; i < kc_mh->ncmds; i++, |
| 419 | lc = (struct load_command *)((uintptr_t)lc + lc->cmdsize)) { |
| 420 | if (lc->cmd == LC_FILESET_ENTRY) { |
| 421 | fse = (struct fileset_entry_command *)(uintptr_t)lc; |
| 422 | /* |
| 423 | * The fileset_entry contains a pointer to the mach-o |
| 424 | * of a kext (or the kernel). Slide/adjust this command, and |
| 425 | * then slide/adjust all the sub-commands in the mach-o. |
| 426 | */ |
| 427 | if (LogFixups) { |
| 428 | dyldLogFunc("[MH] sliding %s" , (char *)((uintptr_t)fse + |
| 429 | (uintptr_t)(fse->entry_id.offset))); |
| 430 | } |
| 431 | mh = (struct mach_header_64 *)((uintptr_t)fse->vmaddr + adj); |
| 432 | if (!pageable) { |
| 433 | /* |
| 434 | * Do not adjust mach headers of entries in pageable KC as that |
| 435 | * would pull those pages in prematurely |
| 436 | */ |
| 437 | kernel_collection_adjust_fileset_entry_addrs(mh, adj); |
| 438 | } |
| 439 | fse->vmaddr += adj; |
| 440 | } else if (lc->cmd == LC_SEGMENT_64) { |
| 441 | /* |
| 442 | * Slide/adjust all LC_SEGMENT_64 commands in the fileset |
| 443 | * (and any sections in those segments) |
| 444 | */ |
| 445 | seg = (struct segment_command_64 *)(uintptr_t)lc; |
| 446 | seg->vmaddr += adj; |
| 447 | sec = (struct section_64 *)((uintptr_t)seg + sizeof(*seg)); |
| 448 | for (j = 0; j < seg->nsects; j++, sec++) { |
| 449 | sec->addr += adj; |
| 450 | } |
| 451 | if (seg->vmsize == 0) { |
| 452 | continue; |
| 453 | } |
| 454 | /* |
| 455 | * Record vmaddr range covered by all non-empty segments in the |
| 456 | * kernel collection. |
| 457 | */ |
| 458 | if (seg->vmaddr < lowest_vmaddr) { |
| 459 | lowest_vmaddr = (uintptr_t)seg->vmaddr; |
| 460 | } |
| 461 | |
| 462 | is_linkedit = strings_are_equal(a: seg->segname, b: "__LINKEDIT" ); |
| 463 | |
| 464 | if (seg->vmaddr + seg->vmsize > highest_vmaddr) { |
| 465 | highest_vmaddr = (uintptr_t)seg->vmaddr + (uintptr_t)seg->vmsize; |
| 466 | if (!is_linkedit) { |
| 467 | highest_nle_vmaddr = highest_vmaddr; |
| 468 | } |
| 469 | } |
| 470 | |
| 471 | if ((seg->maxprot & VM_PROT_WRITE) || is_linkedit) { |
| 472 | continue; |
| 473 | } |
| 474 | /* |
| 475 | * Record vmaddr range covered by non-empty read-only segments |
| 476 | * in the kernel collection (excluding LINKEDIT). |
| 477 | */ |
| 478 | if (seg->vmaddr < lowest_ro_vmaddr) { |
| 479 | lowest_ro_vmaddr = (uintptr_t)seg->vmaddr; |
| 480 | } |
| 481 | if (seg->vmaddr + seg->vmsize > highest_ro_vmaddr) { |
| 482 | highest_ro_vmaddr = (uintptr_t)seg->vmaddr + (uintptr_t)seg->vmsize; |
| 483 | } |
| 484 | |
| 485 | if (!(seg->maxprot & VM_PROT_EXECUTE)) { |
| 486 | continue; |
| 487 | } |
| 488 | /* |
| 489 | * Record vmaddr range covered by contiguous execute segments |
| 490 | * in the kernel collection. |
| 491 | */ |
| 492 | if (seg->vmaddr < lowest_rx_vmaddr && (lowest_rx_vmaddr <= seg->vmaddr + seg->vmsize || lowest_rx_vmaddr == UINTPTR_MAX)) { |
| 493 | lowest_rx_vmaddr = (uintptr_t)seg->vmaddr; |
| 494 | } |
| 495 | if (seg->vmaddr + seg->vmsize > highest_rx_vmaddr && (highest_rx_vmaddr >= seg->vmaddr || highest_rx_vmaddr == 0)) { |
| 496 | highest_rx_vmaddr = (uintptr_t)seg->vmaddr + (uintptr_t)seg->vmsize; |
| 497 | } |
| 498 | } |
| 499 | } |
| 500 | if (kc_lowest_vmaddr) { |
| 501 | *kc_lowest_vmaddr = lowest_vmaddr; |
| 502 | } |
| 503 | if (kc_highest_vmaddr) { |
| 504 | *kc_highest_vmaddr = highest_vmaddr; |
| 505 | } |
| 506 | if (kc_lowest_ro_vmaddr) { |
| 507 | *kc_lowest_ro_vmaddr = lowest_ro_vmaddr; |
| 508 | } |
| 509 | if (kc_highest_ro_vmaddr) { |
| 510 | *kc_highest_ro_vmaddr = highest_ro_vmaddr; |
| 511 | } |
| 512 | if (kc_lowest_rx_vmaddr) { |
| 513 | *kc_lowest_rx_vmaddr = lowest_rx_vmaddr; |
| 514 | } |
| 515 | if (kc_highest_rx_vmaddr) { |
| 516 | *kc_highest_rx_vmaddr = highest_rx_vmaddr; |
| 517 | } |
| 518 | if (kc_highest_nle_vmaddr) { |
| 519 | *kc_highest_nle_vmaddr = highest_nle_vmaddr; |
| 520 | } |
| 521 | } |
| 522 | |
| 523 | /* |
| 524 | * Rebaser functions for the traditional arm64e static kernelcache with |
| 525 | * threaded rebase. |
| 526 | */ |
| 527 | |
| 528 | static void |
| 529 | rebase_chain(uintptr_t chainStartAddress, uint64_t stepMultiplier, uintptr_t baseAddress __unused, uint64_t slide) |
| 530 | { |
| 531 | uint64_t delta = 0; |
| 532 | uintptr_t address = chainStartAddress; |
| 533 | do { |
| 534 | uint64_t value = *(uint64_t*)address; |
| 535 | |
| 536 | #if HAS_APPLE_PAC |
| 537 | uint16_t diversity = (uint16_t)(value >> 32); |
| 538 | bool hasAddressDiversity = (value & (1ULL << 48)) != 0; |
| 539 | ptrauth_key key = (ptrauth_key)((value >> 49) & 0x3); |
| 540 | #endif |
| 541 | bool isAuthenticated = (value & (1ULL << 63)) != 0; |
| 542 | bool isRebase = (value & (1ULL << 62)) == 0; |
| 543 | if (isRebase) { |
| 544 | if (isAuthenticated) { |
| 545 | // The new value for a rebase is the low 32-bits of the threaded value plus the slide. |
| 546 | uint64_t newValue = (value & 0xFFFFFFFF) + slide; |
| 547 | // Add in the offset from the mach_header |
| 548 | newValue += baseAddress; |
| 549 | #if HAS_APPLE_PAC |
| 550 | // We have bits to merge in to the discriminator |
| 551 | uintptr_t discriminator = diversity; |
| 552 | if (hasAddressDiversity) { |
| 553 | // First calculate a new discriminator using the address of where we are trying to store the value |
| 554 | // Only blend if we have a discriminator |
| 555 | if (discriminator) { |
| 556 | discriminator = __builtin_ptrauth_blend_discriminator((void*)address, discriminator); |
| 557 | } else { |
| 558 | discriminator = address; |
| 559 | } |
| 560 | } |
| 561 | switch (key) { |
| 562 | case ptrauth_key_asia: |
| 563 | newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asia, discriminator); |
| 564 | break; |
| 565 | case ptrauth_key_asib: |
| 566 | newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asib, discriminator); |
| 567 | break; |
| 568 | case ptrauth_key_asda: |
| 569 | newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asda, discriminator); |
| 570 | break; |
| 571 | case ptrauth_key_asdb: |
| 572 | newValue = (uintptr_t)__builtin_ptrauth_sign_unauthenticated((void*)newValue, ptrauth_key_asdb, discriminator); |
| 573 | break; |
| 574 | } |
| 575 | #endif |
| 576 | *(uint64_t*)address = newValue; |
| 577 | } else { |
| 578 | // Regular pointer which needs to fit in 51-bits of value. |
| 579 | // C++ RTTI uses the top bit, so we'll allow the whole top-byte |
| 580 | // and the bottom 43-bits to be fit in to 51-bits. |
| 581 | uint64_t top8Bits = value & 0x0007F80000000000ULL; |
| 582 | uint64_t bottom43Bits = value & 0x000007FFFFFFFFFFULL; |
| 583 | uint64_t targetValue = (top8Bits << 13) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); |
| 584 | targetValue = targetValue + slide; |
| 585 | *(uint64_t*)address = targetValue; |
| 586 | } |
| 587 | } |
| 588 | |
| 589 | // The delta is bits [51..61] |
| 590 | // And bit 62 is to tell us if we are a rebase (0) or bind (1) |
| 591 | value &= ~(1ULL << 62); |
| 592 | delta = (value & 0x3FF8000000000000) >> 51; |
| 593 | address += delta * stepMultiplier; |
| 594 | } while (delta != 0); |
| 595 | } |
| 596 | |
| 597 | static bool __unused |
| 598 | rebase_threaded_starts(uint32_t *threadArrayStart, uint32_t *threadArrayEnd, |
| 599 | uintptr_t , uintptr_t , size_t slide) |
| 600 | { |
| 601 | uint32_t = *threadArrayStart; |
| 602 | uint64_t stepMultiplier = (threadStartsHeader & 1) == 1 ? 8 : 4; |
| 603 | for (uint32_t* threadOffset = threadArrayStart + 1; threadOffset != threadArrayEnd; ++threadOffset) { |
| 604 | if (*threadOffset == 0xFFFFFFFF) { |
| 605 | break; |
| 606 | } |
| 607 | rebase_chain(chainStartAddress: macho_header_addr + *threadOffset, stepMultiplier, baseAddress: macho_header_vmaddr, slide); |
| 608 | } |
| 609 | return true; |
| 610 | } |
| 611 | |