+// 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 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;
+ *encryptCmd = NULL;
+
+ const uint32_t cmd_count = mh->ncmds;
+ 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
+ // <rdar://problem/19986776> 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 ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) {
+ #if TARGET_IPHONE_SIMULATOR
+ // Note: should check on all platforms that __LINKEDIT is read-only, but <rdar://problem/22637626&22525618>
+ if ( segCmd->initprot != VM_PROT_READ )
+ throw "malformed mach-o image: __LINKEDIT segment does not have read-only permissions";
+ #endif
+ if ( segCmd->fileoff == 0 )
+ throw "malformed mach-o image: __LINKEDIT has fileoff==0 which overlaps mach_header";
+ if ( linkeditSegCmd != NULL )
+ throw "malformed mach-o image: multiple __LINKEDIT segments";
+ linkeditSegCmd = segCmd;
+ }
+ else {
+ if ( segCmd->initprot & 0xFFFFFFF8 )
+ dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in initprot", segCmd->segname, segCmd->initprot);
+ if ( segCmd->maxprot & 0xFFFFFFF8 )
+ dyld::throwf("malformed mach-o image: %s segment has invalid permission bits (0x%X) in maxprot", segCmd->segname, segCmd->maxprot);
+ if ( (segCmd->initprot != 0) && ((segCmd->initprot & VM_PROT_READ) == 0) )
+ dyld::throwf("malformed mach-o image: %s segment is not mapped readable", segCmd->segname);
+ }
+ if ( (segCmd->fileoff == 0) && (segCmd->filesize != 0) ) {
+ if ( (segCmd->initprot & VM_PROT_READ) == 0 )
+ dyld::throwf("malformed mach-o image: %s segment maps start of file but is not readable", segCmd->segname);
+ if ( (segCmd->initprot & VM_PROT_WRITE) == VM_PROT_WRITE ) {
+ if ( context.strictMachORequired )
+ dyld::throwf("malformed mach-o image: %s segment maps start of file but is writable", segCmd->segname);
+ }
+ if ( segCmd->filesize < (sizeof(macho_header) + mh->sizeofcmds) )
+ dyld::throwf("malformed mach-o image: %s segment does not map all of load commands", segCmd->segname);
+ if ( startOfFileSegCmd != NULL )
+ dyld::throwf("malformed mach-o image: multiple segments map start of file: %s %s", startOfFileSegCmd->segname, segCmd->segname);
+ startOfFileSegCmd = segCmd;
+ }
+ if ( context.strictMachORequired ) {
+ uintptr_t vmStart = segCmd->vmaddr;
+ uintptr_t vmSize = segCmd->vmsize;
+ uintptr_t vmEnd = vmStart + vmSize;
+ uintptr_t fileStart = segCmd->fileoff;
+ uintptr_t fileSize = segCmd->filesize;
+ if ( (intptr_t)(vmSize) < 0 )
+ dyld::throwf("malformed mach-o image: segment load command %s vmsize too large in %s", segCmd->segname, path);
+ if ( vmStart > vmEnd )
+ dyld::throwf("malformed mach-o image: segment load command %s wraps around address space", segCmd->segname);
+ if ( vmSize != fileSize ) {
+ if ( segCmd->initprot == 0 ) {
+ // allow: fileSize == 0 && initprot == 0 e.g. __PAGEZERO
+ // allow: vmSize == 0 && initprot == 0 e.g. __LLVM
+ if ( (fileSize != 0) && (vmSize != 0) )
+ dyld::throwf("malformed mach-o image: unaccessable segment %s has non-zero filesize and vmsize", segCmd->segname);
+ }
+ else {
+ // allow: vmSize > fileSize && initprot != X e.g. __DATA
+ if ( vmSize < fileSize ) {
+ dyld::throwf("malformed mach-o image: segment %s has vmsize < filesize", segCmd->segname);
+ }
+ if ( segCmd->initprot & VM_PROT_EXECUTE ) {
+ dyld::throwf("malformed mach-o image: segment %s has vmsize != filesize and is executable", segCmd->segname);
+ }
+ }
+ }
+ if ( inCache ) {
+ if ( (fileSize != 0) && (segCmd->initprot == (VM_PROT_READ | VM_PROT_EXECUTE)) ) {
+ if ( foundLoadCommandSegment )
+ throw "load commands in multiple segments";
+ foundLoadCommandSegment = true;
+ }
+ }
+ else if ( (fileStart < mh->sizeofcmds) && (fileSize != 0) ) {
+ // <rdar://problem/7942521> 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:
+ if ( cmd->cmdsize != sizeof(linkedit_data_command) )
+ throw "malformed mach-o image: LC_CODE_SIGNATURE size wrong";
+ // <rdar://problem/22799652> 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";
+ // <rdar://problem/22799652> 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";
+ // <rdar://problem/22799652> 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
+ // <rdar://problem/26797345> error when loading iOS Simulator mach-o binary into macOS process
+ case LC_VERSION_MIN_WATCHOS:
+ case LC_VERSION_MIN_TVOS:
+ case LC_VERSION_MIN_IPHONEOS:
+ if ( !context.marzipan )
+ throw "mach-o, but built for simulator (not macOS)";
+ break;
+#endif
+ }
+ cmd = nextCmd;