// Source: https://github.com/apple-oss-distributions/dyld/blob/main/mach_o/Header.cpp#L1519 /* * Copyright (c) 2021 Apple Inc. All rights reserved. * @APPLE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this * file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_LICENSE_HEADER_END@ */ #include #include #include #include #include #include #if !TARGET_OS_EXCLAVEKIT #include #endif #include "Array.h" #include "Header.h" #include "Architecture.h" #include "Misc.h" #include "Policy.h" #include "LoggingStub.h" using mach_o::Architecture; using mach_o::Platform; using mach_o::PlatformAndVersions; using mach_o::Policy; namespace mach_o { // // MARK: --- methods that read mach_header --- // bool Header::hasMachOMagic() const { return ((mh.magic == MH_MAGIC) || (mh.magic == MH_MAGIC_64)); } bool Header::hasMachOBigEndianMagic() const { return ((mh.magic == MH_CIGAM) || (mh.magic == MH_CIGAM_64)); } bool Header::is64() const { return (mh.magic == MH_MAGIC_64); } uint32_t Header::machHeaderSize() const { return is64() ? sizeof(mach_header_64) : sizeof(mach_header); } uint32_t Header::pointerSize() const { if ( mh.magic == MH_MAGIC_64 ) return 8; else return 4; } bool Header::uses16KPages() const { switch ( mh.cputype ) { case CPU_TYPE_ARM64: case CPU_TYPE_ARM64_32: return true; case CPU_TYPE_ARM: // iOS is 16k aligned for armv7/armv7s and watchOS armv7k is 16k aligned return mh.cpusubtype == CPU_SUBTYPE_ARM_V7K; default: return false; } } bool Header::isArch(const char* aName) const { return (strcmp(aName, this->archName()) == 0); } const char* Header::archName() const { return Architecture(&mh).name(); } Architecture Header::arch() const { return Architecture(&mh); } bool Header::inDyldCache() const { return (mh.flags & MH_DYLIB_IN_CACHE); } bool Header::isDyldManaged() const { switch ( mh.filetype ) { case MH_BUNDLE: case MH_EXECUTE: case MH_DYLIB: return ((mh.flags & MH_DYLDLINK) != 0); default: break; } return false; } bool Header::isDylib() const { return (mh.filetype == MH_DYLIB); } bool Header::isBundle() const { return (mh.filetype == MH_BUNDLE); } bool Header::isMainExecutable() const { return (mh.filetype == MH_EXECUTE); } bool Header::isDynamicExecutable() const { if ( mh.filetype != MH_EXECUTE ) return false; // static executables do not have dyld load command return hasLoadCommand(LC_LOAD_DYLINKER); } bool Header::isKextBundle() const { return (mh.filetype == MH_KEXT_BUNDLE); } bool Header::isObjectFile() const { return (mh.filetype == MH_OBJECT); } bool Header::isFileSet() const { return (mh.filetype == MH_FILESET); } bool Header::isPIE() const { return (mh.flags & MH_PIE); } bool Header::isPreload() const { return (mh.filetype == MH_PRELOAD); } bool Header::hasWeakDefs() const { return (mh.flags & MH_WEAK_DEFINES); } bool Header::usesWeakDefs() const { return (mh.flags & MH_BINDS_TO_WEAK); } bool Header::hasThreadLocalVariables() const { return (mh.flags & MH_HAS_TLV_DESCRIPTORS); } const Header* Header::isMachO(std::span content) { if ( content.size() < sizeof(mach_header) ) return nullptr; const Header* mh = (const Header*)content.data(); if ( mh->hasMachOMagic() ) return mh; return nullptr; } bool Header::mayHaveTextFixups() const { // only i386 binaries support text fixups if ( mh.cputype == CPU_TYPE_I386 ) return true; // and x86_64 kext bundles if ( isKextBundle() && (mh.cputype == CPU_TYPE_X86_64) ) return true; return false; } bool Header::hasSubsectionsViaSymbols() const { return (this->mh.flags & MH_SUBSECTIONS_VIA_SYMBOLS) != 0; } bool Header::noReexportedDylibs() const { return (this->mh.flags & MH_NO_REEXPORTED_DYLIBS) != 0; } bool Header::isAppExtensionSafe() const { return (this->mh.flags & MH_APP_EXTENSION_SAFE) != 0; } bool Header::isSimSupport() const { return (this->mh.flags & MH_SIM_SUPPORT) != 0; } // // MARK: --- methods for validating mach-o content --- // PlatformAndVersions Header::platformAndVersions() const { // should be one platform load command (exception is zippered dylibs) __block PlatformAndVersions pvs; forEachPlatformLoadCommand(^(Platform platform, Version32 minOS, Version32 sdk) { Error err = pvs.zip({ platform, minOS, sdk }); assert(err.noError()); }); return pvs; } Error Header::validSemanticsPlatform() const { // should be one platform load command (exception is zippered dylibs) __block PlatformAndVersions pvs; __block Error badPlatform; forEachPlatformLoadCommand(^(Platform platform, Version32 minOS, Version32 sdk) { if ( badPlatform.hasError() ) return; if ( Error err = platform.valid() ) { badPlatform = std::move(err); return; } badPlatform = pvs.zip({ platform, minOS, sdk }); }); if ( badPlatform ) return std::move(badPlatform); #if BUILDING_MACHO_WRITER if ( pvs.platform.empty() ) return Error::none(); // allow empty platform in static linker #endif return pvs.platform.valid(); } Error Header::valid(uint64_t fileSize) const { if ( fileSize < sizeof(mach_header) ) return Error("file is too short"); if ( !hasMachOMagic() ) return Error("not a mach-o file (start is no MH_MAGIC[_64])"); if ( Error err = validStructureLoadCommands(fileSize) ) return err; if ( Error err = validSemanticsPlatform() ) return err; // create policy object Policy policy(arch(), platformAndVersions(), mh.filetype, false); if ( Error err = validSemanticsUUID(policy) ) return err; if ( Error err = validSemanticsInstallName(policy) ) return err; if ( Error err = validSemanticsDependents(policy) ) return err; if ( Error err = validSemanticsRPath(policy) ) return err; if ( Error err = validSemanticsSegments(policy, fileSize) ) return err; if ( Error err = validSemanticsLinkerOptions(policy) ) return err; if ( isMainExecutable() ) { if ( Error err = validSemanticsMain(policy) ) return err; } return Error::none(); } static Error stringOverflow(const load_command* cmd, uint32_t index, uint32_t strOffset) { if ( strOffset >= cmd->cmdsize ) return Error("load command #%d string offset (%u) outside its size (%u)", index, strOffset, cmd->cmdsize); const char* str = (char*)cmd + strOffset; const char* end = (char*)cmd + cmd->cmdsize; for ( const char* s = str; s < end; ++s ) { if ( *s == '\0' ) { return Error::none(); } } return Error("load command #%d string extends beyond end of load command", index); } Error Header::validStructureLoadCommands(uint64_t fileSize) const { // check load command don't exceed file length const uint64_t headerAndLCSize = mh.sizeofcmds + machHeaderSize(); if ( headerAndLCSize > fileSize ) { return Error("load commands length (%llu) exceeds length of file (%llu)", headerAndLCSize, fileSize); } // check for reconized filetype switch ( mh.filetype ) { case MH_EXECUTE: case MH_DYLIB: case MH_DYLINKER: case MH_BUNDLE: case MH_KEXT_BUNDLE: case MH_FILESET: case MH_PRELOAD: case MH_OBJECT: break; default: return Error("unknown filetype %d", mh.filetype); } // walk all load commands and sanity check them __block int index = 1; __block Error lcError; auto lcChecker = ^(const load_command* cmd, bool& stop) { const dylib_command* dylibCmd; const rpath_command* rpathCmd; const sub_umbrella_command* umbrellaCmd; const sub_client_command* clientCmd; const sub_library_command* libraryCmd; const build_version_command* buildVersCmd; const segment_command* segCmd; const segment_command_64* seg64Cmd; const fileset_entry_command* fileSetCmd; switch ( cmd->cmd ) { case LC_ID_DYLIB: case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: dylibCmd = (dylib_command*)cmd; lcError = stringOverflow(cmd, index, dylibCmd->dylib.name.offset); break; case LC_RPATH: rpathCmd = (rpath_command*)cmd; lcError = stringOverflow(cmd, index, rpathCmd->path.offset); break; case LC_SUB_UMBRELLA: umbrellaCmd = (sub_umbrella_command*)cmd; lcError = stringOverflow(cmd, index, umbrellaCmd->sub_umbrella.offset); break; case LC_SUB_CLIENT: clientCmd = (sub_client_command*)cmd; lcError = stringOverflow(cmd, index, clientCmd->client.offset); break; case LC_SUB_LIBRARY: libraryCmd = (sub_library_command*)cmd; lcError = stringOverflow(cmd, index, libraryCmd->sub_library.offset); break; case LC_SYMTAB: if ( cmd->cmdsize != sizeof(symtab_command) ) lcError = Error("load command #%d LC_SYMTAB size wrong", index); break; case LC_DYSYMTAB: if ( cmd->cmdsize != sizeof(dysymtab_command) ) lcError = Error("load command #%d LC_DYSYMTAB size wrong", index); break; case LC_SEGMENT_SPLIT_INFO: if ( cmd->cmdsize != sizeof(linkedit_data_command) ) lcError = Error("load command #%d LC_SEGMENT_SPLIT_INFO size wrong", index); break; case LC_ATOM_INFO: if ( cmd->cmdsize != sizeof(linkedit_data_command) ) lcError = Error("load command #%d LC_ATOM_INFO size wrong", index); break; case LC_FUNCTION_STARTS: if ( cmd->cmdsize != sizeof(linkedit_data_command) ) lcError = Error("load command #%d LC_FUNCTION_STARTS size wrong", index); break; case LC_DYLD_EXPORTS_TRIE: if ( cmd->cmdsize != sizeof(linkedit_data_command) ) lcError = Error("load command #%d LC_DYLD_EXPORTS_TRIE size wrong", index); break; case LC_DYLD_CHAINED_FIXUPS: if ( cmd->cmdsize != sizeof(linkedit_data_command) ) lcError = Error("load command #%d LC_DYLD_CHAINED_FIXUPS size wrong", index); break; case LC_ENCRYPTION_INFO: if ( cmd->cmdsize != sizeof(encryption_info_command) ) lcError = Error("load command #%d LC_ENCRYPTION_INFO size wrong", index); break; case LC_ENCRYPTION_INFO_64: if ( cmd->cmdsize != sizeof(encryption_info_command_64) ) lcError = Error("load command #%d LC_ENCRYPTION_INFO_64 size wrong", index); break; case LC_DYLD_INFO: case LC_DYLD_INFO_ONLY: if ( cmd->cmdsize != sizeof(dyld_info_command) ) lcError = Error("load command #%d LC_DYLD_INFO_ONLY size wrong", index); break; case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: case LC_VERSION_MIN_TVOS: case LC_VERSION_MIN_WATCHOS: if ( cmd->cmdsize != sizeof(version_min_command) ) lcError = Error("load command #%d LC_VERSION_MIN_* size wrong", index); break; case LC_UUID: if ( cmd->cmdsize != sizeof(uuid_command) ) lcError = Error("load command #%d LC_UUID size wrong", index); break; case LC_BUILD_VERSION: buildVersCmd = (build_version_command*)cmd; if ( cmd->cmdsize != (sizeof(build_version_command) + buildVersCmd->ntools * sizeof(build_tool_version)) ) lcError = Error("load command #%d LC_BUILD_VERSION size wrong", index); break; case LC_MAIN: if ( cmd->cmdsize != sizeof(entry_point_command) ) lcError = Error("load command #%d LC_MAIN size wrong", index); break; case LC_SEGMENT: segCmd = (segment_command*)cmd; if ( cmd->cmdsize != (sizeof(segment_command) + segCmd->nsects * sizeof(section)) ) lcError = Error("load command #%d LC_SEGMENT size does not match number of sections", index); break; case LC_SEGMENT_64: seg64Cmd = (segment_command_64*)cmd; if ( cmd->cmdsize != (sizeof(segment_command_64) + seg64Cmd->nsects * sizeof(section_64)) ) lcError = Error("load command #%d LC_SEGMENT_64 size does not match number of sections", index); break; case LC_FILESET_ENTRY: fileSetCmd = (fileset_entry_command*)cmd; lcError = stringOverflow(cmd, index, fileSetCmd->entry_id.offset); break; default: if ( cmd->cmd & LC_REQ_DYLD ) lcError = Error("load command #%d unknown required load command 0x%08X", index, cmd->cmd); break; } ++index; if ( lcError ) stop = true; }; if ( Error err = this->forEachLoadCommand(lcChecker) ) return err; if ( lcError ) return std::move(lcError); /* // check load commands fit in TEXT segment if ( this->isDyldManaged() ) { __block bool foundTEXT = false; __block Error segError; forEachSegment(^(const SegmentInfo& segInfo, bool& stop) { if ( strcmp(segInfo.segName, "__TEXT") == 0 ) { foundTEXT = true; if ( headerAndLCSize > segInfo.fileSize ) { segError = Error("load commands (%llu) exceed length of __TEXT segment (%llu)", headerAndLCSize, segInfo.fileSize); } if ( segInfo.fileOffset != 0 ) { segError = Error("__TEXT segment not start of mach-o (%llu)", segInfo.fileOffset); } stop = true; } }); if ( segError ) return std::move(segError); if ( !foundTEXT ) { return Error("missing __TEXT segment"); } } */ return Error::none(); } Error Header::validSemanticsUUID(const Policy& policy) const { // should have at most one LC_UUID __block unsigned uuidCount = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_UUID ) ++uuidCount; }); if ( uuidCount > 1 ) return Error("too many LC_UUID load commands"); if ( (uuidCount == 0) && policy.enforceHasUUID() ) return Error("missing LC_UUID load command"); return Error::none(); } Error Header::validSemanticsInstallName(const Policy& policy) const { __block const char* installName = nullptr; __block int foundCount = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_ID_DYLIB ) { const dylib_command* dylibCmd = (dylib_command*)cmd; installName = (char*)dylibCmd + dylibCmd->dylib.name.offset; ++foundCount; } }); if ( foundCount > 1 ) return Error("multiple LC_ID_DYLIB found"); if ( this->isDylib() ) { if ( installName == nullptr ) return Error("MH_DYLIB is missing LC_ID_DYLIB"); #if 0 // FIXME: need path plumbed down if ( policy.enforceInstallNamesAreRealPaths() ) { // new binary, so check that part after @xpath/ is real (not symlinks) if ( (strncmp(installName, "@loader_path/", 13) == 0) || (strncmp(installName, "@executable_path/", 17) == 0) ) { if ( const char* s = strchr(installName, '/') ) { while (strncmp(s, "/..", 3) == 0) s += 3; const char* trailingInstallPath = s; const char* trailingRealPath = &path[strlen(path)-strlen(trailingInstallPath)]; if ( strcmp(trailingRealPath, trailingInstallPath) != 0 ) { Error("install name '%s' contains symlinks", installName); } } } } #endif } else { if ( installName != nullptr ) return Error("found LC_ID_DYLIB found in non-MH_DYLIB"); } return Error::none(); } Error Header::validSemanticsDependents(const Policy& policy) const { // gather info __block Error dupDepError; __block int depCount = 0; const char* depPathsBuffer[256]; const char** depPaths = depPathsBuffer; const bool enforceNoDupDylibs = policy.enforceNoDuplicateDylibs(); const bool hasWarningHandler = mach_o::hasWarningHandler(); // don't use forEachDependentDylib, because it synthesizes libSystem.dylib forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { switch ( cmd->cmd ) { case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: { const dylib_command* dylibCmd = (dylib_command*)cmd; const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset; if ( (depCount < 256) && ( enforceNoDupDylibs || hasWarningHandler ) ) { for ( int i = 0; i < depCount; ++i ) { if ( strcmp(loadPath, depPaths[i]) == 0 ) { if ( enforceNoDupDylibs ) { dupDepError = Error("duplicate dependent dylib '%s'", loadPath); stop = true; } else warning(this, "duplicate dependent dylib are deprecated ('%s')", loadPath); } } depPaths[depCount] = loadPath; } ++depCount; } break; } }); if ( dupDepError ) return std::move(dupDepError); // all new binaries must link with something if ( this->isDyldManaged() && policy.enforceHasLinkedDylibs() && (depCount == 0) ) { // except for dylibs in libSystem.dylib which are ok to link with nothing (they are on bottom) const char* libSystemDir = this->builtForPlatform(Platform::driverKit, true) ? "/System/DriverKit/usr/lib/system/" : "/usr/lib/system/"; const char* installName = this->installName(); bool isNotLibSystem = (installName == nullptr) || (strncmp(installName, libSystemDir, strlen(libSystemDir)) != 0); if ( isNotLibSystem ) return Error("missing LC_LOAD_DYLIB (must link with at least libSystem.dylib)"); } return Error::none(); } Error Header::validSemanticsRPath(const Policy& policy) const { const bool enforceNoDupRPath = policy.enforceNoDuplicateRPaths(); if ( !enforceNoDupRPath && !hasWarningHandler() ) return Error::none(); __block Error dupRPathError; __block int rpathCount = 0; const char* rpathsBuffer[64]; const char** rpaths = rpathsBuffer; forEachRPath(^(const char* rPath, bool& stop) { if ( rpathCount < 64 ) { for ( int i = 0; i < rpathCount; ++i ) { if ( strcmp(rPath, rpaths[i]) == 0 ) { if ( enforceNoDupRPath ) { dupRPathError = Error("duplicate LC_RPATH '%s'", rPath); stop = true; } else warning(this, "duplicate LC_RPATH are deprecated ('%s')", rPath); } } rpaths[rpathCount] = rPath; } ++rpathCount; }); return std::move(dupRPathError); } #if !TARGET_OS_EXCLAVEKIT template Error Header::validSegment(const Policy& policy, uint64_t wholeFileSize, const SG* seg) const { if ( greaterThanAddOrOverflow(seg->fileoff, seg->filesize, wholeFileSize) ) return Error("segment '%s' load command content extends beyond end of file", seg->segname); // dyld should support non-allocatable __LLVM segment if ( !isObjectFile() ) { if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) return Error("segment '%s' filesize exceeds vmsize", seg->segname); } // check permission bits if ( (seg->initprot & 0xFFFFFFF8) != 0 ) { return Error("%s segment permissions has invalid bits set (0x%08X)", seg->segname, seg->initprot); } if ( policy.enforceTextSegmentPermissions() ) { if ( (strcmp(seg->segname, "__TEXT") == 0) && (seg->initprot != (VM_PROT_READ | VM_PROT_EXECUTE)) ) return Error("__TEXT segment permissions is not 'r-x'"); } if ( policy.enforceReadOnlyLinkedit() ) { if ( (strcmp(seg->segname, "__LINKEDIT") == 0) && (seg->initprot != VM_PROT_READ) ) return Error("__LINKEDIT segment permissions is not 'r--'"); } if ( policy.enforceDataSegmentPermissions() ) { if ( (strcmp(seg->segname, "__DATA") == 0) && (seg->initprot != (VM_PROT_READ | VM_PROT_WRITE)) ) return Error("__DATA segment permissions is not 'rw-'"); if ( strcmp(seg->segname, "__DATA_CONST") == 0 ) { if ( seg->initprot != (VM_PROT_READ | VM_PROT_WRITE) ) return Error("__DATA_CONST segment permissions is not 'rw-'"); if ( (seg->flags & SG_READ_ONLY) == 0 ) { if ( this->isDylib() && this->hasSplitSegInfo() ) { // dylibs in dyld cache are allowed to not have SG_READ_ONLY set } else { return Error("__DATA_CONST segment missing SG_READ_ONLY flag"); } } } } // check for vmaddr wrapping if ( (seg->vmaddr + seg->vmsize) < seg->vmaddr ) return Error("'%s' segment vm range wraps", seg->segname); // check sections are within its segment const SC* const sectionsStart = (SC*)((char*)seg + sizeof(SG)); const SC* const sectionsEnd = §ionsStart[seg->nsects]; for ( const SC* sect = sectionsStart; (sect < sectionsEnd); ++sect ) { if ( (int64_t)(sect->size) < 0 ) { return Error("section '%s' size too large 0x%lX", sect->sectname, (size_t)sect->size); } else if ( sect->addr < seg->vmaddr ) { return Error("section '%s' start address 0x%lX is before containing segment's address 0x%0lX", sect->sectname, (size_t)sect->addr, (size_t)seg->vmaddr); } else if ( policy.enforceSectionsInSegment() && (sect->addr + sect->size > seg->vmaddr + seg->vmsize) ) { return Error("section '%s' end address 0x%lX is beyond containing segment's end address 0x%0lX", sect->sectname, (size_t)(sect->addr + sect->size), (size_t)(seg->vmaddr + seg->vmsize)); } } return Error::none(); } #endif // !TARGET_OS_EXCLAVEKIT struct Interval { bool overlaps(const Interval& other) const; uint64_t start; uint64_t end; }; bool Interval::overlaps(const Interval& other) const { return ((other.start < this->end) && (other.end > this->start)); } Error Header::validSemanticsSegments(const Policy& policy, uint64_t fileSize) const { // check each segment load command in isolation struct SegRange { Interval vm; Interval file; const char* name; }; STACK_ALLOC_OVERFLOW_SAFE_ARRAY(SegRange, ranges, 12); __block Error lcError; __block bool hasTEXT = false; __block bool hasLINKEDIT = false; __block uintptr_t segmentIndexText = 0; __block uintptr_t segmentIndexLinkedit = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { const segment_command_64* seg64 = (segment_command_64*)cmd; if ( strcmp(seg64->segname, "__TEXT") == 0 ) { hasTEXT = true; segmentIndexText = ranges.count(); } else if ( strcmp(seg64->segname, "__LINKEDIT") == 0 ) { hasLINKEDIT = true; segmentIndexLinkedit = ranges.count(); } lcError = validSegment(policy, fileSize, seg64); ranges.push_back({ { seg64->vmaddr, seg64->vmaddr + seg64->vmsize }, { seg64->fileoff, seg64->fileoff + seg64->filesize }, seg64->segname }); } else if ( cmd->cmd == LC_SEGMENT ) { const segment_command* seg32 = (segment_command*)cmd; if ( strcmp(seg32->segname, "__TEXT") == 0 ) { hasTEXT = true; segmentIndexText = ranges.count(); } else if ( strcmp(seg32->segname, "__LINKEDIT") == 0 ) { hasLINKEDIT = true; segmentIndexLinkedit = ranges.count(); } lcError = validSegment(policy, fileSize, seg32); ranges.push_back({ { seg32->vmaddr, seg32->vmaddr + seg32->vmsize }, { seg32->fileoff, seg32->fileoff + seg32->filesize }, seg32->segname }); } if ( lcError ) stop = true; }); if ( lcError ) return std::move(lcError); // dynamic binaries have further restrictions if ( isDyldManaged() ) { if ( hasTEXT ) { if ( ranges[segmentIndexText].file.start != 0 ) return Error("__TEXT segment fileoffset is not zero"); const uint32_t headerAndLCSize = machHeaderSize() + mh.sizeofcmds; if ( ranges[segmentIndexText].file.end < headerAndLCSize ) return Error("load commands do not fit in __TEXT segment"); } else { return Error("missing __TEXT segment"); } // FIXME: LINKEDIT checks need to move to Analyzer //if ( !hasLINKEDIT ) // return Error("missing __LINKEDIT segment"); } // check for overlapping segments, by looking at every possible pair of segments for ( const SegRange& r1 : ranges ) { for ( const SegRange& r2 : ranges ) { if ( &r1 == &r2 ) continue; if ( r1.vm.overlaps(r2.vm) ) return Error("vm range of segment '%s' overlaps segment '%s'", r1.name, r2.name); if ( r1.file.overlaps(r2.file) ) return Error("file range of segment '%s' overlaps segment '%s'", r1.name, r2.name); } } // check segment load command order matches file content order which matches vm order // skip dyld cache because segments are moved around too much if ( policy.enforceSegmentOrderMatchesLoadCmds() && !inDyldCache() ) { const SegRange* last = nullptr; for ( const SegRange& r : ranges ) { if ( last != nullptr ) { if ( (r.file.start < last->file.start) && (r.file.start != r.file.end) ) return Error("segment '%s' file offset out of order", r.name); if ( r.vm.start < last->vm.start ) { if ( isFileSet() && (strcmp(r.name, "__PRELINK_INFO") == 0) ) { // __PRELINK_INFO may have no vmaddr set } else { return Error("segment '%s' vm address out of order", r.name); } } } last = &r; } } return Error::none(); } Error Header::validSemanticsMain(const Policy& policy) const { if ( this->inDyldCache() && policy.enforceMainFlagsCorrect() ) return Error("MH_EXECUTE has MH_DYLIB_IN_CACHE bit set"); // validate the correct number of LC_MAIN or LC_UNIXTHREAD __block Error lcError; __block uint64_t startAddress = 0; __block const entry_point_command* mainCmd = nullptr; __block const thread_command* threadCmd = nullptr; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { switch ( cmd->cmd ) { case LC_MAIN: if ( mainCmd != nullptr ) lcError = Error("multiple LC_MAIN load commands"); mainCmd = (entry_point_command*)cmd; break; case LC_UNIXTHREAD: if ( threadCmd != nullptr ) lcError = Error("multiple LC_UNIXTHREAD load commands"); threadCmd = (thread_command*)cmd; if ( !entryAddrFromThreadCmd(threadCmd, startAddress) ) lcError = Error("invalid LC_UNIXTHREAD"); break; } }); if ( lcError ) return std::move(lcError); if ( (mainCmd != nullptr) && (threadCmd != nullptr) ) return Error("can't have LC_MAIN and LC_UNIXTHREAD load commands"); if ( this->builtForPlatform(Platform::driverKit) ) { if ( (mainCmd != nullptr) || (threadCmd != nullptr) ) return Error("LC_MAIN not allowed for driverkit"); } else { if ( (mainCmd == nullptr) && (threadCmd == nullptr) ) return Error("missing LC_MAIN or LC_UNIXTHREAD in main executable"); } // FIXME: validate LC_MAIN or LC_UNIXTHREAD points into executable segment return Error::none(); } Error Header::validSemanticsLinkerOptions(const Policy& policy) const { __block Error lcError; forEachLoadCommandSafe(^(const load_command *cmd, bool &stop) { if ( cmd->cmd == LC_LINKER_OPTION ) { const char* begin = (char*)cmd + sizeof(linker_option_command); const char* end = (char*)cmd + cmd->cmdsize; const uint32_t count = ((linker_option_command*)cmd)->count; for ( uint32_t i = 0; i < count; ++i ) { const char* next = begin + strlen(begin) + 1; if ( next > end ) { lcError = Error("malformed LC_LINKER_OPTION command"); stop = true; return; } begin = next; } } }); return std::move(lcError); } Error Header::forEachLoadCommand(void (^callback)(const load_command* cmd, bool& stop)) const { bool stop = false; const load_command* startCmds = nullptr; if ( mh.magic == MH_MAGIC_64 ) startCmds = (load_command*)((char*)this + sizeof(mach_header_64)); else if ( mh.magic == MH_MAGIC ) startCmds = (load_command*)((char*)this + sizeof(mach_header)); else if ( hasMachOBigEndianMagic() ) return Error("big endian mach-o file"); else { const uint32_t* h = (uint32_t*)this; return Error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h[1]); } if ( mh.filetype > 12 ) return Error("unknown mach-o filetype (%u)", mh.filetype); // const uint32_t ptrSize = this->pointerSize(); const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh.sizeofcmds); const load_command* cmd = startCmds; for ( uint32_t i = 1; (i <= mh.ncmds) && !stop; ++i ) { const load_command* nextCmd = (load_command*)((char*)cmd + cmd->cmdsize); if ( cmd >= cmdsEnd ) { return Error("malformed load command (%d of %d) at %p with mh=%p, off end of load commands", i, mh.ncmds, cmd, this); } if ( cmd->cmdsize < 8 ) { return Error("malformed load command (%d of %d) at %p with mh=%p, size (0x%X) too small", i, mh.ncmds, cmd, this, cmd->cmdsize); } #if 0 // check the cmdsize is pointer aligned if ( checks.pointerAlignedLoadCommands ) { if ( (cmd->cmdsize % ptrSize) != 0 ) { return Error("malformed load command (%d of %d) at %p with mh=%p, size (0x%X) is not pointer sized", i, mh.ncmds, cmd, this, cmd->cmdsize); } } #endif if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) { return Error("malformed load command (%d of %d) at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, mh.ncmds, cmd, this, cmd->cmdsize, cmdsEnd); } callback(cmd, stop); cmd = nextCmd; } return Error::none(); } // This forEach is only used after the load commands have been validated, so no need to return Error and handle it void Header::forEachLoadCommandSafe(void (^callback)(const load_command* cmd, bool& stop)) const { if ( Error err = forEachLoadCommand(callback) ) assert("Header::forEachLoadCommand()"); } bool Header::hasLoadCommand(uint32_t cmdNum) const { __block bool hasLC = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == cmdNum ) { hasLC = true; stop = true; } }); return hasLC; } bool Header::isStaticExecutable() const { if ( mh.filetype != MH_EXECUTE ) return false; // static executables do not have dyld load command return !hasLoadCommand(LC_LOAD_DYLINKER); } // // MARK: --- methods that read Platform load commands --- // void Header::forEachPlatformLoadCommand(void (^handler)(Platform platform, Version32 minOS, Version32 sdk)) const { __block bool foundPlatform = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { const build_version_command* buildCmd = (build_version_command*)cmd; const version_min_command* versCmd = (version_min_command*)cmd; uint32_t sdk; switch ( cmd->cmd ) { case LC_BUILD_VERSION: handler(Platform(buildCmd->platform), Version32(buildCmd->minos), Version32(buildCmd->sdk)); foundPlatform = true; break; case LC_VERSION_MIN_MACOSX: sdk = versCmd->sdk; // The original LC_VERSION_MIN_MACOSX did not have an sdk field, assume sdk is same as minOS for those old binaries if ( sdk == 0 ) sdk = versCmd->version; handler(Platform::macOS, Version32(versCmd->version), Version32(sdk)); foundPlatform = true; break; case LC_VERSION_MIN_IPHONEOS: if ( (mh.cputype == CPU_TYPE_X86_64) || (mh.cputype == CPU_TYPE_I386) ) handler(Platform::iOS_simulator, Version32(versCmd->version), Version32(versCmd->sdk)); // old sim binary else handler(Platform::iOS, Version32(versCmd->version), Version32(versCmd->sdk)); foundPlatform = true; break; case LC_VERSION_MIN_TVOS: if ( mh.cputype == CPU_TYPE_X86_64 ) handler(Platform::tvOS_simulator, Version32(versCmd->version), Version32(versCmd->sdk)); // old sim binary else handler(Platform::tvOS, Version32(versCmd->version), Version32(versCmd->sdk)); foundPlatform = true; break; case LC_VERSION_MIN_WATCHOS: if ( (mh.cputype == CPU_TYPE_X86_64) || (mh.cputype == CPU_TYPE_I386) ) handler(Platform::watchOS_simulator, Version32(versCmd->version), Version32(versCmd->sdk)); // old sim binary else handler(Platform::watchOS, Version32(versCmd->version), Version32(versCmd->sdk)); foundPlatform = true; break; } }); #ifndef BUILDING_MACHO_WRITER // no implicit platforms in static linker if ( !foundPlatform ) { // old binary with no explicit platform #if TARGET_OS_OSX if ( (mh.cputype == CPU_TYPE_X86_64) | (mh.cputype == CPU_TYPE_I386) ) handler(Platform::macOS, Version32(10, 5), Version32(10, 5)); // guess it is a macOS 10.5 binary // // The Go linker emits non-standard binaries without a platform and we have to live with it. if ( mh.cputype == CPU_TYPE_ARM64 ) handler(Platform::macOS, Version32(11, 0), Version32(11, 0)); // guess it is a macOS 11.0 binary #endif } #endif } bool Header::builtForPlatform(Platform reqPlatform, bool onlyOnePlatform) const { PlatformAndVersions pvs = platformAndVersions(); if ( pvs.platform == reqPlatform ) return true; if ( onlyOnePlatform ) return false; __block bool match = false; pvs.unzip(^(PlatformAndVersions pvers) { match |= pvers.platform == reqPlatform; }); return match; } bool Header::isZippered() const { return platformAndVersions().platform == Platform::zippered; } bool Header::allowsAlternatePlatform() const { __block bool result = false; this->forEachSection(^(const SectionInfo& info, bool& stop) { if ( (info.sectionName == "__allow_alt_plat") && info.segmentName.starts_with("__DATA") ) { result = true; stop = true; } }); return result; } const char* Header::installName() const { const char* name; Version32 compatVersion; Version32 currentVersion; if ( getDylibInstallName(&name, &compatVersion, ¤tVersion) ) return name; return nullptr; } bool Header::getDylibInstallName(const char** installName, Version32* compatVersion, Version32* currentVersion) const { __block bool found = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_ID_DYLIB ) { const dylib_command* dylibCmd = (dylib_command*)cmd; *compatVersion = Version32(dylibCmd->dylib.compatibility_version); *currentVersion = Version32(dylibCmd->dylib.current_version); *installName = (char*)dylibCmd + dylibCmd->dylib.name.offset; found = true; stop = true; } }); return found; } bool Header::getUuid(uuid_t uuid) const { __block bool found = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_UUID ) { const uuid_command* uc = (const uuid_command*)cmd; memcpy(uuid, uc->uuid, sizeof(uuid_t)); found = true; stop = true; } }); if ( !found ) bzero(uuid, sizeof(uuid_t)); return found; } const char* Header::dependentDylibLoadPath(uint32_t depIndex) const { __block uint32_t curIndex = 0; __block const char* result = nullptr; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { switch ( cmd->cmd ) { case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: { const dylib_command* dylibCmd = (dylib_command*)cmd; if ( curIndex == depIndex ) result = (char*)dylibCmd + dylibCmd->dylib.name.offset; ++curIndex; } break; } }); return result; } uint32_t Header::dependentDylibCount(bool* allDepsAreNormal) const { if ( allDepsAreNormal != nullptr ) *allDepsAreNormal = true; __block unsigned count = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { switch ( cmd->cmd ) { case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: if ( allDepsAreNormal != nullptr ) *allDepsAreNormal = false; // record if any linkages were weak, re-export, or upward ++count; break; case LC_LOAD_DYLIB: ++count; break; } }); return count; } void Header::forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, Version32 compatVersion, Version32 curVersion, bool& stop)) const { __block unsigned count = 0; __block bool stopped = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { switch ( cmd->cmd ) { case LC_LOAD_DYLIB: case LC_LOAD_WEAK_DYLIB: case LC_REEXPORT_DYLIB: case LC_LOAD_UPWARD_DYLIB: { const dylib_command* dylibCmd = (dylib_command*)cmd; const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset; callback(loadPath, (cmd->cmd == LC_LOAD_WEAK_DYLIB), (cmd->cmd == LC_REEXPORT_DYLIB), (cmd->cmd == LC_LOAD_UPWARD_DYLIB), Version32(dylibCmd->dylib.compatibility_version), Version32(dylibCmd->dylib.current_version), stop); ++count; if ( stop ) stopped = true; } break; } }); // everything must link with something if ( (count == 0) && !stopped ) { // The dylibs that make up libSystem can link with nothing // except for dylibs in libSystem.dylib which are ok to link with nothing (they are on bottom) if ( this->builtForPlatform(Platform::driverKit, true) ) { if ( !this->isDylib() || (strncmp(this->installName(), "/System/DriverKit/usr/lib/system/", 33) != 0) ) callback("/System/DriverKit/usr/lib/libSystem.B.dylib", false, false, false, Version32(1, 0), Version32(1, 0), stopped); } else { if ( !this->isDylib() || (strncmp(this->installName(), "/usr/lib/system/", 16) != 0) ) callback("/usr/lib/libSystem.B.dylib", false, false, false, Version32(1, 0), Version32(1, 0), stopped); } } } void Header::forDyldEnv(void (^callback)(const char* envVar, bool& stop)) const { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_DYLD_ENVIRONMENT ) { const dylinker_command* envCmd = (dylinker_command*)cmd; const char* keyEqualsValue = (char*)envCmd + envCmd->name.offset; // only process variables that start with DYLD_ and end in _PATH if ( (strncmp(keyEqualsValue, "DYLD_", 5) == 0) ) { const char* equals = strchr(keyEqualsValue, '='); if ( equals != NULL ) { if ( strncmp(&equals[-5], "_PATH", 5) == 0 ) { callback(keyEqualsValue, stop); } } } } }); } bool Header::entryAddrFromThreadCmd(const thread_command* cmd, uint64_t& addr) const { const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16); const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16); uint32_t flavor = *((uint32_t*)(((char*)cmd) + 8)); switch ( mh.cputype ) { case CPU_TYPE_I386: if ( flavor == 1 ) { // i386_THREAD_STATE addr = regs32[10]; // i386_thread_state_t.eip return true; } break; case CPU_TYPE_X86_64: if ( flavor == 4 ) { // x86_THREAD_STATE64 addr = regs64[16]; // x86_thread_state64_t.rip return true; } break; case CPU_TYPE_ARM: if ( flavor == 1 ) { // ARM_THREAD_STATE addr = regs32[15]; // arm_thread_state_t.pc return true; } break; case CPU_TYPE_ARM64: if ( flavor == 6 ) { // ARM_THREAD_STATE64 addr = regs64[32]; // arm_thread_state64_t.__pc return true; } break; } return false; } // returns false if entry point not found bool Header::getEntry(uint64_t& offset, bool& usesCRT) const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_MAIN ) { entry_point_command* mainCmd = (entry_point_command*)cmd; offset = mainCmd->entryoff; usesCRT = false; result = true; stop = true; } else if ( cmd->cmd == LC_UNIXTHREAD ) { uint64_t startAddress; if ( entryAddrFromThreadCmd((thread_command*)cmd, startAddress) ) { offset = startAddress - preferredLoadAddress(); usesCRT = true; result = true; } stop = true; } }); return result; } bool Header::hasCodeSignature(uint32_t& fileOffset, uint32_t& size) const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_CODE_SIGNATURE ) { linkedit_data_command* sigCmd = (linkedit_data_command*)cmd; fileOffset = sigCmd->dataoff; size = sigCmd->datasize; result = true; stop = true; } }); // FIXME: may need to ignore codesigs from pre 10.9 macOS binaries return result; } bool Header::hasIndirectSymbolTable(uint32_t& fileOffset, uint32_t& count) const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_DYSYMTAB) { dysymtab_command* dySymCmd = (dysymtab_command*)cmd; fileOffset = dySymCmd->indirectsymoff; count = dySymCmd->nindirectsyms; result = true; stop = true; } }); return result; } bool Header::hasSplitSegInfo() const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_SPLIT_INFO) { result = true; stop = true; } }); return result; } bool Header::hasAtomInfo(uint32_t& fileOffset, uint32_t& size) const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_ATOM_INFO) { linkedit_data_command* sigCmd = (linkedit_data_command*)cmd; fileOffset = sigCmd->dataoff; size = sigCmd->datasize; result = true; stop = true; } }); return result; } uint32_t Header::segmentCount() const { __block uint32_t count = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { switch ( cmd->cmd ) { case LC_SEGMENT: case LC_SEGMENT_64: ++count; break; } }); return count; } uint64_t Header::preferredLoadAddress() const { __block uint64_t textVmAddr = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { const segment_command_64* segCmd = (segment_command_64*)cmd; if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { textVmAddr = segCmd->vmaddr; stop = true; } } else if ( cmd->cmd == LC_SEGMENT ) { const segment_command* segCmd = (segment_command*)cmd; if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { textVmAddr = segCmd->vmaddr; stop = true; } } }); return textVmAddr; } int64_t Header::getSlide() const { return (long)this - (long)(this->preferredLoadAddress()); } bool Header::hasDataConst() const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { const segment_command_64* segCmd = (segment_command_64*)cmd; if ( (segCmd->flags & SG_READ_ONLY) != 0 ) result = true; } else if ( cmd->cmd == LC_SEGMENT ) { const segment_command* segCmd = (segment_command*)cmd; if ( (segCmd->flags & SG_READ_ONLY) != 0 ) result = true; } }); return result; } std::string_view Header::segmentName(uint32_t segIndex) const { __block std::string_view result; __block uint32_t segCount = 0; this->forEachSegment(^(const SegmentInfo& info, bool& stop) { if ( segIndex == segCount ) { result = info.segmentName; stop = true; } ++segCount; }); return result; } // LC_SEGMENT stores names as char[16] potentially without a null terminator. This returns a string_view for the given name static std::string_view name16(const char name[16]) { size_t length = strnlen(name, 16); return std::string_view(name, length); } void Header::forEachSegment(void (^callback)(const SegmentInfo& infos, bool& stop)) const { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { const segment_command_64* segCmd = (segment_command_64*)cmd; SegmentInfo segInfo { .segmentName=name16(segCmd->segname), .vmaddr=segCmd->vmaddr, .vmsize=segCmd->vmsize, .fileOffset=(uint32_t)segCmd->fileoff, .fileSize=(uint32_t)segCmd->filesize, .flags=segCmd->flags, .perms=(uint8_t)segCmd->initprot }; callback(segInfo, stop); } else if ( cmd->cmd == LC_SEGMENT ) { const segment_command* segCmd = (segment_command*)cmd; SegmentInfo segInfo { .segmentName=name16(segCmd->segname), .vmaddr=segCmd->vmaddr, .vmsize=segCmd->vmsize, .fileOffset=segCmd->fileoff, .fileSize=segCmd->filesize, .flags=segCmd->flags, .perms=(uint8_t)segCmd->initprot }; callback(segInfo, stop); } }); } void Header::forEachSection(void (^callback)(const SectionInfo&, bool& stop)) const { __block uint64_t prefLoadAddr = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { const segment_command_64* segCmd = (segment_command_64*)cmd; if ( strcmp(segCmd->segname, "__TEXT") == 0 ) prefLoadAddr = segCmd->vmaddr; const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64)); const section_64* const sectionsEnd = §ionsStart[segCmd->nsects]; for ( const section_64* sect = sectionsStart; !stop && (sect < sectionsEnd); ++sect ) { std::string_view sectName = name16(sect->sectname); std::string_view segName = name16(sect->segname); SectionInfo info = { segName, sectName, (uint32_t)segCmd->initprot, sect->flags, sect->align, sect->addr - prefLoadAddr, sect->size, sect->offset, sect->reloff, sect->nreloc, sect->reserved1, sect->reserved2}; callback(info, stop); } } else if ( cmd->cmd == LC_SEGMENT ) { const segment_command* segCmd = (segment_command*)cmd; if ( strcmp(segCmd->segname, "__TEXT") == 0 ) prefLoadAddr = segCmd->vmaddr; const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command)); const section* const sectionsEnd = §ionsStart[segCmd->nsects]; for ( const section* sect = sectionsStart; !stop && (sect < sectionsEnd); ++sect ) { std::string_view sectName = name16(sect->sectname); std::string_view segName = name16(sect->segname); SectionInfo info = { segName, sectName, (uint32_t)segCmd->initprot, sect->flags, sect->align, sect->addr - prefLoadAddr, sect->size, sect->offset, sect->reloff, sect->nreloc, sect->reserved1, sect->reserved2}; callback(info, stop); } } }); } // add any LINKEDIT content file-offset in load commands to this to get content const uint8_t* Header::computeLinkEditBias(bool zeroFillExpanded) const { // When there is no zerofill expansion, just add fileoffset of LINKEDIT content to mach_header to get content // If there is zerofill expansion, then zerofillExpansionAmount() needs to be added in too if ( zeroFillExpanded ) return (uint8_t*)this + zerofillExpansionAmount(); else return (uint8_t*)this; } // When loaded by dyld, LINKEDIT is farther from mach_header than in file bool Header::hasZerofillExpansion() const { return (zerofillExpansionAmount() != 0); } uint64_t Header::zerofillExpansionAmount() const { // need to find LINKEDIT and TEXT to compute difference of file offsets vs vm offsets __block uint64_t result = 0; __block uint64_t textVmAddr = 0; __block uint64_t textFileOffset = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { const segment_command_64* segCmd = (segment_command_64*)cmd; if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { textVmAddr = segCmd->vmaddr; textFileOffset = segCmd->fileoff; } else if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) { uint64_t vmOffsetToLinkedit = segCmd->vmaddr - textVmAddr; uint64_t fileOffsetToLinkedit = segCmd->fileoff; result = vmOffsetToLinkedit - fileOffsetToLinkedit; stop = true; } } else if ( cmd->cmd == LC_SEGMENT ) { const segment_command* segCmd = (segment_command*)cmd; if ( strcmp(segCmd->segname, "__TEXT") == 0 ) { textVmAddr = segCmd->vmaddr; textFileOffset = segCmd->fileoff; } else if ( strcmp(segCmd->segname, "__LINKEDIT") == 0 ) { uint64_t vmOffsetToLinkedit = segCmd->vmaddr - textVmAddr; uint64_t fileOffsetToLinkedit = segCmd->fileoff - textFileOffset; result = vmOffsetToLinkedit - fileOffsetToLinkedit; stop = true; } } }); return result; } bool Header::hasCustomStackSize(uint64_t& size) const { __block bool result = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_MAIN ) { const entry_point_command* entryPointCmd = (entry_point_command*)cmd; size = entryPointCmd->stacksize; result = true; stop = true; } }); return result; } bool Header::isRestricted() const { __block bool result = false; this->forEachSection(^(const SectionInfo& info, bool& stop) { if ( (info.segmentName == "__RESTRICT") && (info.sectionName == "__restrict") ) { result = true; stop = true; } }); return result; } bool Header::hasInterposingTuples() const { __block bool hasInterposing = false; this->forEachSection(^(const SectionInfo& info, bool& stop) { if ( ((info.flags & SECTION_TYPE) == S_INTERPOSING) || ((info.sectionName == "__interpose") && (info.segmentName.starts_with("__DATA") || info.segmentName.starts_with("__AUTH"))) ) { hasInterposing = true; stop = true; } }); return hasInterposing; } bool Header::hasObjC() const { __block bool hasObjCInfo = false; this->forEachSection(^(const SectionInfo& info, bool& stop) { if ( (info.sectionName == "__objc_imageinfo") && info.segmentName.starts_with("__DATA") ) { hasObjCInfo = true; stop = true; } }); return hasObjCInfo; } bool Header::hasEncryptionInfo(uint32_t& cryptId, uint32_t& textOffset, uint32_t& size) const { if ( const encryption_info_command* encCmd = findFairPlayEncryptionLoadCommand() ) { cryptId = encCmd->cryptid; textOffset = encCmd->cryptoff; size = encCmd->cryptsize; return true; } textOffset = 0; size = 0; return false; } bool Header::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size) const { // Note: cryptid is 0 in just-built apps. The AppStore sets cryptid to 1 uint32_t cryptId = 0; return hasEncryptionInfo(cryptId, textOffset, size) && cryptId == 1; } bool Header::canBeFairPlayEncrypted() const { return (findFairPlayEncryptionLoadCommand() != nullptr); } const encryption_info_command* Header::findFairPlayEncryptionLoadCommand() const { __block const encryption_info_command* result = nullptr; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) { result = (encryption_info_command*)cmd; stop = true; } }); return result; } bool Header::hasChainedFixups() const { // arm64e always uses chained fixups if ( Architecture(&mh) == Architecture::arm64e ) { // Not all binaries have fixups at all so check for the load commands return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_CHAINED_FIXUPS); } return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS); } bool Header::hasChainedFixupsLoadCommand() const { return hasLoadCommand(LC_DYLD_CHAINED_FIXUPS); } bool Header::hasOpcodeFixups() const { return hasLoadCommand(LC_DYLD_INFO_ONLY) || hasLoadCommand(LC_DYLD_INFO); } void Header::forEachRPath(void (^callback)(const char* rPath, bool& stop)) const { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_RPATH ) { const char* rpath = (char*)cmd + ((struct rpath_command*)cmd)->path.offset; callback(rpath, stop); } }); } void Header::forEachLinkerOption(void (^callback)(const char* opt, bool& stop)) const { forEachLoadCommandSafe(^(const load_command *cmd, bool &stop) { if ( cmd->cmd == LC_LINKER_OPTION ) { const char* begin = (char*)cmd + sizeof(linker_option_command); const uint32_t count = ((linker_option_command*)cmd)->count; for ( uint32_t i = 0; i < count; ++i ) { const char* next = begin + strlen(begin) + 1; callback(begin, stop); begin = next; } } }); } void Header::forAllowableClient(void (^callback)(const char* clientName, bool& stop)) const { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SUB_CLIENT ) { const char* clientName = (char*)cmd + ((struct sub_client_command*)cmd)->client.offset; callback(clientName, stop); } }); } const char* Header::umbrellaName() const { __block const char* result = nullptr; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SUB_FRAMEWORK ) { result = (char*)cmd + ((struct sub_framework_command*)cmd)->umbrella.offset; } }); return result; } uint32_t Header::headerAndLoadCommandsSize() const { return machHeaderSize() + mh.sizeofcmds; } uint32_t Header::fileSize() const { if ( isObjectFile() ) { // .o files do not have LINKEDIT segment, so use end of symbol table as file size __block uint32_t size = 0; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SYMTAB ) { const symtab_command* symTab = (symtab_command*)cmd; size = symTab->stroff + symTab->strsize; stop = true; } }); return size; } // compute file size from LINKEDIT fileoffset + filesize __block uint32_t lastSegmentOffset = 0; __block uint32_t lastSegmentSize = 0; forEachSegment(^(const SegmentInfo &infos, bool &stop) { if ( infos.fileOffset >= lastSegmentOffset ) { lastSegmentOffset = infos.fileOffset; lastSegmentSize = std::max(infos.fileSize, lastSegmentSize); } }); if ( lastSegmentSize == 0 ) return headerAndLoadCommandsSize(); uint32_t size; if ( __builtin_add_overflow(lastSegmentOffset, lastSegmentSize, &size) || size < headerAndLoadCommandsSize() ) assert("malformed mach-o, size smaller than header and load commands"); return size; } // // MARK: --- methods that create and modify --- // #if BUILDING_MACHO_WRITER Header* Header::make(std::span buffer, uint32_t filetype, uint32_t flags, Architecture arch, bool addImplicitTextSegment) { const size_t minHeaderAlignment = filetype == MH_OBJECT ? 8 : getpagesize(); assert(((uint64_t)buffer.data() & (minHeaderAlignment - 1)) == 0); assert(buffer.size() >= sizeof(mach_header_64)); bzero(buffer.data(), buffer.size()); Header& header = *(Header*)buffer.data(); mach_header& mh = header.mh; if ( arch.isBigEndian() ) { mh.magic = arch.is64() ? MH_CIGAM_64 : MH_CIGAM; mh.filetype = OSSwapBigToHostInt32(filetype); mh.ncmds = 0; mh.sizeofcmds = OSSwapBigToHostInt32(MH_NOUNDEFS | MH_DYLDLINK | MH_TWOLEVEL); mh.flags = OSSwapBigToHostInt32(flags); arch.set(mh); return &header; // can only construct mach_header for big-endian } else { mh.magic = arch.is64() ? MH_MAGIC_64 : MH_MAGIC; mh.filetype = filetype; mh.ncmds = 0; mh.sizeofcmds = 0; mh.flags = flags; arch.set(mh); } if ( addImplicitTextSegment && (filetype != MH_OBJECT) ) { SegmentInfo segInfo { .segmentName="__TEXT", .vmaddr=0, .vmsize=0x1000, .fileOffset=0, .fileSize=0x1000, .perms=(VM_PROT_READ | VM_PROT_EXECUTE) }; header.addSegment(segInfo, std::array { "__text" }); } return &header; } void Header::save(char savedPath[PATH_MAX]) const { ::strcpy(savedPath, "/tmp/mocko-XXXXXX"); int fd = ::mkstemp(savedPath); if ( fd != -1 ) { ::pwrite(fd, this, sizeof(Header), 0); ::close(fd); } } uint32_t Header::pointerAligned(uint32_t value) const { // mach-o requires all load command sizes to be a multiple the pointer size if ( is64() ) return ((value + 7) & (-8)); else return ((value + 3) & (-4)); } load_command* Header::firstLoadCommand() { if ( mh.magic == MH_MAGIC ) return (load_command*)((uint8_t*)this + sizeof(mach_header)); else return (load_command*)((uint8_t*)this + sizeof(mach_header_64)); } // creates space for a new load command, but does not fill in its payload load_command* Header::appendLoadCommand(uint32_t cmd, uint32_t cmdSize) { load_command* thisCmd = (load_command*)((uint8_t*)firstLoadCommand() + mh.sizeofcmds); thisCmd->cmd = cmd; thisCmd->cmdsize = cmdSize; mh.ncmds += 1; mh.sizeofcmds += cmdSize; return thisCmd; } // copies a new load command from another void Header::appendLoadCommand(const load_command* lc) { load_command* thisCmd = (load_command*)((uint8_t*)firstLoadCommand() + mh.sizeofcmds); ::memcpy(thisCmd, lc, lc->cmdsize); mh.ncmds += 1; mh.sizeofcmds += lc->cmdsize; } void Header::addBuildVersion(Platform platform, Version32 minOS, Version32 sdk, std::span tools) { assert(platform != Platform::zippered && "can't add a build command for Platform::zippered, it must be split"); uint32_t lcSize = (uint32_t)(sizeof(build_version_command) + tools.size() * sizeof(build_tool_version)); build_version_command* bv = (build_version_command*)appendLoadCommand(LC_BUILD_VERSION, lcSize); bv->platform = platform.value(); bv->minos = minOS.value(); bv->sdk = sdk.value(); bv->ntools = (uint32_t)tools.size(); if ( bv->ntools != 0 ) memcpy((uint8_t*)bv + sizeof(build_version_command), &tools[0], tools.size() * sizeof(build_tool_version)); } void Header::addMinVersion(Platform platform, Version32 minOS, Version32 sdk) { version_min_command vc; vc.cmdsize = sizeof(version_min_command); vc.version = minOS.value(); vc.sdk = sdk.value(); if ( platform == Platform::macOS ) vc.cmd = LC_VERSION_MIN_MACOSX; else if ( platform == Platform::iOS ) vc.cmd = LC_VERSION_MIN_IPHONEOS; else if ( platform == Platform::watchOS ) vc.cmd = LC_VERSION_MIN_WATCHOS; else if ( platform == Platform::tvOS ) vc.cmd = LC_VERSION_MIN_TVOS; else assert(0 && "unknown platform"); appendLoadCommand((load_command*)&vc); } void Header::setHasThreadLocalVariables() { assert(mh.filetype != MH_OBJECT); mh.flags |= MH_HAS_TLV_DESCRIPTORS; } void Header::setHasWeakDefs() { assert(mh.filetype != MH_OBJECT); mh.flags |= MH_WEAK_DEFINES; } void Header::setUsesWeakDefs() { assert(mh.filetype != MH_OBJECT); mh.flags |= MH_BINDS_TO_WEAK; } void Header::setAppExtensionSafe() { assert(mh.filetype == MH_DYLIB); mh.flags |= MH_APP_EXTENSION_SAFE; } void Header::setSimSupport() { assert(mh.filetype == MH_DYLIB); mh.flags |= MH_SIM_SUPPORT; } void Header::setNoReExportedDylibs() { assert(mh.filetype == MH_DYLIB); mh.flags |= MH_NO_REEXPORTED_DYLIBS; } void Header::addPlatformInfo(Platform platform, Version32 minOS, Version32 sdk, std::span tools) { Architecture arch(&mh); Policy policy(arch, { platform, minOS, sdk }, mh.filetype); switch ( policy.useBuildVersionLoadCommand() ) { case Policy::preferUse: case Policy::mustUse: // three macOS dylibs under libSystem need to be built with old load commands to support old simulator runtimes if ( isSimSupport() && (platform == Platform::macOS) && ((arch == Architecture::x86_64) || (arch == Architecture::i386)) ) addMinVersion(platform, minOS, sdk); else addBuildVersion(platform, minOS, sdk, tools); break; case Policy::preferDontUse: case Policy::mustNotUse: addMinVersion(platform, minOS, sdk); break; } } void Header::addNullUUID() { uuid_command uc; uc.cmd = LC_UUID; uc.cmdsize = sizeof(uuid_command); bzero(uc.uuid, 16); appendLoadCommand((load_command*)&uc); } void Header::addUniqueUUID(uuid_t copyOfUUID) { uuid_command uc; uc.cmd = LC_UUID; uc.cmdsize = sizeof(uuid_command); uuid_generate_random(uc.uuid); appendLoadCommand((load_command*)&uc); if ( copyOfUUID ) memcpy(copyOfUUID, uc.uuid, sizeof(uuid_t)); } void Header::updateUUID(uuid_t uuid) { __block bool found = false; forEachLoadCommandSafe(^(const load_command *cmd, bool &stop) { if ( cmd->cmd == LC_UUID ) { memcpy(((uuid_command*)cmd)->uuid, uuid, 16); found = true; stop = true; } }); assert(found && "updateUUID called without a LC_UUID command"); } void Header::addSegment(const SegmentInfo& info, std::span sectionNames) { if ( is64() ) { uint32_t lcSize = (uint32_t)(sizeof(segment_command_64) + sectionNames.size() * sizeof(section_64)); segment_command_64* sc = (segment_command_64*)appendLoadCommand(LC_SEGMENT_64, lcSize); strncpy(sc->segname, info.segmentName.data(), 16); sc->vmaddr = info.vmaddr; sc->vmsize = info.vmsize; sc->fileoff = info.fileOffset; sc->filesize = info.fileSize; sc->initprot = info.perms; sc->maxprot = info.perms; sc->nsects = (uint32_t)sectionNames.size(); sc->flags = info.flags; section_64* const sect = (section_64*)((uint8_t*)sc + sizeof(struct segment_command_64)); uint32_t sectionIndex = 0; for ( const char* sectName : sectionNames ) { strncpy(sect[sectionIndex].segname, info.segmentName.data(), 16); strncpy(sect[sectionIndex].sectname, sectName, 16); ++sectionIndex; } } else { uint32_t lcSize = (uint32_t)(sizeof(segment_command) + sectionNames.size() * sizeof(section)); segment_command* sc = (segment_command*)appendLoadCommand(LC_SEGMENT, lcSize); strncpy(sc->segname, info.segmentName.data(), 16); sc->vmaddr = (uint32_t)info.vmaddr; sc->vmsize = (uint32_t)info.vmsize; sc->fileoff = info.fileOffset; sc->filesize = info.fileSize; sc->initprot = info.perms; sc->maxprot = info.perms; sc->nsects = (uint32_t)sectionNames.size(); sc->flags = info.flags; section* const sect = (section*)((uint8_t*)sc + sizeof(struct segment_command)); uint32_t sectionIndex = 0; for ( const char* sectName : sectionNames ) { strncpy(sect[sectionIndex].segname, info.segmentName.data(), 16); strncpy(sect[sectionIndex].sectname, sectName, 16); ++sectionIndex; } } } void Header::updateSection(const SectionInfo& info) { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { segment_command_64* segCmd = (segment_command_64*)cmd; if (info.segmentName == segCmd->segname) { section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64)); section_64* const sectionsEnd = §ionsStart[segCmd->nsects]; for ( section_64* sect=sectionsStart; sect < sectionsEnd; ++sect ) { if ( strncmp(info.sectionName.data(), sect->sectname, 16) == 0 ) { sect->addr = info.address; sect->size = info.size; sect->offset = info.fileOffset; sect->align = info.alignment; sect->reloff = info.relocsOffset; sect->nreloc = info.relocsCount; sect->flags = info.flags; sect->reserved1 = info.reserved1; sect->reserved2 = info.reserved2; sect->reserved3 = 0; stop = true; return; } } } } else if ( cmd->cmd == LC_SEGMENT ) { segment_command* segCmd = (segment_command*)cmd; if (info.segmentName == segCmd->segname) { section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command)); section* const sectionsEnd = §ionsStart[segCmd->nsects]; for ( section* sect=sectionsStart; sect < sectionsEnd; ++sect ) { if ( strncmp(info.sectionName.data(), sect->sectname, 16) == 0 ) { sect->addr = (uint32_t)info.address; sect->size = (uint32_t)info.size; sect->offset = info.fileOffset; sect->align = info.alignment; sect->reloff = info.relocsOffset; sect->nreloc = info.relocsCount; sect->flags = info.flags; sect->reserved1 = info.reserved1; sect->reserved2 = info.reserved2; stop = true; return; } } } } }); } void Header::updateSegment(const SegmentInfo& info) { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT_64 ) { segment_command_64* segCmd = (segment_command_64*)cmd; if (info.segmentName == segCmd->segname) { segCmd->vmaddr = info.vmaddr; segCmd->vmsize = info.vmsize; segCmd->fileoff = info.fileOffset; segCmd->filesize = info.fileSize; segCmd->initprot = info.perms; segCmd->maxprot = info.perms; stop = true; return; } } else if ( cmd->cmd == LC_SEGMENT ) { segment_command* segCmd = (segment_command*)cmd; if (info.segmentName == segCmd->segname) { segCmd->vmaddr = (uint32_t)info.vmaddr; segCmd->vmsize = (uint32_t)info.vmsize; segCmd->fileoff = info.fileOffset; segCmd->filesize = info.fileSize; segCmd->initprot = info.perms; segCmd->maxprot = info.perms; stop = true; return; } } }); } void Header::addInstallName(const char* name, Version32 compatVers, Version32 currentVersion) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylib_command) + strlen(name) + 1)); dylib_command* ic = (dylib_command*)appendLoadCommand(LC_ID_DYLIB, alignedSize); ic->dylib.name.offset = sizeof(dylib_command); ic->dylib.current_version = currentVersion.value(); ic->dylib.compatibility_version = compatVers.value(); strcpy((char*)ic + ic->dylib.name.offset, name); } void Header::addDependentDylib(const char* path, bool isWeak, bool isUpward, bool isReexport, Version32 compatVers, Version32 currentVersion) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylib_command) + strlen(path) + 1)); dylib_command* dc = (dylib_command*)appendLoadCommand(LC_LOAD_DYLIB, alignedSize); if ( isReexport ) dc->cmd = LC_REEXPORT_DYLIB; else if ( isUpward ) dc->cmd = LC_LOAD_UPWARD_DYLIB; else if ( isWeak ) dc->cmd = LC_LOAD_WEAK_DYLIB; dc->dylib.name.offset = sizeof(dylib_command); dc->dylib.current_version = currentVersion.value(); dc->dylib.compatibility_version = compatVers.value(); dc->dylib.timestamp = 2; // needs to be some constant value that is different than dylib id load command strcpy((char*)dc + dc->dylib.name.offset, path); } void Header::addLibSystem() { addDependentDylib("/usr/lib/libSystem.B.dylib"); } void Header::addDylibId(CString name, Version32 compatVers, Version32 currentVersion) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylib_command) + name.size() + 1)); dylib_command* dc = (dylib_command*)appendLoadCommand(LC_ID_DYLIB, alignedSize); dc->dylib.name.offset = sizeof(dylib_command); dc->dylib.timestamp = 1; // needs to be some constant value that is different than dependent dylib dc->dylib.current_version = currentVersion.value(); dc->dylib.compatibility_version = compatVers.value(); strcpy((char*)dc + dc->dylib.name.offset, name.c_str()); } void Header::addDyldID() { const char* path = "/usr/lib/dyld"; uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylinker_command) + strlen(path) + 1)); dylinker_command* dc = (dylinker_command*)appendLoadCommand(LC_ID_DYLINKER, alignedSize); dc->name.offset = sizeof(dylinker_command); strcpy((char*)dc + dc->name.offset, path); } void Header::addDynamicLinker() { const char* path = "/usr/lib/dyld"; uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylinker_command) + strlen(path) + 1)); dylinker_command* dc = (dylinker_command*)appendLoadCommand(LC_LOAD_DYLINKER, alignedSize); dc->name.offset = sizeof(dylinker_command); strcpy((char*)dc + dc->name.offset, path); } void Header::addFairPlayEncrypted(uint32_t offset, uint32_t size) { if ( is64() ) { encryption_info_command_64 en64; en64.cmd = LC_ENCRYPTION_INFO_64; en64.cmdsize = sizeof(encryption_info_command_64); en64.cryptoff = offset; en64.cryptsize = size; en64.cryptid = 0; en64.pad = 0; appendLoadCommand((load_command*)&en64); } else { encryption_info_command en32; en32.cmd = LC_ENCRYPTION_INFO; en32.cmdsize = sizeof(encryption_info_command); en32.cryptoff = offset; en32.cryptsize = size; en32.cryptid = 0; appendLoadCommand((load_command*)&en32); } } void Header::addRPath(const char* path) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(rpath_command) + strlen(path) + 1)); rpath_command* rc = (rpath_command*)appendLoadCommand(LC_RPATH, alignedSize); rc->path.offset = sizeof(rpath_command); strcpy((char*)rc + rc->path.offset, path); } void Header::addDyldEnvVar(const char* path) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(dylinker_command) + strlen(path) + 1)); dylinker_command* dc = (dylinker_command*)appendLoadCommand(LC_DYLD_ENVIRONMENT, alignedSize); dc->name.offset = sizeof(dylinker_command); strcpy((char*)dc + dc->name.offset, path); } void Header::addAllowableClient(const char* clientName) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(sub_client_command) + strlen(clientName) + 1)); sub_client_command* ac = (sub_client_command*)appendLoadCommand(LC_SUB_CLIENT, alignedSize); ac->client.offset = sizeof(sub_client_command); strcpy((char*)ac + ac->client.offset, clientName); } void Header::addUmbrellaName(const char* umbrellaName) { uint32_t alignedSize = pointerAligned((uint32_t)(sizeof(sub_framework_command) + strlen(umbrellaName) + 1)); sub_framework_command* ac = (sub_framework_command*)appendLoadCommand(LC_SUB_FRAMEWORK, alignedSize); ac->umbrella.offset = sizeof(sub_framework_command); strcpy((char*)ac + ac->umbrella.offset, umbrellaName); } void Header::addSourceVersion(Version64 vers) { source_version_command svc; svc.cmd = LC_SOURCE_VERSION; svc.cmdsize = sizeof(source_version_command); svc.version = vers.value(); appendLoadCommand((load_command*)&svc); } void Header::setMain(uint32_t offset) { entry_point_command ec; ec.cmd = LC_MAIN; ec.cmdsize = sizeof(entry_point_command); ec.entryoff = offset; ec.stacksize = 0; appendLoadCommand((load_command*)&ec); } void Header::setCustomStackSize(uint64_t stackSize) { __block bool found = false; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if (cmd->cmd == LC_MAIN) { entry_point_command* ec = (entry_point_command*)cmd; ec->stacksize = stackSize; found = true; stop = true; } }); assert(found); } void Header::setUnixEntry(uint64_t startAddr) { // FIXME: support other archs if ( (mh.cputype == CPU_TYPE_ARM64) || (mh.cputype == CPU_TYPE_ARM64_32) ) { uint32_t lcSize = 288; uint32_t* words = (uint32_t*)appendLoadCommand(LC_UNIXTHREAD, lcSize); words[2] = 6; // flavor = ARM_THREAD_STATE64 words[3] = 68; // count = ARM_EXCEPTION_STATE64_COUNT bzero(&words[4], lcSize-16); *(uint64_t*)(&words[68]) = startAddr; // register pc = startAddr } else if ( mh.cputype == CPU_TYPE_X86_64 ) { uint32_t lcSize = 184; uint32_t* words = (uint32_t*)appendLoadCommand(LC_UNIXTHREAD, lcSize); words[2] = 4; // flavor = x86_THREAD_STATE64 words[3] = 42; // count = x86_THREAD_STATE64_COUNT bzero(&words[4], lcSize-16); *(uint64_t*)(&words[36]) = startAddr; // register pc = startAddr } else { assert(0 && "arch not supported"); } } void Header::addCodeSignature(uint32_t fileOffset, uint32_t fileSize) { linkedit_data_command lc; lc.cmd = LC_CODE_SIGNATURE; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = fileOffset; lc.datasize = fileSize; appendLoadCommand((load_command*)&lc); } void Header::setBindOpcodesInfo(uint32_t rebaseOffset, uint32_t rebaseSize, uint32_t bindsOffset, uint32_t bindsSize, uint32_t weakBindsOffset, uint32_t weakBindsSize, uint32_t lazyBindsOffset, uint32_t lazyBindsSize, uint32_t exportTrieOffset, uint32_t exportTrieSize) { dyld_info_command lc; lc.cmd = LC_DYLD_INFO_ONLY; lc.cmdsize = sizeof(dyld_info_command); lc.rebase_off = rebaseOffset; lc.rebase_size = rebaseSize; lc.bind_off = bindsOffset; lc.bind_size = bindsSize; lc.weak_bind_off = weakBindsOffset; lc.weak_bind_size = weakBindsSize; lc.lazy_bind_off = lazyBindsOffset; lc.lazy_bind_size = lazyBindsSize; lc.export_off = exportTrieOffset; lc.export_size = exportTrieSize; appendLoadCommand((load_command*)&lc); } void Header::setChainedFixupsInfo(uint32_t cfOffset, uint32_t cfSize) { linkedit_data_command lc; lc.cmd = LC_DYLD_CHAINED_FIXUPS; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = cfOffset; lc.datasize = cfSize; appendLoadCommand((load_command*)&lc); } void Header::setExportTrieInfo(uint32_t offset, uint32_t size) { linkedit_data_command lc; lc.cmd = LC_DYLD_EXPORTS_TRIE; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = offset; lc.datasize = size; appendLoadCommand((load_command*)&lc); } void Header::setSplitSegInfo(uint32_t offset, uint32_t size) { linkedit_data_command lc; lc.cmd = LC_SEGMENT_SPLIT_INFO; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = offset; lc.datasize = size; appendLoadCommand((load_command*)&lc); } void Header::setDataInCode(uint32_t offset, uint32_t size) { linkedit_data_command lc; lc.cmd = LC_DATA_IN_CODE; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = offset; lc.datasize = size; appendLoadCommand((load_command*)&lc); } void Header::setFunctionStarts(uint32_t offset, uint32_t size) { linkedit_data_command lc; lc.cmd = LC_FUNCTION_STARTS; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = offset; lc.datasize = size; appendLoadCommand((load_command*)&lc); } void Header::setAtomInfo(uint32_t offset, uint32_t size) { linkedit_data_command lc; lc.cmd = LC_ATOM_INFO; lc.cmdsize = sizeof(linkedit_data_command); lc.dataoff = offset; lc.datasize = size; appendLoadCommand((load_command*)&lc); } void Header::setSymbolTable(uint32_t nlistOffset, uint32_t nlistCount, uint32_t stringPoolOffset, uint32_t stringPoolSize, uint32_t localsCount, uint32_t globalsCount, uint32_t undefCount, uint32_t indOffset, uint32_t indCount) { symtab_command stc; stc.cmd = LC_SYMTAB; stc.cmdsize = sizeof(symtab_command); stc.symoff = nlistOffset; stc.nsyms = nlistCount; stc.stroff = stringPoolOffset; stc.strsize = stringPoolSize; appendLoadCommand((load_command*)&stc); dysymtab_command dstc; bzero(&dstc, sizeof(dstc)); dstc.cmd = LC_DYSYMTAB; dstc.cmdsize = sizeof(dysymtab_command); dstc.ilocalsym = 0; dstc.nlocalsym = localsCount; dstc.iextdefsym = localsCount; dstc.nextdefsym = globalsCount; dstc.iundefsym = localsCount+globalsCount; dstc.nundefsym = undefCount; dstc.indirectsymoff = indOffset; dstc.nindirectsyms = indCount; appendLoadCommand((load_command*)&dstc); } void Header::addLinkerOption(std::span buffer, uint32_t count) { uint32_t cmdSize = pointerAligned(sizeof(linker_option_command) + (uint32_t)buffer.size()); linker_option_command* lc = (linker_option_command*)appendLoadCommand(LC_LINKER_OPTION, cmdSize); lc->cmd = LC_LINKER_OPTION; lc->cmdsize = cmdSize; lc->count = count; memcpy((uint8_t*)(lc + 1), buffer.data(), buffer.size()); } Header::LinkerOption Header::LinkerOption::make(std::span opts) { LinkerOption out; out.count = (uint32_t)opts.size(); assert(out.count == opts.size()); for ( CString option : opts ) { if ( option.empty() ) continue; size_t previousSize = out.buffer.size(); out.buffer.resize(previousSize + option.size() + 1); option.strcpy((char*)out.buffer.data() + previousSize); } return out; } load_command* Header::findLoadCommand(uint32_t cmdNum) { __block load_command* result = nullptr; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == cmdNum ) { result = (load_command*)cmd; stop = true; } }); return result; } void Header::removeLoadCommand(void (^callback)(const load_command* cmd, bool& remove, bool& stop)) { bool stop = false; const load_command* startCmds = nullptr; if ( mh.magic == MH_MAGIC_64 ) startCmds = (load_command*)((char*)this + sizeof(mach_header_64)); else if ( mh.magic == MH_MAGIC ) startCmds = (load_command*)((char*)this + sizeof(mach_header)); else if ( hasMachOBigEndianMagic() ) return; // can't process big endian mach-o else { //const uint32_t* h = (uint32_t*)this; //diag.error("file does not start with MH_MAGIC[_64]: 0x%08X 0x%08X", h[0], h [1]); return; // not a mach-o file } const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh.sizeofcmds); auto cmd = (load_command*)startCmds; const uint32_t origNcmds = mh.ncmds; unsigned bytesRemaining = mh.sizeofcmds; for ( uint32_t i = 0; i < origNcmds; ++i ) { bool remove = false; auto nextCmd = (load_command*)((char*)cmd + cmd->cmdsize); if ( cmd->cmdsize < 8 ) { //diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) too small", i, mh.ncmds, cmd, this, cmd->cmdsize); return; } if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) { //diag.error("malformed load command #%d of %d at %p with mh=%p, size (0x%X) is too large, load commands end at %p", i, mh.ncmds, cmd, this, cmd->cmdsize, cmdsEnd); return; } callback(cmd, remove, stop); if ( remove ) { mh.sizeofcmds -= cmd->cmdsize; ::memmove((void*)cmd, (void*)nextCmd, bytesRemaining); mh.ncmds--; } else { bytesRemaining -= cmd->cmdsize; cmd = nextCmd; } if ( stop ) break; } if ( cmd ) ::bzero(cmd, bytesRemaining); } uint32_t Header::relocatableHeaderAndLoadCommandsSize(bool is64, uint32_t sectionCount, uint32_t platformsCount, std::span linkerOptions) { uint32_t size = 0; if ( is64 ) { size += sizeof(mach_header_64); size += sizeof(segment_command_64); size += sizeof(section_64) * sectionCount; } else { size += sizeof(mach_header); size += sizeof(segment_command); size += sizeof(section) * sectionCount; } size += sizeof(symtab_command); size += sizeof(dysymtab_command); size += sizeof(build_version_command) * platformsCount; size += sizeof(linkedit_data_command); for ( Header::LinkerOption opt : linkerOptions ) { size += opt.lcSize(); } return size; } void Header::setRelocatableSectionCount(uint32_t sectionCount) { assert(mh.filetype == MH_OBJECT); if ( is64() ) { uint32_t lcSize = (uint32_t)(sizeof(segment_command_64) + sectionCount * sizeof(section_64)); segment_command_64* sc = (segment_command_64*)appendLoadCommand(LC_SEGMENT_64, lcSize); sc->segname[0] = '\0'; // MH_OBJECT has one segment with no name sc->vmaddr = 0; sc->vmsize = 0; // adjusted in updateRelocatableSegmentSize() sc->fileoff = 0; sc->filesize = 0; // adjusted in updateRelocatableSegmentSize() sc->initprot = 7; sc->maxprot = 7; sc->nsects = sectionCount; // section info to be filled in later by setRelocatableSectionInfo() bzero((uint8_t*)sc + sizeof(segment_command_64), sectionCount * sizeof(section_64)); } else { uint32_t lcSize = (uint32_t)(sizeof(segment_command) + sectionCount * sizeof(section)); segment_command* sc = (segment_command*)appendLoadCommand(LC_SEGMENT, lcSize); sc->segname[0] = '\0'; // MH_OBJECT has one segment with no name sc->vmaddr = 0; sc->vmsize = 0x1000; // FIXME: need dynamic segment layout sc->fileoff = 0; sc->filesize = 0x1000; sc->initprot = 7; sc->maxprot = 7; sc->nsects = sectionCount; // section info to be filled in later by setRelocatableSectionInfo() bzero((uint8_t*)sc + sizeof(segment_command), sectionCount * sizeof(struct section)); } } void Header::updateRelocatableSegmentSize(uint64_t vmSize, uint32_t fileSize) { forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT ) { segment_command* sc = (segment_command*)cmd; sc->vmsize = (uint32_t)vmSize; sc->filesize = fileSize; stop = true; } else if ( cmd->cmd == LC_SEGMENT_64 ) { segment_command_64* sc = (segment_command_64*)cmd; sc->vmsize = vmSize; sc->filesize = fileSize; stop = true; } }); } void Header::setRelocatableSectionInfo(uint32_t sectionIndex, const char* segName, const char* sectName, uint32_t flags, uint64_t address, uint64_t size, uint32_t fileOffset, uint16_t alignment, uint32_t relocsOffset, uint32_t relocsCount) { __block struct section* section32 = nullptr; __block struct section_64* section64 = nullptr; forEachLoadCommandSafe(^(const load_command* cmd, bool& stop) { if ( cmd->cmd == LC_SEGMENT ) { struct section* sections = (struct section*)((uint8_t*)cmd + sizeof(segment_command)); section32 = §ions[sectionIndex]; stop = true; } else if ( cmd->cmd == LC_SEGMENT_64 ) { struct section_64* sections = (struct section_64*)((uint8_t*)cmd + sizeof(segment_command_64)); section64 = §ions[sectionIndex]; stop = true; } }); if ( section64 != nullptr ) { strncpy(section64->segname, segName, 16); strncpy(section64->sectname, sectName, 16); section64->addr = address; section64->size = size; section64->offset = fileOffset; section64->align = alignment; section64->reloff = relocsOffset; section64->nreloc = relocsCount; section64->flags = flags; section64->reserved1 = 0; section64->reserved2 = 0; section64->reserved3 = 0; } else if ( section32 != nullptr ) { strncpy(section32->segname, segName, 16); strncpy(section32->sectname, sectName, 16); section32->addr = (uint32_t)address; section32->size = (uint32_t)size; section32->offset = fileOffset; section32->align = alignment; section32->reloff = relocsOffset; section32->nreloc = relocsCount; section32->flags = flags; section32->reserved1 = 0; section32->reserved2 = 0; } } #endif // BUILDING_MACHO_WRITER } // namespace dyld3