X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/19894a1236eae932b4028640f24ab843f691d4e4..ba4c3badc27ea8c4637b4e91a49725742a02a53c:/src/ImageLoaderMachO.cpp?ds=sidebyside diff --git a/src/ImageLoaderMachO.cpp b/src/ImageLoaderMachO.cpp index 53b0ddd..f63769b 100644 --- a/src/ImageLoaderMachO.cpp +++ b/src/ImageLoaderMachO.cpp @@ -46,12 +46,18 @@ #include #include +#if __has_feature(ptrauth_calls) +#include +#endif + #include "ImageLoaderMachO.h" #include "ImageLoaderMachOCompressed.h" #if SUPPORT_CLASSIC_MACHO #include "ImageLoaderMachOClassic.h" #endif #include "mach-o/dyld_images.h" +#include "Tracing.h" +#include "dyld.h" // use stack guard random value to add padding between dylibs extern "C" long __stack_chk_guard; @@ -60,6 +66,52 @@ extern "C" long __stack_chk_guard; #define LC_LOAD_UPWARD_DYLIB (0x23|LC_REQ_DYLD) /* load of dylib whose initializers run later */ #endif +#ifndef LC_VERSION_MIN_TVOS + #define LC_VERSION_MIN_TVOS 0x2F +#endif + +#ifndef LC_VERSION_MIN_WATCHOS + #define LC_VERSION_MIN_WATCHOS 0x30 +#endif + +#ifndef LC_BUILD_VERSION + #define LC_BUILD_VERSION 0x32 /* build for platform min OS version */ + + /* + * The build_version_command contains the min OS version on which this + * binary was built to run for its platform. The list of known platforms and + * tool values following it. + */ + struct build_version_command { + uint32_t cmd; /* LC_BUILD_VERSION */ + uint32_t cmdsize; /* sizeof(struct build_version_command) plus */ + /* ntools * sizeof(struct build_tool_version) */ + uint32_t platform; /* platform */ + uint32_t minos; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */ + uint32_t ntools; /* number of tool entries following this */ + }; + + struct build_tool_version { + uint32_t tool; /* enum for the tool */ + uint32_t version; /* version number of the tool */ + }; + + /* Known values for the platform field above. */ + #define PLATFORM_MACOS 1 + #define PLATFORM_IOS 2 + #define PLATFORM_TVOS 3 + #define PLATFORM_WATCHOS 4 + #define PLATFORM_BRIDGEOS 5 + + /* Known values for the tool field above. */ + #define TOOL_CLANG 1 + #define TOOL_SWIFT 2 + #define TOOL_LD 3 +#endif + +#define LIBSYSTEM_DYLIB_PATH "/usr/lib/libSystem.B.dylib" + // relocation_info.r_length field has value 3 for 64-bit executables and value 2 for 32-bit executables #if __LP64__ #define LC_SEGMENT_COMMAND LC_SEGMENT_64 @@ -78,14 +130,13 @@ extern "C" long __stack_chk_guard; #endif uint32_t ImageLoaderMachO::fgSymbolTableBinarySearchs = 0; -uint32_t ImageLoaderMachO::fgSymbolTrieSearchs = 0; ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, unsigned int segCount, uint32_t segOffsets[], unsigned int libCount) - : ImageLoader(path, libCount), fMachOData((uint8_t*)mh), fLinkEditBase(NULL), fSlide(0), + : ImageLoader(path, libCount), fCoveredCodeLength(0), fMachOData((uint8_t*)mh), fLinkEditBase(NULL), fSlide(0), fEHFrameSectionOffset(0), fUnwindInfoSectionOffset(0), fDylibIDOffset(0), - fSegmentsCount(segCount), fIsSplitSeg(false), fInSharedCache(false), +fSegmentsCount(segCount), fIsSplitSeg(false), fInSharedCache(false), #if TEXT_RELOC_SUPPORT fTextSegmentRebases(false), fTextSegmentBinds(false), @@ -94,7 +145,7 @@ ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, uns fReadOnlyImportSegment(false), #endif fHasSubLibraries(false), fHasSubUmbrella(false), fInUmbrella(false), fHasDOFSections(false), fHasDashInit(false), - fHasInitializers(false), fHasTerminators(false), fRegisteredAsRequiresCoalescing(false) + fHasInitializers(false), fHasTerminators(false), fNotifyObjC(false), fRetainForObjC(false), fRegisteredAsRequiresCoalescing(false), fOverrideOfCacheImageNum(0) { fIsSplitSeg = ((mh->flags & MH_SPLIT_SEGS) != 0); @@ -117,28 +168,44 @@ ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, uns } +#if __MAC_OS_X_VERSION_MIN_REQUIRED +static uintptr_t pageAlign(uintptr_t value) +{ + return (value + 4095) & (-4096); +} +#endif // determine if this mach-o file has classic or compressed LINKEDIT and number of segments it has -void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool* compressed, +void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* path, bool inCache, bool* compressed, unsigned int* segCount, unsigned int* libCount, const LinkContext& context, - const linkedit_data_command** codeSigCmd) + const linkedit_data_command** codeSigCmd, + const encryption_info_command** encryptCmd) { *compressed = false; *segCount = 0; *libCount = 0; *codeSigCmd = NULL; - struct macho_segment_command* segCmd; - bool foundLoadCommandSegment = false; - uint32_t loadCommandSegmentIndex = 0xFFFFFFFF; - uintptr_t loadCommandSegmentVMStart = 0; - uintptr_t loadCommandSegmentVMEnd = 0; + *encryptCmd = NULL; const uint32_t cmd_count = mh->ncmds; - const struct load_command* const startCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header)); - const struct load_command* const endCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header) + mh->sizeofcmds); + const uint32_t sizeofcmds = mh->sizeofcmds; + if ( sizeofcmds > (MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE-sizeof(macho_header)) ) + dyld::throwf("malformed mach-o: load commands size (%u) > %u", sizeofcmds, MAX_MACH_O_HEADER_AND_LOAD_COMMANDS_SIZE); + if ( cmd_count > (sizeofcmds/sizeof(load_command)) ) + dyld::throwf("malformed mach-o: ncmds (%u) too large to fit in sizeofcmds (%u)", cmd_count, sizeofcmds); + const struct load_command* const startCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header)); + const struct load_command* const endCmds = (struct load_command*)(((uint8_t*)mh) + sizeof(macho_header) + sizeofcmds); const struct load_command* cmd = startCmds; + bool foundLoadCommandSegment = false; + const macho_segment_command* linkeditSegCmd = NULL; + const macho_segment_command* startOfFileSegCmd = NULL; + const dyld_info_command* dyldInfoCmd = NULL; + const symtab_command* symTabCmd = NULL; + const dysymtab_command* dynSymbTabCmd = NULL; for (uint32_t i = 0; i < cmd_count; ++i) { uint32_t cmdLength = cmd->cmdsize; + const macho_segment_command* segCmd; + const dylib_command* dylibCmd; if ( cmdLength < 8 ) { dyld::throwf("malformed mach-o image: load command #%d length (%u) too small in %s", i, cmdLength, path); @@ -151,29 +218,112 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat switch (cmd->cmd) { case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: + if ( cmd->cmdsize != sizeof(dyld_info_command) ) + throw "malformed mach-o image: LC_DYLD_INFO size wrong"; + dyldInfoCmd = (struct dyld_info_command*)cmd; *compressed = true; break; case LC_SEGMENT_COMMAND: segCmd = (struct macho_segment_command*)cmd; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // rdar://problem/19617624 allow unmapped segments on OSX (but not iOS) + if ( ((segCmd->filesize) > pageAlign(segCmd->vmsize)) && (segCmd->vmsize != 0) ) +#else + // dyld should support non-allocatable __LLVM segment + if ( (segCmd->filesize > segCmd->vmsize) && ((segCmd->vmsize != 0) || ((segCmd->flags & SG_NORELOC) == 0)) ) +#endif + dyld::throwf("malformed mach-o image: segment load command %s filesize (0x%0lX) is larger than vmsize (0x%0lX)", segCmd->segname, (long)segCmd->filesize , (long)segCmd->vmsize ); + if ( cmd->cmdsize < sizeof(macho_segment_command) ) + throw "malformed mach-o image: LC_SEGMENT size too small"; + if ( cmd->cmdsize != (sizeof(macho_segment_command) + segCmd->nsects * sizeof(macho_section)) ) + throw "malformed mach-o image: LC_SEGMENT size wrong for number of sections"; // ignore zero-sized segments if ( segCmd->vmsize != 0 ) *segCount += 1; - // all load commands must be in an executable segment - if ( context.codeSigningEnforced && (segCmd->fileoff < mh->sizeofcmds) && (segCmd->filesize != 0) ) { - if ( (segCmd->fileoff != 0) || (segCmd->filesize < (mh->sizeofcmds+sizeof(macho_header))) ) - dyld::throwf("malformed mach-o image: segment %s does not span all load commands", segCmd->segname); - if ( segCmd->initprot != (VM_PROT_READ | VM_PROT_EXECUTE) ) - dyld::throwf("malformed mach-o image: load commands found in segment %s with wrong permissions", segCmd->segname); - if ( foundLoadCommandSegment ) - throw "load commands in multiple segments"; - foundLoadCommandSegment = true; - loadCommandSegmentIndex = i; - loadCommandSegmentVMStart = segCmd->vmaddr; - loadCommandSegmentVMEnd = segCmd->vmaddr + segCmd->vmsize; - if ( (intptr_t)(segCmd->vmsize) < 0) - dyld::throwf("malformed mach-o image: segment load command %s size too large", segCmd->segname); - if ( loadCommandSegmentVMEnd < loadCommandSegmentVMStart ) + if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) { + #if TARGET_IPHONE_SIMULATOR + // Note: should check on all platforms that __LINKEDIT is read-only, but + if ( segCmd->initprot != VM_PROT_READ ) + throw "malformed mach-o image: __LINKEDIT segment does not have read-only permissions"; + #endif + if ( segCmd->fileoff == 0 ) + throw "malformed mach-o image: __LINKEDIT has fileoff==0 which overlaps mach_header"; + if ( linkeditSegCmd != NULL ) + throw "malformed mach-o image: multiple __LINKEDIT segments"; + linkeditSegCmd = segCmd; + } + else { + if ( segCmd->initprot & 0xFFFFFFF8 ) + dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in initprot", segCmd->segname, segCmd->initprot); + if ( segCmd->maxprot & 0xFFFFFFF8 ) + dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in maxprot", segCmd->segname, segCmd->maxprot); + if ( (segCmd->initprot != 0) && ((segCmd->initprot & VM_PROT_READ) == 0) ) + dyld::throwf("malformed mach-o image: %s segment is not mapped readable", segCmd->segname); + } + if ( (segCmd->fileoff == 0) && (segCmd->filesize != 0) ) { + if ( (segCmd->initprot & VM_PROT_READ) == 0 ) + dyld::throwf("malformed mach-o image: %s segment maps start of file but is not readable", segCmd->segname); + if ( (segCmd->initprot & VM_PROT_WRITE) == VM_PROT_WRITE ) { + if ( context.strictMachORequired ) + dyld::throwf("malformed mach-o image: %s segment maps start of file but is writable", segCmd->segname); + } + if ( segCmd->filesize < (sizeof(macho_header) + mh->sizeofcmds) ) + dyld::throwf("malformed mach-o image: %s segment does not map all of load commands", segCmd->segname); + if ( startOfFileSegCmd != NULL ) + dyld::throwf("malformed mach-o image: multiple segments map start of file: %s %s", startOfFileSegCmd->segname, segCmd->segname); + startOfFileSegCmd = segCmd; + } + if ( context.strictMachORequired ) { + uintptr_t vmStart = segCmd->vmaddr; + uintptr_t vmSize = segCmd->vmsize; + uintptr_t vmEnd = vmStart + vmSize; + uintptr_t fileStart = segCmd->fileoff; + uintptr_t fileSize = segCmd->filesize; + if ( (intptr_t)(vmSize) < 0 ) + dyld::throwf("malformed mach-o image: segment load command %s vmsize too large in %s", segCmd->segname, path); + if ( vmStart > vmEnd ) dyld::throwf("malformed mach-o image: segment load command %s wraps around address space", segCmd->segname); + if ( vmSize != fileSize ) { + if ( segCmd->initprot == 0 ) { + // allow: fileSize == 0 && initprot == 0 e.g. __PAGEZERO + // allow: vmSize == 0 && initprot == 0 e.g. __LLVM + if ( (fileSize != 0) && (vmSize != 0) ) + dyld::throwf("malformed mach-o image: unaccessable segment %s has non-zero filesize and vmsize", segCmd->segname); + } + else { + // allow: vmSize > fileSize && initprot != X e.g. __DATA + if ( vmSize < fileSize ) { + dyld::throwf("malformed mach-o image: segment %s has vmsize < filesize", segCmd->segname); + } + if ( segCmd->initprot & VM_PROT_EXECUTE ) { + dyld::throwf("malformed mach-o image: segment %s has vmsize != filesize and is executable", segCmd->segname); + } + } + } + if ( inCache ) { + if ( (fileSize != 0) && (segCmd->initprot == (VM_PROT_READ | VM_PROT_EXECUTE)) ) { + if ( foundLoadCommandSegment ) + throw "load commands in multiple segments"; + foundLoadCommandSegment = true; + } + } + else if ( (fileStart < mh->sizeofcmds) && (fileSize != 0) ) { + // all load commands must be in an executable segment + if ( (fileStart != 0) || (fileSize < (mh->sizeofcmds+sizeof(macho_header))) ) + dyld::throwf("malformed mach-o image: segment %s does not span all load commands", segCmd->segname); + if ( segCmd->initprot != (VM_PROT_READ | VM_PROT_EXECUTE) ) + dyld::throwf("malformed mach-o image: load commands found in segment %s with wrong permissions", segCmd->segname); + if ( foundLoadCommandSegment ) + throw "load commands in multiple segments"; + foundLoadCommandSegment = true; + } + + const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command)); + const struct macho_section* const sectionsEnd = §ionsStart[segCmd->nsects]; + for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if (!inCache && sect->offset != 0 && ((sect->offset + sect->size) > (segCmd->fileoff + segCmd->filesize))) + dyld::throwf("malformed mach-o image: section %s,%s of '%s' exceeds segment %s booundary", sect->segname, sect->sectname, path, segCmd->segname); + } } break; case LC_SEGMENT_COMMAND_WRONG: @@ -184,40 +334,226 @@ void ImageLoaderMachO::sniffLoadCommands(const macho_header* mh, const char* pat case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: *libCount += 1; + // fall thru + case LC_ID_DYLIB: + dylibCmd = (dylib_command*)cmd; + if ( dylibCmd->dylib.name.offset > cmdLength ) + dyld::throwf("malformed mach-o image: dylib load command #%d has offset (%u) outside its size (%u)", i, dylibCmd->dylib.name.offset, cmdLength); + if ( (dylibCmd->dylib.name.offset + strlen((char*)dylibCmd + dylibCmd->dylib.name.offset) + 1) > cmdLength ) + dyld::throwf("malformed mach-o image: dylib load command #%d string extends beyond end of load command", i); break; case LC_CODE_SIGNATURE: - *codeSigCmd = (struct linkedit_data_command*)cmd; // only support one LC_CODE_SIGNATURE per image + if ( cmd->cmdsize != sizeof(linkedit_data_command) ) + throw "malformed mach-o image: LC_CODE_SIGNATURE size wrong"; + // only support one LC_CODE_SIGNATURE per image + if ( *codeSigCmd != NULL ) + throw "malformed mach-o image: multiple LC_CODE_SIGNATURE load commands"; + *codeSigCmd = (struct linkedit_data_command*)cmd; + break; + case LC_ENCRYPTION_INFO: + if ( cmd->cmdsize != sizeof(encryption_info_command) ) + throw "malformed mach-o image: LC_ENCRYPTION_INFO size wrong"; + // only support one LC_ENCRYPTION_INFO per image + if ( *encryptCmd != NULL ) + throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO load commands"; + *encryptCmd = (encryption_info_command*)cmd; break; + case LC_ENCRYPTION_INFO_64: + if ( cmd->cmdsize != sizeof(encryption_info_command_64) ) + throw "malformed mach-o image: LC_ENCRYPTION_INFO_64 size wrong"; + // only support one LC_ENCRYPTION_INFO_64 per image + if ( *encryptCmd != NULL ) + throw "malformed mach-o image: multiple LC_ENCRYPTION_INFO_64 load commands"; + *encryptCmd = (encryption_info_command*)cmd; + break; + case LC_SYMTAB: + if ( cmd->cmdsize != sizeof(symtab_command) ) + throw "malformed mach-o image: LC_SYMTAB size wrong"; + symTabCmd = (symtab_command*)cmd; + break; + case LC_DYSYMTAB: + if ( cmd->cmdsize != sizeof(dysymtab_command) ) + throw "malformed mach-o image: LC_DYSYMTAB size wrong"; + dynSymbTabCmd = (dysymtab_command*)cmd; + break; +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // error when loading iOS Simulator mach-o binary into macOS process + case LC_VERSION_MIN_WATCHOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_IPHONEOS: + if ( !context.marzipan ) + throw "mach-o, but built for simulator (not macOS)"; + break; +#endif } cmd = nextCmd; } - - if ( context.codeSigningEnforced && !foundLoadCommandSegment ) + + if ( context.strictMachORequired && !foundLoadCommandSegment ) throw "load commands not in a segment"; - // verify another segment does not over-map load commands - cmd = startCmds; - if ( context.codeSigningEnforced ) { + if ( linkeditSegCmd == NULL ) + throw "malformed mach-o image: missing __LINKEDIT segment"; + if ( !inCache && (startOfFileSegCmd == NULL) ) + throw "malformed mach-o image: missing __TEXT segment that maps start of file"; + // verify every segment does not overlap another segment + if ( context.strictMachORequired ) { + uintptr_t lastFileStart = 0; + uintptr_t linkeditFileStart = 0; + const struct load_command* cmd1 = startCmds; for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_SEGMENT_COMMAND: - if ( i != loadCommandSegmentIndex ) { - segCmd = (struct macho_segment_command*)cmd; - uintptr_t start = segCmd->vmaddr; - uintptr_t end = segCmd->vmaddr + segCmd->vmsize; - if ( ((start <= loadCommandSegmentVMStart) && (end > loadCommandSegmentVMStart)) - || ((start >= loadCommandSegmentVMStart) && (start < loadCommandSegmentVMEnd)) ) - dyld::throwf("malformed mach-o image: segment %s overlaps load commands", segCmd->segname); + if ( cmd1->cmd == LC_SEGMENT_COMMAND ) { + struct macho_segment_command* segCmd1 = (struct macho_segment_command*)cmd1; + uintptr_t vmStart1 = segCmd1->vmaddr; + uintptr_t vmEnd1 = segCmd1->vmaddr + segCmd1->vmsize; + uintptr_t fileStart1 = segCmd1->fileoff; + uintptr_t fileEnd1 = segCmd1->fileoff + segCmd1->filesize; + + if (fileStart1 > lastFileStart) + lastFileStart = fileStart1; + + if ( strcmp(&segCmd1->segname[0], "__LINKEDIT") == 0 ) { + linkeditFileStart = fileStart1; + } + + const struct load_command* cmd2 = startCmds; + for (uint32_t j = 0; j < cmd_count; ++j) { + if ( cmd2 == cmd1 ) + continue; + if ( cmd2->cmd == LC_SEGMENT_COMMAND ) { + struct macho_segment_command* segCmd2 = (struct macho_segment_command*)cmd2; + uintptr_t vmStart2 = segCmd2->vmaddr; + uintptr_t vmEnd2 = segCmd2->vmaddr + segCmd2->vmsize; + uintptr_t fileStart2 = segCmd2->fileoff; + uintptr_t fileEnd2 = segCmd2->fileoff + segCmd2->filesize; + if ( ((vmStart2 <= vmStart1) && (vmEnd2 > vmStart1) && (vmEnd1 > vmStart1)) + || ((vmStart2 >= vmStart1) && (vmStart2 < vmEnd1) && (vmEnd2 > vmStart2)) ) + dyld::throwf("malformed mach-o image: segment %s vm overlaps segment %s", segCmd1->segname, segCmd2->segname); + if ( ((fileStart2 <= fileStart1) && (fileEnd2 > fileStart1) && (fileEnd1 > fileStart1)) + || ((fileStart2 >= fileStart1) && (fileStart2 < fileEnd1) && (fileEnd2 > fileStart2)) ) + dyld::throwf("malformed mach-o image: segment %s file content overlaps segment %s", segCmd1->segname, segCmd2->segname); } - break; + cmd2 = (const struct load_command*)(((char*)cmd2)+cmd2->cmdsize); + } } - cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + cmd1 = (const struct load_command*)(((char*)cmd1)+cmd1->cmdsize); } + + if (lastFileStart != linkeditFileStart) + dyld::throwf("malformed mach-o image: __LINKEDIT must be last segment"); } - + + // validate linkedit content + if ( (dyldInfoCmd == NULL) && (symTabCmd == NULL) ) + throw "malformed mach-o image: missing LC_SYMTAB or LC_DYLD_INFO"; + if ( dynSymbTabCmd == NULL ) + throw "malformed mach-o image: missing LC_DYSYMTAB"; + + uint32_t linkeditFileOffsetStart = (uint32_t)linkeditSegCmd->fileoff; + uint32_t linkeditFileOffsetEnd = (uint32_t)linkeditSegCmd->fileoff + (uint32_t)linkeditSegCmd->filesize; + + if ( !inCache && (dyldInfoCmd != NULL) && context.strictMachORequired ) { + // validate all LC_DYLD_INFO chunks fit in LINKEDIT and don't overlap + uint32_t offset = linkeditFileOffsetStart; + if ( dyldInfoCmd->rebase_size != 0 ) { + if ( dyldInfoCmd->rebase_size & 0x80000000 ) + throw "malformed mach-o image: dyld rebase info size overflow"; + if ( dyldInfoCmd->rebase_off < offset ) + throw "malformed mach-o image: dyld rebase info underruns __LINKEDIT"; + offset = dyldInfoCmd->rebase_off + dyldInfoCmd->rebase_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld rebase info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->bind_size != 0 ) { + if ( dyldInfoCmd->bind_size & 0x80000000 ) + throw "malformed mach-o image: dyld bind info size overflow"; + if ( dyldInfoCmd->bind_off < offset ) + throw "malformed mach-o image: dyld bind info overlaps rebase info"; + offset = dyldInfoCmd->bind_off + dyldInfoCmd->bind_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld bind info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->weak_bind_size != 0 ) { + if ( dyldInfoCmd->weak_bind_size & 0x80000000 ) + throw "malformed mach-o image: dyld weak bind info size overflow"; + if ( dyldInfoCmd->weak_bind_off < offset ) + throw "malformed mach-o image: dyld weak bind info overlaps bind info"; + offset = dyldInfoCmd->weak_bind_off + dyldInfoCmd->weak_bind_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld weak bind info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->lazy_bind_size != 0 ) { + if ( dyldInfoCmd->lazy_bind_size & 0x80000000 ) + throw "malformed mach-o image: dyld lazy bind info size overflow"; + if ( dyldInfoCmd->lazy_bind_off < offset ) + throw "malformed mach-o image: dyld lazy bind info overlaps weak bind info"; + offset = dyldInfoCmd->lazy_bind_off + dyldInfoCmd->lazy_bind_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld lazy bind info overruns __LINKEDIT"; + } + if ( dyldInfoCmd->export_size != 0 ) { + if ( dyldInfoCmd->export_size & 0x80000000 ) + throw "malformed mach-o image: dyld export info size overflow"; + if ( dyldInfoCmd->export_off < offset ) + throw "malformed mach-o image: dyld export info overlaps lazy bind info"; + offset = dyldInfoCmd->export_off + dyldInfoCmd->export_size; + if ( offset > linkeditFileOffsetEnd ) + throw "malformed mach-o image: dyld export info overruns __LINKEDIT"; + } + } + + if ( symTabCmd != NULL ) { + // validate symbol table fits in LINKEDIT + if ( (symTabCmd->nsyms > 0) && (symTabCmd->symoff < linkeditFileOffsetStart) ) + throw "malformed mach-o image: symbol table underruns __LINKEDIT"; + if ( symTabCmd->nsyms > 0x10000000 ) + throw "malformed mach-o image: symbol table too large"; + uint32_t symbolsSize = symTabCmd->nsyms * sizeof(macho_nlist); + if ( symbolsSize > linkeditSegCmd->filesize ) + throw "malformed mach-o image: symbol table overruns __LINKEDIT"; + if ( symTabCmd->symoff + symbolsSize < symTabCmd->symoff ) + throw "malformed mach-o image: symbol table size wraps"; + if ( symTabCmd->symoff + symbolsSize > symTabCmd->stroff ) + throw "malformed mach-o image: symbol table overlaps symbol strings"; + if ( symTabCmd->stroff + symTabCmd->strsize < symTabCmd->stroff ) + throw "malformed mach-o image: symbol string size wraps"; + if ( symTabCmd->stroff + symTabCmd->strsize > linkeditFileOffsetEnd ) { + // let old apps overflow as long as it stays within mapped page + if ( context.strictMachORequired || (symTabCmd->stroff + symTabCmd->strsize > ((linkeditFileOffsetEnd + 4095) & (-4096))) ) + throw "malformed mach-o image: symbol strings overrun __LINKEDIT"; + } + // validate indirect symbol table + if ( dynSymbTabCmd->nindirectsyms != 0 ) { + if ( dynSymbTabCmd->indirectsymoff < linkeditFileOffsetStart ) + throw "malformed mach-o image: indirect symbol table underruns __LINKEDIT"; + if ( dynSymbTabCmd->nindirectsyms > 0x10000000 ) + throw "malformed mach-o image: indirect symbol table too large"; + uint32_t indirectTableSize = dynSymbTabCmd->nindirectsyms * sizeof(uint32_t); + if ( indirectTableSize > linkeditSegCmd->filesize ) + throw "malformed mach-o image: indirect symbol table overruns __LINKEDIT"; + if ( dynSymbTabCmd->indirectsymoff + indirectTableSize < dynSymbTabCmd->indirectsymoff ) + throw "malformed mach-o image: indirect symbol table size wraps"; + if ( context.strictMachORequired && (dynSymbTabCmd->indirectsymoff + indirectTableSize > symTabCmd->stroff) ) + throw "malformed mach-o image: indirect symbol table overruns string pool"; + } + if ( (dynSymbTabCmd->nlocalsym > symTabCmd->nsyms) || (dynSymbTabCmd->ilocalsym > symTabCmd->nsyms) ) + throw "malformed mach-o image: indirect symbol table local symbol count exceeds total symbols"; + if ( dynSymbTabCmd->ilocalsym + dynSymbTabCmd->nlocalsym < dynSymbTabCmd->ilocalsym ) + throw "malformed mach-o image: indirect symbol table local symbol count wraps"; + if ( (dynSymbTabCmd->nextdefsym > symTabCmd->nsyms) || (dynSymbTabCmd->iextdefsym > symTabCmd->nsyms) ) + throw "malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols"; + if ( dynSymbTabCmd->iextdefsym + dynSymbTabCmd->nextdefsym < dynSymbTabCmd->iextdefsym ) + throw "malformed mach-o image: indirect symbol table extern symbol count wraps"; + if ( (dynSymbTabCmd->nundefsym > symTabCmd->nsyms) || (dynSymbTabCmd->iundefsym > symTabCmd->nsyms) ) + throw "malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols"; + if ( dynSymbTabCmd->iundefsym + dynSymbTabCmd->nundefsym < dynSymbTabCmd->iundefsym ) + throw "malformed mach-o image: indirect symbol table undefined symbol count wraps"; + } + + // fSegmentsArrayCount is only 8-bits if ( *segCount > 255 ) dyld::throwf("malformed mach-o image: more than 255 segments in %s", path); - + // fSegmentsArrayCount is only 8-bits if ( *libCount > 4095 ) dyld::throwf("malformed mach-o image: more than 4095 dependent libraries in %s", path); @@ -237,7 +573,8 @@ ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, unsigned int segCount; unsigned int libCount; const linkedit_data_command* codeSigCmd; - sniffLoadCommands(mh, path, &compressed, &segCount, &libCount, context, &codeSigCmd); + const encryption_info_command* encryptCmd; + sniffLoadCommands(mh, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); // instantiate concrete class based on content of load commands if ( compressed ) return ImageLoaderMachOCompressed::instantiateMainExecutable(mh, slide, path, segCount, libCount, context); @@ -251,31 +588,21 @@ ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, // create image by mapping in a mach-o file -ImageLoader* ImageLoaderMachO::instantiateFromFile(const char* path, int fd, const uint8_t firstPage[4096], uint64_t offsetInFat, +ImageLoader* ImageLoaderMachO::instantiateFromFile(const char* path, int fd, const uint8_t firstPages[], size_t firstPagesSize, uint64_t offsetInFat, uint64_t lenInFat, const struct stat& info, const LinkContext& context) { - // get load commands - const unsigned int dataSize = sizeof(macho_header) + ((macho_header*)firstPage)->sizeofcmds; - uint8_t buffer[dataSize]; - const uint8_t* fileData = firstPage; - if ( dataSize > 4096 ) { - // only read more if cmds take up more space than first page - fileData = buffer; - memcpy(buffer, firstPage, 4096); - pread(fd, &buffer[4096], dataSize-4096, offsetInFat+4096); - } - bool compressed; unsigned int segCount; unsigned int libCount; const linkedit_data_command* codeSigCmd; - sniffLoadCommands((const macho_header*)fileData, path, &compressed, &segCount, &libCount, context, &codeSigCmd); + const encryption_info_command* encryptCmd; + sniffLoadCommands((const macho_header*)firstPages, path, false, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); // instantiate concrete class based on content of load commands if ( compressed ) - return ImageLoaderMachOCompressed::instantiateFromFile(path, fd, fileData, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); + return ImageLoaderMachOCompressed::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, encryptCmd, context); else #if SUPPORT_CLASSIC_MACHO - return ImageLoaderMachOClassic::instantiateFromFile(path, fd, fileData, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); + return ImageLoaderMachOClassic::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); #else throw "missing LC_DYLD_INFO load command"; #endif @@ -289,7 +616,8 @@ ImageLoader* ImageLoaderMachO::instantiateFromCache(const macho_header* mh, cons unsigned int segCount; unsigned int libCount; const linkedit_data_command* codeSigCmd; - sniffLoadCommands(mh, path, &compressed, &segCount, &libCount, context, &codeSigCmd); + const encryption_info_command* encryptCmd; + sniffLoadCommands(mh, path, true, &compressed, &segCount, &libCount, context, &codeSigCmd, &encryptCmd); // instantiate concrete class based on content of load commands if ( compressed ) return ImageLoaderMachOCompressed::instantiateFromCache(mh, path, slide, info, segCount, libCount, context); @@ -308,7 +636,8 @@ ImageLoader* ImageLoaderMachO::instantiateFromMemory(const char* moduleName, con unsigned int segCount; unsigned int libCount; const linkedit_data_command* sigcmd; - sniffLoadCommands(mh, moduleName, &compressed, &segCount, &libCount, context, &sigcmd); + const encryption_info_command* encryptCmd; + sniffLoadCommands(mh, moduleName, false, &compressed, &segCount, &libCount, context, &sigcmd, &encryptCmd); // instantiate concrete class based on content of load commands if ( compressed ) return ImageLoaderMachOCompressed::instantiateFromMemory(moduleName, mh, len, segCount, libCount, context); @@ -337,13 +666,19 @@ int ImageLoaderMachO::crashIfInvalidCodeSignature() } -void ImageLoaderMachO::parseLoadCmds() +void ImageLoaderMachO::parseLoadCmds(const LinkContext& context) { // now that segments are mapped in, get real fMachOData, fLinkEditBase, and fSlide for(unsigned int i=0; i < fSegmentsCount; ++i) { // set up pointer to __LINKEDIT segment - if ( strcmp(segName(i),"__LINKEDIT") == 0 ) + if ( strcmp(segName(i),"__LINKEDIT") == 0 ) { + #if !__MAC_OS_X_VERSION_MIN_REQUIRED + // historically, macOS never did this check + if ( segFileOffset(i) > fCoveredCodeLength ) + dyld::throwf("cannot load '%s' (segment outside of code signature)", this->getShortName()); + #endif fLinkEditBase = (uint8_t*)(segActualLoadAddress(i) - segFileOffset(i)); + } #if TEXT_RELOC_SUPPORT // __TEXT segment always starts at beginning of file and contains mach_header and load commands if ( segExecutable(i) ) { @@ -417,6 +752,13 @@ void ImageLoaderMachO::parseLoadCmds() { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; const bool isTextSeg = (strcmp(seg->segname, "__TEXT") == 0); + #if __i386__ && __MAC_OS_X_VERSION_MIN_REQUIRED + const bool isObjCSeg = (strcmp(seg->segname, "__OBJC") == 0); + if ( isObjCSeg ) + fNotifyObjC = true; + #else + const bool isDataSeg = (strncmp(seg->segname, "__DATA", 6) == 0); + #endif const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { @@ -431,6 +773,32 @@ void ImageLoaderMachO::parseLoadCmds() fEHFrameSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); else if ( isTextSeg && (strcmp(sect->sectname, "__unwind_info") == 0) ) fUnwindInfoSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); + + #if __i386__ && __MAC_OS_X_VERSION_MIN_REQUIRED + else if ( isObjCSeg ) { + if ( strcmp(sect->sectname, "__image_info") == 0 ) { + const uint32_t* imageInfo = (uint32_t*)(sect->addr + fSlide); + uint32_t flags = imageInfo[1]; + if ( (flags & 4) && (((macho_header*)fMachOData)->filetype != MH_EXECUTE) ) + dyld::throwf("cannot load '%s' because Objective-C garbage collection is not supported", getPath()); + } + else if ( ((macho_header*)fMachOData)->filetype == MH_DYLIB ) { + fRetainForObjC = true; + } + } + #else + else if ( isDataSeg && (strncmp(sect->sectname, "__objc_imageinfo", 16) == 0) ) { + #if __MAC_OS_X_VERSION_MIN_REQUIRED + const uint32_t* imageInfo = (uint32_t*)(sect->addr + fSlide); + uint32_t flags = imageInfo[1]; + if ( (flags & 4) && (((macho_header*)fMachOData)->filetype != MH_EXECUTE) ) + dyld::throwf("cannot load '%s' because Objective-C garbage collection is not supported", getPath()); + #endif + fNotifyObjC = true; + } + else if ( isDataSeg && (strncmp(sect->sectname, "__objc_", 7) == 0) && (((macho_header*)fMachOData)->filetype == MH_DYLIB) ) + fRetainForObjC = true; + #endif } } break; @@ -451,6 +819,8 @@ void ImageLoaderMachO::parseLoadCmds() break; case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: minOSVersionCmd = (version_min_command*)cmd; break; default: @@ -638,7 +1008,7 @@ void ImageLoaderMachO::UnmapSegments() unsigned int textSegmentIndex = 0; for(unsigned int i=0; i < fSegmentsCount; ++i) { //dyld::log("unmap %s at 0x%08lX\n", seg->getName(), seg->getActualLoadAddress(this)); - if ( strcmp(segName(i), "__TEXT") == 0 ) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) { textSegmentIndex = i; } else { @@ -766,53 +1136,87 @@ void ImageLoaderMachO::setSlide(intptr_t slide) void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile, const LinkContext& context) { + dyld3::ScopedTimer(DBG_DYLD_TIMING_ATTACH_CODESIGNATURE, 0, 0, 0); // if dylib being loaded has no code signature load command - if ( codeSigCmd == NULL ) { -#if __MAC_OS_X_VERSION_MIN_REQUIRED - bool codeSigningEnforced = context.codeSigningEnforced; - if ( context.mainExecutableCodeSigned && !codeSigningEnforced ) { - static bool codeSignEnforcementDynamicallyEnabled = false; - if ( !codeSignEnforcementDynamicallyEnabled ) { - uint32_t flags; - if ( csops(0, CS_OPS_STATUS, &flags, sizeof(flags)) != -1 ) { - if ( flags & CS_ENFORCEMENT ) { - codeSignEnforcementDynamicallyEnabled = true; - } - } - } - codeSigningEnforced = codeSignEnforcementDynamicallyEnabled; - } - // if we require dylibs to be code signed - if ( codeSigningEnforced ) { - // if there is a non-load command based code signature, use it - off_t offset = (off_t)offsetInFatFile; - if ( fcntl(fd, F_FINDSIGS, &offset, sizeof(offset)) != -1 ) - return; - // otherwise gracefully return from dlopen() - dyld::throwf("required code signature missing for '%s'\n", this->getPath()); - } -#endif + if ( codeSigCmd == NULL) { + disableCoverageCheck(); } else { #if __MAC_OS_X_VERSION_MIN_REQUIRED // ignore code signatures in binaries built with pre-10.9 tools if ( this->sdkVersion() < DYLD_MACOSX_VERSION_10_9 ) { + disableCoverageCheck(); return; } #endif + fsignatures_t siginfo; - siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file + siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file siginfo.fs_blob_start=(void*)(long)(codeSigCmd->dataoff); // start of CD in mach-o file siginfo.fs_blob_size=codeSigCmd->datasize; // size of CD - int result = fcntl(fd, F_ADDFILESIGS, &siginfo); + int result = fcntl(fd, F_ADDFILESIGS_RETURN, &siginfo); + +#if TARGET_IPHONE_SIMULATOR + // rdar://problem/18759224> check range covered by the code directory after loading + // Attempt to fallback only if we are in the simulator + + if ( result == -1 ) { + result = fcntl(fd, F_ADDFILESIGS, &siginfo); + siginfo.fs_file_start = codeSigCmd->dataoff; + } +#endif + if ( result == -1 ) { if ( (errno == EPERM) || (errno == EBADEXEC) ) dyld::throwf("code signature invalid for '%s'\n", this->getPath()); - if ( context.verboseCodeSignatures ) + if ( context.verboseCodeSignatures ) dyld::log("dyld: Failed registering code signature for %s, errno=%d\n", this->getPath(), errno); + siginfo.fs_file_start = UINT64_MAX; + } else if ( context.verboseCodeSignatures ) { + dyld::log("dyld: Registered code signature for %s\n", this->getPath()); + } + fCoveredCodeLength = siginfo.fs_file_start; + } + + { + fchecklv checkInfo; + char messageBuffer[512]; + messageBuffer[0] = '\0'; + checkInfo.lv_file_start = offsetInFatFile; + checkInfo.lv_error_message_size = sizeof(messageBuffer); + checkInfo.lv_error_message = messageBuffer; + int res = fcntl(fd, F_CHECK_LV, &checkInfo); + if ( res == -1 ) { + dyld::throwf("code signature in (%s) not valid for use in process using Library Validation: %s", this->getPath(), messageBuffer); + } + } +} + +void ImageLoaderMachO::validateFirstPages(const struct linkedit_data_command* codeSigCmd, int fd, const uint8_t *fileData, size_t lenFileData, off_t offsetInFat, const LinkContext& context) +{ +#if __MAC_OS_X_VERSION_MIN_REQUIRED + // rdar://problem/21839703> 15A226d: dyld crashes in mageLoaderMachO::validateFirstPages during dlopen() after encountering an mmap failure + // We need to ignore older code signatures because they will be bad. + if ( this->sdkVersion() < DYLD_MACOSX_VERSION_10_9 ) { + return; + } +#endif + if (codeSigCmd != NULL) { + void *fdata = xmmap(NULL, lenFileData, PROT_READ, MAP_SHARED, fd, offsetInFat); + if ( fdata == MAP_FAILED ) { + int errnoCopy = errno; + if ( errnoCopy == EPERM ) { + if ( dyld::sandboxBlockedMmap(getPath()) ) + dyld::throwf("file system sandbox blocked mmap() of '%s'", getPath()); + else + dyld::throwf("code signing blocked mmap() of '%s'", getPath()); + } else - dyld::log("dyld: Registered code signature for %s\n", this->getPath()); + dyld::throwf("mmap() errno=%d validating first page of '%s'", errnoCopy, getPath()); } + if ( memcmp(fdata, fileData, lenFileData) != 0 ) + dyld::throwf("mmap() page compare failed for '%s'", getPath()); + munmap(fdata, lenFileData); } } @@ -826,7 +1230,7 @@ const char* ImageLoaderMachO::getInstallPath() const return NULL; } -void ImageLoaderMachO::registerInterposing() +void ImageLoaderMachO::registerInterposing(const LinkContext& context) { // mach-o files advertise interposing by having a __DATA __interpose section struct InterposeData { uintptr_t replacement; uintptr_t replacee; }; @@ -842,14 +1246,20 @@ void ImageLoaderMachO::registerInterposing() const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ( ((sect->flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sect->sectname, "__interpose") == 0) && (strcmp(seg->segname, "__DATA") == 0)) ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("interpose section has malformed address range for %s\n", this->getPath()); const InterposeData* interposeArray = (InterposeData*)(sect->addr + fSlide); const size_t count = sect->size / sizeof(InterposeData); - for (size_t i=0; i < count; ++i) { + for (size_t j=0; j < count; ++j) { ImageLoader::InterposeTuple tuple; - tuple.replacement = interposeArray[i].replacement; + tuple.replacement = interposeArray[j].replacement; tuple.neverImage = this; tuple.onlyImage = NULL; - tuple.replacee = interposeArray[i].replacee; + tuple.replacee = interposeArray[j].replacee; + // ignore interposing on a weak function that does not exist + if ( tuple.replacee == 0 ) + continue; // verify that replacement is in this image if ( this->containsAddress((void*)tuple.replacement) ) { // chain to any existing interpositions @@ -870,36 +1280,53 @@ void ImageLoaderMachO::registerInterposing() } } -uint32_t ImageLoaderMachO::sdkVersion() const +uint32_t ImageLoaderMachO::sdkVersion(const mach_header* mh) { - const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; - const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const uint32_t cmd_count = mh->ncmds; + const struct load_command* const cmds = (struct load_command*)(((char*)mh) + sizeof(macho_header)); const struct load_command* cmd = cmds; const struct version_min_command* versCmd; + const struct build_version_command* buildVersCmd; for (uint32_t i = 0; i < cmd_count; ++i) { switch ( cmd->cmd ) { case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: versCmd = (version_min_command*)cmd; return versCmd->sdk; + case LC_BUILD_VERSION: + buildVersCmd = (build_version_command*)cmd; + return buildVersCmd->sdk; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } return 0; } +uint32_t ImageLoaderMachO::sdkVersion() const +{ + return ImageLoaderMachO::sdkVersion(machHeader()); +} + uint32_t ImageLoaderMachO::minOSVersion(const mach_header* mh) { const uint32_t cmd_count = mh->ncmds; const struct load_command* const cmds = (struct load_command*)(((char*)mh) + sizeof(macho_header)); const struct load_command* cmd = cmds; const struct version_min_command* versCmd; + const struct build_version_command* buildVersCmd; for (uint32_t i = 0; i < cmd_count; ++i) { switch ( cmd->cmd ) { case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: versCmd = (version_min_command*)cmd; return versCmd->version; + case LC_BUILD_VERSION: + buildVersCmd = (build_version_command*)cmd; + return buildVersCmd->minos; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } @@ -912,7 +1339,7 @@ uint32_t ImageLoaderMachO::minOSVersion() const } -void* ImageLoaderMachO::getThreadPC() const +void* ImageLoaderMachO::getEntryFromLC_MAIN() const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; @@ -933,36 +1360,33 @@ void* ImageLoaderMachO::getThreadPC() const } -void* ImageLoaderMachO::getMain() const +void* ImageLoaderMachO::getEntryFromLC_UNIXTHREAD() const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; const struct load_command* cmd = cmds; for (uint32_t i = 0; i < cmd_count; ++i) { - switch (cmd->cmd) { - case LC_UNIXTHREAD: - { - #if __i386__ - const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->eip + fSlide); - #elif __x86_64__ - const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->rip + fSlide); - #elif __arm__ - const arm_thread_state_t* registers = (arm_thread_state_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->__pc + fSlide); - #elif __arm64__ - const arm_thread_state64_t* registers = (arm_thread_state64_t*)(((char*)cmd) + 16); - void* entry = (void*)(registers->__pc + fSlide); - #else - #warning need processor specific code - #endif - // verify entry point is in image - if ( this->containsAddress(entry) ) { - return entry; - } - } - break; + if ( cmd->cmd == LC_UNIXTHREAD ) { + #if __i386__ + const i386_thread_state_t* registers = (i386_thread_state_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->eip + fSlide); + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + #elif __x86_64__ + const x86_thread_state64_t* registers = (x86_thread_state64_t*)(((char*)cmd) + 16); + void* entry = (void*)(registers->rip + fSlide); + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + #elif __arm64__ && !__arm64e__ + // temp support until is fixed + const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16); + void* entry = (void*)(regs64[32] + fSlide); // arm_thread_state64_t.__pc + // verify entry point is in image + if ( this->containsAddress(entry) ) + return entry; + #endif } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } @@ -1011,7 +1435,7 @@ void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) { if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) { DependentLibraryInfo* lib = &libs[0]; - lib->name = "/usr/lib/libSystem.B.dylib"; + lib->name = LIBSYSTEM_DYLIB_PATH; lib->info.checksum = 0; lib->info.minVersion = 0; lib->info.maxVersion = 0; @@ -1049,7 +1473,7 @@ void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) } } -ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo() +ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo(const LibraryInfo&) { LibraryInfo info; if ( fDylibIDOffset != 0 ) { @@ -1077,7 +1501,7 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorpath.offset; if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) { - if ( context.processIsRestricted && (context.mainExecutable == this) ) { + if ( !context.allowAtPaths && (context.mainExecutable == this) ) { dyld::warn("LC_RPATH %s in %s being ignored in restricted program because of @loader_path\n", path, this->getPath()); break; } @@ -1093,7 +1517,7 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } @@ -1108,10 +1532,11 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } +#if SUPPORT_ROOT_PATH else if ( (path[0] == '/') && (context.rootPaths != NULL) ) { // DYLD_ROOT_PATH should apply to LC_RPATH rpaths // DYLD_ROOT_PATH can be a list of paths, but at this point we can only support one, so use first combination that exists @@ -1133,6 +1558,7 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vector Delay calling setNeverUnload() until we know this is not for dlopen_preflight() + if ( fRetainForObjC ) + this->setNeverUnload(); + + // dylibs with thread local variables cannot be unloaded because there is no way to clean up all threads + if ( !this->inSharedCache() && (this->machHeader()->flags & MH_HAS_TLV_DESCRIPTORS) ) + this->setNeverUnload(); + // if prebound and loaded at prebound address, then no need to rebase if ( this->usablePrebinding(context) ) { // skip rebasing because prebinding is valid @@ -1212,7 +1646,7 @@ void ImageLoaderMachO::doRebase(const LinkContext& context) #endif // do actual rebasing - this->rebase(context); + this->rebase(context, fSlide); #if TEXT_RELOC_SUPPORT // if there were __TEXT fixups, restore write protection @@ -1243,10 +1677,10 @@ void ImageLoaderMachO::makeTextSegmentWritable(const LinkContext& context, bool } #endif -const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, bool searchReExports, const ImageLoader** foundIn) const +const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name, bool searchReExports, const char* thisPath, const ImageLoader** foundIn) const { // look in this image first - const ImageLoader::Symbol* result = this->findExportedSymbol(name, foundIn); + const ImageLoader::Symbol* result = this->findShallowExportedSymbol(name, foundIn); if ( result != NULL ) return result; @@ -1255,7 +1689,8 @@ const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name if ( libReExported(i) ) { ImageLoader* image = libImage(i); if ( image != NULL ) { - const Symbol* result = image->findExportedSymbol(name, searchReExports, foundIn); + const char* reExPath = libPath(i); + result = image->findExportedSymbol(name, searchReExports, reExPath, foundIn); if ( result != NULL ) return result; } @@ -1270,7 +1705,7 @@ const ImageLoader::Symbol* ImageLoaderMachO::findExportedSymbol(const char* name uintptr_t ImageLoaderMachO::getExportedSymbolAddress(const Symbol* sym, const LinkContext& context, - const ImageLoader* requestor, bool runResolver) const + const ImageLoader* requestor, bool runResolver, const char* symbolName) const { return this->getSymbolAddress(sym, requestor, context, runResolver); } @@ -1382,8 +1817,51 @@ void ImageLoaderMachO::getUnwindInfo(dyld_unwind_sections* info) } } +intptr_t ImageLoaderMachO::computeSlide(const mach_header* mh) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + const macho_segment_command* seg = (macho_segment_command*)cmd; + if ( strcmp(seg->segname, "__TEXT") == 0 ) + return (char*)mh - (char*)(seg->vmaddr); + } + cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize); + } + return 0; +} -bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) +bool ImageLoaderMachO::findSection(const mach_header* mh, const char* segmentName, const char* sectionName, void** sectAddress, size_t* sectSize) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + { + const macho_segment_command* seg = (macho_segment_command*)cmd; + const macho_section* const sectionsStart = (macho_section*)((char*)seg + sizeof(macho_segment_command)); + const macho_section* const sectionsEnd = §ionsStart[seg->nsects]; + for (const macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { + if ( (strcmp(sect->segname, segmentName) == 0) && (strcmp(sect->sectname, sectionName) == 0) ) { + *sectAddress = (void*)(sect->addr + computeSlide(mh)); + *sectSize = sect->size; + return true; + } + } + } + break; + } + cmd = (const load_command*)(((char*)cmd)+cmd->cmdsize); + } + return false; +} + + +const macho_section* ImageLoaderMachO::findSection(const void* imageInterior) const { const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; @@ -1399,13 +1877,7 @@ bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segme const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ((sect->addr <= unslidInteriorAddress) && (unslidInteriorAddress < (sect->addr+sect->size))) { - if ( segmentName != NULL ) - *segmentName = sect->segname; - if ( sectionName != NULL ) - *sectionName = sect->sectname; - if ( sectionOffset != NULL ) - *sectionOffset = unslidInteriorAddress - sect->addr; - return true; + return sect; } } } @@ -1414,16 +1886,62 @@ bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segme } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); } + return nullptr; +} + + +bool ImageLoaderMachO::findSection(const void* imageInterior, const char** segmentName, const char** sectionName, size_t* sectionOffset) +{ + if (const struct macho_section* sect = findSection(imageInterior)) { + const uintptr_t unslidInteriorAddress = (uintptr_t)imageInterior - this->getSlide(); + if ( segmentName != NULL ) + *segmentName = sect->segname; + if ( sectionName != NULL ) + *sectionName = sect->sectname; + if ( sectionOffset != NULL ) + *sectionOffset = unslidInteriorAddress - sect->addr; + return true; + } return false; } +const char* ImageLoaderMachO::libPath(unsigned int index) const +{ + const uint32_t cmd_count = ((macho_header*)fMachOData)->ncmds; + const struct load_command* const cmds = (struct load_command*)&fMachOData[sizeof(macho_header)]; + const struct load_command* cmd = cmds; + unsigned count = 0; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch ( cmd->cmd ) { + case LC_LOAD_DYLIB: + case LC_LOAD_WEAK_DYLIB: + case LC_REEXPORT_DYLIB: + case LC_LOAD_UPWARD_DYLIB: + if ( index == count ) { + const struct dylib_command* dylibCmd = (struct dylib_command*)cmd; + return (char*)cmd + dylibCmd->dylib.name.offset; + } + ++count; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + + // if image linked with nothing and we implicitly added libSystem.dylib, return that + if ( needsAddedLibSystemDepency(libraryCount(), (macho_header*)fMachOData) ) { + return LIBSYSTEM_DYLIB_PATH; + } + + return NULL; +} + void __attribute__((noreturn)) ImageLoaderMachO::throwSymbolNotFound(const LinkContext& context, const char* symbol, const char* referencedFrom, const char* fromVersMismatch, const char* expectedIn) { // record values for possible use by CrashReporter or Finder - (*context.setErrorStrings)(dyld_error_kind_symbol_missing, referencedFrom, expectedIn, symbol); + (*context.setErrorStrings)(DYLD_EXIT_REASON_SYMBOL_MISSING, referencedFrom, expectedIn, symbol); dyld::throwf("Symbol not found: %s\n Referenced from: %s%s\n Expected in: %s\n", symbol, referencedFrom, fromVersMismatch, expectedIn); } @@ -1452,24 +1970,29 @@ const void* ImageLoaderMachO::getEnd() const return (const void*)lastAddress; } +uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t baseVMAddress, + uintptr_t location, uintptr_t value, + uint8_t type, const char* symbolName, + intptr_t addend, const char* inPath, const char* toPath, const char* msg, + ExtraBindData *extraBindData, uintptr_t slide) +{ + auto logBind = [&]() { + if ( !context.verboseBind ) + return; + if ( addend != 0 ) { + dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n", + msg, shortName(inPath), (uintptr_t)location, + ((toPath != NULL) ? shortName(toPath) : ""), + symbolName, (uintptr_t)location, value, addend); + } else { + dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n", + msg, shortName(inPath), (uintptr_t)location, + ((toPath != NULL) ? shortName(toPath) : ""), + symbolName, (uintptr_t)location, value); + } + }; + -uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t location, uintptr_t value, - const ImageLoader* targetImage, uint8_t type, const char* symbolName, - intptr_t addend, const char* msg) -{ - // log - if ( context.verboseBind ) { - if ( addend != 0 ) - dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX + %ld\n", - msg, this->getShortName(), (uintptr_t)location, - ((targetImage != NULL) ? targetImage->getShortName() : ""), - symbolName, (uintptr_t)location, value, addend); - else - dyld::log("dyld: %sbind: %s:0x%08lX = %s:%s, *0x%08lX = 0x%08lX\n", - msg, this->getShortName(), (uintptr_t)location, - ((targetImage != NULL) ? targetImage->getShortName() : "import-missing>"), - symbolName, (uintptr_t)location, value); - } #if LOG_BINDINGS // dyld::logBindings("%s: %s\n", targetImage->getShortName(), symbolName); #endif @@ -1481,22 +2004,45 @@ uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t l uint32_t value32; switch (type) { case BIND_TYPE_POINTER: + logBind(); // test first so we don't needless dirty pages if ( *locationToFix != newValue ) *locationToFix = newValue; break; - case BIND_TYPE_TEXT_ABSOLUTE32: + case BIND_TYPE_TEXT_ABSOLUTE32: + logBind(); loc32 = (uint32_t*)locationToFix; value32 = (uint32_t)newValue; if ( *loc32 != value32 ) *loc32 = value32; break; - case BIND_TYPE_TEXT_PCREL32: + case BIND_TYPE_TEXT_PCREL32: + logBind(); loc32 = (uint32_t*)locationToFix; value32 = (uint32_t)(newValue - (((uintptr_t)locationToFix) + 4)); if ( *loc32 != value32 ) *loc32 = value32; + break; + case BIND_TYPE_THREADED_BIND: + logBind(); + // test first so we don't needless dirty pages + if ( *locationToFix != newValue ) + *locationToFix = newValue; + break; + case BIND_TYPE_THREADED_REBASE: { + // Regular pointer which needs to fit in 51-bits of value. + // C++ RTTI uses the top bit, so we'll allow the whole top-byte + // and the signed-extended bottom 43-bits to be fit in to 51-bits. + uint64_t top8Bits = *locationToFix & 0x0007F80000000000ULL; + uint64_t bottom43Bits = *locationToFix & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + newValue = (uintptr_t)(targetValue + slide); + if ( context.verboseRebase ) { + dyld::log("dyld: rebase: %s:*0x%08lX += 0x%08lX = 0x%08lX\n", shortName(inPath), (uintptr_t)locationToFix, slide, newValue); + } + *locationToFix = newValue; break; + } default: dyld::throwf("bad bind type %d", type); } @@ -1527,7 +2073,7 @@ struct DATAdyld { // These are defined in dyldStartup.s extern "C" void stub_binding_helper(); - +extern "C" int _dyld_func_lookup(const char* name, void** address); void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) { @@ -1543,18 +2089,36 @@ void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) for (uint32_t i = 0; i < cmd_count; ++i) { if ( cmd->cmd == LC_SEGMENT_COMMAND ) { const struct macho_segment_command* seg = (struct macho_segment_command*)cmd; - if ( strcmp(seg->segname, "__DATA") == 0 ) { + if ( strncmp(seg->segname, "__DATA", 6) == 0 ) { const struct macho_section* const sectionsStart = (struct macho_section*)((char*)seg + sizeof(struct macho_segment_command)); const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ( strcmp(sect->sectname, "__dyld" ) == 0 ) { struct DATAdyld* dd = (struct DATAdyld*)(sect->addr + fSlide); - #if !__arm64__ + #if !__arm64__ && !__ARM_ARCH_7K__ if ( sect->size > offsetof(DATAdyld, dyldLazyBinder) ) { if ( dd->dyldLazyBinder != (void*)&stub_binding_helper ) dd->dyldLazyBinder = (void*)&stub_binding_helper; } #endif // !__arm64__ + // Add work around for existing apps that have deprecated __dyld section + const char* installNm = this->getInstallPath(); + if ( (mh->filetype != MH_DYLIB) || (installNm == NULL) || (strcmp(installNm, "/usr/lib/system/libdyld.dylib") != 0) ) { + #if TARGET_OS_OSX + // don't allow macOS apps build with 10.14 or later SDK and targeting 10.8 or later to have a __dyld section + if ( (minOSVersion() >= 0x000a0800) && (sdkVersion() >= 0x000a0e00) ) + dyld::throwf("__dyld section not supported in %s", this->getPath()); + #endif + #if TARGET_OS_IOS || TARGET_OS_TV + // don't allow iOS apps build with 12.0 or later SDK to have a __dyld section + if ( sdkVersion() >= 0x000c0000 ) + dyld::throwf("__dyld section not supported in %s", this->getPath()); + #endif + #if TARGET_OS_WATCH + if ( sdkVersion() >= 0x00050000 ) + dyld::throwf("__dyld section not supported in %s", this->getPath()); + #endif + } if ( sect->size > offsetof(DATAdyld, dyldFuncLookup) ) { if ( dd->dyldFuncLookup != (void*)&_dyld_func_lookup ) dd->dyldFuncLookup = (void*)&_dyld_func_lookup; @@ -1574,7 +2138,7 @@ void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) // match what crt1.o supplies, then the program has a custom entry point. // This means it might be doing something that needs to be executed before // initializers are run. - if ( memcmp(this->getMain(), sStandardEntryPointInstructions, 16) != 0 ) { + if ( memcmp(this->getEntryFromLC_UNIXTHREAD(), sStandardEntryPointInstructions, 16) != 0 ) { if ( context.verboseInit ) dyld::log("dyld: program uses non-standard entry point so delaying running of initializers\n"); context.setRunInitialzersOldWay(); @@ -1616,24 +2180,24 @@ void ImageLoaderMachO::lookupProgramVars(const LinkContext& context) const vars.mh = (macho_header*)fMachOData; // lookup _NXArgc - sym = this->findExportedSymbol("_NXArgc", false, NULL); + sym = this->findShallowExportedSymbol("_NXArgc", NULL); if ( sym != NULL ) - vars.NXArgcPtr = (int*)this->getExportedSymbolAddress(sym, context, this, false); + vars.NXArgcPtr = (int*)this->getExportedSymbolAddress(sym, context, this, false, NULL); // lookup _NXArgv - sym = this->findExportedSymbol("_NXArgv", false, NULL); + sym = this->findShallowExportedSymbol("_NXArgv", NULL); if ( sym != NULL ) - vars.NXArgvPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false); + vars.NXArgvPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false, NULL); // lookup _environ - sym = this->findExportedSymbol("_environ", false, NULL); + sym = this->findShallowExportedSymbol("_environ", NULL); if ( sym != NULL ) - vars.environPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false); + vars.environPtr = (const char***)this->getExportedSymbolAddress(sym, context, this, false, NULL); // lookup __progname - sym = this->findExportedSymbol("___progname", false, NULL); + sym = this->findShallowExportedSymbol("___progname", NULL); if ( sym != NULL ) - vars.__prognamePtr = (const char**)this->getExportedSymbolAddress(sym, context, this, false); + vars.__prognamePtr = (const char**)this->getExportedSymbolAddress(sym, context, this, false, NULL); context.setNewProgramVars(vars); } @@ -1644,7 +2208,10 @@ bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const // if prebound and loaded at prebound address, and all libraries are same as when this was prebound, then no need to bind if ( ((this->isPrebindable() && (this->getSlide() == 0)) || fInSharedCache) && this->usesTwoLevelNameSpace() - && this->allDependentLibrariesAsWhenPreBound() ) { +#if !USES_CHAINED_BINDS + && this->allDependentLibrariesAsWhenPreBound() +#endif + ) { // allow environment variables to disable prebinding if ( context.bindFlat ) return false; @@ -1662,6 +2229,14 @@ bool ImageLoaderMachO::usablePrebinding(const LinkContext& context) const return false; } +static void *stripPointer(void *ptr) { +#if __has_feature(ptrauth_calls) + return __builtin_ptrauth_strip(ptr, ptrauth_key_asia); +#else + return ptr; +#endif +} + void ImageLoaderMachO::doImageInit(const LinkContext& context) { @@ -1673,13 +2248,23 @@ void ImageLoaderMachO::doImageInit(const LinkContext& context) switch (cmd->cmd) { case LC_ROUTINES_COMMAND: Initializer func = (Initializer)(((struct macho_routines_command*)cmd)->init_address + fSlide); +#if __has_feature(ptrauth_calls) + func = (Initializer)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0); +#endif // verify initializers are in image - if ( ! this->containsAddress((void*)func) ) { + if ( ! this->containsAddress(stripPointer((void*)func)) ) { dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); } + if ( ! dyld::gProcessInfo->libSystemInitialized ) { + // libSystem initializer must run first + dyld::throwf("-init function in image (%s) that does not link with libSystem.dylib\n", this->getPath()); + } if ( context.verboseInit ) dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath()); - func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + { + dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0); + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + } break; } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); @@ -1703,15 +2288,33 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) if ( type == S_MOD_INIT_FUNC_POINTERS ) { Initializer* inits = (Initializer*)(sect->addr + fSlide); const size_t count = sect->size / sizeof(uintptr_t); - for (size_t i=0; i < count; ++i) { - Initializer func = inits[i]; + // Ensure __mod_init_func section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("__mod_init_funcs section has malformed address range for %s\n", this->getPath()); + for (size_t j=0; j < count; ++j) { + Initializer func = inits[j]; // verify initializers are in image - if ( ! this->containsAddress((void*)func) ) { + if ( ! this->containsAddress(stripPointer((void*)func)) ) { dyld::throwf("initializer function %p not in mapped image for %s\n", func, this->getPath()); } + if ( ! dyld::gProcessInfo->libSystemInitialized ) { + // libSystem initializer must run first + const char* installPath = getInstallPath(); + if ( (installPath == NULL) || (strcmp(installPath, LIBSYSTEM_DYLIB_PATH) != 0) ) + dyld::throwf("initializer in image (%s) that does not link with libSystem.dylib\n", this->getPath()); + } if ( context.verboseInit ) dyld::log("dyld: calling initializer function %p in %s\n", func, this->getPath()); - func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + bool haveLibSystemHelpersBefore = (dyld::gLibSystemHelpers != NULL); + { + dyld3::ScopedTimer(DBG_DYLD_TIMING_STATIC_INITIALIZER, (uint64_t)fMachOData, (uint64_t)func, 0); + func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + } + bool haveLibSystemHelpersAfter = (dyld::gLibSystemHelpers != NULL); + if ( !haveLibSystemHelpersBefore && haveLibSystemHelpersAfter ) { + // now safe to use malloc() and other calls in libSystem.dylib + dyld::gProcessInfo->libSystemInitialized = true; + } } } } @@ -1723,9 +2326,6 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) - - - void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector& dofs) { if ( fHasDOFSections ) { @@ -1742,6 +2342,9 @@ void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector< const struct macho_section* const sectionsEnd = §ionsStart[seg->nsects]; for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { if ( (sect->flags & SECTION_TYPE) == S_DTRACE_DOF ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("DOF section has malformed address range for %s\n", this->getPath()); ImageLoader::DOFInfo info; info.dof = (void*)(sect->addr + fSlide); info.imageHeader = this->machHeader(); @@ -1797,12 +2400,18 @@ void ImageLoaderMachO::doTermination(const LinkContext& context) for (const struct macho_section* sect=sectionsStart; sect < sectionsEnd; ++sect) { const uint8_t type = sect->flags & SECTION_TYPE; if ( type == S_MOD_TERM_FUNC_POINTERS ) { + // Ensure section is within segment + if ( (sect->addr < seg->vmaddr) || (sect->addr+sect->size > seg->vmaddr+seg->vmsize) || (sect->addr+sect->size < sect->addr) ) + dyld::throwf("DOF section has malformed address range for %s\n", this->getPath()); Terminator* terms = (Terminator*)(sect->addr + fSlide); const size_t count = sect->size / sizeof(uintptr_t); - for (size_t i=count; i > 0; --i) { - Terminator func = terms[i-1]; + for (size_t j=count; j > 0; --j) { + Terminator func = terms[j-1]; +#if __has_feature(ptrauth_calls) + func = (Terminator)__builtin_ptrauth_sign_unauthenticated((void*)func, ptrauth_key_asia, 0); +#endif // verify terminators are in image - if ( ! this->containsAddress((void*)func) ) { + if ( ! this->containsAddress(stripPointer((void*)func)) ) { dyld::throwf("termination function %p not in mapped image for %s\n", func, this->getPath()); } if ( context.verboseInit ) @@ -1818,9 +2427,9 @@ void ImageLoaderMachO::doTermination(const LinkContext& context) } -void ImageLoaderMachO::printStatistics(unsigned int imageCount, const InitializerTimingList& timingInfo) +void ImageLoaderMachO::printStatisticsDetails(unsigned int imageCount, const InitializerTimingList& timingInfo) { - ImageLoader::printStatistics(imageCount, timingInfo); + ImageLoader::printStatisticsDetails(imageCount, timingInfo); dyld::log("total symbol trie searches: %d\n", fgSymbolTrieSearchs); dyld::log("total symbol table binary searches: %d\n", fgSymbolTableBinarySearchs); dyld::log("total images defining weak symbols: %u\n", fgImagesHasWeakDefinitions); @@ -1863,7 +2472,7 @@ intptr_t ImageLoaderMachO::assignSegmentAddresses(const LinkContext& context) } else if ( ! this->segmentsCanSlide() ) { for(unsigned int i=0, e=segmentCount(); i < e; ++i) { - if ( strcmp(segName(i), "__PAGEZERO") == 0 ) + if ( (strcmp(segName(i), "__PAGEZERO") == 0) && (segFileSize(i) == 0) && (segPreferredLoadAddress(i) == 0) ) continue; if ( !reserveAddressRange(segPreferredLoadAddress(i), segSize(i)) ) dyld::throwf("can't map unslidable segment %s to 0x%lX with size 0x%lX", segName(i), segPreferredLoadAddress(i), segSize(i)); @@ -1923,19 +2532,21 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF } // map in all segments for(unsigned int i=0, e=segmentCount(); i < e; ++i) { - vm_offset_t fileOffset = segFileOffset(i) + offsetInFat; + vm_offset_t fileOffset = (vm_offset_t)(segFileOffset(i) + offsetInFat); vm_size_t size = segFileSize(i); uintptr_t requestedLoadAddress = segPreferredLoadAddress(i) + slide; int protection = 0; if ( !segUnaccessible(i) ) { - // If has text-relocs, don't set x-bit initially. - // Instead set it later after text-relocs have been done. - if ( segExecutable(i) && !(segHasRebaseFixUps(i) && (slide != 0)) ) + if ( segExecutable(i) ) protection |= PROT_EXEC; if ( segReadable(i) ) protection |= PROT_READ; - if ( segWriteable(i) ) + if ( segWriteable(i) ) { protection |= PROT_WRITE; + // rdar://problem/22525618 force __LINKEDIT to always be mapped read-only + if ( strcmp(segName(i), "__LINKEDIT") == 0 ) + protection = PROT_READ; + } } #if __i386__ // initially map __IMPORT segments R/W so dyld can update them @@ -1950,8 +2561,16 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF } void* loadAddress = xmmap((void*)requestedLoadAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, fileOffset); if ( loadAddress == ((void*)(-1)) ) { - dyld::throwf("mmap() error %d at address=0x%08lX, size=0x%08lX segment=%s in Segment::map() mapping %s", - errno, requestedLoadAddress, (uintptr_t)size, segName(i), getPath()); + int mmapErr = errno; + if ( mmapErr == EPERM ) { + if ( dyld::sandboxBlockedMmap(getPath()) ) + dyld::throwf("file system sandbox blocked mmap() of '%s'", this->getPath()); + else + dyld::throwf("code signing blocked mmap() of '%s'", this->getPath()); + } + else + dyld::throwf("mmap() errno=%d at address=0x%08lX, size=0x%08lX segment=%s in Segment::map() mapping %s", + mmapErr, requestedLoadAddress, (uintptr_t)size, segName(i), getPath()); } } // update stats @@ -2017,12 +2636,13 @@ void ImageLoaderMachO::segProtect(unsigned int segIndex, const ImageLoader::Link } } +#if TEXT_RELOC_SUPPORT void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader::LinkContext& context) { vm_address_t addr = segActualLoadAddress(segIndex); vm_size_t size = segSize(segIndex); const bool setCurrentPermissions = false; - vm_prot_t protection = VM_PROT_WRITE | VM_PROT_READ; + vm_prot_t protection = VM_PROT_WRITE | VM_PROT_READ | VM_PROT_COPY; if ( segExecutable(segIndex) && !segHasRebaseFixUps(segIndex) ) protection |= VM_PROT_EXECUTE; kern_return_t r = vm_protect(mach_task_self(), addr, size, setCurrentPermissions, protection); @@ -2035,5 +2655,202 @@ void ImageLoaderMachO::segMakeWritable(unsigned int segIndex, const ImageLoader: (protection & PROT_READ) ? 'r' : '.', (protection & PROT_WRITE) ? 'w' : '.', (protection & PROT_EXEC) ? 'x' : '.' ); } } +#endif +const char* ImageLoaderMachO::findClosestSymbol(const mach_header* mh, const void* addr, const void** closestAddr) +{ + // called by dladdr() + // only works with compressed LINKEDIT if classic symbol table is also present + const dysymtab_command* dynSymbolTable = NULL; + const symtab_command* symtab = NULL; + const macho_segment_command* seg; + const uint8_t* unslidLinkEditBase = NULL; + bool linkEditBaseFound = false; + intptr_t slide = 0; + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_SEGMENT_COMMAND: + seg = (macho_segment_command*)cmd; + if ( strcmp(seg->segname, "__LINKEDIT") == 0 ) { + unslidLinkEditBase = (uint8_t*)(seg->vmaddr - seg->fileoff); + linkEditBaseFound = true; + } + else if ( strcmp(seg->segname, "__TEXT") == 0 ) { + slide = (uintptr_t)mh - seg->vmaddr; + } + break; + case LC_SYMTAB: + symtab = (symtab_command*)cmd; + break; + case LC_DYSYMTAB: + dynSymbolTable = (dysymtab_command*)cmd; + break; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + // no symbol table => no lookup by address + if ( (symtab == NULL) || (dynSymbolTable == NULL) || !linkEditBaseFound ) + return NULL; + + const uint8_t* linkEditBase = unslidLinkEditBase + slide; + const char* symbolTableStrings = (const char*)&linkEditBase[symtab->stroff]; + const macho_nlist* symbolTable = (macho_nlist*)(&linkEditBase[symtab->symoff]); + + uintptr_t targetAddress = (uintptr_t)addr - slide; + const struct macho_nlist* bestSymbol = NULL; + // first walk all global symbols + const struct macho_nlist* const globalsStart = &symbolTable[dynSymbolTable->iextdefsym]; + const struct macho_nlist* const globalsEnd= &globalsStart[dynSymbolTable->nextdefsym]; + for (const struct macho_nlist* s = globalsStart; s < globalsEnd; ++s) { + if ( (s->n_type & N_TYPE) == N_SECT ) { + if ( bestSymbol == NULL ) { + if ( s->n_value <= targetAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + // next walk all local symbols + const struct macho_nlist* const localsStart = &symbolTable[dynSymbolTable->ilocalsym]; + const struct macho_nlist* const localsEnd= &localsStart[dynSymbolTable->nlocalsym]; + for (const struct macho_nlist* s = localsStart; s < localsEnd; ++s) { + if ( ((s->n_type & N_TYPE) == N_SECT) && ((s->n_type & N_STAB) == 0) ) { + if ( bestSymbol == NULL ) { + if ( s->n_value <= targetAddress ) + bestSymbol = s; + } + else if ( (s->n_value <= targetAddress) && (bestSymbol->n_value < s->n_value) ) { + bestSymbol = s; + } + } + } + if ( bestSymbol != NULL ) { +#if __arm__ + if (bestSymbol->n_desc & N_ARM_THUMB_DEF) + *closestAddr = (void*)((bestSymbol->n_value | 1) + slide); + else + *closestAddr = (void*)(bestSymbol->n_value + slide); +#else + *closestAddr = (void*)(bestSymbol->n_value + slide); +#endif + return &symbolTableStrings[bestSymbol->n_un.n_strx]; + } + return NULL; +} + +bool ImageLoaderMachO::getLazyBindingInfo(uint32_t& lazyBindingInfoOffset, const uint8_t* lazyInfoStart, const uint8_t* lazyInfoEnd, + uint8_t* segIndex, uintptr_t* segOffset, int* ordinal, const char** symbolName, bool* doneAfterBind) +{ + if ( lazyBindingInfoOffset > (lazyInfoEnd-lazyInfoStart) ) + return false; + uint8_t type = BIND_TYPE_POINTER; + uint8_t symboFlags = 0; + bool done = false; + const uint8_t* p = &lazyInfoStart[lazyBindingInfoOffset]; + while ( !done && (p < lazyInfoEnd) ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + *doneAfterBind = false; + return true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + *ordinal = immediate; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + *ordinal = (int)read_uleb128(p, lazyInfoEnd); + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + // the special ordinals are negative numbers + if ( immediate == 0 ) + *ordinal = 0; + else { + int8_t signExtended = BIND_OPCODE_MASK | immediate; + *ordinal = signExtended; + } + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + *symbolName = (char*)p; + symboFlags = immediate; + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + *segIndex = immediate; + *segOffset = read_uleb128(p, lazyInfoEnd); + break; + case BIND_OPCODE_DO_BIND: + *doneAfterBind = ((*p & BIND_OPCODE_MASK) == BIND_OPCODE_DONE); + lazyBindingInfoOffset += p - &lazyInfoStart[lazyBindingInfoOffset]; + return true; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + case BIND_OPCODE_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + default: + return false; + } + } + return false; +} + +const dyld_info_command* ImageLoaderMachO::findDyldInfoLoadCommand(const mach_header* mh) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + switch (cmd->cmd) { + case LC_DYLD_INFO: + case LC_DYLD_INFO_ONLY: + return (dyld_info_command*)cmd; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + return NULL; +} + + +uintptr_t ImageLoaderMachO::segPreferredAddress(const mach_header* mh, unsigned segIndex) +{ + const uint32_t cmd_count = mh->ncmds; + const load_command* const cmds = (load_command*)((char*)mh + sizeof(macho_header)); + const load_command* cmd = cmds; + unsigned curSegIndex = 0; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd == LC_SEGMENT_COMMAND ) { + if ( segIndex == curSegIndex ) { + const macho_segment_command* segCmd = (macho_segment_command*)cmd; + return segCmd->vmaddr; + } + ++curSegIndex; + } + cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); + } + return 0; +} + + + +uintptr_t ImageLoaderMachO::imageBaseAddress() const { + //printf("imageBaseAddress: %s %d->%d\n", getPath(), 0, segmentCount()); + for (unsigned int i = 0, e = segmentCount(); i != e; ++i) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) + return segPreferredLoadAddress(i); + } + return 0; +}