X-Git-Url: https://git.saurik.com/apple/ld64.git/blobdiff_plain/a645023da60d22e86be13f7b4d97adeff8bc6665..HEAD:/src/other/machochecker.cpp diff --git a/src/other/machochecker.cpp b/src/other/machochecker.cpp index 295fa19..8e95458 100644 --- a/src/other/machochecker.cpp +++ b/src/other/machochecker.cpp @@ -27,13 +27,16 @@ #include #include #include +#include #include #include #include #include #include -#include +#include + +#include "configure.h" #include "MachOFileAbstraction.hpp" #include "Architectures.hpp" @@ -73,13 +76,33 @@ static uint64_t read_uleb128(const uint8_t*& p, const uint8_t* end) return result; } + +static int64_t read_sleb128(const uint8_t*& p, const uint8_t* end) +{ + int64_t result = 0; + int bit = 0; + uint8_t byte; + do { + if (p == end) + throwf("malformed sleb128"); + byte = *p++; + result |= ((byte & 0x7f) << bit); + bit += 7; + } while (byte & 0x80); + // sign extend negative numbers + if ( (byte & 0x40) != 0 ) + result |= (-1LL) << bit; + return result; +} + + template class MachOChecker { public: static bool validFile(const uint8_t* fileContent); - static MachOChecker* make(const uint8_t* fileContent, uint32_t fileLength, const char* path) - { return new MachOChecker(fileContent, fileLength, path); } + static MachOChecker* make(const uint8_t* fileContent, uint32_t fileLength, const char* path, const char* verifierDstRoot) + { return new MachOChecker(fileContent, fileLength, path, verifierDstRoot); } virtual ~MachOChecker() {} @@ -88,32 +111,56 @@ private: typedef typename A::P::E E; typedef typename A::P::uint_t pint_t; - class CStringEquals + // utility classes for using std::unordered_map with c-strings + struct CStringHash { + size_t operator()(const char* __s) const { + size_t __h = 0; + for ( ; *__s; ++__s) + __h = 5 * __h + *__s; + return __h; + }; + }; + struct CStringEquals { - public: bool operator()(const char* left, const char* right) const { return (strcmp(left, right) == 0); } }; - typedef __gnu_cxx::hash_set, CStringEquals> StringSet; + typedef std::unordered_set StringSet; - MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path); + MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path, const char* verifierDstRoot); void checkMachHeader(); void checkLoadCommands(); void checkSection(const macho_segment_command

* segCmd, const macho_section

* sect); uint8_t loadCommandSizeMask(); void checkSymbolTable(); + void checkInitTerms(); + void checkThreadedRebaseBind(); void checkIndirectSymbolTable(); void checkRelocations(); void checkExternalReloation(const macho_relocation_info

* reloc); void checkLocalReloation(const macho_relocation_info

* reloc); + void verify(); + 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, pint_t& pointeeAddr); + bool addressIsBindingSite(pint_t addr); + pint_t getInitialStackPointer(const macho_thread_command

*); + pint_t getEntryPoint(const macho_thread_command

*); + const char* archName(); + + const char* fPath; + const char* fDstRoot; const macho_header

* fHeader; uint32_t fLength; + const char* fInstallName; const char* fStrings; const char* fStringsEnd; const macho_nlist

* fSymbols; @@ -126,22 +173,26 @@ private: const macho_relocation_info

* fExternalRelocations; uint32_t fExternalRelocationsCount; 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; const macho_dyld_info_command

* fDyldInfo; uint32_t fSectionCount; std::vector*>fSegments; }; - template <> -bool MachOChecker::validFile(const uint8_t* fileContent) +bool MachOChecker::validFile(const uint8_t* fileContent) { const macho_header

* header = (const macho_header

*)fileContent; if ( header->magic() != MH_MAGIC ) return false; - if ( header->cputype() != CPU_TYPE_POWERPC ) + if ( header->cputype() != CPU_TYPE_I386 ) return false; switch (header->filetype()) { case MH_EXECUTE: @@ -154,12 +205,12 @@ bool MachOChecker::validFile(const uint8_t* fileContent) } template <> -bool MachOChecker::validFile(const uint8_t* fileContent) +bool MachOChecker::validFile(const uint8_t* fileContent) { const macho_header

* header = (const macho_header

*)fileContent; if ( header->magic() != MH_MAGIC_64 ) return false; - if ( header->cputype() != CPU_TYPE_POWERPC64 ) + if ( header->cputype() != CPU_TYPE_X86_64 ) return false; switch (header->filetype()) { case MH_EXECUTE: @@ -171,13 +222,14 @@ bool MachOChecker::validFile(const uint8_t* fileContent) return false; } +#if SUPPORT_ARCH_arm_any template <> -bool MachOChecker::validFile(const uint8_t* fileContent) +bool MachOChecker::validFile(const uint8_t* fileContent) { const macho_header

* header = (const macho_header

*)fileContent; if ( header->magic() != MH_MAGIC ) return false; - if ( header->cputype() != CPU_TYPE_I386 ) + if ( header->cputype() != CPU_TYPE_ARM ) return false; switch (header->filetype()) { case MH_EXECUTE: @@ -188,14 +240,16 @@ bool MachOChecker::validFile(const uint8_t* fileContent) } return false; } +#endif +#if SUPPORT_ARCH_arm64 template <> -bool MachOChecker::validFile(const uint8_t* fileContent) +bool MachOChecker::validFile(const uint8_t* fileContent) { const macho_header

* header = (const macho_header

*)fileContent; if ( header->magic() != MH_MAGIC_64 ) return false; - if ( header->cputype() != CPU_TYPE_X86_64 ) + if ( header->cputype() != CPU_TYPE_ARM64 ) return false; switch (header->filetype()) { case MH_EXECUTE: @@ -206,42 +260,115 @@ bool MachOChecker::validFile(const uint8_t* fileContent) } return false; } +#endif -template <> -bool MachOChecker::validFile(const uint8_t* fileContent) -{ - const macho_header

* header = (const macho_header

*)fileContent; - if ( header->magic() != MH_MAGIC ) - return false; - if ( header->cputype() != CPU_TYPE_ARM ) - return false; - switch (header->filetype()) { - case MH_EXECUTE: - case MH_DYLIB: - case MH_BUNDLE: - case MH_DYLINKER: - return true; - } - return false; -} template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x03; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x07; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x03; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x07; } template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x03; } +template <> uint8_t MachOChecker::loadCommandSizeMask() { return 0x07; } + + +template <> +x86::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(7); +} + +template <> +x86_64::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(7); +} + +template <> +arm::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(13); +} + +template <> +arm64::P::uint_t MachOChecker::getInitialStackPointer(const macho_thread_command* threadInfo) +{ + throw "LC_UNIXTHREAD not supported for arm64"; +} + + +template <> +ppc::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(0); +} + + +template <> +x86::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(10); +} + +template <> +x86_64::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(16); +} + +template <> +arm::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) +{ + return threadInfo->thread_register(15); +} + +template <> +arm64::P::uint_t MachOChecker::getEntryPoint(const macho_thread_command* threadInfo) +{ + throw "LC_UNIXTHREAD not supported for arm64"; +} + + +template +const char* MachOChecker::archName() +{ + switch ( fHeader->cputype() ) { + case CPU_TYPE_I386: + return "i386"; + case CPU_TYPE_X86_64: + if ( fHeader->cpusubtype() == CPU_SUBTYPE_X86_64_H ) + return "x86_64h"; + else + return "x86_64"; + case CPU_TYPE_ARM: + switch ( fHeader->cpusubtype() ) { + case CPU_SUBTYPE_ARM_V7: + return "armv7"; + case CPU_SUBTYPE_ARM_V7S: + return "armv7s"; + case CPU_SUBTYPE_ARM_V7K: + return "armv7k"; + } + return "arm"; + case CPU_TYPE_ARM64: + return "arm64"; + } + return "unknown"; +} + template -MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, const char* path) - : fHeader(NULL), fLength(fileLength), fStrings(NULL), fSymbols(NULL), fSymbolCount(0), fDynamicSymbolTable(NULL), fIndirectTableCount(0), +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), fFirstSegment(NULL), fFirstWritableSegment(NULL), fDyldInfo(NULL), fSectionCount(0) + fWriteableSegmentWithAddrOver4G(false), fSlidableImage(false), fHasLC_RPATH(false), fIsDebugVariant(false), fFirstSegment(NULL), fFirstWritableSegment(NULL), + fTEXTSegment(NULL), fDyldInfo(NULL), fSectionCount(0) { // sanity check if ( ! validFile(fileContent) ) throw "not a mach-o file that can be checked"; fPath = strdup(path); + fDstRoot = verifierDstRoot ? strdup(verifierDstRoot) : NULL; fHeader = (const macho_header

*)fileContent; // sanity check header @@ -255,6 +382,13 @@ MachOChecker::MachOChecker(const uint8_t* fileContent, uint32_t fileLength, c checkRelocations(); checkSymbolTable(); + + checkInitTerms(); + + checkThreadedRebaseBind(); + + if ( verifierDstRoot != NULL ) + verify(); } @@ -265,11 +399,23 @@ void MachOChecker::checkMachHeader() throw "sizeofcmds in mach_header is larger than file"; uint32_t flags = fHeader->flags(); - const uint32_t invalidBits = MH_INCRLINK | MH_LAZY_INIT | 0xFE000000; + const uint32_t invalidBits = MH_INCRLINK | MH_LAZY_INIT | 0xF8000000; if ( flags & invalidBits ) throw "invalid bits in mach_header flags"; if ( (flags & MH_NO_REEXPORTED_DYLIBS) && (fHeader->filetype() != MH_DYLIB) ) throw "MH_NO_REEXPORTED_DYLIBS bit of mach_header flags only valid for dylibs"; + + switch ( fHeader->filetype() ) { + case MH_EXECUTE: + fSlidableImage = ( flags & MH_PIE ); + break; + case MH_DYLIB: + case MH_BUNDLE: + fSlidableImage = true; + break; + default: + throw "not a mach-o file type supported by this tool"; + } } template @@ -277,11 +423,14 @@ void MachOChecker::checkLoadCommands() { // check that all load commands fit within the load command space file const macho_encryption_info_command

* encryption_info = NULL; + const macho_thread_command

* threadInfo = NULL; + const macho_entry_point_command

* entryPoint = NULL; const uint8_t* const endOfFile = (uint8_t*)fHeader + fLength; const uint8_t* const endOfLoadCommands = (uint8_t*)fHeader + sizeof(macho_header

) + fHeader->sizeofcmds(); const uint32_t cmd_count = fHeader->ncmds(); const macho_load_command

* const cmds = (macho_load_command

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

)); const macho_load_command

* cmd = cmds; + const macho_dylib_command

* dylibID; for (uint32_t i = 0; i < cmd_count; ++i) { uint32_t size = cmd->cmdsize(); if ( (size & this->loadCommandSizeMask()) != 0 ) @@ -294,12 +443,10 @@ void MachOChecker::checkLoadCommands() switch ( cmd->cmd() ) { case macho_segment_command

::CMD: case LC_SYMTAB: - case LC_UNIXTHREAD: case LC_DYSYMTAB: case LC_LOAD_DYLIB: - case LC_ID_DYLIB: - case LC_LOAD_DYLINKER: case LC_ID_DYLINKER: + case LC_LOAD_DYLINKER: case macho_routines_command

::CMD: case LC_SUB_FRAMEWORK: case LC_SUB_CLIENT: @@ -314,13 +461,33 @@ void MachOChecker::checkLoadCommands() case LC_LOAD_UPWARD_DYLIB: case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: + case LC_VERSION_MIN_TVOS: + case LC_VERSION_MIN_WATCHOS: case LC_FUNCTION_STARTS: + case LC_DYLD_ENVIRONMENT: + case LC_DATA_IN_CODE: + case LC_DYLIB_CODE_SIGN_DRS: + case LC_SOURCE_VERSION: + case LC_NOTE: + case LC_BUILD_VERSION: + break; + case LC_RPATH: + fHasLC_RPATH = true; + break; + case LC_ID_DYLIB: + dylibID = (macho_dylib_command

*)cmd; + if ( dylibID->name_offset() > size ) + throwf("malformed mach-o: LC_ID_DYLIB load command has offset (%u) outside its size (%u)", dylibID->name_offset(), size); + if ( (dylibID->name_offset() + strlen(dylibID->name()) + 1) > size ) + throwf("malformed mach-o: LC_ID_DYLIB load command string extends beyond end of load command"); + fInstallName = dylibID->name(); break; case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: fDyldInfo = (macho_dyld_info_command

*)cmd; break; case LC_ENCRYPTION_INFO: + case LC_ENCRYPTION_INFO_64: encryption_info = (macho_encryption_info_command

*)cmd; break; case LC_SUB_UMBRELLA: @@ -328,6 +495,16 @@ void MachOChecker::checkLoadCommands() if ( fHeader->flags() & MH_NO_REEXPORTED_DYLIBS ) throw "MH_NO_REEXPORTED_DYLIBS bit of mach_header flags should not be set in an image with LC_SUB_LIBRARY or LC_SUB_UMBRELLA"; break; + case LC_MAIN: + if ( fHeader->filetype() != MH_EXECUTE ) + throw "LC_MAIN can only be used in MH_EXECUTE file types"; + entryPoint = (macho_entry_point_command

*)cmd; + break; + case LC_UNIXTHREAD: + if ( fHeader->filetype() != MH_EXECUTE ) + throw "LC_UNIXTHREAD can only be used in MH_EXECUTE file types"; + threadInfo = (macho_thread_command

*)cmd; + break; default: throwf("load command #%d is an unknown kind 0x%X", i, cmd->cmd()); } @@ -339,6 +516,7 @@ void MachOChecker::checkLoadCommands() std::vector > segmentAddressRanges; std::vector > segmentFileOffsetRanges; const macho_segment_command

* linkEditSegment = NULL; + const macho_segment_command

* stackSegment = NULL; for (uint32_t i = 0; i < cmd_count; ++i) { if ( cmd->cmd() == macho_segment_command

::CMD ) { const macho_segment_command

* segCmd = (const macho_segment_command

*)cmd; @@ -361,7 +539,7 @@ void MachOChecker::checkLoadCommands() else { throw "overlapping segment vm addresses"; } - segmentAddressRanges.push_back(std::make_pair(startAddr, endAddr)); + segmentAddressRanges.push_back(std::make_pair(startAddr, endAddr)); } // see if this overlaps another segment file offset range uint64_t startOffset = segCmd->fileoff(); @@ -378,7 +556,7 @@ void MachOChecker::checkLoadCommands() else { throw "overlapping segment file data"; } - segmentFileOffsetRanges.push_back(std::make_pair(startOffset, endOffset)); + segmentFileOffsetRanges.push_back(std::make_pair(startOffset, endOffset)); // check is within file bounds if ( (startOffset > fLength) || (endOffset > fLength) ) throw "segment file data is past end of file"; @@ -389,32 +567,39 @@ void MachOChecker::checkLoadCommands() if ( endOffset > fLength ) throw "segment fileoff+filesize does not fit in file"; - // keep LINKEDIT segment + // record special segments if ( strcmp(segCmd->segname(), "__LINKEDIT") == 0 ) linkEditSegment = segCmd; + else if ( strcmp(segCmd->segname(), "__UNIXSTACK") == 0 ) + stackSegment = segCmd; // cache interesting segments if ( fFirstSegment == NULL ) fFirstSegment = segCmd; + if ( (fTEXTSegment == NULL) && (strcmp(segCmd->segname(), "__TEXT") == 0) ) + fTEXTSegment = segCmd; if ( (segCmd->initprot() & VM_PROT_WRITE) != 0 ) { if ( fFirstWritableSegment == NULL ) fFirstWritableSegment = segCmd; 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

)); const macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; for(const macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { - // check all sections are within segment + // check all non-zero sized sections are within segment if ( sect->addr() < startAddr ) throwf("section %s vm address not within segment", sect->sectname()); if ( (sect->addr()+sect->size()) > endAddr ) throwf("section %s vm address not within segment", sect->sectname()); if ( ((sect->flags() & SECTION_TYPE) != S_ZEROFILL) && ((sect->flags() & SECTION_TYPE) != S_THREAD_LOCAL_ZEROFILL) - && (segCmd->filesize() != 0) ) { + && (segCmd->filesize() != 0) + && (sect->size() != 0) ) { if ( sect->offset() < startOffset ) throwf("section %s file offset not within segment", sect->sectname()); if ( (sect->offset()+sect->size()) > endOffset ) @@ -431,6 +616,49 @@ void MachOChecker::checkLoadCommands() if ( linkEditSegment == NULL ) throw "no __LINKEDIT segment"; + // verify there was an executable __TEXT segment and load commands are in it + if ( fTEXTSegment == NULL ) + throw "no __TEXT segment"; + if ( fTEXTSegment->initprot() != (VM_PROT_READ|VM_PROT_EXECUTE) ) + throw "__TEXT segment does not have r-x init permissions"; + //if ( fTEXTSegment->maxprot() != (VM_PROT_READ|VM_PROT_EXECUTE|VM_PROT_WRITE) ) + // throw "__TEXT segment does not have rwx max permissions"; + if ( fTEXTSegment->fileoff() != 0 ) + throw "__TEXT segment does not start at mach_header"; + if ( fTEXTSegment->filesize() < (sizeof(macho_header

)+fHeader->sizeofcmds()) ) + throw "__TEXT segment smaller than load commands"; + + // verify if custom stack used, that stack is in __UNIXSTACK segment + if ( threadInfo != NULL ) { + pint_t initialSP = getInitialStackPointer(threadInfo); + if ( initialSP != 0 ) { + if ( stackSegment == NULL ) + throw "LC_UNIXTHREAD specifics custom initial stack pointer, but no __UNIXSTACK segment"; + if ( (initialSP < stackSegment->vmaddr()) || (initialSP > (stackSegment->vmaddr()+stackSegment->vmsize())) ) + throw "LC_UNIXTHREAD specifics custom initial stack pointer which does not point into __UNIXSTACK segment"; + } + } + + // verify __UNIXSTACK is zero fill + if ( stackSegment != NULL ) { + if ( (stackSegment->filesize() != 0) || (stackSegment->fileoff() != 0) ) + throw "__UNIXSTACK is not a zero-fill segment"; + if ( stackSegment->vmsize() < 4096 ) + throw "__UNIXSTACK segment is too small"; + } + + // verify entry point is in __TEXT segment + if ( threadInfo != NULL ) { + pint_t initialPC = getEntryPoint(threadInfo); + if ( (initialPC < fTEXTSegment->vmaddr()) || (initialPC >= (fTEXTSegment->vmaddr()+fTEXTSegment->vmsize())) ) + throwf("entry point 0x%0llX is outside __TEXT segment", (long long)initialPC); + } + else if ( entryPoint != NULL ) { + pint_t initialOffset = entryPoint->entryoff(); + if ( (initialOffset < fTEXTSegment->fileoff()) || (initialOffset >= (fTEXTSegment->fileoff()+fTEXTSegment->filesize())) ) + throwf("entry point 0x%0llX is outside __TEXT segment", (long long)initialOffset); + } + // checks for executables bool isStaticExecutable = false; if ( fHeader->filetype() == MH_EXECUTE ) { @@ -446,15 +674,19 @@ void MachOChecker::checkLoadCommands() cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); } if ( isStaticExecutable ) { - if ( fHeader->flags() != MH_NOUNDEFS ) + if ( (fHeader->flags() != MH_NOUNDEFS) && (fHeader->flags() != (MH_NOUNDEFS|MH_PIE)) ) throw "invalid bits in mach_header flags for static executable"; } } // verify encryption info if ( encryption_info != NULL ) { - if ( fHeader->filetype() != MH_EXECUTE ) - throw "LC_ENCRYPTION_INFO load command is only legal in main executables"; + switch ( fHeader->filetype() ) { + case MH_EXECUTE: case MH_DYLIB: case MH_BUNDLE: + break; // okay + default: + throw "LC_ENCRYPTION_INFO load command is not allowed in this file type"; + } if ( encryption_info->cryptoff() < (sizeof(macho_header

) + fHeader->sizeofcmds()) ) throw "LC_ENCRYPTION_INFO load command has cryptoff covers some load commands"; if ( (encryption_info->cryptoff() % 4096) != 0 ) @@ -470,6 +702,16 @@ void MachOChecker::checkLoadCommands() } } + // verify dylib has LC_ID_DYLIB + if ( fHeader->filetype() == MH_DYLIB ) { + if ( fInstallName == NULL ) + throw "MH_DYLIB missing LC_ID_DYLIB"; + } + else { + if ( fInstallName != NULL ) + throw "LC_ID_DYLIB found but file type is not MH_DYLIB"; + } + // check LC_SYMTAB, LC_DYSYMTAB, and LC_SEGMENT_SPLIT_INFO cmd = cmds; bool foundDynamicSymTab = false; @@ -482,8 +724,8 @@ void MachOChecker::checkLoadCommands() fSymbols = (const macho_nlist

*)((char*)fHeader + symtab->symoff()); if ( symtab->symoff() < linkEditSegment->fileoff() ) throw "symbol table not in __LINKEDIT"; - if ( (symtab->symoff() + fSymbolCount*sizeof(macho_nlist

*)) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) ) - throw "symbol table end not in __LINKEDIT"; + if ( (symtab->symoff() + fSymbolCount*sizeof(macho_nlist

*)) > symtab->stroff() ) + throw "symbol table overlaps string pool"; if ( (symtab->symoff() % sizeof(pint_t)) != 0 ) throw "symbol table start not pointer aligned"; fStrings = (char*)fHeader + symtab->stroff(); @@ -494,13 +736,11 @@ void MachOChecker::checkLoadCommands() throw "string pool extends beyond __LINKEDIT"; if ( (symtab->stroff() % 4) != 0 ) // work around until rdar://problem/4737991 is fixed throw "string pool start not pointer aligned"; - if ( (symtab->strsize() % sizeof(pint_t)) != 0 ) - throw "string pool size not a multiple of pointer size"; } break; case LC_DYSYMTAB: { - if ( isStaticExecutable ) + if ( isStaticExecutable &&! fSlidableImage ) throw "LC_DYSYMTAB should not be used in static executable"; foundDynamicSymTab = true; fDynamicSymbolTable = (macho_dysymtab_command

*)cmd; @@ -509,7 +749,7 @@ void MachOChecker::checkLoadCommands() if ( fIndirectTableCount != 0 ) { if ( fDynamicSymbolTable->indirectsymoff() < linkEditSegment->fileoff() ) throw "indirect symbol table not in __LINKEDIT"; - if ( (fDynamicSymbolTable->indirectsymoff()+fIndirectTableCount*8) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) ) + if ( (fDynamicSymbolTable->indirectsymoff()+fIndirectTableCount*4) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) ) throw "indirect symbol table not in __LINKEDIT"; if ( (fDynamicSymbolTable->indirectsymoff() % sizeof(pint_t)) != 0 ) throw "indirect symbol table not pointer aligned"; @@ -564,6 +804,32 @@ void MachOChecker::checkLoadCommands() throw "function starts data size not a multiple of pointer size"; } break; + case LC_DATA_IN_CODE: + { + const macho_linkedit_data_command

* info = (macho_linkedit_data_command

*)cmd; + if ( info->dataoff() < linkEditSegment->fileoff() ) + throw "data-in-code data not in __LINKEDIT"; + if ( (info->dataoff()+info->datasize()) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) ) + throw "data-in-code data not in __LINKEDIT"; + if ( (info->dataoff() % sizeof(pint_t)) != 0 ) + throw "data-in-code data table not pointer aligned"; + if ( (info->datasize() % sizeof(pint_t)) != 0 ) + throw "data-in-code data size not a multiple of pointer size"; + } + break; + case LC_DYLIB_CODE_SIGN_DRS: + { + const macho_linkedit_data_command

* info = (macho_linkedit_data_command

*)cmd; + if ( info->dataoff() < linkEditSegment->fileoff() ) + throw "dependent dylib DR data not in __LINKEDIT"; + if ( (info->dataoff()+info->datasize()) > (linkEditSegment->fileoff()+linkEditSegment->filesize()) ) + throw "dependent dylib DR data not in __LINKEDIT"; + if ( (info->dataoff() % sizeof(pint_t)) != 0 ) + throw "dependent dylib DR data table not pointer aligned"; + if ( (info->datasize() % sizeof(pint_t)) != 0 ) + throw "dependent dylib DR data size not a multiple of pointer size"; + } + break; } cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); } @@ -583,9 +849,145 @@ void MachOChecker::checkSection(const macho_segment_command

* segCmd, const throwf("section offset should be zero for zero-fill section %s", sect->sectname()); } + // check section's segment name matches segment +// if ( strncmp(sect->segname(), segCmd->segname(), 16) != 0 ) +// throwf("section %s in segment %s has wrong segment name", sect->sectname(), segCmd->segname()); + // 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() +{ + 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(); +} + + +template +void MachOChecker::verifyInstallName() +{ + // Don't allow @rpath to be used as -install_name for OS dylibs + if ( strncmp(fInstallName, "@rpath/", 7) == 0 ) { + printf("os_dylib_rpath_install_name\tfatal\t-install_name uses @rpath in arch %s\n", archName()); + } + else { + // Verify -install_name match actual path of dylib + const char* installPathWithinDstRoot = &fPath[strlen(fDstRoot)]; + if ( strcmp(installPathWithinDstRoot, fInstallName) != 0 ) { + // see if install name is a symlink to actual file + bool symlinkToDylib = false; + char absDstPath[PATH_MAX]; + if ( realpath(fDstRoot, absDstPath) != NULL ) { + char fullInstallNamePath[PATH_MAX]; + strlcpy(fullInstallNamePath, absDstPath, PATH_MAX); + strlcat(fullInstallNamePath, fInstallName, PATH_MAX); + char absInstallNamePath[PATH_MAX]; + if ( realpath(fullInstallNamePath, absInstallNamePath) != NULL ) { + char absFPath[PATH_MAX]; + if ( realpath(fPath, absFPath) != NULL ) { + if ( strcmp(absInstallNamePath, absFPath) == 0 ) + symlinkToDylib = true; + } + } + } + if ( !symlinkToDylib ) + printf("os_dylib_bad_install_name\twarn\t-install_name does not match install location in arch %s\n", archName()); + } + } + +} + +template +void MachOChecker::verifyNoRpaths() +{ + // Don't allow OS dylibs to add rpaths + if ( fHasLC_RPATH ) { + printf("os_dylib_rpath\twarn\tcontains LC_RPATH load command in arch %s\n", archName()); + } +} + + +template +void MachOChecker::verifyNoFlatLookups() +{ + if ( (fHeader->flags() & MH_TWOLEVEL) == 0 ) { + printf("os_dylib_flat_namespace\twarn\tbuilt with -flat_namespace in arch %s\n", archName()); + return; + } + + if ( fDynamicSymbolTable != NULL ) { + const macho_nlist

* const undefinesStart = &fSymbols[fDynamicSymbolTable->iundefsym()]; + const macho_nlist

* const undefinesEnd = &undefinesStart[fDynamicSymbolTable->nundefsym()]; + for(const macho_nlist

* sym = undefinesStart; sym < undefinesEnd; ++sym) { + //printf("0x%04X %s\n", sym->n_desc(), &fStrings[sym->n_strx()]); + if ( GET_LIBRARY_ORDINAL(sym->n_desc()) == DYNAMIC_LOOKUP_ORDINAL ) { + const char* symName = &fStrings[sym->n_strx()]; + printf("os_dylib_undefined_dynamic_lookup\twarn\tbuilt with -undefined dynamic_lookup for symbol %s in arch %s\n", symName, archName()); + } + } + } +} + +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() { @@ -626,9 +1028,49 @@ void MachOChecker::checkIndirectSymbolTable() } cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); } + + + if ( fDynamicSymbolTable->ilocalsym() != 0 ) + throwf("start of local symbols (%d) not at start of symbol table", fDynamicSymbolTable->ilocalsym()); + + if ( fDynamicSymbolTable->ilocalsym() > fSymbolCount ) + throwf("start of local symbols out of range (%d > %d) in indirect symbol table", fDynamicSymbolTable->ilocalsym(), fSymbolCount); + if ( fDynamicSymbolTable->ilocalsym() + fDynamicSymbolTable->nlocalsym() > fSymbolCount ) { + throwf("local symbols out of range (%d+%d > %d) in indirect symbol table", + fDynamicSymbolTable->ilocalsym(), fDynamicSymbolTable->nlocalsym(), fSymbolCount); + } + + if ( fDynamicSymbolTable->iextdefsym() > fSymbolCount ) + throwf("start of extern symbols out of range (%d > %d) in indirect symbol table", fDynamicSymbolTable->iextdefsym(), fSymbolCount); + if ( fDynamicSymbolTable->iextdefsym() != fDynamicSymbolTable->ilocalsym() + fDynamicSymbolTable->nlocalsym() ) { + throwf("start of extern symbols (%d) not contiguous to local symbols (%d+%d) in indirect symbol table", + fDynamicSymbolTable->iextdefsym(), fDynamicSymbolTable->ilocalsym(), fDynamicSymbolTable->nlocalsym() ); + } + if ( fDynamicSymbolTable->iextdefsym() + fDynamicSymbolTable->nextdefsym() > fSymbolCount ) { + throwf("extern symbols out of range (%d+%d > %d) in indirect symbol table", + fDynamicSymbolTable->iextdefsym(), fDynamicSymbolTable->nextdefsym(), fSymbolCount); + } + + if ( fDynamicSymbolTable->iundefsym() > fSymbolCount ) + throwf("start of undefined symbols out of range (%d > %d) in indirect symbol table", fDynamicSymbolTable->iundefsym(), fSymbolCount); + if ( fDynamicSymbolTable->iundefsym() != fDynamicSymbolTable->iextdefsym() + fDynamicSymbolTable->nextdefsym() ) { + throwf("start of undefined symbols (%d) not contiguous to extern symbols (%d+%d) in indirect symbol table", + fDynamicSymbolTable->iundefsym(), fDynamicSymbolTable->iextdefsym(), fDynamicSymbolTable->nextdefsym()); + } + if ( fDynamicSymbolTable->iundefsym() + fDynamicSymbolTable->nundefsym() > fSymbolCount ) { + throwf("undefined symbols out of range (%d+%d > %d) in indirect symbol table", + fDynamicSymbolTable->iundefsym(), fDynamicSymbolTable->nundefsym(), fSymbolCount); + } + + if ( fDynamicSymbolTable->iundefsym() + fDynamicSymbolTable->nundefsym() != fSymbolCount ) { + throwf("end undefined symbols (%d+%d) not at end of all symbols (%d) in indirect symbol table", + fDynamicSymbolTable->iundefsym(), fDynamicSymbolTable->nundefsym(), fSymbolCount ); + } } + + template void MachOChecker::checkSymbolTable() { @@ -674,52 +1116,227 @@ void MachOChecker::checkSymbolTable() } -template <> -ppc::P::uint_t MachOChecker::relocBase() -{ - if ( fHeader->flags() & MH_SPLIT_SEGS ) - return fFirstWritableSegment->vmaddr(); - else - return fFirstSegment->vmaddr(); -} - -template <> -ppc64::P::uint_t MachOChecker::relocBase() -{ - if ( fWriteableSegmentWithAddrOver4G ) - return fFirstWritableSegment->vmaddr(); - else - return fFirstSegment->vmaddr(); -} - -template <> -x86::P::uint_t MachOChecker::relocBase() -{ - if ( fHeader->flags() & MH_SPLIT_SEGS ) - return fFirstWritableSegment->vmaddr(); - else - return fFirstSegment->vmaddr(); -} - -template <> -x86_64::P::uint_t MachOChecker::relocBase() -{ - // check for split-seg - return fFirstWritableSegment->vmaddr(); -} - -template <> -arm::P::uint_t MachOChecker::relocBase() -{ - if ( fHeader->flags() & MH_SPLIT_SEGS ) - return fFirstWritableSegment->vmaddr(); - else - return fFirstSegment->vmaddr(); -} - - template -bool MachOChecker::addressInWritableSegment(pint_t address) +void MachOChecker::checkInitTerms() +{ + const macho_load_command

* const cmds = (macho_load_command

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

)); + const uint32_t cmd_count = fHeader->ncmds(); + const macho_load_command

* cmd = cmds; + for (uint32_t i = 0; i < cmd_count; ++i) { + if ( cmd->cmd() == macho_segment_command

::CMD ) { + const macho_segment_command

* segCmd = (const macho_segment_command

*)cmd; + const macho_section

* const sectionsStart = (macho_section

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

)); + const macho_section

* const sectionsEnd = §ionsStart[segCmd->nsects()]; + for(const macho_section

* sect = sectionsStart; sect < sectionsEnd; ++sect) { + // make sure all magic sections that use indirect symbol table fit within it + uint32_t count; + pint_t* arrayStart; + pint_t* arrayEnd; + const char* kind = "initializer"; + switch ( sect->flags() & SECTION_TYPE ) { + case S_MOD_TERM_FUNC_POINTERS: + kind = "terminator"; + // fall through + case S_MOD_INIT_FUNC_POINTERS: + count = sect->size() / sizeof(pint_t); + if ( (count*sizeof(pint_t)) != sect->size() ) + 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()); + arrayStart = (pint_t*)((char*)fHeader + sect->offset()); + arrayEnd = (pint_t*)((char*)fHeader + sect->offset() + sect->size()); + // 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, *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); + 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; + } + } + } + cmd = (const macho_load_command

*)(((uint8_t*)cmd)+cmd->cmdsize()); + } + +} + +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 <> +x86::P::uint_t MachOChecker::relocBase() +{ + if ( fHeader->flags() & MH_SPLIT_SEGS ) + return fFirstWritableSegment->vmaddr(); + else + return fFirstSegment->vmaddr(); +} + +template <> +x86_64::P::uint_t MachOChecker::relocBase() +{ + // check for split-seg + return fFirstWritableSegment->vmaddr(); +} + +template <> +arm::P::uint_t MachOChecker::relocBase() +{ + if ( fHeader->flags() & MH_SPLIT_SEGS ) + return fFirstWritableSegment->vmaddr(); + else + return fFirstSegment->vmaddr(); +} + +template <> +arm64::P::uint_t MachOChecker::relocBase() +{ + return fFirstWritableSegment->vmaddr(); +} + + +template +bool MachOChecker::addressInWritableSegment(pint_t address) { const macho_load_command

* const cmds = (macho_load_command

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

)); const uint32_t cmd_count = fHeader->ncmds(); @@ -748,37 +1365,6 @@ bool MachOChecker::addressInWritableSegment(pint_t address) } -template <> -void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_length() != 2 ) - throw "bad external relocation length"; - if ( reloc->r_type() != GENERIC_RELOC_VANILLA ) - throw "unknown external relocation type"; - if ( reloc->r_pcrel() != 0 ) - throw "bad external relocation pc_rel"; - if ( reloc->r_extern() == 0 ) - throw "local relocation found with external relocations"; - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throw "external relocation address not in writable segment"; - // FIX: check r_symbol -} - -template <> -void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_length() != 3 ) - throw "bad external relocation length"; - if ( reloc->r_type() != GENERIC_RELOC_VANILLA ) - throw "unknown external relocation type"; - if ( reloc->r_pcrel() != 0 ) - throw "bad external relocation pc_rel"; - if ( reloc->r_extern() == 0 ) - throw "local relocation found with external relocations"; - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throw "external relocation address not in writable segment"; - // FIX: check r_symbol -} template <> void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) @@ -813,12 +1399,13 @@ void MachOChecker::checkExternalReloation(const macho_relocation_info

// FIX: check r_symbol } +#if SUPPORT_ARCH_arm_any template <> void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) { if ( reloc->r_length() != 2 ) throw "bad external relocation length"; - if ( reloc->r_type() != ARM_RELOC_VANILLA ) + if ( reloc->r_type() != ARM_RELOC_VANILLA ) throw "unknown external relocation type"; if ( reloc->r_pcrel() != 0 ) throw "bad external relocation pc_rel"; @@ -828,43 +1415,17 @@ void MachOChecker::checkExternalReloation(const macho_relocation_info

* r throw "external relocation address not in writable segment"; // FIX: check r_symbol } +#endif - +#if SUPPORT_ARCH_arm64 template <> -void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) +void MachOChecker::checkExternalReloation(const macho_relocation_info

* reloc) { - if ( reloc->r_address() & R_SCATTERED ) { - // scattered - const macho_scattered_relocation_info

* sreloc = (const macho_scattered_relocation_info

*)reloc; - // FIX - - } - else { - // ignore pair relocs - if ( reloc->r_type() == PPC_RELOC_PAIR ) - return; - // FIX - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throwf("local relocation address 0x%08X not in writable segment", reloc->r_address()); - } + throw "external relocations not used for arm64"; } +#endif -template <> -void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) -{ - if ( reloc->r_length() != 3 ) - throw "bad local relocation length"; - if ( reloc->r_type() != GENERIC_RELOC_VANILLA ) - throw "unknown local relocation type"; - if ( reloc->r_pcrel() != 0 ) - throw "bad local relocation pc_rel"; - if ( reloc->r_extern() != 0 ) - throw "external relocation found with local relocations"; - if ( ! this->addressInWritableSegment(reloc->r_address() + this->relocBase()) ) - throw "local relocation address not in writable segment"; -} - template <> void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) { @@ -886,6 +1447,7 @@ void MachOChecker::checkLocalReloation(const macho_relocation_info

* r throw "local relocation address not in writable segment"; } +#if SUPPORT_ARCH_arm_any template <> void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) { @@ -906,6 +1468,16 @@ void MachOChecker::checkLocalReloation(const macho_relocation_info

* relo throw "local relocation address not in writable segment"; } } +#endif + +#if SUPPORT_ARCH_arm64 +template <> +void MachOChecker::checkLocalReloation(const macho_relocation_info

* reloc) +{ + throw "local relocations not used for arm64"; +} +#endif + template void MachOChecker::checkRelocations() @@ -1050,9 +1622,344 @@ bool MachOChecker::hasTextRelocInRange(pint_t rangeStart, pint_t rangeEnd) } } } + return false; +} + +template +bool MachOChecker::addressIsRebaseSite(pint_t targetAddr, pint_t& pointeeAddr) +{ + // look at local relocs + const macho_relocation_info

* const localRelocsEnd = &fLocalRelocations[fLocalRelocationsCount]; + for (const macho_relocation_info

* reloc = fLocalRelocations; reloc < localRelocsEnd; ++reloc) { + pint_t relocAddress = reloc->r_address() + this->relocBase(); + if ( relocAddress == targetAddr ) + return true; + } + // look rebase info + if ( fDyldInfo != NULL ) { + const uint8_t* p = (uint8_t*)fHeader + fDyldInfo->rebase_off(); + const uint8_t* end = &p[fDyldInfo->rebase_size()]; + + uint8_t type = 0; + uint64_t segOffset = 0; + uint32_t count; + uint32_t skip; + int segIndex; + pint_t segStartAddr = 0; + pint_t addr; + bool done = false; + while ( !done && (p < end) ) { + uint8_t immediate = *p & REBASE_IMMEDIATE_MASK; + uint8_t opcode = *p & REBASE_OPCODE_MASK; + ++p; + switch (opcode) { + case REBASE_OPCODE_DONE: + done = true; + break; + case REBASE_OPCODE_SET_TYPE_IMM: + type = immediate; + break; + case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB: + segIndex = immediate; + segStartAddr = segStartAddress(segIndex); + segOffset = read_uleb128(p, end); + break; + case REBASE_OPCODE_ADD_ADDR_ULEB: + segOffset += read_uleb128(p, end); + break; + case REBASE_OPCODE_ADD_ADDR_IMM_SCALED: + segOffset += immediate*sizeof(pint_t); + break; + case REBASE_OPCODE_DO_REBASE_IMM_TIMES: + for (int i=0; i < immediate; ++i) { + addr = segStartAddr+segOffset; + if ( addr == targetAddr ) + return true; + //printf("%-7s %-16s 0x%08llX %s\n", segName, sectionName(segIndex, segStartAddr+segOffset), segStartAddr+segOffset, typeName); + segOffset += sizeof(pint_t); + } + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES: + count = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + addr = segStartAddr+segOffset; + if ( addr == targetAddr ) + return true; + //printf("%-7s %-16s 0x%08llX %s\n", segName, sectionName(segIndex, segStartAddr+segOffset), segStartAddr+segOffset, typeName); + segOffset += sizeof(pint_t); + } + break; + case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB: + addr = segStartAddr+segOffset; + if ( addr == targetAddr ) + return true; + //printf("%-7s %-16s 0x%08llX %s\n", segName, sectionName(segIndex, segStartAddr+segOffset), segStartAddr+segOffset, typeName); + segOffset += read_uleb128(p, end) + sizeof(pint_t); + break; + case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB: + count = read_uleb128(p, end); + skip = read_uleb128(p, end); + for (uint32_t i=0; i < count; ++i) { + addr = segStartAddr+segOffset; + if ( addr == targetAddr ) + return true; + //printf("%-7s %-16s 0x%08llX %s\n", segName, sectionName(segIndex, segStartAddr+segOffset), segStartAddr+segOffset, typeName); + segOffset += skip + sizeof(pint_t); + } + break; + 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) +{ + // look at external relocs + const macho_relocation_info

* const externRelocsEnd = &fExternalRelocations[fExternalRelocationsCount]; + for (const macho_relocation_info

* reloc = fExternalRelocations; reloc < externRelocsEnd; ++reloc) { + pint_t relocAddress = reloc->r_address() + this->relocBase(); + if ( relocAddress == targetAddr ) + return true; + } + // 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; + 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: + if ( (segStartAddr+segOffset) == targetAddr ) + return true; + segOffset += sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB: + if ( (segStartAddr+segOffset) == targetAddr ) + return true; + segOffset += read_uleb128(p, end) + sizeof(pint_t); + break; + case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED: + if ( (segStartAddr+segOffset) == targetAddr ) + return true; + 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) { + if ( (segStartAddr+segOffset) == targetAddr ) + return true; + 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); + } + } + } + return false; } -static void check(const char* path) + +static void check(const char* path, const char* verifierDstRoot) { struct stat stat_buf; @@ -1077,56 +1984,55 @@ static void check(const char* path) unsigned int cputype = OSSwapBigToHostInt32(archs[i].cputype); switch(cputype) { - case CPU_TYPE_POWERPC: - if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); - else - throw "in universal file, ppc slice does not contain ppc mach-o"; - break; case CPU_TYPE_I386: if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); + MachOChecker::make(p + offset, size, path, verifierDstRoot); else throw "in universal file, i386 slice does not contain i386 mach-o"; break; - case CPU_TYPE_POWERPC64: - if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); - else - throw "in universal file, ppc64 slice does not contain ppc64 mach-o"; - break; case CPU_TYPE_X86_64: if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); + MachOChecker::make(p + offset, size, path, verifierDstRoot); else throw "in universal file, x86_64 slice does not contain x86_64 mach-o"; break; +#if SUPPORT_ARCH_arm_any case CPU_TYPE_ARM: if ( MachOChecker::validFile(p + offset) ) - MachOChecker::make(p + offset, size, path); + MachOChecker::make(p + offset, size, path, verifierDstRoot); else throw "in universal file, arm slice does not contain arm mach-o"; break; +#endif +#if SUPPORT_ARCH_arm64 + case CPU_TYPE_ARM64: + if ( MachOChecker::validFile(p + offset) ) + MachOChecker::make(p + offset, size, path, verifierDstRoot); + else + throw "in universal file, arm64 slice does not contain arm mach-o"; + break; +#endif default: throwf("in universal file, unknown architecture slice 0x%x\n", cputype); } } } else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); - } - else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); - } - else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); } else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); } +#if SUPPORT_ARCH_arm_any else if ( MachOChecker::validFile(p) ) { - MachOChecker::make(p, length, path); + MachOChecker::make(p, length, path, verifierDstRoot); + } +#endif +#if SUPPORT_ARCH_arm64 + else if ( MachOChecker::validFile(p) ) { + MachOChecker::make(p, length, path, verifierDstRoot); } +#endif else { throw "not a known file type"; } @@ -1139,29 +2045,52 @@ static void check(const char* path) int main(int argc, const char* argv[]) { - try { - for(int i=1; i < argc; ++i) { - const char* arg = argv[i]; - if ( arg[0] == '-' ) { - if ( strcmp(arg, "-no_content") == 0 ) { - + bool progress = false; + const char* verifierDstRoot = NULL; + int result = 0; + for(int i=1; i < argc; ++i) { + const char* arg = argv[i]; + if ( arg[0] == '-' ) { + if ( strcmp(arg, "-progress") == 0 ) { + progress = true; + } + else if ( strcmp(arg, "-verifier_dstroot") == 0 ) { + verifierDstRoot = argv[++i]; + } + 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)(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\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 { + throwf("unknown option: %s\n", arg); + } + } + else { + bool success = true; + try { + check(arg, verifierDstRoot); + } + catch (const char* msg) { + if ( verifierDstRoot ) { + printf("os_dylib_malformed\twarn\t%s\n", msg); } else { - throwf("unknown option: %s\n", arg); + fprintf(stderr, "machocheck failed: %s\n", msg); + result = 1; + success = false; } } - else { - check(arg); - } + if ( success && progress ) + printf("ok: %s\n", arg); } } - catch (const char* msg) { - fprintf(stderr, "machocheck failed: %s\n", msg); - return 1; - } - return 0; + return result; } - -