uint8_t loadCommandSizeMask();
void checkSymbolTable();
void checkInitTerms();
+ void checkThreadedRebaseBind();
void checkIndirectSymbolTable();
void checkRelocations();
void checkExternalReloation(const macho_relocation_info<P>* reloc);
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<P>*);
pint_t getEntryPoint(const macho_thread_command<P>*);
bool fWriteableSegmentWithAddrOver4G;
bool fSlidableImage;
bool fHasLC_RPATH;
+ bool fIsDebugVariant;
+ uint64_t fBaseAddress = 0;
const macho_segment_command<P>* fFirstSegment;
const macho_segment_command<P>* fFirstWritableSegment;
const macho_segment_command<P>* fTEXTSegment;
MachOChecker<A>::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
checkInitTerms();
+ checkThreadedRebaseBind();
+
if ( verifierDstRoot != NULL )
verify();
}
if ( segCmd->vmaddr() > 0x100000000ULL )
fWriteableSegmentWithAddrOver4G = true;
}
+ if ( (segCmd->fileoff() == 0) && (segCmd->filesize() != 0) )
+ fBaseAddress = segCmd->vmaddr();
// check section ranges
const macho_section<P>* const sectionsStart = (macho_section<P>*)((char*)segCmd + sizeof(macho_segment_command<P>));
// 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 <typename A>
void MachOChecker<A>::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();
}
}
}
+template <typename A>
+void MachOChecker<A>::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<P>* cmd = (macho_load_command<P>*)((uint8_t*)fHeader + sizeof(macho_header<P>));
+ for (uint32_t i = 0; i < cmd_count; ++i) {
+ const macho_build_version_command<P>* 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<P>*)cmd;
+ if ( buildVersCmd->platform() != PLATFORM_IOSMAC )
+ bad = true;
+ break;
+ }
+ cmd = (const macho_load_command<P>*)(((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 <typename A>
void MachOChecker<A>::checkIndirectSymbolTable()
{
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;
}
+template <typename A>
+void MachOChecker<A>::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 <>
}
template <typename A>
-bool MachOChecker<A>::addressIsRebaseSite(pint_t targetAddr)
+bool MachOChecker<A>::addressIsRebaseSite(pint_t targetAddr, pint_t& pointeeAddr)
{
// look at local relocs
const macho_relocation_info<P>* const localRelocsEnd = &fLocalRelocations[fLocalRelocationsCount];
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 <typename A>
bool MachOChecker<A>::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);
}
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 {