X-Git-Url: https://git.saurik.com/apple/ld64.git/blobdiff_plain/f410558f5d60087e4c310119a1751b437121c3b9..HEAD:/src/other/machochecker.cpp diff --git a/src/other/machochecker.cpp b/src/other/machochecker.cpp index ef6ba48..8e95458 100644 --- a/src/other/machochecker.cpp +++ b/src/other/machochecker.cpp @@ -134,6 +134,7 @@ private: uint8_t loadCommandSizeMask(); void checkSymbolTable(); void checkInitTerms(); + void checkThreadedRebaseBind(); void checkIndirectSymbolTable(); void checkRelocations(); void checkExternalReloation(const macho_relocation_info

* reloc); @@ -142,12 +143,13 @@ private: void verifyInstallName(); void verifyNoRpaths(); void verifyNoFlatLookups(); + void verifyiOSMac(); pint_t relocBase(); bool addressInWritableSegment(pint_t address); bool hasTextRelocInRange(pint_t start, pint_t end); pint_t segStartAddress(uint8_t segIndex); - bool addressIsRebaseSite(pint_t addr); + bool addressIsRebaseSite(pint_t addr, pint_t& pointeeAddr); bool addressIsBindingSite(pint_t addr); pint_t getInitialStackPointer(const macho_thread_command

*); pint_t getEntryPoint(const macho_thread_command

*); @@ -173,6 +175,8 @@ private: bool fWriteableSegmentWithAddrOver4G; bool fSlidableImage; bool fHasLC_RPATH; + bool fIsDebugVariant; + uint64_t fBaseAddress = 0; const macho_segment_command

* fFirstSegment; const macho_segment_command

* fFirstWritableSegment; const macho_segment_command

* fTEXTSegment; @@ -356,7 +360,7 @@ template MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path, const char* verifierDstRoot) : fHeader(NULL), fLength(fileLength), fInstallName(NULL), fStrings(NULL), fSymbols(NULL), fSymbolCount(0), fDynamicSymbolTable(NULL), fIndirectTableCount(0), fLocalRelocations(NULL), fLocalRelocationsCount(0), fExternalRelocations(NULL), fExternalRelocationsCount(0), - fWriteableSegmentWithAddrOver4G(false), fSlidableImage(false), fHasLC_RPATH(false), fFirstSegment(NULL), fFirstWritableSegment(NULL), + fWriteableSegmentWithAddrOver4G(false), fSlidableImage(false), fHasLC_RPATH(false), fIsDebugVariant(false), fFirstSegment(NULL), fFirstWritableSegment(NULL), fTEXTSegment(NULL), fDyldInfo(NULL), fSectionCount(0) { // sanity check @@ -381,6 +385,8 @@ MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, c checkInitTerms(); + checkThreadedRebaseBind(); + if ( verifierDstRoot != NULL ) verify(); } @@ -578,6 +584,8 @@ void MachOChecker::checkLoadCommands() if ( segCmd->vmaddr() > 0x100000000ULL ) fWriteableSegmentWithAddrOver4G = true; } + if ( (segCmd->fileoff() == 0) && (segCmd->filesize() != 0) ) + fBaseAddress = segCmd->vmaddr(); // check section ranges const macho_section

* const sectionsStart = (macho_section

*)((char*)segCmd + sizeof(macho_segment_command

)); @@ -848,19 +856,35 @@ void MachOChecker::checkSection(const macho_segment_command

* segCmd, const // more section tests here } +static bool endsWith(const char* str, const char* suffix) +{ + size_t suffixLen = strlen(suffix); + size_t strLen = strlen(str); + if ( strLen > suffixLen ) { + return (strcmp(&str[strLen-suffixLen], suffix) == 0); + } + return false; +} template void MachOChecker::verify() { - bool sharedCacheCandidate = false; - if ( fInstallName != NULL ) { - if ( (strncmp(fInstallName, "/usr/lib/", 9) == 0) || (strncmp(fInstallName, "/System/Library/", 16) == 0) ) { - sharedCacheCandidate = true; - verifyInstallName(); - verifyNoRpaths(); + if ( endsWith(fPath, "_asan.dylib") || endsWith(fPath, "_asan") || endsWith(fPath, "_debug.dylib") || endsWith(fPath, "_debug") ) + fIsDebugVariant = true; + + if ( (fDstRoot != NULL) && (strlen(fDstRoot) < strlen(fPath)) ) { + const char* installLocationInDstRoot = &fPath[strlen(fDstRoot)]; + if ( installLocationInDstRoot[0] != '/' ) + --installLocationInDstRoot; + if ( (strncmp(installLocationInDstRoot, "/usr/lib/", 9) == 0) || (strncmp(installLocationInDstRoot, "/System/Library/", 16) == 0) ) { + if ( !fIsDebugVariant && (strstr(fPath, ".app/") == NULL) ) { + verifyInstallName(); + verifyNoRpaths(); + } } } verifyNoFlatLookups(); + verifyiOSMac(); } @@ -929,6 +953,41 @@ void MachOChecker::verifyNoFlatLookups() } } +template +void MachOChecker::verifyiOSMac() +{ + const char* fileLocationWithinDstRoot = &fPath[strlen(fDstRoot)]; + if ( strncmp(fileLocationWithinDstRoot, "/System/iOSSupport/", 19) == 0 ) { + // everything in /System/iOSSupport/ should be iOSMac only + bool bad = false; + const uint32_t cmd_count = fHeader->ncmds(); + const macho_load_command

* cmd = (macho_load_command

*)((uint8_t*)fHeader + sizeof(macho_header

)); + for (uint32_t i = 0; i < cmd_count; ++i) { + const macho_build_version_command

* buildVersCmd; + switch ( cmd->cmd() ) { + case LC_VERSION_MIN_MACOSX: + case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: + bad = true; + break; + case LC_BUILD_VERSION: + buildVersCmd = (macho_build_version_command

*)cmd; + if ( buildVersCmd->platform() != PLATFORM_IOSMAC ) + bad = true; + break; + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + if ( bad ) + printf("macos_in_ios_support\twarn\tnon-iOSMac in /System/iOSSupport/ in arch %s\n", archName()); + } + else { + // maybe someday warn about iOSMac only stuff not in /System/iOSSupport/ + } +} + + template void MachOChecker::checkIndirectSymbolTable() { @@ -1084,23 +1143,28 @@ void MachOChecker::checkInitTerms() throwf("%s section size is not an even multiple of element size", sect->sectname()); if ( (sect->addr() % sizeof(pint_t)) != 0 ) throwf("%s section size is not pointer size aligned", sect->sectname()); - // check each pointer in array points within TEXT arrayStart = (pint_t*)((char*)fHeader + sect->offset()); arrayEnd = (pint_t*)((char*)fHeader + sect->offset() + sect->size()); - for (pint_t* p=arrayStart; p < arrayEnd; ++p) { - pint_t pointer = P::getP(*p); - if ( (pointer < fTEXTSegment->vmaddr()) || (pointer >= (fTEXTSegment->vmaddr()+fTEXTSegment->vmsize())) ) - throwf("%s 0x%08llX points outside __TEXT segment", kind, (long long)pointer); - } // check each pointer in array will be rebased and not bound if ( fSlidableImage ) { pint_t sectionBeginAddr = sect->addr(); pint_t sectionEndddr = sect->addr() + sect->size(); - for(pint_t addr = sectionBeginAddr; addr < sectionEndddr; addr += sizeof(pint_t)) { + for(pint_t addr = sectionBeginAddr, *p = arrayStart; addr < sectionEndddr; addr += sizeof(pint_t), ++p) { if ( addressIsBindingSite(addr) ) throwf("%s at 0x%0llX has binding to external symbol", kind, (long long)addr); - if ( ! addressIsRebaseSite(addr) ) + pint_t pointer = P::getP(*p); + if ( ! addressIsRebaseSite(addr, pointer) ) throwf("%s at 0x%0llX is not rebased", kind, (long long)addr); + // check each pointer in array points within TEXT + if ( (pointer < fTEXTSegment->vmaddr()) || (pointer >= (fTEXTSegment->vmaddr()+fTEXTSegment->vmsize())) ) + throwf("%s 0x%08llX points outside __TEXT segment", kind, (long long)pointer); + } + } else { + // check each pointer in array points within TEXT + for (pint_t* p=arrayStart; p < arrayEnd; ++p) { + pint_t pointer = P::getP(*p); + if ( (pointer < fTEXTSegment->vmaddr()) || (pointer >= (fTEXTSegment->vmaddr()+fTEXTSegment->vmsize())) ) + throwf("%s 0x%08llX 3 points outside __TEXT segment", kind, (long long)pointer); } } break; @@ -1112,6 +1176,131 @@ void MachOChecker::checkInitTerms() } +template +void MachOChecker::checkThreadedRebaseBind() +{ + // look bind info + if ( fDyldInfo != NULL ) { + const uint8_t* p = (uint8_t*)fHeader + fDyldInfo->bind_off(); + const uint8_t* end = &p[fDyldInfo->bind_size()]; + + uint8_t type = 0; + uint64_t segOffset = 0; + uint32_t count; + uint32_t skip; + const char* symbolName = NULL; + int libraryOrdinal = 0; + int segIndex; + int64_t addend = 0; + pint_t segStartAddr = 0; + uint64_t ordinalTableSize = 0; + bool useThreadedRebaseBind = false; + bool done = false; + while ( !done && (p < end) ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + done = true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + libraryOrdinal = immediate; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + libraryOrdinal = read_uleb128(p, end); + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + // the special ordinals are negative numbers + if ( immediate == 0 ) + libraryOrdinal = 0; + else { + int8_t signExtended = BIND_OPCODE_MASK | immediate; + libraryOrdinal = signExtended; + } + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + symbolName = (char*)p; + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + addend = read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segStartAddr = segStartAddress(segIndex); + segOffset = read_uleb128(p, end); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + segOffset += sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + segOffset += immediate*sizeof(pint_t) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + segOffset += skip + sizeof(pint_t); + } + break; + case BIND_OPCODE_THREADED: + // Note the immediate is a sub opcode + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + ordinalTableSize = read_uleb128(p, end); + useThreadedRebaseBind = true; + break; + case BIND_SUBOPCODE_THREADED_APPLY: { + if ( !useThreadedRebaseBind ) { + throwf("BIND_SUBOPCODE_THREADED_APPLY without ordinal table"); + } + uint64_t delta = 0; + do { + const uint8_t* pointerLocation = (uint8_t*)fHeader + fSegments[segIndex]->fileoff() + segOffset; + uint64_t value = P::getP(*(uint64_t*)pointerLocation); + bool isRebase = (value & (1ULL << 62)) == 0; + + if (isRebase) { + //printf("(rebase): %-7s %-16s 0x%08llX %s\n", segName, sectionName(segIndex, segStartAddr+segOffset), segStartAddr+segOffset, typeName); + } else { + // the ordinal is bits [0..15] + uint16_t ordinal = value & 0xFFFF; + if (ordinal >= ordinalTableSize) { + throwf("bind ordinal is out of range"); + } + } + + // The delta is bits [51..61] + // And bit 62 is to tell us if we are a rebase (0) or bind (1) + value &= ~(1ULL << 62); + delta = ( value & 0x3FF8000000000000 ) >> 51; + segOffset += delta * sizeof(pint_t); + } while ( delta != 0); + break; + } + default: + throwf("unknown threaded bind subopcode %d", immediate); + } + break; + default: + throwf("bad bind opcode %d", *p); + } + } + } +} + template <> @@ -1437,7 +1626,7 @@ bool MachOChecker::hasTextRelocInRange(pint_t rangeStart, pint_t rangeEnd) } template -bool MachOChecker::addressIsRebaseSite(pint_t targetAddr) +bool MachOChecker::addressIsRebaseSite(pint_t targetAddr, pint_t& pointeeAddr) { // look at local relocs const macho_relocation_info

* const localRelocsEnd = &fLocalRelocations[fLocalRelocationsCount]; @@ -1521,12 +1710,125 @@ bool MachOChecker::addressIsRebaseSite(pint_t targetAddr) default: throwf("bad rebase opcode %d", *p); } - } + } + + // If we have no rebase opcodes, then we may be using the threaded rebase/bind combined + // format and need to parse the bind opcodes instead. + if ( (fDyldInfo->rebase_size() == 0) && (fDyldInfo->bind_size() != 0) ) { + const uint8_t* p = (uint8_t*)fHeader + fDyldInfo->bind_off(); + const uint8_t* end = &p[fDyldInfo->bind_size()]; + + uint8_t segIndex = 0; + uint64_t segOffset = 0; + uint32_t count; + uint32_t skip; + pint_t segStartAddr = 0; + bool done = false; + while ( !done && (p < end) ) { + uint8_t immediate = *p & BIND_IMMEDIATE_MASK; + uint8_t opcode = *p & BIND_OPCODE_MASK; + ++p; + switch (opcode) { + case BIND_OPCODE_DONE: + done = true; + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM: + break; + case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB: + read_uleb128(p, end); + break; + case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM: + break; + case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM: + while (*p != '\0') + ++p; + ++p; + break; + case BIND_OPCODE_SET_TYPE_IMM: + break; + case BIND_OPCODE_SET_ADDEND_SLEB: + read_sleb128(p, end); + break; + case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segStartAddr = segStartAddress(segIndex); + segOffset = read_uleb128(p, end); + break; + case BIND_OPCODE_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end); + break; + case BIND_OPCODE_DO_BIND: + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + segOffset += immediate*sizeof(pint_t) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + segOffset += skip + sizeof(pint_t); + } + break; + case BIND_OPCODE_THREADED: + // Note the immediate is a sub opcode + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + count = read_uleb128(p, end); + break; + case BIND_SUBOPCODE_THREADED_APPLY: { + uint64_t delta = 0; + do { + uint8_t* pointerLocation = (uint8_t*)fHeader + fSegments[segIndex]->fileoff() + segOffset; + uint64_t value = P::getP(*(uint64_t*)pointerLocation); +#if SUPPORT_ARCH_arm64e + bool isAuthenticated = (value & (1ULL << 63)) != 0; +#endif + bool isRebase = (value & (1ULL << 62)) == 0; + if ( isRebase && ( (segStartAddr+segOffset) == targetAddr ) ) { + +#if SUPPORT_ARCH_arm64e + if (isAuthenticated) { + uint64_t targetValue = value & 0xFFFFFFFFULL; + targetValue += fBaseAddress; + pointeeAddr = (pint_t)targetValue; + } else +#endif + { + // 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 = value & 0x0007F80000000000ULL; + uint64_t bottom43Bits = value & 0x000007FFFFFFFFFFULL; + uint64_t targetValue = ( top8Bits << 13 ) | (((intptr_t)(bottom43Bits << 21) >> 21) & 0x00FFFFFFFFFFFFFF); + pointeeAddr = (pint_t)targetValue; + } + return true; + } + + // The delta is bits [51..61] + // And bit 62 is to tell us if we are a rebase (0) or bind (1) + value &= ~(1ULL << 62); + delta = ( value & 0x3FF8000000000000 ) >> 51; + segOffset += delta * sizeof(pint_t); + } while ( delta != 0 ); + break; + } + default: + throwf("unknown threaded bind subopcode %d", immediate); + } + break; + default: + throwf("bad bind opcode %d", *p); + } + } + } } return false; } - template bool MachOChecker::addressIsBindingSite(pint_t targetAddr) { @@ -1619,6 +1921,35 @@ bool MachOChecker::addressIsBindingSite(pint_t targetAddr) segOffset += skip + sizeof(pint_t); } break; + case BIND_OPCODE_THREADED: + // Note the immediate is a sub opcode + switch (immediate) { + case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB: + count = read_uleb128(p, end); + break; + case BIND_SUBOPCODE_THREADED_APPLY: { + uint64_t delta = 0; + do { + uint8_t* pointerLocation = (uint8_t*)fHeader + fSegments[segIndex]->fileoff() + segOffset; + uint64_t value = P::getP(*(uint64_t*)pointerLocation); + bool isRebase = (value & (1ULL << 62)) == 0; + if (!isRebase) { + if ( (segStartAddr+segOffset) == targetAddr ) + return true; + } + + // The delta is bits [51..61] + // And bit 62 is to tell us if we are a rebase (0) or bind (1) + value &= ~(1ULL << 62); + delta = ( value & 0x3FF8000000000000 ) >> 51; + segOffset += delta * sizeof(pint_t); + } while ( delta != 0 ); + break; + } + default: + throwf("unknown threaded bind subopcode %d", immediate); + } + break; default: throwf("bad bind opcode %d", *p); } @@ -1729,10 +2060,11 @@ int main(int argc, const char* argv[]) else if ( strcmp(arg, "-verifier_error_list") == 0 ) { printf("os_dylib_rpath_install_name\tOS dylibs (those in /usr/lib/ or /System/Library/) must be built with -install_name that is an absolute path - not an @rpath\n"); printf("os_dylib_bad_install_name\tOS dylibs (those in /usr/lib/ or /System/Library/) must be built with -install_name matching their file system location\n"); - printf("os_dylib_rpath\tOS dylibs should not contain LC_RPATH load commands (from -rpath linker option)\n"); + printf("os_dylib_rpath\tOS dylibs should not contain LC_RPATH load commands (from -rpath linker option)(remove LD_RUNPATH_SEARCH_PATHS Xcode build setting)\n"); printf("os_dylib_flat_namespace\tOS dylibs should not be built with -flat_namespace\n"); printf("os_dylib_undefined_dynamic_lookup\tOS dylibs should not be built with -undefined dynamic_lookup\n"); - printf("os_dylib_malformed\the mach-o is malformed\n"); + printf("os_dylib_malformed\tthe mach-o file is malformed\n"); + printf("macos_in_ios_support\t/System/iOSSupport/ should only contain mach-o files that support iosmac\n"); return 0; } else {