X-Git-Url: https://git.saurik.com/apple/dyld.git/blobdiff_plain/832b6fce7c321434378950ecd081b6c34cc3a24f..ba4c3badc27ea8c4637b4e91a49725742a02a53c:/src/ImageLoaderMachO.cpp?ds=sidebyside diff --git a/src/ImageLoaderMachO.cpp b/src/ImageLoaderMachO.cpp index 3a8ad7d..f63769b 100644 --- a/src/ImageLoaderMachO.cpp +++ b/src/ImageLoaderMachO.cpp @@ -40,14 +40,24 @@ #include #include #include +#include #include #include #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; @@ -56,30 +66,77 @@ 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 #define LC_ROUTINES_COMMAND LC_ROUTINES_64 + #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT struct macho_segment_command : public segment_command_64 {}; struct macho_section : public section_64 {}; struct macho_routines_command : public routines_command_64 {}; #else #define LC_SEGMENT_COMMAND LC_SEGMENT #define LC_ROUTINES_COMMAND LC_ROUTINES + #define LC_SEGMENT_COMMAND_WRONG LC_SEGMENT_64 struct macho_segment_command : public segment_command {}; struct macho_section : public section {}; struct macho_routines_command : public routines_command {}; #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), @@ -88,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); @@ -103,7 +160,7 @@ ImageLoaderMachO::ImageLoaderMachO(const macho_header* mh, const char* path, uns // ignore zero-sized segments if ( segCmd->vmsize != 0 ) { // record offset of load command - segOffsets[segIndex++] = (uint8_t*)segCmd - fMachOData; + segOffsets[segIndex++] = (uint32_t)((uint8_t*)segCmd - fMachOData); } } cmd = (const struct load_command*)(((char*)cmd)+cmd->cmdsize); @@ -111,73 +168,392 @@ 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, - unsigned int* segCount, unsigned int* libCount, - const linkedit_data_command** codeSigCmd) +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 encryption_info_command** encryptCmd) { *compressed = false; *segCount = 0; *libCount = 0; *codeSigCmd = NULL; - struct macho_segment_command* segCmd; -#if CODESIGNING_SUPPORT - bool foundLoadCommandSegment = false; -#endif + *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); + } + const struct load_command* const nextCmd = (const struct load_command*)(((char*)cmd)+cmdLength); + if ( (nextCmd > endCmds) || (nextCmd < cmd) ) { + dyld::throwf("malformed mach-o image: load command #%d length (%u) would exceed sizeofcmds (%u) in %s", + i, cmdLength, mh->sizeofcmds, path); + } 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; -#if CODESIGNING_SUPPORT - // all load commands must be in an executable segment - if ( (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); - foundLoadCommandSegment = true; + 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; } -#endif + 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: + dyld::throwf("malformed mach-o image: wrong LC_SEGMENT[_64] for architecture"); break; case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: 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 } - uint32_t cmdLength = cmd->cmdsize; - cmd = (const struct load_command*)(((char*)cmd)+cmdLength); - if ( (cmd > endCmds) || (cmd < startCmds) ) { - dyld::throwf("malformed mach-o image: load command #%d length (%u) would exceed sizeofcmds (%u) in %s", - i, cmdLength, mh->sizeofcmds, path); - } + cmd = nextCmd; } - -#if CODESIGNING_SUPPORT - if ( ! foundLoadCommandSegment ) + + if ( context.strictMachORequired && !foundLoadCommandSegment ) throw "load commands not in a segment"; -#endif - + 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) { + 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); + } + cmd2 = (const struct load_command*)(((char*)cmd2)+cmd2->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); @@ -197,40 +573,39 @@ ImageLoader* ImageLoaderMachO::instantiateMainExecutable(const macho_header* mh, unsigned int segCount; unsigned int libCount; const linkedit_data_command* codeSigCmd; - sniffLoadCommands(mh, path, &compressed, &segCount, &libCount, &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); else +#if SUPPORT_CLASSIC_MACHO return ImageLoaderMachOClassic::instantiateMainExecutable(mh, slide, path, segCount, libCount, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif } // 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, &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 - return ImageLoaderMachOClassic::instantiateFromFile(path, fd, fileData, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); +#if SUPPORT_CLASSIC_MACHO + return ImageLoaderMachOClassic::instantiateFromFile(path, fd, firstPages, firstPagesSize, offsetInFat, lenInFat, info, segCount, libCount, codeSigCmd, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif } // create image by using cached mach-o file @@ -241,12 +616,17 @@ 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, &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); else +#if SUPPORT_CLASSIC_MACHO return ImageLoaderMachOClassic::instantiateFromCache(mh, path, slide, info, segCount, libCount, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif } // create image by copying an in-memory mach-o file @@ -256,26 +636,52 @@ ImageLoader* ImageLoaderMachO::instantiateFromMemory(const char* moduleName, con unsigned int segCount; unsigned int libCount; const linkedit_data_command* sigcmd; - sniffLoadCommands(mh, moduleName, &compressed, &segCount, &libCount, &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); else +#if SUPPORT_CLASSIC_MACHO return ImageLoaderMachOClassic::instantiateFromMemory(moduleName, mh, len, segCount, libCount, context); +#else + throw "missing LC_DYLD_INFO load command"; +#endif } +int ImageLoaderMachO::crashIfInvalidCodeSignature() +{ + // Now that segments are mapped in, try reading from first executable segment. + // If code signing is enabled the kernel will validate the code signature + // when paging in, and kill the process if invalid. + for(unsigned int i=0; i < fSegmentsCount; ++i) { + if ( (segFileOffset(i) == 0) && (segFileSize(i) != 0) ) { + // return read value to ensure compiler does not optimize away load + int* p = (int*)segActualLoadAddress(i); + return *p; + } + } + return 0; +} -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 ( strcmp(segName(i),"__TEXT") == 0 ) { + if ( segExecutable(i) ) { if ( segHasRebaseFixUps(i) && (fSlide != 0) ) fTextSegmentRebases = true; if ( segHasBindFixUps(i) ) @@ -346,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) { @@ -357,9 +770,35 @@ void ImageLoaderMachO::parseLoadCmds() else if ( type == S_DTRACE_DOF ) fHasDOFSections = true; else if ( isTextSeg && (strcmp(sect->sectname, "__eh_frame") == 0) ) - fEHFrameSectionOffset = (uint8_t*)sect - fMachOData; + fEHFrameSectionOffset = (uint32_t)((uint8_t*)sect - fMachOData); else if ( isTextSeg && (strcmp(sect->sectname, "__unwind_info") == 0) ) - fUnwindInfoSectionOffset = (uint8_t*)sect - fMachOData;; + 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; @@ -368,7 +807,7 @@ void ImageLoaderMachO::parseLoadCmds() break; case LC_ID_DYLIB: { - fDylibIDOffset = (uint8_t*)cmd - fMachOData; + fDylibIDOffset = (uint32_t)((uint8_t*)cmd - fMachOData); } break; case LC_RPATH: @@ -380,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: @@ -520,6 +961,7 @@ uintptr_t ImageLoaderMachO::segActualEndAddress(unsigned int segIndex) const bool ImageLoaderMachO::segHasRebaseFixUps(unsigned int segIndex) const { +#if TEXT_RELOC_SUPPORT // scan sections for fix-up bit const macho_segment_command* segCmd = segLoadCommand(segIndex); const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command)); @@ -528,11 +970,13 @@ bool ImageLoaderMachO::segHasRebaseFixUps(unsigned int segIndex) const if ( (sect->flags & S_ATTR_LOC_RELOC) != 0 ) return true; } +#endif return false; } bool ImageLoaderMachO::segHasBindFixUps(unsigned int segIndex) const { +#if TEXT_RELOC_SUPPORT // scan sections for fix-up bit const macho_segment_command* segCmd = segLoadCommand(segIndex); const struct macho_section* const sectionsStart = (struct macho_section*)((char*)segCmd + sizeof(struct macho_segment_command)); @@ -541,6 +985,7 @@ bool ImageLoaderMachO::segHasBindFixUps(unsigned int segIndex) const if ( (sect->flags & S_ATTR_EXT_RELOC) != 0 ) return true; } +#endif return false; } @@ -563,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 { @@ -590,7 +1035,7 @@ void ImageLoaderMachO::preFetchDATA(int fd, uint64_t offsetInFat, const LinkCont // prefetch writable segment that have mmap'ed regions radvisory advice; advice.ra_offset = offsetInFat + segFileOffset(i); - advice.ra_count = segFileSize(i); + advice.ra_count = (int)segFileSize(i); // limit prefetch to 1MB (256 pages) if ( advice.ra_count > 1024*1024 ) advice.ra_count = 1024*1024; @@ -689,19 +1134,91 @@ void ImageLoaderMachO::setSlide(intptr_t slide) fSlide = slide; } -#if CODESIGNING_SUPPORT -void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile) +void ImageLoaderMachO::loadCodeSignature(const struct linkedit_data_command* codeSigCmd, int fd, uint64_t offsetInFatFile, const LinkContext& context) { - fsignatures_t siginfo; - siginfo.fs_file_start=offsetInFatFile; // start of mach-o slice in fat file - siginfo.fs_blob_start=(void*)(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); - if ( result == -1 ) - dyld::log("dyld: F_ADDFILESIGS failed for %s with errno=%d\n", this->getPath(), errno); - //dyld::log("dyld: registered code signature for %s\n", this->getPath()); + dyld3::ScopedTimer(DBG_DYLD_TIMING_ATTACH_CODESIGNATURE, 0, 0, 0); + // if dylib being loaded has no code signature load command + 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_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_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 ) + 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::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); + } +} const char* ImageLoaderMachO::getInstallPath() const @@ -713,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; }; @@ -729,15 +1246,23 @@ 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 unsigned int count = sect->size / sizeof(InterposeData); - for (uint32_t i=0; i < count; ++i) { + const size_t count = sect->size / sizeof(InterposeData); + for (size_t j=0; j < count; ++j) { ImageLoader::InterposeTuple tuple; - tuple.replacement = interposeArray[i].replacement; - tuple.replacementImage = this; - tuple.replacee = interposeArray[i].replacee; + tuple.replacement = interposeArray[j].replacement; + tuple.neverImage = this; + tuple.onlyImage = NULL; + 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 for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { if ( it->replacee == tuple.replacee ) { tuple.replacee = it->replacement; @@ -755,7 +1280,66 @@ void ImageLoaderMachO::registerInterposing() } } -void* ImageLoaderMachO::getThreadPC() const +uint32_t ImageLoaderMachO::sdkVersion(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->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); + } + return 0; +} + +uint32_t ImageLoaderMachO::minOSVersion() const +{ + return ImageLoaderMachO::minOSVersion(machHeader()); +} + + +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)]; @@ -776,33 +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); - #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); } @@ -851,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; @@ -889,7 +1473,7 @@ void ImageLoaderMachO::doGetDependentLibraries(DependentLibraryInfo libs[]) } } -ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo() +ImageLoader::LibraryInfo ImageLoaderMachO::doGetLibraryInfo(const LibraryInfo&) { LibraryInfo info; if ( fDylibIDOffset != 0 ) { @@ -916,8 +1500,8 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorpath.offset; - if ( strncmp(path, "@loader_path/", 13) == 0 ) { - if ( context.processIsRestricted && (context.mainExecutable == this) ) { + if ( (strncmp(path, "@loader_path", 12) == 0) && ((path[12] == '/') || (path[12] == '\0')) ) { + 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; } @@ -926,15 +1510,14 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorgetPath()); break; } @@ -943,25 +1526,25 @@ 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 bool found = false; for(const char** rp = context.rootPaths; *rp != NULL; ++rp) { char newPath[PATH_MAX]; - strcpy(newPath, *rp); - strcat(newPath, path); + strlcpy(newPath, *rp, PATH_MAX); + strlcat(newPath, path, PATH_MAX); struct stat stat_buf; if ( stat(newPath, &stat_buf) != -1 ) { //dyld::log("combined DYLD_ROOT_PATH and LC_RPATH: %s\n", newPath); @@ -975,6 +1558,7 @@ void ImageLoaderMachO::getRPaths(const LinkContext& context, std::vectorncmds; @@ -1007,6 +1592,14 @@ bool ImageLoaderMachO::getUUID(uuid_t uuid) const void ImageLoaderMachO::doRebase(const LinkContext& context) { + // 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 @@ -1053,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 @@ -1066,29 +1659,28 @@ void ImageLoaderMachO::doRebase(const LinkContext& context) #if TEXT_RELOC_SUPPORT void ImageLoaderMachO::makeTextSegmentWritable(const LinkContext& context, bool writeable) { - int textSegmentIndex = 0; for(unsigned int i=0; i < fSegmentsCount; ++i) { - if ( strcmp(segName(i), "__TEXT") == 0 ) { - textSegmentIndex = i; - break; + if ( segExecutable(i) ) { + if ( writeable ) { + segMakeWritable(i, context); + } + else { + #if !__i386__ && !__x86_64__ + // some processors require range to be invalidated before it is made executable + sys_icache_invalidate((void*)segActualLoadAddress(i), segSize(textSegmentIndex)); + #endif + segProtect(i, context); + } } } - if ( writeable ) { - segMakeWritable(textSegmentIndex, context); - } - else { - // iPhoneOS requires range to be invalidated before it is made executable - sys_icache_invalidate((void*)segActualLoadAddress(textSegmentIndex), segSize(textSegmentIndex)); - segProtect(textSegmentIndex, context); - } } #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; @@ -1097,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; } @@ -1112,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); } @@ -1120,18 +1713,9 @@ uintptr_t ImageLoaderMachO::getExportedSymbolAddress(const Symbol* sym, const Li uintptr_t ImageLoaderMachO::getSymbolAddress(const Symbol* sym, const ImageLoader* requestor, const LinkContext& context, bool runResolver) const { - uintptr_t result = exportedSymbolAddress(context, sym, runResolver); + uintptr_t result = exportedSymbolAddress(context, sym, requestor, runResolver); // check for interposing overrides - for (std::vector::iterator it=fgInterposingTuples.begin(); it != fgInterposingTuples.end(); it++) { - // replace all references to 'replacee' with 'replacement' - if ( (result == it->replacee) && (requestor != it->replacementImage) ) { - if ( context.verboseInterposing ) { - dyld::log("dyld interposing: replace 0x%lX with 0x%lX in %s\n", - it->replacee, it->replacement, this->getPath()); - } - result = it->replacement; - } - } + result = interposedAddress(context, result, requestor); return result; } @@ -1233,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)]; @@ -1250,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; } } } @@ -1265,16 +1886,64 @@ 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* expectedIn) + 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); - dyld::throwf("Symbol not found: %s\n Referenced from: %s\n Expected in: %s\n", symbol, referencedFrom, expectedIn); + (*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); } const mach_header* ImageLoaderMachO::machHeader() const @@ -1301,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 @@ -1330,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); + 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); } @@ -1368,7 +2065,7 @@ uintptr_t ImageLoaderMachO::bindLocation(const LinkContext& context, uintptr_t l #endif struct DATAdyld { - void* dyldLazyBinder; // filled in at launch by dyld to point into dyld to &stub_binding_helper_interface + void* dyldLazyBinder; // filled in at launch by dyld to point into dyld to &stub_binding_helper void* dyldFuncLookup; // filled in at launch by dyld to point into dyld to &_dyld_func_lookup // the following only exist in main executables built for 10.5 or later ProgramVars vars; @@ -1376,8 +2073,7 @@ struct DATAdyld { // These are defined in dyldStartup.s extern "C" void stub_binding_helper(); -extern "C" bool dyld_func_lookup(const char* name, uintptr_t* address); - +extern "C" int _dyld_func_lookup(const char* name, void** address); void ImageLoaderMachO::setupLazyPointerHandler(const LinkContext& context) { @@ -1393,19 +2089,39 @@ 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__ && !__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; + if ( dd->dyldFuncLookup != (void*)&_dyld_func_lookup ) + dd->dyldFuncLookup = (void*)&_dyld_func_lookup; } if ( mh->filetype == MH_EXECUTE ) { // there are two ways to get the program variables @@ -1422,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(); @@ -1464,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); } @@ -1492,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; @@ -1510,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) { @@ -1521,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 0x%p in %s\n", func, this->getPath()); - func(context.argc, context.argv, context.envp, context.apple, &context.programVars); + dyld::log("dyld: calling -init function %p in %s\n", func, this->getPath()); + { + 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); @@ -1550,16 +2287,34 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) const uint8_t type = sect->flags & SECTION_TYPE; if ( type == S_MOD_INIT_FUNC_POINTERS ) { Initializer* inits = (Initializer*)(sect->addr + fSlide); - const uint32_t count = sect->size / sizeof(uintptr_t); - for (uint32_t i=0; i < count; ++i) { - Initializer func = inits[i]; + const size_t count = sect->size / sizeof(uintptr_t); + // 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; + } } } } @@ -1571,9 +2326,6 @@ void ImageLoaderMachO::doModInitFunctions(const LinkContext& context) - - - void ImageLoaderMachO::doGetDOFSections(const LinkContext& context, std::vector& dofs) { if ( fHasDOFSections ) { @@ -1590,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(); @@ -1645,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 uint32_t count = sect->size / sizeof(uintptr_t); - for (uint32_t i=count; i > 0; --i) { - Terminator func = terms[i-1]; + const size_t count = sect->size / sizeof(uintptr_t); + 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 ) @@ -1666,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); @@ -1688,7 +2449,13 @@ intptr_t ImageLoaderMachO::assignSegmentAddresses(const LinkContext& context) uintptr_t highAddr = 0; for(unsigned int i=0, e=segmentCount(); i < e; ++i) { const uintptr_t segLow = segPreferredLoadAddress(i); - const uintptr_t segHigh = (segLow + segSize(i) + 4095) & -4096; + const uintptr_t segHigh = dyld_page_round(segLow + segSize(i)); + if ( segLow < highAddr ) { + if ( dyld_page_size > 4096 ) + dyld::throwf("can't map segments into 16KB pages"); + else + dyld::throwf("overlapping segments"); + } if ( segLow < lowAddr ) lowAddr = segLow; if ( segHigh > highAddr ) @@ -1705,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)); @@ -1725,16 +2492,16 @@ uintptr_t ImageLoaderMachO::reserveAnAddressRange(size_t length, const ImageLoad // in PIE programs, load initial dylibs after main executable so they don't have fixed addresses either if ( fgNextPIEDylibAddress != 0 ) { // add small (0-3 pages) random padding between dylibs - addr = fgNextPIEDylibAddress + (__stack_chk_guard/fgNextPIEDylibAddress & (sizeof(long)-1))*4096; + addr = fgNextPIEDylibAddress + (__stack_chk_guard/fgNextPIEDylibAddress & (sizeof(long)-1))*dyld_page_size; //dyld::log("padding 0x%08llX, guard=0x%08llX\n", (long long)(addr - fgNextPIEDylibAddress), (long long)(__stack_chk_guard)); - kern_return_t r = vm_allocate(mach_task_self(), &addr, size, VM_FLAGS_FIXED); + kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_FIXED | VM_MAKE_TAG(VM_MEMORY_DYLIB)); if ( r == KERN_SUCCESS ) { fgNextPIEDylibAddress = addr + size; return addr; } fgNextPIEDylibAddress = 0; } - kern_return_t r = vm_allocate(mach_task_self(), &addr, size, VM_FLAGS_ANYWHERE); + kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_ANYWHERE | VM_MAKE_TAG(VM_MEMORY_DYLIB)); if ( r != KERN_SUCCESS ) throw "out of address space"; @@ -1745,7 +2512,7 @@ bool ImageLoaderMachO::reserveAddressRange(uintptr_t start, size_t length) { vm_address_t addr = start; vm_size_t size = length; - kern_return_t r = vm_allocate(mach_task_self(), &addr, size, false /*only this range*/); + kern_return_t r = vm_alloc(&addr, size, VM_FLAGS_FIXED | VM_MAKE_TAG(VM_MEMORY_DYLIB)); if ( r != KERN_SUCCESS ) return false; return true; @@ -1757,24 +2524,29 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF { // find address range for image intptr_t slide = this->assignSegmentAddresses(context); - if ( context.verboseMapping ) - dyld::log("dyld: Mapping %s\n", this->getPath()); + if ( context.verboseMapping ) { + if ( offsetInFat != 0 ) + dyld::log("dyld: Mapping %s (slice offset=%llu)\n", this->getPath(), (unsigned long long)offsetInFat); + else + dyld::log("dyld: Mapping %s\n", this->getPath()); + } // 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. - // The iPhone OS does not like it when you make executable code writable. - 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 @@ -1787,10 +2559,18 @@ void ImageLoaderMachO::mapSegments(int fd, uint64_t offsetInFat, uint64_t lenInF dyld::throwf("truncated mach-o error: segment %s extends to %llu which is past end of file %llu", segName(i), (uint64_t)(fileOffset+size), fileLen); } - void* loadAddress = mmap((void*)requestedLoadAddress, size, protection, MAP_FIXED | MAP_PRIVATE, fd, fileOffset); + 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 @@ -1856,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); @@ -1874,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; +}