/* $Id: ldrMachO.cpp $ */ /** @file * kLdr - The Module Interpreter for the MACH-O format. */ /* * Copyright (C) 2018-2019 Oracle Corporation * * This file is part of VirtualBox Open Source Edition (OSE), as * available from http://www.virtualbox.org. This file is free software; * you can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) as published by the Free Software * Foundation, in version 2 as it comes in the "COPYING" file of the * VirtualBox OSE distribution. VirtualBox OSE is distributed in the * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. * * The contents of this file may alternatively be used under the terms * of the Common Development and Distribution License Version 1.0 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the * VirtualBox OSE distribution, in which case the provisions of the * CDDL are applicable instead of those of the GPL. * * You may elect to license modified versions of this file under the * terms and conditions of either the GPL or the CDDL or both. * -------------------------------------------------------------------- * * This code is based on: * * Copyright (c) 2006-2013 Knut St. Osmundsen * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_LDR #include #include "internal/iprt.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "internal/ldr.h" /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** @def RTLDRMODMACHO_STRICT * Define RTLDRMODMACHO_STRICT to enabled strict checks in RTLDRMODMACHO. */ #define RTLDRMODMACHO_STRICT 1 #define RTLDRMODMACHO_STRICT2 /** @def RTLDRMODMACHO_ASSERT * Assert that an expression is true when KLDR_STRICT is defined. */ #ifdef RTLDRMODMACHO_STRICT # define RTLDRMODMACHO_ASSERT(expr) Assert(expr) #else # define RTLDRMODMACHO_ASSERT(expr) do {} while (0) #endif /** @def RTLDRMODMACHO_CHECK_RETURN * Checks that an expression is true and return if it isn't. * This is a debug aid. */ #ifdef RTLDRMODMACHO_STRICT2 # define RTLDRMODMACHO_CHECK_RETURN(expr, rc) AssertReturn(expr, rc) #else # define RTLDRMODMACHO_CHECK_RETURN(expr, rc) do { if (RT_LIKELY(expr)) {/* likely */ } else return (rc); } while (0) #endif /** @def RTLDRMODMACHO_CHECK_RETURN * Checks that an expression is true and return if it isn't. * This is a debug aid. */ #ifdef RTLDRMODMACHO_STRICT2 # define RTLDRMODMACHO_FAILED_RETURN(rc) AssertFailedReturn(rc) #else # define RTLDRMODMACHO_FAILED_RETURN(rc) return (rc) #endif /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Mach-O section details. */ typedef struct RTLDRMODMACHOSECT { /** The size of the section (in bytes). */ RTLDRADDR cb; /** The link address of this section. */ RTLDRADDR LinkAddress; /** The RVA of this section. */ RTLDRADDR RVA; /** The file offset of this section. * This is -1 if the section doesn't have a file backing. */ RTFOFF offFile; /** The number of fixups. */ uint32_t cFixups; /** The array of fixups. (lazy loaded) */ macho_relocation_union_t *paFixups; /** Array of virgin data running parallel to paFixups */ PRTUINT64U pauFixupVirginData; /** The file offset of the fixups for this section. * This is -1 if the section doesn't have any fixups. */ RTFOFF offFixups; /** Mach-O section flags. */ uint32_t fFlags; /** kLdr segment index. */ uint32_t iSegment; /** Pointer to the Mach-O section structure. */ void *pvMachoSection; } RTLDRMODMACHOSECT, *PRTLDRMODMACHOSECT; /** * Extra per-segment info. * * This is corresponds to a kLdr segment, not a Mach-O segment! */ typedef struct RTLDRMODMACHOSEG { /** Common segment info. */ RTLDRSEG SegInfo; /** The orignal segment number (in case we had to resort it). */ uint32_t iOrgSegNo; /** The number of sections in the segment. */ uint32_t cSections; /** Pointer to the sections belonging to this segment. * The array resides in the big memory chunk allocated for * the module handle, so it doesn't need freeing. */ PRTLDRMODMACHOSECT paSections; } RTLDRMODMACHOSEG, *PRTLDRMODMACHOSEG; /** * Instance data for the Mach-O MH_OBJECT module interpreter. * @todo interpret the other MH_* formats. */ typedef struct RTLDRMODMACHO { /** Core module structure. */ RTLDRMODINTERNAL Core; /** The minium cpu this module was built for. * This might not be accurate, so use kLdrModCanExecuteOn() to check. */ RTLDRCPU enmCpu; /** The number of segments in the module. */ uint32_t cSegments; /** Pointer to the RDR file mapping of the raw file bits. NULL if not mapped. */ const void *pvBits; /** Pointer to the user mapping. */ void *pvMapping; /** The module open flags. */ uint32_t fOpenFlags; /** The offset of the image. (FAT fun.) */ RTFOFF offImage; /** The link address. */ RTLDRADDR LinkAddress; /** The size of the mapped image. */ RTLDRADDR cbImage; /** Whether we're capable of loading the image. */ bool fCanLoad; /** Whether we're creating a global offset table segment. * This dependes on the cputype and image type. */ bool fMakeGot; /** The size of a indirect GOT jump stub entry. * This is 0 if not needed. */ uint32_t cbJmpStub; /** Effective file type. If the original was a MH_OBJECT file, the * corresponding MH_DSYM needs the segment translation of a MH_OBJECT too. * The MH_DSYM normally has a separate __DWARF segment, but this is * automatically skipped during the transation. */ uint32_t uEffFileType; /** Pointer to the load commands. (endian converted) */ uint8_t *pbLoadCommands; /** The Mach-O header. (endian converted) * @remark The reserved field is only valid for real 64-bit headers. */ mach_header_64_t Hdr; /** The offset of the symbol table. */ RTFOFF offSymbols; /** The number of symbols. */ uint32_t cSymbols; /** The pointer to the loaded symbol table. */ void *pvaSymbols; /** The offset of the string table. */ RTFOFF offStrings; /** The size of the of the string table. */ uint32_t cchStrings; /** Pointer to the loaded string table. */ char *pchStrings; /** Pointer to the dynamic symbol table command if present. */ dysymtab_command_t *pDySymTab; /** The indirect symbol table (size given by pDySymTab->nindirectsymb). * @remarks Host endian. */ uint32_t *paidxIndirectSymbols; /** Dynamic relocations, first pDySymTab->nextrel external relocs followed by * pDySymTab->nlocrel local ones. */ macho_relocation_union_t *paRelocations; /** Array of virgin data running parallel to paRelocations */ PRTUINT64U pauRelocationsVirginData; /** The image UUID, all zeros if not found. */ uint8_t abImageUuid[16]; /** The code signature offset. */ uint32_t offCodeSignature; /** The code signature size (0 if not signed). */ uint32_t cbCodeSignature; /** Pointer to the code signature blob if loaded. */ union { uint8_t *pb; PCRTCRAPLCSSUPERBLOB pSuper; } PtrCodeSignature; /** File offset of segment 0 (relative to Mach-O header). */ uint64_t offSeg0ForCodeSign; /** File size of segment 0. */ uint64_t cbSeg0ForCodeSign; /** Segment 0 flags. */ uint64_t fSeg0ForCodeSign; /** The RVA of the Global Offset Table. */ RTLDRADDR GotRVA; /** The RVA of the indirect GOT jump stubs. */ RTLDRADDR JmpStubsRVA; /** The number of sections. */ uint32_t cSections; /** Pointer to the section array running in parallel to the Mach-O one. */ PRTLDRMODMACHOSECT paSections; /** Array of segments parallel to the one in KLDRMOD. */ RTLDRMODMACHOSEG aSegments[1]; } RTLDRMODMACHO; /** Pointer instance data for an Mach-O module. */ typedef RTLDRMODMACHO *PRTLDRMODMACHO; /** * Code directory data. */ typedef struct RTLDRMACHCODEDIR { PCRTCRAPLCSCODEDIRECTORY pCodeDir; /** The slot type. */ uint32_t uSlot; /** The naturalized size. */ uint32_t cb; /** The digest type. */ RTDIGESTTYPE enmDigest; } RTLDRMACHCODEDIR; /** Pointer to code directory data. */ typedef RTLDRMACHCODEDIR *PRTLDRMACHCODEDIR; /** * Decoded apple Mach-O signature data. * @note The raw signature data lives in RTLDRMODMACHO::PtrCodeSignature. */ typedef struct RTLDRMACHOSIGNATURE { /** Number of code directory slots. */ uint32_t cCodeDirs; /** Code directories. */ RTLDRMACHCODEDIR aCodeDirs[6]; /** The index of the PKCS#7 slot. */ uint32_t idxPkcs7; /** The size of the PKCS#7 data. */ uint32_t cbPkcs7; /** Pointer to the PKCS#7 data. */ uint8_t const *pbPkcs7; /** Parsed PKCS#7 data. */ RTCRPKCS7CONTENTINFO ContentInfo; /** Pointer to the decoded SignedData inside the ContentInfo member. */ PRTCRPKCS7SIGNEDDATA pSignedData; } RTLDRMACHOSIGNATURE; /** Pointer to decoded apple code signing data. */ typedef RTLDRMACHOSIGNATURE *PRTLDRMACHOSIGNATURE; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ #if 0 static int32_t kldrModMachONumberOfImports(PRTLDRMODINTERNAL pMod, const void *pvBits); #endif static DECLCALLBACK(int) rtldrMachO_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser); static int kldrModMachOPreParseLoadCommands(uint8_t *pbLoadCommands, const mach_header_32_t *pHdr, PRTLDRREADER pRdr, RTFOFF offImage, uint32_t fOpenFlags, uint32_t *pcSegments, uint32_t *pcSections, uint32_t *pcbStringPool, bool *pfCanLoad, PRTLDRADDR pLinkAddress, uint8_t *puEffFileType, PRTERRINFO pErrInfo); static int kldrModMachOParseLoadCommands(PRTLDRMODMACHO pThis, char *pbStringPool, uint32_t cbStringPool); static int kldrModMachOLoadObjSymTab(PRTLDRMODMACHO pThis); static int kldrModMachOLoadFixups(PRTLDRMODMACHO pThis, RTFOFF offFixups, uint32_t cFixups, macho_relocation_union_t **ppaFixups); static int kldrModMachODoQuerySymbol32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol, const char *pchSymbol, uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind); static int kldrModMachODoQuerySymbol64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol, const char *pchSymbol, uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind); static int kldrModMachODoEnumSymbols32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser); static int kldrModMachODoEnumSymbols64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser); static int kldrModMachOObjDoImports(PRTLDRMODMACHO pThis, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser); static int kldrModMachOObjDoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress); static int kldrModMachOApplyFixupsGeneric32Bit(PRTLDRMODMACHO pThis, uint8_t *pbSectBits, size_t cbSectBits, RTLDRADDR uBitsRva, RTLDRADDR uBitsLinkAddr, const macho_relocation_union_t *paFixups, const uint32_t cFixups, PCRTUINT64U const pauVirginData, macho_nlist_32_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress); static int kldrModMachOApplyFixupsAMD64(PRTLDRMODMACHO pThis, uint8_t *pbSectBits, size_t cbSectBits, RTLDRADDR uBitsRva, const macho_relocation_union_t *paFixups, const uint32_t cFixups, PCRTUINT64U const pauVirginData, macho_nlist_64_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress); static int kldrModMachOMakeGOT(PRTLDRMODMACHO pThis, void *pvBits, RTLDRADDR NewBaseAddress); /*static int kldrModMachODoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress, RTLDRADDR OldBaseAddress); static int kldrModMachODoImports(PRTLDRMODMACHO pThis, void *pvMapping, PFNRTLDRIMPORT pfnGetImport, void *pvUser);*/ /** * Separate function for reading creating the Mach-O module instance to * simplify cleanup on failure. */ static int kldrModMachODoCreate(PRTLDRREADER pRdr, RTFOFF offImage, uint32_t fOpenFlags, PRTLDRMODMACHO *ppModMachO, PRTERRINFO pErrInfo) { *ppModMachO = NULL; /* * Read the Mach-O header. */ union { mach_header_32_t Hdr32; mach_header_64_t Hdr64; } s; Assert(&s.Hdr32.magic == &s.Hdr64.magic); Assert(&s.Hdr32.flags == &s.Hdr64.flags); int rc = pRdr->pfnRead(pRdr, &s, sizeof(s), offImage); if (rc) return RTErrInfoSetF(pErrInfo, rc, "Error reading Mach-O header at %RTfoff: %Rrc", offImage, rc); if ( s.Hdr32.magic != IMAGE_MACHO32_SIGNATURE && s.Hdr32.magic != IMAGE_MACHO64_SIGNATURE) { if ( s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE_OE || s.Hdr32.magic == IMAGE_MACHO64_SIGNATURE_OE) return VERR_LDRMACHO_OTHER_ENDIAN_NOT_SUPPORTED; return VERR_INVALID_EXE_SIGNATURE; } /* sanity checks. */ if ( s.Hdr32.sizeofcmds > pRdr->pfnSize(pRdr) - sizeof(mach_header_32_t) || s.Hdr32.sizeofcmds < sizeof(load_command_t) * s.Hdr32.ncmds || (s.Hdr32.flags & ~MH_VALID_FLAGS)) return VERR_LDRMACHO_BAD_HEADER; bool fMakeGot; uint8_t cbJmpStub; switch (s.Hdr32.cputype) { case CPU_TYPE_X86: fMakeGot = false; cbJmpStub = 0; break; case CPU_TYPE_X86_64: fMakeGot = s.Hdr32.filetype == MH_OBJECT || s.Hdr32.filetype == MH_KEXT_BUNDLE; cbJmpStub = fMakeGot ? 8 : 0; break; default: return VERR_LDRMACHO_UNSUPPORTED_MACHINE; } if ( s.Hdr32.filetype != MH_OBJECT && s.Hdr32.filetype != MH_EXECUTE && s.Hdr32.filetype != MH_DYLIB && s.Hdr32.filetype != MH_BUNDLE && s.Hdr32.filetype != MH_DSYM && s.Hdr32.filetype != MH_KEXT_BUNDLE) return VERR_LDRMACHO_UNSUPPORTED_FILE_TYPE; /* * Read and pre-parse the load commands to figure out how many segments we'll be needing. */ uint8_t *pbLoadCommands = (uint8_t *)RTMemAlloc(s.Hdr32.sizeofcmds); if (!pbLoadCommands) return VERR_NO_MEMORY; rc = pRdr->pfnRead(pRdr, pbLoadCommands, s.Hdr32.sizeofcmds, s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE || s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE_OE ? sizeof(mach_header_32_t) + offImage : sizeof(mach_header_64_t) + offImage); uint32_t cSegments = 0; uint32_t cSections = 0; uint32_t cbStringPool = 0; bool fCanLoad = true; RTLDRADDR LinkAddress = NIL_RTLDRADDR; uint8_t uEffFileType = 0; if (RT_SUCCESS(rc)) rc = kldrModMachOPreParseLoadCommands(pbLoadCommands, &s.Hdr32, pRdr, offImage, fOpenFlags, &cSegments, &cSections, &cbStringPool, &fCanLoad, &LinkAddress, &uEffFileType, pErrInfo); if (RT_FAILURE(rc)) { RTMemFree(pbLoadCommands); return rc; } cSegments += fMakeGot; /* * Calc the instance size, allocate and initialize it. */ size_t const cbModAndSegs = RT_ALIGN_Z(RT_UOFFSETOF_DYN(RTLDRMODMACHO, aSegments[cSegments]) + sizeof(RTLDRMODMACHOSECT) * cSections, 16); PRTLDRMODMACHO pThis = (PRTLDRMODMACHO)RTMemAlloc(cbModAndSegs + cbStringPool); if (!pThis) return VERR_NO_MEMORY; *ppModMachO = pThis; pThis->pbLoadCommands = pbLoadCommands; pThis->offImage = offImage; /* Core & CPU.*/ pThis->Core.u32Magic = 0; /* set by caller */ pThis->Core.eState = LDR_STATE_OPENED; pThis->Core.pOps = NULL; /* set by caller. */ pThis->Core.pReader = pRdr; switch (s.Hdr32.cputype) { case CPU_TYPE_X86: pThis->Core.enmArch = RTLDRARCH_X86_32; pThis->Core.enmEndian = RTLDRENDIAN_LITTLE; switch (s.Hdr32.cpusubtype) { case CPU_SUBTYPE_I386_ALL: /* == CPU_SUBTYPE_386 */ pThis->enmCpu = RTLDRCPU_X86_32_BLEND; break; case CPU_SUBTYPE_486: pThis->enmCpu = RTLDRCPU_I486; break; case CPU_SUBTYPE_486SX: pThis->enmCpu = RTLDRCPU_I486SX; break; case CPU_SUBTYPE_PENT: /* == CPU_SUBTYPE_586 */ pThis->enmCpu = RTLDRCPU_I586; break; case CPU_SUBTYPE_PENTPRO: case CPU_SUBTYPE_PENTII_M3: case CPU_SUBTYPE_PENTII_M5: case CPU_SUBTYPE_CELERON: case CPU_SUBTYPE_CELERON_MOBILE: case CPU_SUBTYPE_PENTIUM_3: case CPU_SUBTYPE_PENTIUM_3_M: case CPU_SUBTYPE_PENTIUM_3_XEON: pThis->enmCpu = RTLDRCPU_I686; break; case CPU_SUBTYPE_PENTIUM_M: case CPU_SUBTYPE_PENTIUM_4: case CPU_SUBTYPE_PENTIUM_4_M: case CPU_SUBTYPE_XEON: case CPU_SUBTYPE_XEON_MP: pThis->enmCpu = RTLDRCPU_P4; break; default: /* Hack for kextutil output. */ if ( s.Hdr32.cpusubtype == 0 && s.Hdr32.filetype == MH_OBJECT) break; return VERR_LDRMACHO_UNSUPPORTED_MACHINE; } break; case CPU_TYPE_X86_64: pThis->Core.enmArch = RTLDRARCH_AMD64; pThis->Core.enmEndian = RTLDRENDIAN_LITTLE; switch (s.Hdr32.cpusubtype & ~CPU_SUBTYPE_MASK) { case CPU_SUBTYPE_X86_64_ALL: pThis->enmCpu = RTLDRCPU_AMD64_BLEND; break; default: return VERR_LDRMACHO_UNSUPPORTED_MACHINE; } break; default: return VERR_LDRMACHO_UNSUPPORTED_MACHINE; } pThis->Core.enmFormat = RTLDRFMT_MACHO; switch (s.Hdr32.filetype) { case MH_OBJECT: pThis->Core.enmType = RTLDRTYPE_OBJECT; break; case MH_EXECUTE: pThis->Core.enmType = RTLDRTYPE_EXECUTABLE_FIXED; break; case MH_DYLIB: pThis->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; break; case MH_BUNDLE: pThis->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; break; case MH_KEXT_BUNDLE:pThis->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; break; case MH_DSYM: pThis->Core.enmType = RTLDRTYPE_DEBUG_INFO; break; default: return VERR_LDRMACHO_UNSUPPORTED_FILE_TYPE; } /* RTLDRMODMACHO */ pThis->cSegments = cSegments; pThis->pvBits = NULL; pThis->pvMapping = NULL; pThis->fOpenFlags = fOpenFlags; pThis->Hdr = s.Hdr64; if ( s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE || s.Hdr32.magic == IMAGE_MACHO32_SIGNATURE_OE) pThis->Hdr.reserved = 0; pThis->LinkAddress = LinkAddress; pThis->cbImage = 0; pThis->fCanLoad = fCanLoad; pThis->fMakeGot = fMakeGot; pThis->cbJmpStub = cbJmpStub; pThis->uEffFileType = uEffFileType; pThis->offSymbols = 0; pThis->cSymbols = 0; pThis->pvaSymbols = NULL; pThis->pDySymTab = NULL; pThis->paRelocations = NULL; pThis->pauRelocationsVirginData = NULL; pThis->paidxIndirectSymbols = NULL; pThis->offStrings = 0; pThis->cchStrings = 0; pThis->pchStrings = NULL; memset(pThis->abImageUuid, 0, sizeof(pThis->abImageUuid)); pThis->offCodeSignature = 0; pThis->cbCodeSignature = 0; pThis->PtrCodeSignature.pb = NULL; pThis->GotRVA = NIL_RTLDRADDR; pThis->JmpStubsRVA = NIL_RTLDRADDR; pThis->cSections = cSections; pThis->paSections = (PRTLDRMODMACHOSECT)&pThis->aSegments[pThis->cSegments]; /* * Setup the KLDRMOD segment array. */ rc = kldrModMachOParseLoadCommands(pThis, (char *)pThis + cbModAndSegs, cbStringPool); /* * We're done. */ return rc; } /** * Converts, validates and preparses the load commands before we carve * out the module instance. * * The conversion that's preformed is format endian to host endian. The * preparsing has to do with segment counting, section counting and string pool * sizing. * * Segment are created in two different ways, depending on the file type. * * For object files there is only one segment command without a given segment * name. The sections inside that segment have different segment names and are * not sorted by their segname attribute. We create one segment for each * section, with the segment name being 'segname.sectname' in order to hopefully * keep the names unique. Debug sections does not get segments. * * For non-object files, one kLdr segment is created for each Mach-O segment. * Debug segments is not exposed by kLdr via the kLdr segment table, but via the * debug enumeration callback API. * * @returns IPRT status code. * @param pbLoadCommands The load commands to parse. * @param pHdr The header. * @param pRdr The file reader. * @param offImage The image header (FAT fun). * @param fOpenFlags RTLDR_O_XXX. * @param pcSegments Where to store the segment count. * @param pcSections Where to store the section count. * @param pcbStringPool Where to store the string pool size. * @param pfCanLoad Where to store the can-load-image indicator. * @param pLinkAddress Where to store the image link address (i.e. the * lowest segment address). * @param puEffFileType Where to store the effective file type. * @param pErrInfo Where to return additional error info. Optional. */ static int kldrModMachOPreParseLoadCommands(uint8_t *pbLoadCommands, const mach_header_32_t *pHdr, PRTLDRREADER pRdr, RTFOFF offImage, uint32_t fOpenFlags, uint32_t *pcSegments, uint32_t *pcSections, uint32_t *pcbStringPool, bool *pfCanLoad, PRTLDRADDR pLinkAddress, uint8_t *puEffFileType, PRTERRINFO pErrInfo) { union { uint8_t *pb; load_command_t *pLoadCmd; segment_command_32_t *pSeg32; segment_command_64_t *pSeg64; thread_command_t *pThread; symtab_command_t *pSymTab; dysymtab_command_t *pDySymTab; uuid_command_t *pUuid; } u; const uint64_t cbFile = pRdr->pfnSize(pRdr) - offImage; int const fConvertEndian = pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE || pHdr->magic == IMAGE_MACHO64_SIGNATURE_OE; uint32_t cSegments = 0; uint32_t cSections = 0; size_t cbStringPool = 0; uint32_t cLeft = pHdr->ncmds; uint32_t cbLeft = pHdr->sizeofcmds; uint8_t *pb = pbLoadCommands; int cSegmentCommands = 0; int cSymbolTabs = 0; uint32_t cSymbols = 0; /* Copy of u.pSymTab->nsyms. */ uint32_t cDySymbolTabs = 0; bool fDySymbolTabWithRelocs = false; uint32_t cSectionsWithRelocs = 0; uint8_t uEffFileType = *puEffFileType = pHdr->filetype; *pcSegments = 0; *pcSections = 0; *pcbStringPool = 0; *pfCanLoad = true; *pLinkAddress = ~(RTLDRADDR)0; while (cLeft-- > 0) { u.pb = pb; /* * Convert and validate command header. */ RTLDRMODMACHO_CHECK_RETURN(cbLeft >= sizeof(load_command_t), VERR_LDRMACHO_BAD_LOAD_COMMAND); if (fConvertEndian) { u.pLoadCmd->cmd = RT_BSWAP_U32(u.pLoadCmd->cmd); u.pLoadCmd->cmdsize = RT_BSWAP_U32(u.pLoadCmd->cmdsize); } RTLDRMODMACHO_CHECK_RETURN(u.pLoadCmd->cmdsize <= cbLeft, VERR_LDRMACHO_BAD_LOAD_COMMAND); cbLeft -= u.pLoadCmd->cmdsize; pb += u.pLoadCmd->cmdsize; /* * Segment macros for avoiding code duplication. */ /* Validation code shared with the 64-bit variant. */ #define VALIDATE_AND_ADD_SEGMENT(a_cBits) \ do { \ bool fSkipSeg = !strcmp(pSrcSeg->segname, "__DWARF") /* Note: Not for non-object files. */ \ || ( !strcmp(pSrcSeg->segname, "__CTF") /* Their CTF tool did/does weird things, */ \ && pSrcSeg->vmsize == 0) /* overlapping vmaddr and zero vmsize. */ \ || (cSectionsLeft > 0 && (pFirstSect->flags & S_ATTR_DEBUG)); \ \ /* MH_DSYM files for MH_OBJECT files must have MH_OBJECT segment translation. */ \ if ( uEffFileType == MH_DSYM \ && cSegmentCommands == 0 \ && pSrcSeg->segname[0] == '\0') \ *puEffFileType = uEffFileType = MH_OBJECT; \ \ RTLDRMODMACHO_CHECK_RETURN( pSrcSeg->filesize == 0 \ || ( pSrcSeg->fileoff <= cbFile \ && (uint64_t)pSrcSeg->fileoff + pSrcSeg->filesize <= cbFile), \ VERR_LDRMACHO_BAD_LOAD_COMMAND); \ RTLDRMODMACHO_CHECK_RETURN( pSrcSeg->filesize <= pSrcSeg->vmsize \ || (fSkipSeg && !strcmp(pSrcSeg->segname, "__CTF") /* see above */), \ VERR_LDRMACHO_BAD_LOAD_COMMAND); \ RTLDRMODMACHO_CHECK_RETURN(!(~pSrcSeg->maxprot & pSrcSeg->initprot), \ VERR_LDRMACHO_BAD_LOAD_COMMAND); \ RTLDRMODMACHO_CHECK_RETURN(!(pSrcSeg->flags & ~(SG_HIGHVM | SG_FVMLIB | SG_NORELOC | SG_PROTECTED_VERSION_1)), \ VERR_LDRMACHO_BAD_LOAD_COMMAND); \ RTLDRMODMACHO_CHECK_RETURN( pSrcSeg->nsects * sizeof(section_##a_cBits##_t) \ <= u.pLoadCmd->cmdsize - sizeof(segment_command_##a_cBits##_t), \ VERR_LDRMACHO_BAD_LOAD_COMMAND); \ RTLDRMODMACHO_CHECK_RETURN( uEffFileType != MH_OBJECT \ || cSegmentCommands == 0 \ || ( cSegmentCommands == 1 \ && uEffFileType == MH_OBJECT \ && pHdr->filetype == MH_DSYM \ && fSkipSeg), \ VERR_LDRMACHO_BAD_OBJECT_FILE); \ cSegmentCommands++; \ \ /* Add the segment, if not object file. */ \ if (!fSkipSeg && uEffFileType != MH_OBJECT) \ { \ cbStringPool += RTStrNLen(&pSrcSeg->segname[0], sizeof(pSrcSeg->segname)) + 1; \ cSegments++; \ if (cSegments == 1) /* The link address is set by the first segment. */ \ *pLinkAddress = pSrcSeg->vmaddr; \ } \ } while (0) /* Validation code shared with the 64-bit variant. */ #define VALIDATE_AND_ADD_SECTION(a_cBits) \ do { \ int fFileBits; \ \ /* validate */ \ if (uEffFileType != MH_OBJECT) \ RTLDRMODMACHO_CHECK_RETURN(!strcmp(pSect->segname, pSrcSeg->segname),\ VERR_LDRMACHO_BAD_SECTION); \ \ switch (pSect->flags & SECTION_TYPE) \ { \ case S_ZEROFILL: \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \ fFileBits = 0; \ break; \ case S_REGULAR: \ case S_CSTRING_LITERALS: \ case S_COALESCED: \ case S_4BYTE_LITERALS: \ case S_8BYTE_LITERALS: \ case S_16BYTE_LITERALS: \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \ fFileBits = 1; \ break; \ \ case S_SYMBOL_STUBS: \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \ /* reserved2 == stub size. 0 has been seen (corecrypto.kext) */ \ RTLDRMODMACHO_CHECK_RETURN(pSect->reserved2 < 64, VERR_LDRMACHO_BAD_SECTION); \ fFileBits = 1; \ break; \ \ case S_NON_LAZY_SYMBOL_POINTERS: \ case S_LAZY_SYMBOL_POINTERS: \ case S_LAZY_DYLIB_SYMBOL_POINTERS: \ /* (reserved 1 = is indirect symbol table index) */ \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \ Log(("ldrMachO: Can't load because of section flags: %#x\n", pSect->flags & SECTION_TYPE)); \ *pfCanLoad = false; \ fFileBits = -1; /* __DATA.__got in the 64-bit mach_kernel has bits, any things without bits? */ \ break; \ \ case S_MOD_INIT_FUNC_POINTERS: \ /** @todo this requires a query API or flag... (e.g. C++ constructors) */ \ RTLDRMODMACHO_CHECK_RETURN(fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION), \ VERR_LDRMACHO_UNSUPPORTED_INIT_SECTION); \ RT_FALL_THRU(); \ case S_MOD_TERM_FUNC_POINTERS: \ /** @todo this requires a query API or flag... (e.g. C++ destructors) */ \ RTLDRMODMACHO_CHECK_RETURN(fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION), \ VERR_LDRMACHO_UNSUPPORTED_TERM_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \ fFileBits = 1; \ break; /* ignored */ \ \ case S_LITERAL_POINTERS: \ case S_DTRACE_DOF: \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved1, VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reserved2, VERR_LDRMACHO_BAD_SECTION); \ fFileBits = 1; \ break; \ \ case S_INTERPOSING: \ case S_GB_ZEROFILL: \ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_UNSUPPORTED_SECTION); \ \ default: \ RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_UNKNOWN_SECTION); \ } \ RTLDRMODMACHO_CHECK_RETURN(!(pSect->flags & ~( S_ATTR_PURE_INSTRUCTIONS | S_ATTR_NO_TOC | S_ATTR_STRIP_STATIC_SYMS \ | S_ATTR_NO_DEAD_STRIP | S_ATTR_LIVE_SUPPORT | S_ATTR_SELF_MODIFYING_CODE \ | S_ATTR_DEBUG | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_EXT_RELOC \ | S_ATTR_LOC_RELOC | SECTION_TYPE)), \ VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN((pSect->flags & S_ATTR_DEBUG) == (pFirstSect->flags & S_ATTR_DEBUG), \ VERR_LDRMACHO_MIXED_DEBUG_SECTION_FLAGS); \ \ RTLDRMODMACHO_CHECK_RETURN(pSect->addr - pSrcSeg->vmaddr <= pSrcSeg->vmsize, \ VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN( pSect->addr - pSrcSeg->vmaddr + pSect->size <= pSrcSeg->vmsize \ || !strcmp(pSrcSeg->segname, "__CTF") /* see above */, \ VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(pSect->align < 31, \ VERR_LDRMACHO_BAD_SECTION); \ /* Workaround for buggy ld64 (or as, llvm, ++) that produces a misaligned __TEXT.__unwind_info. */ \ /* Seen: pSect->align = 4, pSect->addr = 0x5ebe14. Just adjust the alignment down. */ \ if ( ((RT_BIT_32(pSect->align) - UINT32_C(1)) & pSect->addr) \ && pSect->align == 4 \ && strcmp(pSect->sectname, "__unwind_info") == 0) \ pSect->align = 2; \ RTLDRMODMACHO_CHECK_RETURN(!((RT_BIT_32(pSect->align) - UINT32_C(1)) & pSect->addr), \ VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN(!((RT_BIT_32(pSect->align) - UINT32_C(1)) & pSrcSeg->vmaddr), \ VERR_LDRMACHO_BAD_SECTION); \ \ /* Adjust the section offset before we check file offset. */ \ offSect = (offSect + RT_BIT_64(pSect->align) - UINT64_C(1)) & ~(RT_BIT_64(pSect->align) - UINT64_C(1)); \ if (pSect->addr) \ { \ RTLDRMODMACHO_CHECK_RETURN(offSect <= pSect->addr - pSrcSeg->vmaddr, VERR_LDRMACHO_BAD_SECTION); \ if (offSect < pSect->addr - pSrcSeg->vmaddr) \ offSect = pSect->addr - pSrcSeg->vmaddr; \ } \ \ if (fFileBits && pSect->offset == 0 && pSrcSeg->fileoff == 0 && pHdr->filetype == MH_DSYM) \ fFileBits = 0; \ if (fFileBits) \ { \ if (uEffFileType != MH_OBJECT) \ { \ RTLDRMODMACHO_CHECK_RETURN(pSect->offset == pSrcSeg->fileoff + offSect, \ VERR_LDRMACHO_NON_CONT_SEG_BITS); \ RTLDRMODMACHO_CHECK_RETURN(pSect->offset - pSrcSeg->fileoff <= pSrcSeg->filesize, \ VERR_LDRMACHO_BAD_SECTION); \ } \ RTLDRMODMACHO_CHECK_RETURN(pSect->offset <= cbFile, \ VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN((uint64_t)pSect->offset + pSect->size <= cbFile, \ VERR_LDRMACHO_BAD_SECTION); \ } \ else \ RTLDRMODMACHO_CHECK_RETURN(pSect->offset == 0, VERR_LDRMACHO_BAD_SECTION); \ \ if (!pSect->nreloc) \ RTLDRMODMACHO_CHECK_RETURN(!pSect->reloff, \ VERR_LDRMACHO_BAD_SECTION); \ else \ { \ RTLDRMODMACHO_CHECK_RETURN(pSect->reloff <= cbFile, \ VERR_LDRMACHO_BAD_SECTION); \ RTLDRMODMACHO_CHECK_RETURN( (uint64_t)pSect->reloff \ + (RTFOFF)pSect->nreloc * sizeof(macho_relocation_info_t) \ <= cbFile, \ VERR_LDRMACHO_BAD_SECTION); \ cSectionsWithRelocs++; \ } \ \ /* Validate against file type (pointless?) and count the section, for object files add segment. */ \ switch (uEffFileType) \ { \ case MH_OBJECT: \ if ( !(pSect->flags & S_ATTR_DEBUG) \ && strcmp(pSect->segname, "__DWARF")) \ { \ cbStringPool += RTStrNLen(&pSect->segname[0], sizeof(pSect->segname)) + 1; \ cbStringPool += RTStrNLen(&pSect->sectname[0], sizeof(pSect->sectname)) + 1; \ cSegments++; \ if (cSegments == 1) /* The link address is set by the first segment. */ \ *pLinkAddress = pSect->addr; \ } \ RT_FALL_THRU(); \ case MH_EXECUTE: \ case MH_DYLIB: \ case MH_BUNDLE: \ case MH_DSYM: \ case MH_KEXT_BUNDLE: \ cSections++; \ break; \ default: \ RTLDRMODMACHO_FAILED_RETURN(VERR_INVALID_PARAMETER); \ } \ \ /* Advance the section offset, since we're also aligning it. */ \ offSect += pSect->size; \ } while (0) /* VALIDATE_AND_ADD_SECTION */ /* * Convert endian if needed, parse and validate the command. */ switch (u.pLoadCmd->cmd) { case LC_SEGMENT_32: { segment_command_32_t *pSrcSeg = (segment_command_32_t *)u.pLoadCmd; section_32_t *pFirstSect = (section_32_t *)(pSrcSeg + 1); section_32_t *pSect = pFirstSect; uint32_t cSectionsLeft = pSrcSeg->nsects; uint64_t offSect = 0; /* Convert and verify the segment. */ RTLDRMODMACHO_CHECK_RETURN(u.pLoadCmd->cmdsize >= sizeof(segment_command_32_t), VERR_LDRMACHO_BAD_LOAD_COMMAND); RTLDRMODMACHO_CHECK_RETURN( pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE || pHdr->magic == IMAGE_MACHO32_SIGNATURE, VERR_LDRMACHO_BIT_MIX); if (fConvertEndian) { pSrcSeg->vmaddr = RT_BSWAP_U32(pSrcSeg->vmaddr); pSrcSeg->vmsize = RT_BSWAP_U32(pSrcSeg->vmsize); pSrcSeg->fileoff = RT_BSWAP_U32(pSrcSeg->fileoff); pSrcSeg->filesize = RT_BSWAP_U32(pSrcSeg->filesize); pSrcSeg->maxprot = RT_BSWAP_U32(pSrcSeg->maxprot); pSrcSeg->initprot = RT_BSWAP_U32(pSrcSeg->initprot); pSrcSeg->nsects = RT_BSWAP_U32(pSrcSeg->nsects); pSrcSeg->flags = RT_BSWAP_U32(pSrcSeg->flags); } VALIDATE_AND_ADD_SEGMENT(32); /* * Convert, validate and parse the sections. */ cSectionsLeft = pSrcSeg->nsects; pFirstSect = pSect = (section_32_t *)(pSrcSeg + 1); while (cSectionsLeft-- > 0) { if (fConvertEndian) { pSect->addr = RT_BSWAP_U32(pSect->addr); pSect->size = RT_BSWAP_U32(pSect->size); pSect->offset = RT_BSWAP_U32(pSect->offset); pSect->align = RT_BSWAP_U32(pSect->align); pSect->reloff = RT_BSWAP_U32(pSect->reloff); pSect->nreloc = RT_BSWAP_U32(pSect->nreloc); pSect->flags = RT_BSWAP_U32(pSect->flags); pSect->reserved1 = RT_BSWAP_U32(pSect->reserved1); pSect->reserved2 = RT_BSWAP_U32(pSect->reserved2); } VALIDATE_AND_ADD_SECTION(32); /* next */ pSect++; } break; } case LC_SEGMENT_64: { segment_command_64_t *pSrcSeg = (segment_command_64_t *)u.pLoadCmd; section_64_t *pFirstSect = (section_64_t *)(pSrcSeg + 1); section_64_t *pSect = pFirstSect; uint32_t cSectionsLeft = pSrcSeg->nsects; uint64_t offSect = 0; /* Convert and verify the segment. */ RTLDRMODMACHO_CHECK_RETURN(u.pLoadCmd->cmdsize >= sizeof(segment_command_64_t), VERR_LDRMACHO_BAD_LOAD_COMMAND); RTLDRMODMACHO_CHECK_RETURN( pHdr->magic == IMAGE_MACHO64_SIGNATURE_OE || pHdr->magic == IMAGE_MACHO64_SIGNATURE, VERR_LDRMACHO_BIT_MIX); if (fConvertEndian) { pSrcSeg->vmaddr = RT_BSWAP_U64(pSrcSeg->vmaddr); pSrcSeg->vmsize = RT_BSWAP_U64(pSrcSeg->vmsize); pSrcSeg->fileoff = RT_BSWAP_U64(pSrcSeg->fileoff); pSrcSeg->filesize = RT_BSWAP_U64(pSrcSeg->filesize); pSrcSeg->maxprot = RT_BSWAP_U32(pSrcSeg->maxprot); pSrcSeg->initprot = RT_BSWAP_U32(pSrcSeg->initprot); pSrcSeg->nsects = RT_BSWAP_U32(pSrcSeg->nsects); pSrcSeg->flags = RT_BSWAP_U32(pSrcSeg->flags); } VALIDATE_AND_ADD_SEGMENT(64); /* * Convert, validate and parse the sections. */ while (cSectionsLeft-- > 0) { if (fConvertEndian) { pSect->addr = RT_BSWAP_U64(pSect->addr); pSect->size = RT_BSWAP_U64(pSect->size); pSect->offset = RT_BSWAP_U32(pSect->offset); pSect->align = RT_BSWAP_U32(pSect->align); pSect->reloff = RT_BSWAP_U32(pSect->reloff); pSect->nreloc = RT_BSWAP_U32(pSect->nreloc); pSect->flags = RT_BSWAP_U32(pSect->flags); pSect->reserved1 = RT_BSWAP_U32(pSect->reserved1); pSect->reserved2 = RT_BSWAP_U32(pSect->reserved2); } VALIDATE_AND_ADD_SECTION(64); /* next */ pSect++; } break; } /* LC_SEGMENT_64 */ case LC_SYMTAB: { size_t cbSym; if (fConvertEndian) { u.pSymTab->symoff = RT_BSWAP_U32(u.pSymTab->symoff); u.pSymTab->nsyms = RT_BSWAP_U32(u.pSymTab->nsyms); u.pSymTab->stroff = RT_BSWAP_U32(u.pSymTab->stroff); u.pSymTab->strsize = RT_BSWAP_U32(u.pSymTab->strsize); } /* verify */ cbSym = pHdr->magic == IMAGE_MACHO32_SIGNATURE || pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE ? sizeof(macho_nlist_32_t) : sizeof(macho_nlist_64_t); if ( u.pSymTab->symoff >= cbFile || (uint64_t)u.pSymTab->symoff + u.pSymTab->nsyms * cbSym > cbFile) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); if ( u.pSymTab->stroff >= cbFile || (uint64_t)u.pSymTab->stroff + u.pSymTab->strsize > cbFile) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); /* Only one object table, please. */ cSymbolTabs++; if (cSymbolTabs != 1) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_OBJECT_FILE); cSymbols = u.pSymTab->nsyms; break; } case LC_DYSYMTAB: { if (pHdr->filetype == MH_OBJECT) RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSet(pErrInfo, VERR_LDRMACHO_BAD_OBJECT_FILE, "Not expecting LC_DYSYMTAB in MH_OBJECT")); if (fConvertEndian) { u.pDySymTab->ilocalsym = RT_BSWAP_U32(u.pDySymTab->ilocalsym); u.pDySymTab->nlocalsym = RT_BSWAP_U32(u.pDySymTab->nlocalsym); u.pDySymTab->iextdefsym = RT_BSWAP_U32(u.pDySymTab->iextdefsym); u.pDySymTab->nextdefsym = RT_BSWAP_U32(u.pDySymTab->nextdefsym); u.pDySymTab->iundefsym = RT_BSWAP_U32(u.pDySymTab->iundefsym); u.pDySymTab->nundefsym = RT_BSWAP_U32(u.pDySymTab->nundefsym); u.pDySymTab->tocoff = RT_BSWAP_U32(u.pDySymTab->tocoff); u.pDySymTab->ntoc = RT_BSWAP_U32(u.pDySymTab->ntoc); u.pDySymTab->modtaboff = RT_BSWAP_U32(u.pDySymTab->modtaboff); u.pDySymTab->nmodtab = RT_BSWAP_U32(u.pDySymTab->nmodtab); u.pDySymTab->extrefsymoff = RT_BSWAP_U32(u.pDySymTab->extrefsymoff); u.pDySymTab->nextrefsym = RT_BSWAP_U32(u.pDySymTab->nextrefsym); u.pDySymTab->indirectsymboff = RT_BSWAP_U32(u.pDySymTab->indirectsymboff); u.pDySymTab->nindirectsymb = RT_BSWAP_U32(u.pDySymTab->nindirectsymb); u.pDySymTab->extreloff = RT_BSWAP_U32(u.pDySymTab->extreloff); u.pDySymTab->nextrel = RT_BSWAP_U32(u.pDySymTab->nextrel); u.pDySymTab->locreloff = RT_BSWAP_U32(u.pDySymTab->locreloff); u.pDySymTab->nlocrel = RT_BSWAP_U32(u.pDySymTab->nlocrel); } /* verify */ RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->ilocalsym + u.pDySymTab->nlocalsym <= cSymbols, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "ilocalsym=%#x + nlocalsym=%#x vs cSymbols=%#x", u.pDySymTab->ilocalsym, u.pDySymTab->nlocalsym, cSymbols)); RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->iextdefsym + u.pDySymTab->nextdefsym <= cSymbols, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "iextdefsym=%#x + nextdefsym=%#x vs cSymbols=%#x", u.pDySymTab->iextdefsym, u.pDySymTab->nextdefsym, cSymbols)); RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->iundefsym + u.pDySymTab->nundefsym <= cSymbols, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "iundefsym=%#x + nundefsym=%#x vs cSymbols=%#x", u.pDySymTab->iundefsym, u.pDySymTab->nundefsym, cSymbols)); RTLDRMODMACHO_CHECK_RETURN( (uint64_t)u.pDySymTab->tocoff + u.pDySymTab->ntoc * sizeof(dylib_table_of_contents_t) <= cbFile, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "tocoff=%#x + ntoc=%#x vs cbFile=%#RX64", u.pDySymTab->tocoff, u.pDySymTab->ntoc, cbFile)); const uint32_t cbModTabEntry = pHdr->magic == IMAGE_MACHO32_SIGNATURE || pHdr->magic == IMAGE_MACHO32_SIGNATURE_OE ? sizeof(dylib_module_32_t) : sizeof(dylib_module_64_t); RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->modtaboff + u.pDySymTab->nmodtab * cbModTabEntry <= cbFile, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "modtaboff=%#x + nmodtab=%#x cbModTabEntry=%#x vs cbFile=%#RX64", u.pDySymTab->modtaboff, u.pDySymTab->nmodtab, cbModTabEntry, cbFile)); RTLDRMODMACHO_CHECK_RETURN( (uint64_t)u.pDySymTab->extrefsymoff + u.pDySymTab->nextrefsym * sizeof(dylib_reference_t) <= cbFile, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "extrefsymoff=%#x + nextrefsym=%#x vs cbFile=%#RX64", u.pDySymTab->extrefsymoff, u.pDySymTab->nextrefsym, cbFile)); RTLDRMODMACHO_CHECK_RETURN( (uint64_t)u.pDySymTab->indirectsymboff + u.pDySymTab->nindirectsymb * sizeof(uint32_t) <= cbFile, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "indirectsymboff=%#x + nindirectsymb=%#x vs cbFile=%#RX64", u.pDySymTab->indirectsymboff, u.pDySymTab->nindirectsymb, cbFile)); RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->extreloff + u.pDySymTab->nextrel * sizeof(macho_relocation_info_t) <= cbFile, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "extreloff=%#x + nextrel=%#x vs cbFile=%#RX64", u.pDySymTab->extreloff, u.pDySymTab->nextrel, cbFile)); RTLDRMODMACHO_CHECK_RETURN((uint64_t)u.pDySymTab->locreloff + u.pDySymTab->nlocrel * sizeof(macho_relocation_info_t) <= cbFile, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "locreloff=%#x + nlocrel=%#x vs cbFile=%#RX64", u.pDySymTab->locreloff, u.pDySymTab->nlocrel, cbFile)); cDySymbolTabs++; fDySymbolTabWithRelocs |= (u.pDySymTab->nlocrel + u.pDySymTab->nextrel) != 0; break; } case LC_THREAD: case LC_UNIXTHREAD: { uint32_t *pu32 = (uint32_t *)(u.pb + sizeof(load_command_t)); uint32_t cItemsLeft = (u.pThread->cmdsize - sizeof(load_command_t)) / sizeof(uint32_t); while (cItemsLeft) { /* convert & verify header items ([0] == flavor, [1] == uint32_t count). */ if (cItemsLeft < 2) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); if (fConvertEndian) { pu32[0] = RT_BSWAP_U32(pu32[0]); pu32[1] = RT_BSWAP_U32(pu32[1]); } if (pu32[1] + 2 > cItemsLeft) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); /* convert & verify according to flavor. */ switch (pu32[0]) { /** @todo */ default: break; } /* next */ cItemsLeft -= pu32[1] + 2; pu32 += pu32[1] + 2; } break; } case LC_UUID: if (u.pUuid->cmdsize != sizeof(uuid_command_t)) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); /** @todo Check anything here need converting? */ break; case LC_CODE_SIGNATURE: if (u.pUuid->cmdsize != sizeof(linkedit_data_command_t)) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); break; case LC_VERSION_MIN_MACOSX: case LC_VERSION_MIN_IPHONEOS: if (u.pUuid->cmdsize != sizeof(version_min_command_t)) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); break; case LC_SOURCE_VERSION: /* Harmless. It just gives a clue regarding the source code revision/version. */ case LC_DATA_IN_CODE: /* Ignore */ case LC_DYLIB_CODE_SIGN_DRS:/* Ignore */ /** @todo valid command size. */ break; case LC_FUNCTION_STARTS: /** @todo dylib++ */ /* Ignore for now. */ break; case LC_ID_DYLIB: /** @todo dylib */ case LC_LOAD_DYLIB: /** @todo dylib */ case LC_LOAD_DYLINKER: /** @todo dylib */ case LC_TWOLEVEL_HINTS: /** @todo dylib */ case LC_LOAD_WEAK_DYLIB: /** @todo dylib */ case LC_ID_DYLINKER: /** @todo dylib */ case LC_RPATH: /** @todo dylib */ case LC_SEGMENT_SPLIT_INFO: /** @todo dylib++ */ case LC_REEXPORT_DYLIB: /** @todo dylib */ case LC_DYLD_INFO: /** @todo dylib */ case LC_DYLD_INFO_ONLY: /** @todo dylib */ case LC_LOAD_UPWARD_DYLIB: /** @todo dylib */ case LC_DYLD_ENVIRONMENT: /** @todo dylib */ case LC_MAIN: /** @todo parse this and find and entry point or smth. */ /** @todo valid command size. */ if (!(fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))) RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_UNSUPPORTED_LOAD_COMMAND, "cmd=%#x", u.pLoadCmd->cmd)); Log(("ldrMachO: Can't load because of load command: %#x\n", u.pLoadCmd->cmd)); *pfCanLoad = false; break; case LC_LOADFVMLIB: case LC_IDFVMLIB: case LC_IDENT: case LC_FVMFILE: case LC_PREPAGE: case LC_PREBOUND_DYLIB: case LC_ROUTINES: case LC_ROUTINES_64: case LC_SUB_FRAMEWORK: case LC_SUB_UMBRELLA: case LC_SUB_CLIENT: case LC_SUB_LIBRARY: case LC_PREBIND_CKSUM: case LC_SYMSEG: RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_UNSUPPORTED_LOAD_COMMAND, "cmd=%#x", u.pLoadCmd->cmd)); default: RTLDRMODMACHO_FAILED_RETURN(RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_UNKNOWN_LOAD_COMMAND, "cmd=%#x", u.pLoadCmd->cmd)); } } /* be strict. */ if (cbLeft) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_LOAD_COMMAND); RTLDRMODMACHO_CHECK_RETURN(cDySymbolTabs <= 1, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "More than one LC_DYSYMTAB command: %u", cDySymbolTabs)); RTLDRMODMACHO_CHECK_RETURN(!fDySymbolTabWithRelocs || cSectionsWithRelocs == 0, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "Have relocations both in sections and LC_DYSYMTAB")); if (!cSegments) RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_OBJECT_FILE); switch (uEffFileType) { case MH_OBJECT: case MH_EXECUTE: RTLDRMODMACHO_CHECK_RETURN(!fDySymbolTabWithRelocs || (fOpenFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)), RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "Did not expect relocations in LC_DYSYMTAB (file type %u)", uEffFileType)); break; case MH_DYLIB: case MH_BUNDLE: case MH_KEXT_BUNDLE: RTLDRMODMACHO_CHECK_RETURN(cDySymbolTabs > 0, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "No LC_DYSYMTAB command (file type %u)", uEffFileType)); RTLDRMODMACHO_CHECK_RETURN(fDySymbolTabWithRelocs || cSectionsWithRelocs == 0, RTErrInfoSetF(pErrInfo, VERR_LDRMACHO_BAD_LOAD_COMMAND, "Expected relocations in LC_DYSYMTAB (file type %u)", uEffFileType)); break; case MH_DSYM: break; } /* * Set return values and return. */ *pcSegments = cSegments; *pcSections = cSections; *pcbStringPool = (uint32_t)cbStringPool; return VINF_SUCCESS; } /** * Parses the load commands after we've carved out the module instance. * * This fills in the segment table and perhaps some other properties. * * @returns IPRT status code. * @param pThis The module. * @param pbStringPool The string pool * @param cbStringPool The size of the string pool. */ static int kldrModMachOParseLoadCommands(PRTLDRMODMACHO pThis, char *pbStringPool, uint32_t cbStringPool) { union { const uint8_t *pb; const load_command_t *pLoadCmd; const segment_command_32_t *pSeg32; const segment_command_64_t *pSeg64; const symtab_command_t *pSymTab; const uuid_command_t *pUuid; const linkedit_data_command_t *pData; } u; uint32_t cLeft = pThis->Hdr.ncmds; uint32_t cbLeft = pThis->Hdr.sizeofcmds; const uint8_t *pb = pThis->pbLoadCommands; PRTLDRMODMACHOSEG pDstSeg = &pThis->aSegments[0]; PRTLDRMODMACHOSECT pSectExtra = pThis->paSections; const uint32_t cSegments = pThis->cSegments; PRTLDRMODMACHOSEG pSegItr; bool fFirstSeg = true; RT_NOREF(cbStringPool); while (cLeft-- > 0) { u.pb = pb; cbLeft -= u.pLoadCmd->cmdsize; pb += u.pLoadCmd->cmdsize; /* * Convert endian if needed, parse and validate the command. */ switch (u.pLoadCmd->cmd) { case LC_SEGMENT_32: { const segment_command_32_t *pSrcSeg = (const segment_command_32_t *)u.pLoadCmd; section_32_t *pFirstSect = (section_32_t *)(pSrcSeg + 1); section_32_t *pSect = pFirstSect; uint32_t cSectionsLeft = pSrcSeg->nsects; /* Adds a segment, used by the macro below and thus shared with the 64-bit segment variant. */ #define NEW_SEGMENT(a_cBits, a_achName1, a_fObjFile, a_achName2, a_SegAddr, a_cbSeg, a_fFileBits, a_offFile, a_cbFile) \ do { \ pDstSeg->SegInfo.pszName = pbStringPool; \ pDstSeg->SegInfo.cchName = (uint32_t)RTStrNLen(a_achName1, sizeof(a_achName1)); \ memcpy(pbStringPool, a_achName1, pDstSeg->SegInfo.cchName); \ pbStringPool += pDstSeg->SegInfo.cchName; \ if (a_fObjFile) \ { /* MH_OBJECT: Add '.sectname' - sections aren't sorted by segments. */ \ size_t cchName2 = RTStrNLen(a_achName2, sizeof(a_achName2)); \ *pbStringPool++ = '.'; \ memcpy(pbStringPool, a_achName2, cchName2); \ pbStringPool += cchName2; \ pDstSeg->SegInfo.cchName += (uint32_t)cchName2; \ } \ *pbStringPool++ = '\0'; \ pDstSeg->SegInfo.SelFlat = 0; \ pDstSeg->SegInfo.Sel16bit = 0; \ pDstSeg->SegInfo.fFlags = 0; \ pDstSeg->SegInfo.fProt = RTMEM_PROT_READ | RTMEM_PROT_WRITE | RTMEM_PROT_EXEC; /** @todo fixme! */ \ pDstSeg->SegInfo.cb = (a_cbSeg); \ pDstSeg->SegInfo.Alignment = 1; /* updated while parsing sections. */ \ pDstSeg->SegInfo.LinkAddress = (a_SegAddr); \ if (a_fFileBits) \ { \ pDstSeg->SegInfo.offFile = (RTFOFF)((a_offFile) + pThis->offImage); \ pDstSeg->SegInfo.cbFile = (RTFOFF)(a_cbFile); \ } \ else \ { \ pDstSeg->SegInfo.offFile = -1; \ pDstSeg->SegInfo.cbFile = -1; \ } \ pDstSeg->SegInfo.RVA = (a_SegAddr) - pThis->LinkAddress; \ pDstSeg->SegInfo.cbMapped = 0; \ \ pDstSeg->iOrgSegNo = (uint32_t)(pDstSeg - &pThis->aSegments[0]); \ pDstSeg->cSections = 0; \ pDstSeg->paSections = pSectExtra; \ } while (0) /* Closes the new segment - part of NEW_SEGMENT. */ #define CLOSE_SEGMENT() \ do { \ pDstSeg->cSections = (uint32_t)(pSectExtra - pDstSeg->paSections); \ pDstSeg++; \ } while (0) /* Shared with the 64-bit variant. */ #define ADD_SEGMENT_AND_ITS_SECTIONS(a_cBits) \ do { \ bool fAddSegOuter = false; \ \ /* \ * Check that the segment name is unique. We couldn't do that \ * in the preparsing stage. \ */ \ if (pThis->uEffFileType != MH_OBJECT) \ for (pSegItr = &pThis->aSegments[0]; pSegItr != pDstSeg; pSegItr++) \ if (!strncmp(pSegItr->SegInfo.pszName, pSrcSeg->segname, sizeof(pSrcSeg->segname))) \ RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_DUPLICATE_SEGMENT_NAME); \ \ /* \ * Create a new segment, unless we're supposed to skip this one. \ */ \ if ( pThis->uEffFileType != MH_OBJECT \ && (cSectionsLeft == 0 || !(pFirstSect->flags & S_ATTR_DEBUG)) \ && strcmp(pSrcSeg->segname, "__DWARF") \ && strcmp(pSrcSeg->segname, "__CTF") ) \ { \ NEW_SEGMENT(a_cBits, pSrcSeg->segname, false /*a_fObjFile*/, "" /*a_achName2*/, \ pSrcSeg->vmaddr, pSrcSeg->vmsize, \ pSrcSeg->filesize != 0, pSrcSeg->fileoff, pSrcSeg->filesize); \ fAddSegOuter = true; \ } \ \ /* \ * Convert and parse the sections. \ */ \ while (cSectionsLeft-- > 0) \ { \ /* New segment if object file. */ \ bool fAddSegInner = false; \ if ( pThis->uEffFileType == MH_OBJECT \ && !(pSect->flags & S_ATTR_DEBUG) \ && strcmp(pSrcSeg->segname, "__DWARF") \ && strcmp(pSrcSeg->segname, "__CTF") ) \ { \ Assert(!fAddSegOuter); \ NEW_SEGMENT(a_cBits, pSect->segname, true /*a_fObjFile*/, pSect->sectname, \ pSect->addr, pSect->size, \ pSect->offset != 0, pSect->offset, pSect->size); \ fAddSegInner = true; \ } \ \ /* Section data extract. */ \ pSectExtra->cb = pSect->size; \ pSectExtra->RVA = pSect->addr - pDstSeg->SegInfo.LinkAddress; \ pSectExtra->LinkAddress = pSect->addr; \ if (pSect->offset) \ pSectExtra->offFile = pSect->offset + pThis->offImage; \ else \ pSectExtra->offFile = -1; \ pSectExtra->cFixups = pSect->nreloc; \ pSectExtra->paFixups = NULL; \ pSectExtra->pauFixupVirginData = NULL; \ if (pSect->nreloc) \ pSectExtra->offFixups = pSect->reloff + pThis->offImage; \ else \ pSectExtra->offFixups = -1; \ pSectExtra->fFlags = pSect->flags; \ pSectExtra->iSegment = (uint32_t)(pDstSeg - &pThis->aSegments[0]); \ pSectExtra->pvMachoSection = pSect; \ \ /* Update the segment alignment, if we're not skipping it. */ \ if ( (fAddSegOuter || fAddSegInner) \ && pDstSeg->SegInfo.Alignment < ((RTLDRADDR)1 << pSect->align)) \ pDstSeg->SegInfo.Alignment = (RTLDRADDR)1 << pSect->align; \ \ /* Next section, and if object file next segment. */ \ pSectExtra++; \ pSect++; \ if (fAddSegInner) \ CLOSE_SEGMENT(); \ } \ \ /* Close the segment and advance. */ \ if (fAddSegOuter) \ CLOSE_SEGMENT(); \ \ /* Take down 'execSeg' info for signing */ \ if (fFirstSeg) \ { \ fFirstSeg = false; \ pThis->offSeg0ForCodeSign = pSrcSeg->fileoff; \ pThis->cbSeg0ForCodeSign = pSrcSeg->filesize; /** @todo file or vm size? */ \ pThis->fSeg0ForCodeSign = pSrcSeg->flags; \ } \ } while (0) /* ADD_SEGMENT_AND_ITS_SECTIONS */ ADD_SEGMENT_AND_ITS_SECTIONS(32); break; } case LC_SEGMENT_64: { const segment_command_64_t *pSrcSeg = (const segment_command_64_t *)u.pLoadCmd; section_64_t *pFirstSect = (section_64_t *)(pSrcSeg + 1); section_64_t *pSect = pFirstSect; uint32_t cSectionsLeft = pSrcSeg->nsects; ADD_SEGMENT_AND_ITS_SECTIONS(64); break; } case LC_SYMTAB: switch (pThis->uEffFileType) { case MH_OBJECT: case MH_EXECUTE: case MH_DYLIB: case MH_BUNDLE: case MH_DSYM: case MH_KEXT_BUNDLE: pThis->offSymbols = u.pSymTab->symoff + pThis->offImage; pThis->cSymbols = u.pSymTab->nsyms; pThis->offStrings = u.pSymTab->stroff + pThis->offImage; pThis->cchStrings = u.pSymTab->strsize; break; } break; case LC_DYSYMTAB: pThis->pDySymTab = (dysymtab_command_t *)u.pb; break; case LC_UUID: memcpy(pThis->abImageUuid, u.pUuid->uuid, sizeof(pThis->abImageUuid)); break; case LC_CODE_SIGNATURE: pThis->offCodeSignature = u.pData->dataoff; pThis->cbCodeSignature = u.pData->datasize; break; default: break; } /* command switch */ } /* while more commands */ Assert(pDstSeg == &pThis->aSegments[cSegments - pThis->fMakeGot]); /* * Adjust mapping addresses calculating the image size. */ { bool fLoadLinkEdit = false; PRTLDRMODMACHOSECT pSectExtraItr; RTLDRADDR uNextRVA = 0; RTLDRADDR cb; uint32_t cSegmentsToAdjust = cSegments - pThis->fMakeGot; uint32_t c; for (;;) { /* Check if there is __DWARF segment at the end and make sure it's left out of the RVA negotiations and image loading. */ if ( cSegmentsToAdjust > 0 && !strcmp(pThis->aSegments[cSegmentsToAdjust - 1].SegInfo.pszName, "__DWARF")) { cSegmentsToAdjust--; pThis->aSegments[cSegmentsToAdjust].SegInfo.RVA = NIL_RTLDRADDR; pThis->aSegments[cSegmentsToAdjust].SegInfo.cbMapped = 0; continue; } /* If we're skipping the __LINKEDIT segment, check for it and adjust the number of segments we'll be messing with here. ASSUMES it's last (by now anyway). */ if ( !fLoadLinkEdit && cSegmentsToAdjust > 0 && !strcmp(pThis->aSegments[cSegmentsToAdjust - 1].SegInfo.pszName, "__LINKEDIT")) { cSegmentsToAdjust--; pThis->aSegments[cSegmentsToAdjust].SegInfo.RVA = NIL_RTLDRADDR; pThis->aSegments[cSegmentsToAdjust].SegInfo.cbMapped = 0; continue; } break; } /* Adjust RVAs. */ c = cSegmentsToAdjust; for (pDstSeg = &pThis->aSegments[0]; c-- > 0; pDstSeg++) { uNextRVA = RTLDR_ALIGN_ADDR(uNextRVA, pDstSeg->SegInfo.Alignment); cb = pDstSeg->SegInfo.RVA - uNextRVA; if (cb >= 0x00100000) /* 1MB */ { pDstSeg->SegInfo.RVA = uNextRVA; //pThis->pMod->fFlags |= KLDRMOD_FLAGS_NON_CONTIGUOUS_LINK_ADDRS; } uNextRVA = pDstSeg->SegInfo.RVA + pDstSeg->SegInfo.cb; } /* Calculate the cbMapping members. */ c = cSegmentsToAdjust; for (pDstSeg = &pThis->aSegments[0]; c-- > 1; pDstSeg++) { cb = pDstSeg[1].SegInfo.RVA - pDstSeg->SegInfo.RVA; pDstSeg->SegInfo.cbMapped = (size_t)cb == cb ? (size_t)cb : ~(size_t)0; } cb = RTLDR_ALIGN_ADDR(pDstSeg->SegInfo.cb, pDstSeg->SegInfo.Alignment); pDstSeg->SegInfo.cbMapped = (size_t)cb == cb ? (size_t)cb : ~(size_t)0; /* Set the image size. */ pThis->cbImage = pDstSeg->SegInfo.RVA + cb; /* Fixup the section RVAs (internal). */ c = cSegmentsToAdjust; uNextRVA = pThis->cbImage; pDstSeg = &pThis->aSegments[0]; for (pSectExtraItr = pThis->paSections; pSectExtraItr != pSectExtra; pSectExtraItr++) { if (pSectExtraItr->iSegment < c) pSectExtraItr->RVA += pDstSeg[pSectExtraItr->iSegment].SegInfo.RVA; else { pSectExtraItr->RVA = uNextRVA; uNextRVA += RTLDR_ALIGN_ADDR(pSectExtraItr->cb, 64); } } } /* * Make the GOT segment if necessary. */ if (pThis->fMakeGot) { uint32_t cbPtr = ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) ? sizeof(uint32_t) : sizeof(uint64_t); uint32_t cbGot = pThis->cSymbols * cbPtr; uint32_t cbJmpStubs; pThis->GotRVA = pThis->cbImage; if (pThis->cbJmpStub) { cbGot = RT_ALIGN_Z(cbGot, 64); pThis->JmpStubsRVA = pThis->GotRVA + cbGot; cbJmpStubs = pThis->cbJmpStub * pThis->cSymbols; } else { pThis->JmpStubsRVA = NIL_RTLDRADDR; cbJmpStubs = 0; } pDstSeg = &pThis->aSegments[cSegments - 1]; pDstSeg->SegInfo.pszName = "GOT"; pDstSeg->SegInfo.cchName = 3; pDstSeg->SegInfo.SelFlat = 0; pDstSeg->SegInfo.Sel16bit = 0; pDstSeg->SegInfo.fFlags = 0; pDstSeg->SegInfo.fProt = RTMEM_PROT_READ; pDstSeg->SegInfo.cb = cbGot + cbJmpStubs; pDstSeg->SegInfo.Alignment = 64; pDstSeg->SegInfo.LinkAddress = pThis->LinkAddress + pThis->GotRVA; pDstSeg->SegInfo.offFile = -1; pDstSeg->SegInfo.cbFile = -1; pDstSeg->SegInfo.RVA = pThis->GotRVA; pDstSeg->SegInfo.cbMapped = (size_t)RTLDR_ALIGN_ADDR(cbGot + cbJmpStubs, pDstSeg->SegInfo.Alignment); pDstSeg->iOrgSegNo = UINT32_MAX; pDstSeg->cSections = 0; pDstSeg->paSections = NULL; pThis->cbImage += pDstSeg->SegInfo.cbMapped; } return VINF_SUCCESS; } /** * @interface_method_impl{RTLDROPS,pfnClose} */ static DECLCALLBACK(int) rtldrMachO_Close(PRTLDRMODINTERNAL pMod) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); RTLDRMODMACHO_ASSERT(!pThis->pvMapping); uint32_t i = pThis->cSegments; while (i-- > 0) { uint32_t j = pThis->aSegments[i].cSections; while (j-- > 0) { RTMemFree(pThis->aSegments[i].paSections[j].paFixups); pThis->aSegments[i].paSections[j].paFixups = NULL; RTMemFree(pThis->aSegments[i].paSections[j].pauFixupVirginData); pThis->aSegments[i].paSections[j].pauFixupVirginData = NULL; } } RTMemFree(pThis->pbLoadCommands); pThis->pbLoadCommands = NULL; RTMemFree(pThis->pchStrings); pThis->pchStrings = NULL; RTMemFree(pThis->pvaSymbols); pThis->pvaSymbols = NULL; RTMemFree(pThis->paidxIndirectSymbols); pThis->paidxIndirectSymbols = NULL; RTMemFree(pThis->paRelocations); pThis->paRelocations = NULL; RTMemFree(pThis->pauRelocationsVirginData); pThis->pauRelocationsVirginData = NULL; RTMemFree(pThis->PtrCodeSignature.pb); pThis->PtrCodeSignature.pb = NULL; return VINF_SUCCESS; } /** * Gets the right base address. * * @param pThis The interpreter module instance * @param pBaseAddress The base address, IN & OUT. Optional. */ static void kldrModMachOAdjustBaseAddress(PRTLDRMODMACHO pThis, PRTLDRADDR pBaseAddress) { /* * Adjust the base address. */ if (*pBaseAddress == RTLDR_BASEADDRESS_LINK) *pBaseAddress = pThis->LinkAddress; } /** * Resolves a linker generated symbol. * * The Apple linker generates symbols indicating the start and end of sections * and segments. This function checks for these and returns the right value. * * @returns VINF_SUCCESS or VERR_SYMBOL_NOT_FOUND. * @param pThis The interpreter module instance. * @param pchSymbol The symbol. * @param cchSymbol The length of the symbol. * @param BaseAddress The base address to apply when calculating the * value. * @param puValue Where to return the symbol value. */ static int kldrModMachOQueryLinkerSymbol(PRTLDRMODMACHO pThis, const char *pchSymbol, size_t cchSymbol, RTLDRADDR BaseAddress, PRTLDRADDR puValue) { /* * Match possible name prefixes. */ static const struct { const char *pszPrefix; uint32_t cchPrefix; bool fSection; bool fStart; } s_aPrefixes[] = { { "section$start$", (uint8_t)sizeof("section$start$") - 1, true, true }, { "section$end$", (uint8_t)sizeof("section$end$") - 1, true, false}, { "segment$start$", (uint8_t)sizeof("segment$start$") - 1, false, true }, { "segment$end$", (uint8_t)sizeof("segment$end$") - 1, false, false}, }; size_t cchSectName = 0; const char *pchSectName = ""; size_t cchSegName = 0; const char *pchSegName = NULL; uint32_t iPrefix = RT_ELEMENTS(s_aPrefixes) - 1; uint32_t iSeg; RTLDRADDR uValue; for (;;) { uint8_t const cchPrefix = s_aPrefixes[iPrefix].cchPrefix; if ( cchSymbol > cchPrefix && strncmp(pchSymbol, s_aPrefixes[iPrefix].pszPrefix, cchPrefix) == 0) { pchSegName = pchSymbol + cchPrefix; cchSegName = cchSymbol - cchPrefix; break; } /* next */ if (!iPrefix) return VERR_SYMBOL_NOT_FOUND; iPrefix--; } /* * Split the remainder into segment and section name, if necessary. */ if (s_aPrefixes[iPrefix].fSection) { pchSectName = (const char *)memchr(pchSegName, '$', cchSegName); if (!pchSectName) return VERR_SYMBOL_NOT_FOUND; cchSegName = pchSectName - pchSegName; pchSectName++; cchSectName = cchSymbol - (pchSectName - pchSymbol); } /* * Locate the segment. */ if (!pThis->cSegments) return VERR_SYMBOL_NOT_FOUND; for (iSeg = 0; iSeg < pThis->cSegments; iSeg++) { if ( pThis->aSegments[iSeg].SegInfo.cchName >= cchSegName && memcmp(pThis->aSegments[iSeg].SegInfo.pszName, pchSegName, cchSegName) == 0) { section_32_t const *pSect; if ( pThis->aSegments[iSeg].SegInfo.cchName == cchSegName && pThis->Hdr.filetype != MH_OBJECT /* Good enough for __DWARF segs in MH_DHSYM, I hope. */) break; pSect = (section_32_t *)pThis->aSegments[iSeg].paSections[0].pvMachoSection; if ( pThis->uEffFileType == MH_OBJECT && pThis->aSegments[iSeg].SegInfo.cchName > cchSegName + 1 && pThis->aSegments[iSeg].SegInfo.pszName[cchSegName] == '.' && strncmp(&pThis->aSegments[iSeg].SegInfo.pszName[cchSegName + 1], pSect->sectname, sizeof(pSect->sectname)) == 0 && pThis->aSegments[iSeg].SegInfo.cchName - cchSegName - 1 <= sizeof(pSect->sectname) ) break; } } if (iSeg >= pThis->cSegments) return VERR_SYMBOL_NOT_FOUND; if (!s_aPrefixes[iPrefix].fSection) { /* * Calculate the segment start/end address. */ uValue = pThis->aSegments[iSeg].SegInfo.RVA; if (!s_aPrefixes[iPrefix].fStart) uValue += pThis->aSegments[iSeg].SegInfo.cb; } else { /* * Locate the section. */ uint32_t iSect = pThis->aSegments[iSeg].cSections; if (!iSect) return VERR_SYMBOL_NOT_FOUND; for (;;) { section_32_t *pSect = (section_32_t *)pThis->aSegments[iSeg].paSections[iSect].pvMachoSection; if ( cchSectName <= sizeof(pSect->sectname) && memcmp(pSect->sectname, pchSectName, cchSectName) == 0 && ( cchSectName == sizeof(pSect->sectname) || pSect->sectname[cchSectName] == '\0') ) break; /* next */ if (!iSect) return VERR_SYMBOL_NOT_FOUND; iSect--; } uValue = pThis->aSegments[iSeg].paSections[iSect].RVA; if (!s_aPrefixes[iPrefix].fStart) uValue += pThis->aSegments[iSeg].paSections[iSect].cb; } /* * Convert from RVA to load address. */ uValue += BaseAddress; if (puValue) *puValue = uValue; return VINF_SUCCESS; } /** * @interface_method_impl{RTLDROPS,pfnGetSymbolEx} */ static DECLCALLBACK(int) rtldrMachO_GetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress, uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); RT_NOREF(pvBits); //RT_NOREF(pszVersion); //RT_NOREF(pfnGetForwarder); //RT_NOREF(pvUser); uint32_t fKind = RTLDRSYMKIND_REQ_FLAT; uint32_t *pfKind = &fKind; size_t cchSymbol = pszSymbol ? strlen(pszSymbol) : 0; /* * Resolve defaults. */ kldrModMachOAdjustBaseAddress(pThis, &BaseAddress); /* * Refuse segmented requests for now. */ RTLDRMODMACHO_CHECK_RETURN( !pfKind || (*pfKind & RTLDRSYMKIND_REQ_TYPE_MASK) == RTLDRSYMKIND_REQ_FLAT, VERR_LDRMACHO_TODO); /* * Take action according to file type. */ int rc; if ( pThis->Hdr.filetype == MH_OBJECT || pThis->Hdr.filetype == MH_EXECUTE /** @todo dylib, execute, dsym: symbols */ || pThis->Hdr.filetype == MH_DYLIB || pThis->Hdr.filetype == MH_BUNDLE || pThis->Hdr.filetype == MH_DSYM || pThis->Hdr.filetype == MH_KEXT_BUNDLE) { rc = kldrModMachOLoadObjSymTab(pThis); if (RT_SUCCESS(rc)) { if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) rc = kldrModMachODoQuerySymbol32Bit(pThis, (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols, pThis->pchStrings, pThis->cchStrings, BaseAddress, iOrdinal, pszSymbol, (uint32_t)cchSymbol, pValue, pfKind); else rc = kldrModMachODoQuerySymbol64Bit(pThis, (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols, pThis->pchStrings, pThis->cchStrings, BaseAddress, iOrdinal, pszSymbol, (uint32_t)cchSymbol, pValue, pfKind); } /* * Check for link-editor generated symbols and supply what we can. * * As small service to clients that insists on adding a '_' prefix * before querying symbols, we will ignore the prefix. */ if ( rc == VERR_SYMBOL_NOT_FOUND && cchSymbol > sizeof("section$end$") - 1 && ( pszSymbol[0] == 's' || (pszSymbol[1] == 's' && pszSymbol[0] == '_') ) && memchr(pszSymbol, '$', cchSymbol) ) { if (pszSymbol[0] == '_') rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol + 1, cchSymbol - 1, BaseAddress, pValue); else rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol, cchSymbol, BaseAddress, pValue); } } else rc = VERR_LDRMACHO_TODO; return rc; } /** * Lookup a symbol in a 32-bit symbol table. * * @returns IPRT status code. * @param pThis * @param paSyms Pointer to the symbol table. * @param cSyms Number of symbols in the table. * @param pchStrings Pointer to the string table. * @param cchStrings Size of the string table. * @param BaseAddress Adjusted base address, see kLdrModQuerySymbol. * @param iSymbol See kLdrModQuerySymbol. * @param pchSymbol See kLdrModQuerySymbol. * @param cchSymbol See kLdrModQuerySymbol. * @param puValue See kLdrModQuerySymbol. * @param pfKind See kLdrModQuerySymbol. */ static int kldrModMachODoQuerySymbol32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol, const char *pchSymbol, uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind) { /* * Find a valid symbol matching the search criteria. */ if (iSymbol == UINT32_MAX) { /* simplify validation. */ /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (cchStrings <= cchSymbol + 1) return VERR_SYMBOL_NOT_FOUND; cchStrings -= cchSymbol + 1; /* external symbols are usually at the end, so search the other way. */ for (iSymbol = cSyms - 1; iSymbol != UINT32_MAX; iSymbol--) { const char *psz; /* Skip irrellevant and non-public symbols. */ if (paSyms[iSymbol].n_type & MACHO_N_STAB) continue; if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) continue; if (!(paSyms[iSymbol].n_type & MACHO_N_EXT)) /*??*/ continue; if (paSyms[iSymbol].n_type & MACHO_N_PEXT) /*??*/ continue; /* get name */ if (!paSyms[iSymbol].n_un.n_strx) continue; if ((uint32_t)paSyms[iSymbol].n_un.n_strx >= cchStrings) continue; psz = &pchStrings[paSyms[iSymbol].n_un.n_strx]; if (psz[cchSymbol + 1]) continue; /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (*psz != '_' || memcmp(psz + 1, pchSymbol, cchSymbol)) continue; /* match! */ break; } if (iSymbol == UINT32_MAX) return VERR_SYMBOL_NOT_FOUND; } else { if (iSymbol >= cSyms) return VERR_SYMBOL_NOT_FOUND; if (paSyms[iSymbol].n_type & MACHO_N_STAB) return VERR_SYMBOL_NOT_FOUND; if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) return VERR_SYMBOL_NOT_FOUND; } /* * Calc the return values. */ if (pfKind) { if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) *pfKind = RTLDRSYMKIND_32BIT | RTLDRSYMKIND_NO_TYPE; else *pfKind = RTLDRSYMKIND_64BIT | RTLDRSYMKIND_NO_TYPE; if (paSyms[iSymbol].n_desc & N_WEAK_DEF) *pfKind |= RTLDRSYMKIND_WEAK; } switch (paSyms[iSymbol].n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSect; RTLDRADDR offSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSymbol].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSect = &pThis->paSections[paSyms[iSymbol].n_sect - 1]; offSect = paSyms[iSymbol].n_value - pSect->LinkAddress; RTLDRMODMACHO_CHECK_RETURN( offSect <= pSect->cb || ( paSyms[iSymbol].n_sect == 1 /* special hack for __mh_execute_header */ && offSect == 0U - pSect->RVA && pThis->uEffFileType != MH_OBJECT), VERR_LDRMACHO_BAD_SYMBOL); if (puValue) *puValue = BaseAddress + pSect->RVA + offSect; if ( pfKind && (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE))) *pfKind = (*pfKind & ~RTLDRSYMKIND_TYPE_MASK) | RTLDRSYMKIND_CODE; break; } case MACHO_N_ABS: if (puValue) *puValue = paSyms[iSymbol].n_value; /*if (pfKind) pfKind |= RTLDRSYMKIND_ABS;*/ break; case MACHO_N_PBUD: case MACHO_N_INDR: /** @todo implement indirect and prebound symbols. */ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); } return VINF_SUCCESS; } /** * Lookup a symbol in a 64-bit symbol table. * * @returns IPRT status code. * @param pThis * @param paSyms Pointer to the symbol table. * @param cSyms Number of symbols in the table. * @param pchStrings Pointer to the string table. * @param cchStrings Size of the string table. * @param BaseAddress Adjusted base address, see kLdrModQuerySymbol. * @param iSymbol See kLdrModQuerySymbol. * @param pchSymbol See kLdrModQuerySymbol. * @param cchSymbol See kLdrModQuerySymbol. * @param puValue See kLdrModQuerySymbol. * @param pfKind See kLdrModQuerySymbol. */ static int kldrModMachODoQuerySymbol64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t iSymbol, const char *pchSymbol, uint32_t cchSymbol, PRTLDRADDR puValue, uint32_t *pfKind) { /* * Find a valid symbol matching the search criteria. */ if (iSymbol == UINT32_MAX) { /* simplify validation. */ /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (cchStrings <= cchSymbol + 1) return VERR_SYMBOL_NOT_FOUND; cchStrings -= cchSymbol + 1; /* external symbols are usually at the end, so search the other way. */ for (iSymbol = cSyms - 1; iSymbol != UINT32_MAX; iSymbol--) { const char *psz; /* Skip irrellevant and non-public symbols. */ if (paSyms[iSymbol].n_type & MACHO_N_STAB) continue; if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) continue; if (!(paSyms[iSymbol].n_type & MACHO_N_EXT)) /*??*/ continue; if (paSyms[iSymbol].n_type & MACHO_N_PEXT) /*??*/ continue; /* get name */ if (!paSyms[iSymbol].n_un.n_strx) continue; if ((uint32_t)paSyms[iSymbol].n_un.n_strx >= cchStrings) continue; psz = &pchStrings[paSyms[iSymbol].n_un.n_strx]; if (psz[cchSymbol + 1]) continue; if (*psz != '_' || memcmp(psz + 1, pchSymbol, cchSymbol)) continue; /* match! */ break; } if (iSymbol == UINT32_MAX) return VERR_SYMBOL_NOT_FOUND; } else { if (iSymbol >= cSyms) return VERR_SYMBOL_NOT_FOUND; if (paSyms[iSymbol].n_type & MACHO_N_STAB) return VERR_SYMBOL_NOT_FOUND; if ((paSyms[iSymbol].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) return VERR_SYMBOL_NOT_FOUND; } /* * Calc the return values. */ if (pfKind) { if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) *pfKind = RTLDRSYMKIND_32BIT | RTLDRSYMKIND_NO_TYPE; else *pfKind = RTLDRSYMKIND_64BIT | RTLDRSYMKIND_NO_TYPE; if (paSyms[iSymbol].n_desc & N_WEAK_DEF) *pfKind |= RTLDRSYMKIND_WEAK; } switch (paSyms[iSymbol].n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSect; RTLDRADDR offSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSymbol].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSect = &pThis->paSections[paSyms[iSymbol].n_sect - 1]; offSect = paSyms[iSymbol].n_value - pSect->LinkAddress; RTLDRMODMACHO_CHECK_RETURN( offSect <= pSect->cb || ( paSyms[iSymbol].n_sect == 1 /* special hack for __mh_execute_header */ && offSect == 0U - pSect->RVA && pThis->uEffFileType != MH_OBJECT), VERR_LDRMACHO_BAD_SYMBOL); if (puValue) *puValue = BaseAddress + pSect->RVA + offSect; if ( pfKind && (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE))) *pfKind = (*pfKind & ~RTLDRSYMKIND_TYPE_MASK) | RTLDRSYMKIND_CODE; break; } case MACHO_N_ABS: if (puValue) *puValue = paSyms[iSymbol].n_value; /*if (pfKind) pfKind |= RTLDRSYMKIND_ABS;*/ break; case MACHO_N_PBUD: case MACHO_N_INDR: /** @todo implement indirect and prebound symbols. */ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); } return VINF_SUCCESS; } /** * @interface_method_impl{RTLDROPS,pfnEnumSymbols} */ static DECLCALLBACK(int) rtldrMachO_EnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); RT_NOREF(pvBits); /* * Resolve defaults. */ kldrModMachOAdjustBaseAddress(pThis, &BaseAddress); /* * Take action according to file type. */ int rc; if ( pThis->Hdr.filetype == MH_OBJECT || pThis->Hdr.filetype == MH_EXECUTE /** @todo dylib, execute, dsym: symbols */ || pThis->Hdr.filetype == MH_DYLIB || pThis->Hdr.filetype == MH_BUNDLE || pThis->Hdr.filetype == MH_DSYM || pThis->Hdr.filetype == MH_KEXT_BUNDLE) { rc = kldrModMachOLoadObjSymTab(pThis); if (RT_SUCCESS(rc)) { if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) rc = kldrModMachODoEnumSymbols32Bit(pThis, (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols, pThis->pchStrings, pThis->cchStrings, BaseAddress, fFlags, pfnCallback, pvUser); else rc = kldrModMachODoEnumSymbols64Bit(pThis, (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols, pThis->pchStrings, pThis->cchStrings, BaseAddress, fFlags, pfnCallback, pvUser); } } else RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); return rc; } /** * Enum a 32-bit symbol table. * * @returns IPRT status code. * @param pThis * @param paSyms Pointer to the symbol table. * @param cSyms Number of symbols in the table. * @param pchStrings Pointer to the string table. * @param cchStrings Size of the string table. * @param BaseAddress Adjusted base address, see kLdrModEnumSymbols. * @param fFlags See kLdrModEnumSymbols. * @param pfnCallback See kLdrModEnumSymbols. * @param pvUser See kLdrModEnumSymbols. */ static int kldrModMachODoEnumSymbols32Bit(PRTLDRMODMACHO pThis, const macho_nlist_32_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser) { const uint32_t fKindBase = pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE ? RTLDRSYMKIND_32BIT : RTLDRSYMKIND_64BIT; uint32_t iSym; int rc; /* * Iterate the symbol table. */ for (iSym = 0; iSym < cSyms; iSym++) { uint32_t fKind; RTLDRADDR uValue; const char *psz; size_t cch; /* Skip debug symbols and undefined symbols. */ if (paSyms[iSym].n_type & MACHO_N_STAB) continue; if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) continue; /* Skip non-public symbols unless they are requested explicitly. */ if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL)) { if (!(paSyms[iSym].n_type & MACHO_N_EXT)) /*??*/ continue; if (paSyms[iSym].n_type & MACHO_N_PEXT) /*??*/ continue; if (!paSyms[iSym].n_un.n_strx) continue; } /* * Gather symbol info */ /* name */ RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_un.n_strx < cchStrings, VERR_LDRMACHO_BAD_SYMBOL); psz = &pchStrings[paSyms[iSym].n_un.n_strx]; cch = strlen(psz); if (!cch) psz = NULL; /* kind & value */ fKind = fKindBase; if (paSyms[iSym].n_desc & N_WEAK_DEF) fKind |= RTLDRSYMKIND_WEAK; switch (paSyms[iSym].n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSym].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSect = &pThis->paSections[paSyms[iSym].n_sect - 1]; uValue = paSyms[iSym].n_value - pSect->LinkAddress; RTLDRMODMACHO_CHECK_RETURN( uValue <= pSect->cb || ( paSyms[iSym].n_sect == 1 /* special hack for __mh_execute_header */ && uValue == 0U - pSect->RVA && pThis->uEffFileType != MH_OBJECT), VERR_LDRMACHO_BAD_SYMBOL); uValue += BaseAddress + pSect->RVA; if (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE)) fKind |= RTLDRSYMKIND_CODE; else fKind |= RTLDRSYMKIND_NO_TYPE; break; } case MACHO_N_ABS: uValue = paSyms[iSym].n_value; fKind |= RTLDRSYMKIND_NO_TYPE /*RTLDRSYMKIND_ABS*/; break; case MACHO_N_PBUD: case MACHO_N_INDR: /** @todo implement indirect and prebound symbols. */ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); } /* * Do callback. */ /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (cch > 1 && *psz == '_') psz++; rc = pfnCallback(&pThis->Core, psz, iSym, uValue/*, fKind*/, pvUser); if (rc != VINF_SUCCESS) return rc; } return VINF_SUCCESS; } /** * Enum a 64-bit symbol table. * * @returns IPRT status code. * @param pThis * @param paSyms Pointer to the symbol table. * @param cSyms Number of symbols in the table. * @param pchStrings Pointer to the string table. * @param cchStrings Size of the string table. * @param BaseAddress Adjusted base address, see kLdrModEnumSymbols. * @param fFlags See kLdrModEnumSymbols. * @param pfnCallback See kLdrModEnumSymbols. * @param pvUser See kLdrModEnumSymbols. */ static int kldrModMachODoEnumSymbols64Bit(PRTLDRMODMACHO pThis, const macho_nlist_64_t *paSyms, uint32_t cSyms, const char *pchStrings, uint32_t cchStrings, RTLDRADDR BaseAddress, uint32_t fFlags, PFNRTLDRENUMSYMS pfnCallback, void *pvUser) { const uint32_t fKindBase = pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE ? RTLDRSYMKIND_64BIT : RTLDRSYMKIND_32BIT; uint32_t iSym; int rc; /* * Iterate the symbol table. */ for (iSym = 0; iSym < cSyms; iSym++) { uint32_t fKind; RTLDRADDR uValue; const char *psz; size_t cch; /* Skip debug symbols and undefined symbols. */ if (paSyms[iSym].n_type & MACHO_N_STAB) continue; if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) continue; /* Skip non-public symbols unless they are requested explicitly. */ if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL)) { if (!(paSyms[iSym].n_type & MACHO_N_EXT)) /*??*/ continue; if (paSyms[iSym].n_type & MACHO_N_PEXT) /*??*/ continue; if (!paSyms[iSym].n_un.n_strx) continue; } /* * Gather symbol info */ /* name */ RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_un.n_strx < cchStrings, VERR_LDRMACHO_BAD_SYMBOL); psz = &pchStrings[paSyms[iSym].n_un.n_strx]; cch = strlen(psz); if (!cch) psz = NULL; /* kind & value */ fKind = fKindBase; if (paSyms[iSym].n_desc & N_WEAK_DEF) fKind |= RTLDRSYMKIND_WEAK; switch (paSyms[iSym].n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)(paSyms[iSym].n_sect - 1) < pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSect = &pThis->paSections[paSyms[iSym].n_sect - 1]; uValue = paSyms[iSym].n_value - pSect->LinkAddress; RTLDRMODMACHO_CHECK_RETURN( uValue <= pSect->cb || ( paSyms[iSym].n_sect == 1 /* special hack for __mh_execute_header */ && uValue == 0U - pSect->RVA && pThis->uEffFileType != MH_OBJECT), VERR_LDRMACHO_BAD_SYMBOL); uValue += BaseAddress + pSect->RVA; if (pSect->fFlags & (S_ATTR_PURE_INSTRUCTIONS | S_ATTR_SELF_MODIFYING_CODE)) fKind |= RTLDRSYMKIND_CODE; else fKind |= RTLDRSYMKIND_NO_TYPE; break; } case MACHO_N_ABS: uValue = paSyms[iSym].n_value; fKind |= RTLDRSYMKIND_NO_TYPE /*RTLDRSYMKIND_ABS*/; break; case MACHO_N_PBUD: case MACHO_N_INDR: /** @todo implement indirect and prebound symbols. */ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); } /* * Do callback. */ /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (cch > 1 && *psz == '_') psz++; rc = pfnCallback(&pThis->Core, psz, iSym, uValue/*, fKind*/, pvUser); if (rc != VINF_SUCCESS) return rc; } return VINF_SUCCESS; } #if 0 /** @copydoc kLdrModGetImport */ static int kldrModMachOGetImport(PRTLDRMODINTERNAL pMod, const void *pvBits, uint32_t iImport, char *pszName, size_t cchName) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); RT_NOREF(pvBits); RT_NOREF(iImport); RT_NOREF(pszName); RT_NOREF(cchName); if (pThis->Hdr.filetype == MH_OBJECT) return KLDR_ERR_IMPORT_ORDINAL_OUT_OF_BOUNDS; /* later */ return KLDR_ERR_IMPORT_ORDINAL_OUT_OF_BOUNDS; } /** @copydoc kLdrModNumberOfImports */ static int32_t kldrModMachONumberOfImports(PRTLDRMODINTERNAL pMod, const void *pvBits) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); RT_NOREF(pvBits); if (pThis->Hdr.filetype == MH_OBJECT) return VINF_SUCCESS; /* later */ return VINF_SUCCESS; } /** @copydoc kLdrModGetStackInfo */ static int kldrModMachOGetStackInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PKLDRSTACKINFO pStackInfo) { /*PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);*/ RT_NOREF(pMod); RT_NOREF(pvBits); RT_NOREF(BaseAddress); pStackInfo->Address = NIL_RTLDRADDR; pStackInfo->LinkAddress = NIL_RTLDRADDR; pStackInfo->cbStack = pStackInfo->cbStackThread = 0; /* later */ return VINF_SUCCESS; } /** @copydoc kLdrModQueryMainEntrypoint */ static int kldrModMachOQueryMainEntrypoint(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PRTLDRADDR pMainEPAddress) { #if 0 PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); int rc; /* * Resolve base address alias if any. */ rc = kldrModMachOBitsAndBaseAddress(pThis, NULL, &BaseAddress); if (RT_FAILURE(rc)) return rc; /* * Convert the address from the header. */ *pMainEPAddress = pThis->Hdrs.OptionalHeader.AddressOfEntryPoint ? BaseAddress + pThis->Hdrs.OptionalHeader.AddressOfEntryPoint : NIL_RTLDRADDR; #else *pMainEPAddress = NIL_RTLDRADDR; RT_NOREF(pvBits); RT_NOREF(BaseAddress); RT_NOREF(pMod); #endif return VINF_SUCCESS; } #endif /** * @interface_method_impl{RTLDROPS,pfnEnumDbgInfo} */ static DECLCALLBACK(int) rtldrMachO_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, PFNRTLDRENUMDBG pfnCallback, void *pvUser) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); int rc = VINF_SUCCESS; uint32_t iSect; RT_NOREF(pvBits); for (iSect = 0; iSect < pThis->cSections; iSect++) { /* (32-bit & 64-bit starts the same way) */ section_32_t *pMachOSect = (section_32_t *)pThis->paSections[iSect].pvMachoSection; char szTmp[sizeof(pMachOSect->sectname) + 1]; if (strcmp(pMachOSect->segname, "__DWARF")) continue; memcpy(szTmp, pMachOSect->sectname, sizeof(pMachOSect->sectname)); szTmp[sizeof(pMachOSect->sectname)] = '\0'; RTLDRDBGINFO DbgInfo; DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; DbgInfo.iDbgInfo = iSect; DbgInfo.LinkAddress = pThis->paSections[iSect].LinkAddress; DbgInfo.cb = pThis->paSections[iSect].cb; DbgInfo.pszExtFile = NULL; DbgInfo.u.Dwarf.pszSection = szTmp; rc = pfnCallback(&pThis->Core, &DbgInfo, pvUser); if (rc != VINF_SUCCESS) break; } return rc; } #if 0 /** @copydoc kLdrModHasDbgInfo */ static int kldrModMachOHasDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits) { /*PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core);*/ #if 0 /* * Base this entirely on the presence of a debug directory. */ if ( pThis->Hdrs.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].Size < sizeof(IMAGE_DEBUG_DIRECTORY) /* screw borland linkers */ || !pThis->Hdrs.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG].VirtualAddress) return KLDR_ERR_NO_DEBUG_INFO; return VINF_SUCCESS; #else RT_NOREF(pMod); RT_NOREF(pvBits); return VERR_LDR_NO_DEBUG_INFO; #endif } /** @copydoc kLdrModMap */ static int kldrModMachOMap(PRTLDRMODINTERNAL pMod) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); unsigned fFixed; uint32_t i; void *pvBase; int rc; if (!pThis->fCanLoad) return VERR_LDRMACHO_TODO; /* * Already mapped? */ if (pThis->pvMapping) return KLDR_ERR_ALREADY_MAPPED; /* * Map it. */ /* fixed image? */ fFixed = pMod->enmType == KLDRTYPE_EXECUTABLE_FIXED || pMod->enmType == KLDRTYPE_SHARED_LIBRARY_FIXED; if (!fFixed) pvBase = NULL; else { pvBase = (void *)(uintptr_t)pMod->aSegments[0].LinkAddress; if ((uintptr_t)pvBase != pMod->aSegments[0].LinkAddress) return VERR_LDR_ADDRESS_OVERFLOW; } /* try do the prepare */ rc = kRdrMap(pMod->pRdr, &pvBase, pMod->cSegments, pMod->aSegments, fFixed); if (RT_FAILURE(rc)) return rc; /* * Update the segments with their map addresses. */ for (i = 0; i < pMod->cSegments; i++) { if (pMod->aSegments[i].RVA != NIL_RTLDRADDR) pMod->aSegments[i].MapAddress = (uintptr_t)pvBase + (uintptr_t)pMod->aSegments[i].RVA; } pThis->pvMapping = pvBase; return VINF_SUCCESS; } /** @copydoc kLdrModUnmap */ static int kldrModMachOUnmap(PRTLDRMODINTERNAL pMod) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); uint32_t i; int rc; /* * Mapped? */ if (!pThis->pvMapping) return KLDR_ERR_NOT_MAPPED; /* * Try unmap the image. */ rc = kRdrUnmap(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments); if (RT_FAILURE(rc)) return rc; /* * Update the segments to reflect that they aren't mapped any longer. */ pThis->pvMapping = NULL; for (i = 0; i < pMod->cSegments; i++) pMod->aSegments[i].MapAddress = 0; return VINF_SUCCESS; } /** @copydoc kLdrModAllocTLS */ static int kldrModMachOAllocTLS(PRTLDRMODINTERNAL pMod, void *pvMapping) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); /* * Mapped? */ if ( pvMapping == KLDRMOD_INT_MAP && !pThis->pvMapping ) return KLDR_ERR_NOT_MAPPED; return VINF_SUCCESS; } /** @copydoc kLdrModFreeTLS */ static void kldrModMachOFreeTLS(PRTLDRMODINTERNAL pMod, void *pvMapping) { RT_NOREF(pMod); RT_NOREF(pvMapping); } /** @copydoc kLdrModReload */ static int kldrModMachOReload(PRTLDRMODINTERNAL pMod) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); /* * Mapped? */ if (!pThis->pvMapping) return KLDR_ERR_NOT_MAPPED; /* the file provider does it all */ return kRdrRefresh(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments); } /** @copydoc kLdrModFixupMapping */ static int kldrModMachOFixupMapping(PRTLDRMODINTERNAL pMod, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); int rc, rc2; /* * Mapped? */ if (!pThis->pvMapping) return KLDR_ERR_NOT_MAPPED; /* * Before doing anything we'll have to make all pages writable. */ rc = kRdrProtect(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments, 1 /* unprotect */); if (RT_FAILURE(rc)) return rc; /* * Resolve imports and apply base relocations. */ rc = rtldrMachO_RelocateBits(pMod, pThis->pvMapping, (uintptr_t)pThis->pvMapping, pThis->LinkAddress, pfnGetImport, pvUser); /* * Restore protection. */ rc2 = kRdrProtect(pMod->pRdr, pThis->pvMapping, pMod->cSegments, pMod->aSegments, 0 /* protect */); if (RT_SUCCESS(rc) && RT_FAILURE(rc2) rc = rc2; return rc; } #endif /** * Worker for resolving an undefined 32-bit symbol table entry. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pSym The symbol table entry. * @param BaseAddress The module base address. * @param pfnGetImport The callback for resolving an imported symbol. * @param pvUser User argument to the callback. */ DECLINLINE(int) rtdlrModMachOHandleUndefinedSymbol32(PRTLDRMODMACHO pThis, macho_nlist_32_t *pSym, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { RTLDRADDR Value = NIL_RTLDRADDR; /** @todo Implement N_REF_TO_WEAK. */ RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_desc & N_REF_TO_WEAK), VERR_LDRMACHO_TODO); /* Get the symbol name. */ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_un.n_strx < pThis->cchStrings, VERR_LDRMACHO_BAD_SYMBOL); const char *pszSymbol = &pThis->pchStrings[pSym->n_un.n_strx]; size_t cchSymbol = strlen(pszSymbol); /* Check for linker defined symbols relating to sections and segments. */ int rc; if ( cchSymbol <= sizeof("section$end$") - 1 || *pszSymbol != 's' || memchr(pszSymbol, '$', cchSymbol) == NULL) rc = VERR_SYMBOL_NOT_FOUND; else rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol, cchSymbol, BaseAddress, &Value); /* Ask the user for an address to the symbol. */ //uint32_t fKind = RTLDRSYMKIND_REQ_FLAT; /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (RT_FAILURE_NP(rc)) rc = pfnGetImport(&pThis->Core, NULL /*pszModule*/, pszSymbol + (pszSymbol[0] == '_'), UINT32_MAX, &Value/*, &fKind*/, pvUser); if (RT_SUCCESS(rc)) { /* likely */ } /* If weak reference we can continue, otherwise fail? */ else if (pSym->n_desc & N_WEAK_REF) Value = 0; else return rc; /* Update the symbol. */ pSym->n_value = (uint32_t)Value; if (pSym->n_value == Value) return VINF_SUCCESS; return VERR_LDR_ADDRESS_OVERFLOW; } /** * Worker for resolving an undefined 64-bit symbol table entry. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pSym The symbol table entry. * @param BaseAddress The module base address. * @param pfnGetImport The callback for resolving an imported symbol. * @param pvUser User argument to the callback. */ DECLINLINE(int) rtdlrModMachOHandleUndefinedSymbol64(PRTLDRMODMACHO pThis, macho_nlist_64_t *pSym, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { RTLDRADDR Value = NIL_RTLDRADDR; /** @todo Implement N_REF_TO_WEAK. */ RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_desc & N_REF_TO_WEAK), VERR_LDRMACHO_TODO); /* Get the symbol name. */ RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_un.n_strx < pThis->cchStrings, VERR_LDRMACHO_BAD_SYMBOL); const char *pszSymbol = &pThis->pchStrings[pSym->n_un.n_strx]; size_t cchSymbol = strlen(pszSymbol); /* Check for linker defined symbols relating to sections and segments. */ int rc; if ( cchSymbol <= sizeof("section$end$") - 1 || *pszSymbol != 's' || memchr(pszSymbol, '$', cchSymbol) == NULL) rc = VERR_SYMBOL_NOT_FOUND; else rc = kldrModMachOQueryLinkerSymbol(pThis, pszSymbol, cchSymbol, BaseAddress, &Value); /* Ask the user for an address to the symbol. */ //uint32_t fKind = RTLDRSYMKIND_REQ_FLAT; /** @todo figure out a better way to deal with underscore prefixes. sigh. */ if (RT_FAILURE_NP(rc)) rc = pfnGetImport(&pThis->Core, NULL /*pszModule*/, pszSymbol + (pszSymbol[0] == '_'), UINT32_MAX, &Value/*, &fKind*/, pvUser); if (RT_SUCCESS(rc)) { /* likely */ } /* If weak reference we can continue, otherwise fail? */ else if (pSym->n_desc & N_WEAK_REF) Value = 0; else return rc; /* Update the symbol. */ pSym->n_value = (uint64_t)Value; if (pSym->n_value == Value) return VINF_SUCCESS; return VERR_LDR_ADDRESS_OVERFLOW; } /** * MH_OBJECT: Resolves undefined symbols (imports). * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param BaseAddress The module base address. * @param pfnGetImport The callback for resolving an imported symbol. * @param pvUser User argument to the callback. */ static int kldrModMachOObjDoImports(PRTLDRMODMACHO pThis, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { /* * Ensure that we've got the symbol table. */ int rc = kldrModMachOLoadObjSymTab(pThis); if (RT_FAILURE(rc)) return rc; /* * Iterate the symbol table and resolve undefined symbols. * We currently ignore REFERENCE_TYPE. */ const uint32_t cSyms = pThis->cSymbols; if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) { macho_nlist_32_t *paSyms = (macho_nlist_32_t *)pThis->pvaSymbols; for (uint32_t iSym = 0; iSym < cSyms; iSym++) { /* skip stabs */ if (paSyms[iSym].n_type & MACHO_N_STAB) continue; if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) { rc = rtdlrModMachOHandleUndefinedSymbol32(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser); if (RT_FAILURE(rc)) break; } else if (paSyms[iSym].n_desc & N_WEAK_DEF) { /** @todo implement weak symbols. */ /*return VERR_LDRMACHO_TODO; - ignored for now. */ } } } else { /* (Identical to the 32-bit code, just different paSym type. (and n_strx is unsigned)) */ macho_nlist_64_t *paSyms = (macho_nlist_64_t *)pThis->pvaSymbols; for (uint32_t iSym = 0; iSym < cSyms; iSym++) { /* skip stabs */ if (paSyms[iSym].n_type & MACHO_N_STAB) continue; if ((paSyms[iSym].n_type & MACHO_N_TYPE) == MACHO_N_UNDF) { rc = rtdlrModMachOHandleUndefinedSymbol64(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser); if (RT_FAILURE(rc)) break; } else if (paSyms[iSym].n_desc & N_WEAK_DEF) { /** @todo implement weak symbols. */ /*return VERR_LDRMACHO_TODO; - ignored for now. */ } } } return rc; } /** * Dylib: Resolves undefined symbols (imports). * * This is conceptually identically to kldrModMachOObjDoImports, only * LC_DYSYMTAB helps us avoid working over the whole symbol table. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param BaseAddress The module base address. * @param pfnGetImport The callback for resolving an imported symbol. * @param pvUser User argument to the callback. */ static int kldrModMachODylibDoImports(PRTLDRMODMACHO pThis, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { /* * There must be a LC_DYSYMTAB. * We might be lucky, though, and not have any imports. */ dysymtab_command_t const *pDySymTab = pThis->pDySymTab; AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2); if (pDySymTab->nundefsym == 0) return VINF_SUCCESS; /* * Ensure that we've got the symbol table. */ int rc = kldrModMachOLoadObjSymTab(pThis); if (RT_FAILURE(rc)) return rc; /* * Iterate the give symbol table section containing undefined symbols and resolve them. */ uint32_t const cSyms = pDySymTab->iundefsym + pDySymTab->nundefsym; if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) { macho_nlist_32_t *paSyms = (macho_nlist_32_t *)pThis->pvaSymbols; for (uint32_t iSym = pDySymTab->iundefsym; RT_SUCCESS(rc) && iSym < cSyms; iSym++) { AssertContinue((paSyms[iSym].n_type & (MACHO_N_TYPE | MACHO_N_STAB)) == MACHO_N_UNDF); rc = rtdlrModMachOHandleUndefinedSymbol32(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser); } } else { /* (Identical to the 32-bit code, just different paSym type. (and n_strx is unsigned)) */ macho_nlist_64_t *paSyms = (macho_nlist_64_t *)pThis->pvaSymbols; for (uint32_t iSym = pDySymTab->iundefsym; RT_SUCCESS(rc) && iSym < cSyms; iSym++) { AssertContinue((paSyms[iSym].n_type & (MACHO_N_TYPE | MACHO_N_STAB)) == MACHO_N_UNDF); rc = rtdlrModMachOHandleUndefinedSymbol64(pThis, &paSyms[iSym], BaseAddress, pfnGetImport, pvUser); } } return rc; } static int kldrModMachODylibDoIndirectSymbols(PRTLDRMODMACHO pThis, void *pvBits, RTLDRADDR offDelta) { /* * There must be a LC_DYSYMTAB. * We might be lucky, though, and not have any imports. */ dysymtab_command_t const *pDySymTab = pThis->pDySymTab; AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2); uint32_t const cIndirectSymbols = pDySymTab->nindirectsymb; if (cIndirectSymbols == 0) return VINF_SUCCESS; /* * Ensure that we've got the symbol table. */ int rc = kldrModMachOLoadObjSymTab(pThis); if (RT_FAILURE(rc)) return rc; /* * Load the indirect symbol table. */ if (!pThis->paidxIndirectSymbols) { uint32_t *paidxIndirectSymbols = (uint32_t *)RTMemAlloc(cIndirectSymbols * sizeof(uint32_t)); if (!paidxIndirectSymbols) return VERR_NO_MEMORY; rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paidxIndirectSymbols, cIndirectSymbols * sizeof(uint32_t), pDySymTab->indirectsymboff); if (RT_SUCCESS(rc)) pThis->paidxIndirectSymbols = paidxIndirectSymbols; else { RTMemFree(paidxIndirectSymbols); return rc; } /* Byte swap if needed. */ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE) for (uint32_t i = 0; i < cIndirectSymbols; i++) paidxIndirectSymbols[i] = RT_BSWAP_U32(paidxIndirectSymbols[i]); } uint32_t const *paidxIndirectSymbols = pThis->paidxIndirectSymbols; /* * Process the sections using indirect symbols. */ const uint32_t cSymbols = pThis->cSymbols; if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) { macho_nlist_32_t const *paSymbols = (macho_nlist_32_t *)pThis->pvaSymbols; for (uint32_t iSect = 0; iSect < pThis->cSections; iSect++) { section_32_t const *pSect = (section_32_t const *)pThis->paSections[iSect].pvMachoSection; switch (pSect->flags & SECTION_TYPE) { case S_NON_LAZY_SYMBOL_POINTERS: case S_LAZY_SYMBOL_POINTERS: { uint32_t *pauDstPtrs = (uint32_t *)((uintptr_t)pvBits + pThis->paSections[iSect].RVA); uint32_t const cDstPtrs = pThis->paSections[iSect].cb / sizeof(pauDstPtrs[0]); uint32_t const idxSrcSkip = pSect->reserved1; if ((uint64_t)idxSrcSkip + cDstPtrs > cIndirectSymbols) return VERR_BAD_EXE_FORMAT; /// @todo better error code. for (uint32_t i = 0; i < cDstPtrs; i++) { uint32_t const idxSym = paidxIndirectSymbols[idxSrcSkip + i]; if (idxSym == INDIRECT_SYMBOL_LOCAL) pauDstPtrs[i] += (int32_t)offDelta; else if (idxSym != INDIRECT_SYMBOL_ABS) { AssertMsgReturn(idxSym < cSymbols, ("i=%#x idxSym=%#x cSymbols=%#x iSect=%#x\n", i, idxSym, cSymbols, iSect), VERR_BAD_EXE_FORMAT); /// @todo better error code. pauDstPtrs[i] = paSymbols[idxSym].n_value; } } break; } case S_SYMBOL_STUBS: if ( pThis->Core.enmArch == RTLDRARCH_X86_32 && (pSect->flags & S_ATTR_SELF_MODIFYING_CODE) && pSect->reserved2 == 5) { uint32_t uDstRva = pThis->paSections[iSect].RVA; uint8_t *pbDst = (uint8_t *)((uintptr_t)pvBits + uDstRva); uint32_t const cDstPtrs = pThis->paSections[iSect].cb / 5; uint32_t const idxSrcSkip = pSect->reserved1; if ((uint64_t)idxSrcSkip + cDstPtrs > cIndirectSymbols) return VERR_BAD_EXE_FORMAT; /// @todo better error code. for (uint32_t i = 0; i < cDstPtrs; i++, uDstRva += 5, pbDst += 5) { uint32_t const idxSym = paidxIndirectSymbols[idxSrcSkip + i]; if (idxSym != INDIRECT_SYMBOL_ABS && idxSym != INDIRECT_SYMBOL_LOCAL) { AssertMsgReturn(idxSym < cSymbols, ("i=%#x idxSym=%#x cSymbols=%#x iSect=%#x\n", i, idxSym, cSymbols, iSect), VERR_BAD_EXE_FORMAT); /// @todo better error code. pbDst[0] = 0xeb; /* JMP rel32 */ uint32_t offDisp = paSymbols[idxSym].n_value - (uint32_t)uDstRva - 5; pbDst[1] = (uint8_t)offDisp; offDisp >>= 8; pbDst[2] = (uint8_t)offDisp; offDisp >>= 8; pbDst[3] = (uint8_t)offDisp; offDisp >>= 8; pbDst[4] = (uint8_t)offDisp; } } break; } break; } } } else { /* Exact like for 32-bit, except for 64-bit symbol table, 64-bit addresses and no need to process S_SYMBOL_STUBS. */ macho_nlist_64_t const *paSymbols = (macho_nlist_64_t *)pThis->pvaSymbols; for (uint32_t iSect = 0; iSect < pThis->cSections; iSect++) { section_64_t const *pSect = (section_64_t const *)pThis->paSections[iSect].pvMachoSection; switch (pSect->flags & SECTION_TYPE) { case S_NON_LAZY_SYMBOL_POINTERS: case S_LAZY_SYMBOL_POINTERS: { uint64_t *pauDstPtrs = (uint64_t *)((uintptr_t)pvBits + pThis->paSections[iSect].RVA); uint32_t const cDstPtrs = pThis->paSections[iSect].cb / sizeof(pauDstPtrs[0]); uint32_t const idxSrcSkip = pSect->reserved1; if ((uint64_t)idxSrcSkip + cDstPtrs > cIndirectSymbols) return VERR_BAD_EXE_FORMAT; /// @todo better error code. for (uint32_t i = 0; i < cDstPtrs; i++) { uint32_t const idxSym = paidxIndirectSymbols[idxSrcSkip + i]; if (idxSym == INDIRECT_SYMBOL_LOCAL) pauDstPtrs[i] += (int64_t)offDelta; else if (idxSym != INDIRECT_SYMBOL_ABS) { AssertMsgReturn(idxSym < cSymbols, ("i=%#x idxSym=%#x cSymbols=%#x iSect=%#x\n", i, idxSym, cSymbols, iSect), VERR_BAD_EXE_FORMAT); /// @todo better error code. pauDstPtrs[i] = paSymbols[idxSym].n_value; } } break; } case S_SYMBOL_STUBS: if ( pThis->Core.enmArch == RTLDRARCH_X86_32 && (pSect->flags & S_ATTR_SELF_MODIFYING_CODE) && pSect->reserved2 == 5) return VERR_BAD_EXE_FORMAT; break; } } } return VINF_SUCCESS; } /** * MH_OBJECT: Applies base relocations to an (unprotected) image mapping. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pvMapping The mapping to fixup. * @param NewBaseAddress The address to fixup the mapping to. */ static int kldrModMachOObjDoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress) { /* * Ensure that we've got the symbol table. */ int rc = kldrModMachOLoadObjSymTab(pThis); if (RT_FAILURE(rc)) return rc; /* * Iterate over the segments and their sections and apply fixups. */ rc = VINF_SUCCESS; for (uint32_t iSeg = 0; RT_SUCCESS(rc) && iSeg < pThis->cSegments; iSeg++) { PRTLDRMODMACHOSEG pSeg = &pThis->aSegments[iSeg]; for (uint32_t iSect = 0; iSect < pSeg->cSections; iSect++) { PRTLDRMODMACHOSECT pSect = &pSeg->paSections[iSect]; /* skip sections without fixups. */ if (!pSect->cFixups) continue; AssertReturn(pSect->paFixups, VERR_INTERNAL_ERROR_4); AssertReturn(pSect->pauFixupVirginData, VERR_INTERNAL_ERROR_4); /* * Apply the fixups. */ uint8_t *pbSectBits = (uint8_t *)pvMapping + (uintptr_t)pSect->RVA; if (pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE) /** @todo this aint right. */ rc = kldrModMachOApplyFixupsGeneric32Bit(pThis, pbSectBits, (size_t)pSect->cb, pSect->RVA, pSect->LinkAddress, pSect->paFixups, pSect->cFixups, pSect->pauFixupVirginData, (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress); else if ( pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE && pThis->Hdr.cputype == CPU_TYPE_X86_64) rc = kldrModMachOApplyFixupsAMD64(pThis, pbSectBits, (size_t)pSect->cb, pSect->RVA, pSect->paFixups, pSect->cFixups, pSect->pauFixupVirginData, (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress); else RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); if (RT_FAILURE(rc)) break; } } return rc; } /** * Dylib: Applies base relocations to an (unprotected) image mapping. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pvMapping The mapping to fixup. * @param NewBaseAddress The address to fixup the mapping to. */ static int kldrModMachODylibDoFixups(PRTLDRMODMACHO pThis, void *pvMapping, RTLDRADDR NewBaseAddress) { /* * There must be a LC_DYSYMTAB. * We might be lucky, though, and not have any imports. */ dysymtab_command_t const *pDySymTab = pThis->pDySymTab; AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2); uint32_t cRelocations = pDySymTab->nlocrel + pDySymTab->nextrel; if (cRelocations == 0) return VINF_SUCCESS; /* * Ensure that we've got the symbol table. */ int rc = kldrModMachOLoadObjSymTab(pThis); if (RT_FAILURE(rc)) return rc; /* * Load the relocations if needed. */ macho_relocation_union_t const *paRelocations = pThis->paRelocations; if (!paRelocations) { uint32_t *paRawRelocs = (uint32_t *)RTMemAlloc(cRelocations * sizeof(macho_relocation_union_t)); if (!paRawRelocs) return VERR_NO_MEMORY; if (pDySymTab->nextrel) rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paRawRelocs, pDySymTab->nextrel * sizeof(macho_relocation_union_t), pDySymTab->extreloff); if (pDySymTab->nlocrel && RT_SUCCESS(rc)) rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, (uint8_t *)paRawRelocs + pDySymTab->nextrel * sizeof(macho_relocation_union_t), pDySymTab->nlocrel * sizeof(macho_relocation_union_t), pDySymTab->locreloff); if (RT_SUCCESS(rc)) pThis->paRelocations = (macho_relocation_union_t *)paRawRelocs; else { RTMemFree(paRawRelocs); return rc; } /* Byte swap if needed. */ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE) { for (uint32_t i = 0; i < cRelocations; i++) { paRawRelocs[i * 2] = RT_BSWAP_U32(paRawRelocs[i * 2]); paRawRelocs[i * 2 + 1] = RT_BSWAP_U32(paRawRelocs[i * 2 + 1]); } ASMCompilerBarrier(); } paRelocations = pThis->paRelocations; } /* * Apply the fixups. */ if (pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE) /** @todo this aint right. */ return kldrModMachOApplyFixupsGeneric32Bit(pThis, (uint8_t *)pvMapping, (size_t)pThis->cbImage, 0, pThis->LinkAddress, paRelocations, cRelocations, pThis->pauRelocationsVirginData, (macho_nlist_32_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress); if ( pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE && pThis->Hdr.cputype == CPU_TYPE_X86_64) return kldrModMachOApplyFixupsAMD64(pThis, (uint8_t *)pvMapping, (size_t)pThis->cbImage, 0, paRelocations, cRelocations, pThis->pauRelocationsVirginData, (macho_nlist_64_t *)pThis->pvaSymbols, pThis->cSymbols, NewBaseAddress); RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); } /** * Applies generic fixups to a section in an image of the same endian-ness * as the host CPU. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pbBits Pointer to the bits to fix up. * @param cbBits Size of the bits to fix up. * @param uBitsRva The RVA of the bits. * @param uBitsLinkAddr The link address of the bits. * @param paFixups The fixups. * @param cFixups Number of fixups. * @param pauVirginData The virgin data / addends. Parallel to paFixups. * @param paSyms Pointer to the symbol table. * @param cSyms Number of symbols. * @param NewBaseAddress The new base image address. */ static int kldrModMachOApplyFixupsGeneric32Bit(PRTLDRMODMACHO pThis, uint8_t *pbBits, size_t cbBits, RTLDRADDR uBitsRva, RTLDRADDR uBitsLinkAddr, const macho_relocation_union_t *paFixups, const uint32_t cFixups, PCRTUINT64U const pauVirginData, macho_nlist_32_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress) { /* * Iterate the fixups and apply them. */ for (uint32_t iFixup = 0; iFixup < cFixups; iFixup++) { macho_relocation_union_t Fixup = paFixups[iFixup]; RTLDRADDR SymAddr = ~(RTLDRADDR)0; RTPTRUNION uFix; if (!(Fixup.r.r_address & R_SCATTERED)) { /* sanity */ RTLDRMODMACHO_CHECK_RETURN((uint32_t)Fixup.r.r_address + RT_BIT_32(Fixup.r.r_length) <= cbBits, VERR_LDR_BAD_FIXUP); /* Calc the fixup address. */ uFix.pv = pbBits + Fixup.r.r_address; /* * Calc the symbol value. */ /* Calc the linked symbol address / addend. */ switch (Fixup.r.r_length) { case 0: SymAddr = (int8_t)pauVirginData[iFixup].au8[0]; break; case 1: SymAddr = (int16_t)pauVirginData[iFixup].au16[0]; break; case 2: SymAddr = (int32_t)pauVirginData[iFixup].au32[0]; break; case 3: SymAddr = (int64_t)pauVirginData[iFixup].u; break; default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } if (Fixup.r.r_pcrel) SymAddr += Fixup.r.r_address + uBitsLinkAddr; /* Add symbol / section address. */ if (Fixup.r.r_extern) { const macho_nlist_32_t *pSym; if (Fixup.r.r_symbolnum >= cSyms) return VERR_LDR_BAD_FIXUP; pSym = &paSyms[Fixup.r.r_symbolnum]; if (pSym->n_type & MACHO_N_STAB) return VERR_LDR_BAD_FIXUP; switch (pSym->n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSymSect = &pThis->paSections[pSym->n_sect - 1]; SymAddr += pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress; break; } case MACHO_N_UNDF: case MACHO_N_ABS: SymAddr += pSym->n_value; break; case MACHO_N_INDR: case MACHO_N_PBUD: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL); } } else if (Fixup.r.r_symbolnum != R_ABS) { PRTLDRMODMACHOSECT pSymSect; if (Fixup.r.r_symbolnum > pThis->cSections) return VERR_LDR_BAD_FIXUP; pSymSect = &pThis->paSections[Fixup.r.r_symbolnum - 1]; SymAddr -= pSymSect->LinkAddress; SymAddr += pSymSect->RVA + NewBaseAddress; } /* adjust for PC relative */ if (Fixup.r.r_pcrel) SymAddr -= Fixup.r.r_address + uBitsRva + NewBaseAddress; } else { PRTLDRMODMACHOSECT pSymSect; uint32_t iSymSect; RTLDRADDR Value; /* sanity */ RTLDRMODMACHO_ASSERT(Fixup.s.r_scattered); RTLDRMODMACHO_CHECK_RETURN((uint32_t)Fixup.s.r_address + RT_BIT_32(Fixup.s.r_length) <= cbBits, VERR_LDR_BAD_FIXUP); /* Calc the fixup address. */ uFix.pv = pbBits + Fixup.s.r_address; /* * Calc the symbol value. */ /* The addend is stored in the code. */ switch (Fixup.s.r_length) { case 0: SymAddr = (int8_t)pauVirginData[iFixup].au8[0]; break; case 1: SymAddr = (int16_t)pauVirginData[iFixup].au16[0]; break; case 2: SymAddr = (int32_t)pauVirginData[iFixup].au32[0]; break; case 3: SymAddr = (int64_t)pauVirginData[iFixup].u; break; default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } if (Fixup.s.r_pcrel) SymAddr += Fixup.s.r_address; Value = Fixup.s.r_value; SymAddr -= Value; /* (-> addend only) */ /* Find the section number from the r_value. */ pSymSect = NULL; for (iSymSect = 0; iSymSect < pThis->cSections; iSymSect++) { RTLDRADDR off = Value - pThis->paSections[iSymSect].LinkAddress; if (off < pThis->paSections[iSymSect].cb) { pSymSect = &pThis->paSections[iSymSect]; break; } else if (off == pThis->paSections[iSymSect].cb) /* edge case */ pSymSect = &pThis->paSections[iSymSect]; } if (!pSymSect) return VERR_LDR_BAD_FIXUP; /* Calc the symbol address. */ SymAddr += Value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress; if (Fixup.s.r_pcrel) SymAddr -= Fixup.s.r_address + uBitsRva + NewBaseAddress; Fixup.r.r_length = ((scattered_relocation_info_t *)&paFixups[iFixup])->r_length; Fixup.r.r_type = ((scattered_relocation_info_t *)&paFixups[iFixup])->r_type; } /* * Write back the fixed up value. */ if (Fixup.r.r_type == GENERIC_RELOC_VANILLA) { switch (Fixup.r.r_length) { case 0: *uFix.pu8 = (uint8_t)SymAddr; break; case 1: *uFix.pu16 = (uint16_t)SymAddr; break; case 2: *uFix.pu32 = (uint32_t)SymAddr; break; case 3: *uFix.pu64 = (uint64_t)SymAddr; break; } } else if (Fixup.r.r_type <= GENERIC_RELOC_LOCAL_SECTDIFF) return VERR_LDRMACHO_UNSUPPORTED_FIXUP_TYPE; else return VERR_LDR_BAD_FIXUP; } return VINF_SUCCESS; } /** * Applies AMD64 fixups to a section. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pbBits Pointer to the section bits. * @param cbBits Size of the bits to fix up. * @param uBitsRva The RVA of the bits. * @param paFixups The fixups. * @param cFixups Number of fixups. * @param pauVirginData The virgin data / addends. Parallel to paFixups. * @param paSyms Pointer to the symbol table. * @param cSyms Number of symbols. * @param NewBaseAddress The new base image address. */ static int kldrModMachOApplyFixupsAMD64(PRTLDRMODMACHO pThis, uint8_t *pbBits, size_t cbBits, RTLDRADDR uBitsRva, const macho_relocation_union_t *paFixups, const uint32_t cFixups, PCRTUINT64U const pauVirginData, macho_nlist_64_t *paSyms, uint32_t cSyms, RTLDRADDR NewBaseAddress) { /* * Iterate the fixups and apply them. */ for (uint32_t iFixup = 0; iFixup < cFixups; iFixup++) { macho_relocation_union_t Fixup = paFixups[iFixup]; /* AMD64 doesn't use scattered fixups. */ RTLDRMODMACHO_CHECK_RETURN(!(Fixup.r.r_address & R_SCATTERED), VERR_LDR_BAD_FIXUP); /* sanity */ RTLDRMODMACHO_CHECK_RETURN((uint32_t)Fixup.r.r_address + RT_BIT_32(Fixup.r.r_length) <= cbBits, VERR_LDR_BAD_FIXUP); /* calc fixup addresses. */ RTPTRUNION uFix; uFix.pv = pbBits + Fixup.r.r_address; /* * Calc the symbol value. */ /* Calc the linked symbol address / addend. */ RTLDRADDR SymAddr; switch (Fixup.r.r_length) { case 2: SymAddr = (int32_t)pauVirginData[iFixup].au32[0]; break; case 3: SymAddr = (int64_t)pauVirginData[iFixup].u; break; default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } /* Add symbol / section address. */ if (Fixup.r.r_extern) { const macho_nlist_64_t *pSym; RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_symbolnum < cSyms, VERR_LDR_BAD_FIXUP); pSym = &paSyms[Fixup.r.r_symbolnum]; RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_type & MACHO_N_STAB), VERR_LDR_BAD_FIXUP); switch (Fixup.r.r_type) { /* GOT references just needs to have their symbol verified. Later, we'll optimize GOT building here using a parallel sym->got array. */ case X86_64_RELOC_GOT_LOAD: case X86_64_RELOC_GOT: switch (pSym->n_type & MACHO_N_TYPE) { case MACHO_N_SECT: case MACHO_N_UNDF: case MACHO_N_ABS: break; case MACHO_N_INDR: case MACHO_N_PBUD: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL); } SymAddr = sizeof(uint64_t) * Fixup.r.r_symbolnum + pThis->GotRVA + NewBaseAddress; RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_length == 2, VERR_LDR_BAD_FIXUP); SymAddr -= 4; break; /* Verify the r_pcrel field for signed fixups on the way into the default case. */ case X86_64_RELOC_BRANCH: case X86_64_RELOC_SIGNED: case X86_64_RELOC_SIGNED_1: case X86_64_RELOC_SIGNED_2: case X86_64_RELOC_SIGNED_4: RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP); RT_FALL_THRU(); default: { /* Adjust with fixup specific addend and verify unsigned/r_pcrel. */ switch (Fixup.r.r_type) { case X86_64_RELOC_UNSIGNED: RTLDRMODMACHO_CHECK_RETURN(!Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP); break; case X86_64_RELOC_BRANCH: RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_length == 2, VERR_LDR_BAD_FIXUP); SymAddr -= 4; break; case X86_64_RELOC_SIGNED: case X86_64_RELOC_SIGNED_1: case X86_64_RELOC_SIGNED_2: case X86_64_RELOC_SIGNED_4: SymAddr -= 4; break; default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } switch (pSym->n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSymSect = &pThis->paSections[pSym->n_sect - 1]; SymAddr += pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress; break; } case MACHO_N_UNDF: /* branch to an external symbol may have to take a short detour. */ if ( Fixup.r.r_type == X86_64_RELOC_BRANCH && SymAddr + Fixup.r.r_address + uBitsRva + NewBaseAddress - pSym->n_value + UINT64_C(0x80000000) >= UINT64_C(0xffffff20)) { RTLDRMODMACHO_CHECK_RETURN(pThis->JmpStubsRVA != NIL_RTLDRADDR, VERR_LDR_ADDRESS_OVERFLOW); SymAddr += pThis->cbJmpStub * Fixup.r.r_symbolnum + pThis->JmpStubsRVA + NewBaseAddress; } else SymAddr += pSym->n_value; break; case MACHO_N_ABS: SymAddr += pSym->n_value; break; case MACHO_N_INDR: case MACHO_N_PBUD: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL); } break; } /* * This is a weird customer, it will always be follows by an UNSIGNED fixup. * The value is calculated: target - pair_target. * Note! The linker generally eliminate these when linking modules rather * than objects (-r). */ case X86_64_RELOC_SUBTRACTOR: { /* Deal with the SUBTRACT symbol first, by subtracting it from SymAddr. */ switch (pSym->n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSymSect = &pThis->paSections[pSym->n_sect - 1]; SymAddr -= pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress; break; } case MACHO_N_UNDF: case MACHO_N_ABS: SymAddr -= pSym->n_value; break; case MACHO_N_INDR: case MACHO_N_PBUD: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL); } /* Load the 2nd fixup, check sanity. */ iFixup++; RTLDRMODMACHO_CHECK_RETURN(!Fixup.r.r_pcrel && iFixup < cFixups, VERR_LDR_BAD_FIXUP); macho_relocation_info_t const Fixup2 = paFixups[iFixup].r; RTLDRMODMACHO_CHECK_RETURN( Fixup2.r_address == Fixup.r.r_address && Fixup2.r_length == Fixup.r.r_length && Fixup2.r_type == X86_64_RELOC_UNSIGNED && !Fixup2.r_pcrel && Fixup2.r_symbolnum < cSyms, VERR_LDR_BAD_FIXUP); if (Fixup2.r_extern) { RTLDRMODMACHO_CHECK_RETURN(Fixup2.r_symbolnum < cSyms, VERR_LDR_BAD_FIXUP); pSym = &paSyms[Fixup2.r_symbolnum]; RTLDRMODMACHO_CHECK_RETURN(!(pSym->n_type & MACHO_N_STAB), VERR_LDR_BAD_FIXUP); /* Add its value to SymAddr. */ switch (pSym->n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)pSym->n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSymSect = &pThis->paSections[pSym->n_sect - 1]; SymAddr += pSym->n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress; break; } case MACHO_N_UNDF: case MACHO_N_ABS: SymAddr += pSym->n_value; break; case MACHO_N_INDR: case MACHO_N_PBUD: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_BAD_SYMBOL); } } else if (Fixup2.r_symbolnum != R_ABS) { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN(Fixup2.r_symbolnum <= pThis->cSections, VERR_LDR_BAD_FIXUP); pSymSect = &pThis->paSections[Fixup2.r_symbolnum - 1]; SymAddr += pSymSect->RVA - pSymSect->LinkAddress + NewBaseAddress; } else RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } break; } } else { /* verify against fixup type and make adjustments */ switch (Fixup.r.r_type) { case X86_64_RELOC_UNSIGNED: RTLDRMODMACHO_CHECK_RETURN(!Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP); break; case X86_64_RELOC_BRANCH: RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP); SymAddr += 4; /* dunno what the assmbler/linker really is doing here... */ break; case X86_64_RELOC_SIGNED: case X86_64_RELOC_SIGNED_1: case X86_64_RELOC_SIGNED_2: case X86_64_RELOC_SIGNED_4: RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel, VERR_LDR_BAD_FIXUP); break; /*case X86_64_RELOC_GOT_LOAD:*/ /*case X86_64_RELOC_GOT: */ /*case X86_64_RELOC_SUBTRACTOR: - must be r_extern=1 says as. */ default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } if (Fixup.r.r_symbolnum != R_ABS) { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_symbolnum <= pThis->cSections, VERR_LDR_BAD_FIXUP); pSymSect = &pThis->paSections[Fixup.r.r_symbolnum - 1]; SymAddr -= pSymSect->LinkAddress; SymAddr += pSymSect->RVA + NewBaseAddress; if (Fixup.r.r_pcrel) SymAddr += Fixup.r.r_address; } } /* adjust for PC relative */ if (Fixup.r.r_pcrel) SymAddr -= Fixup.r.r_address + uBitsRva + NewBaseAddress; /* * Write back the fixed up value. */ switch (Fixup.r.r_length) { case 3: *uFix.pu64 = (uint64_t)SymAddr; break; case 2: RTLDRMODMACHO_CHECK_RETURN(Fixup.r.r_pcrel || Fixup.r.r_type == X86_64_RELOC_SUBTRACTOR, VERR_LDR_BAD_FIXUP); RTLDRMODMACHO_CHECK_RETURN((int32_t)SymAddr == (int64_t)SymAddr, VERR_LDR_ADDRESS_OVERFLOW); *uFix.pu32 = (uint32_t)SymAddr; break; default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } } return VINF_SUCCESS; } /** * Loads the symbol table (LC_SYMTAB). * * The symbol table is pointed to by RTLDRMODMACHO::pvaSymbols. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. */ static int kldrModMachOLoadObjSymTab(PRTLDRMODMACHO pThis) { int rc = VINF_SUCCESS; if ( !pThis->pvaSymbols && pThis->cSymbols) { size_t cbSyms; size_t cbSym; void *pvSyms; void *pvStrings; /* sanity */ RTLDRMODMACHO_CHECK_RETURN( pThis->offSymbols && (!pThis->cchStrings || pThis->offStrings), VERR_LDRMACHO_BAD_OBJECT_FILE); /* allocate */ cbSym = pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE ? sizeof(macho_nlist_32_t) : sizeof(macho_nlist_64_t); cbSyms = pThis->cSymbols * cbSym; RTLDRMODMACHO_CHECK_RETURN(cbSyms / cbSym == pThis->cSymbols, VERR_LDRMACHO_BAD_SYMTAB_SIZE); rc = VERR_NO_MEMORY; pvSyms = RTMemAlloc(cbSyms); if (pvSyms) { if (pThis->cchStrings) pvStrings = RTMemAlloc(pThis->cchStrings); else pvStrings = RTMemAllocZ(4); if (pvStrings) { /* read */ rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvSyms, cbSyms, pThis->offSymbols); if (RT_SUCCESS(rc) && pThis->cchStrings) rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvStrings, pThis->cchStrings, pThis->offStrings); if (RT_SUCCESS(rc)) { pThis->pvaSymbols = pvSyms; pThis->pchStrings = (char *)pvStrings; /* perform endian conversion? */ if (pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) { uint32_t cLeft = pThis->cSymbols; macho_nlist_32_t *pSym = (macho_nlist_32_t *)pvSyms; while (cLeft-- > 0) { pSym->n_un.n_strx = RT_BSWAP_U32(pSym->n_un.n_strx); pSym->n_desc = (int16_t)RT_BSWAP_U16(pSym->n_desc); pSym->n_value = RT_BSWAP_U32(pSym->n_value); pSym++; } } else if (pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE) { uint32_t cLeft = pThis->cSymbols; macho_nlist_64_t *pSym = (macho_nlist_64_t *)pvSyms; while (cLeft-- > 0) { pSym->n_un.n_strx = RT_BSWAP_U32(pSym->n_un.n_strx); pSym->n_desc = (int16_t)RT_BSWAP_U16(pSym->n_desc); pSym->n_value = RT_BSWAP_U64(pSym->n_value); pSym++; } } return VINF_SUCCESS; } RTMemFree(pvStrings); } RTMemFree(pvSyms); } } else RTLDRMODMACHO_ASSERT(pThis->pchStrings || pThis->Hdr.filetype == MH_DSYM); return rc; } /** * Loads the fixups at the given address and performs endian * conversion if necessary. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param offFixups The file offset of the fixups. * @param cFixups The number of fixups to load. * @param ppaFixups Where to put the pointer to the allocated fixup array. */ static int kldrModMachOLoadFixups(PRTLDRMODMACHO pThis, RTFOFF offFixups, uint32_t cFixups, macho_relocation_union_t **ppaFixups) { macho_relocation_union_t *paFixups; size_t cbFixups; /* allocate the memory. */ cbFixups = cFixups * sizeof(*paFixups); RTLDRMODMACHO_CHECK_RETURN(cbFixups / sizeof(*paFixups) == cFixups, VERR_LDRMACHO_BAD_SYMTAB_SIZE); paFixups = (macho_relocation_union_t *)RTMemAlloc(cbFixups); if (!paFixups) return VERR_NO_MEMORY; /* read the fixups. */ int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paFixups, cbFixups, offFixups); if (RT_SUCCESS(rc)) { *ppaFixups = paFixups; /* do endian conversion if necessary. */ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE) { uint32_t iFixup; for (iFixup = 0; iFixup < cFixups; iFixup++) { uint32_t *pu32 = (uint32_t *)&paFixups[iFixup]; pu32[0] = RT_BSWAP_U32(pu32[0]); pu32[1] = RT_BSWAP_U32(pu32[1]); } } } else RTMemFree(paFixups); return rc; } /** * Loads virgin data (addends) for an array of fixups. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pbBits The virgin bits to lift the data from * @param cbBits The number of virgin bytes. * @param paFixups The fixups. * @param cFixups Number of fixups * @param pszName Name for logging. * @param ppauVirginData Where to return the virgin data. */ static int rtldrMachOLoadVirginData(PRTLDRMODMACHO pThis, uint8_t const *pbBits, size_t cbBits, macho_relocation_union_t const *paFixups, uint32_t cFixups, const char *pszName, PRTUINT64U *ppauVirginData) { /* * In case we jettisoned the fixups, we will leave virgin data. */ if (*ppauVirginData) return VINF_SUCCESS; #ifdef LOG_ENABLED /* * Ensure that we've got the symbol table if we're logging fixups. */ if (LogIs5Enabled()) { int rc = kldrModMachOLoadObjSymTab(pThis); if (RT_FAILURE(rc)) return rc; } #endif /* * Allocate memory and iterate the fixups to get the data. */ PRTUINT64U pauVirginData = *ppauVirginData = (PRTUINT64U)RTMemAllocZ(sizeof(uint64_t) * cFixups); if (pauVirginData) { Log5(("Fixups for %s: (%u)\n", pszName, cFixups)); for (uint32_t i = 0; i < cFixups; i++) { uint32_t off; uint32_t cShift; if (!paFixups[i].s.r_scattered) { off = paFixups[i].r.r_address; cShift = paFixups[i].r.r_length; } else { off = paFixups[i].s.r_address; cShift = paFixups[i].s.r_length; } RTLDRMODMACHO_CHECK_RETURN(off + RT_BIT_32(cShift) <= cbBits, VERR_LDR_BAD_FIXUP); /** @todo This ASSUMES same endian in the image and on the host. Would need * to check target cpu (pThis->Core.enmArch) endianness against host to get * it right... (outside the loop, obviously) */ switch (cShift) { case 3: pauVirginData[i].u = RT_MAKE_U64_FROM_U8(pbBits[off], pbBits[off + 1], pbBits[off + 2], pbBits[off + 3], pbBits[off + 4], pbBits[off + 5], pbBits[off + 6], pbBits[off + 7]); break; case 2: pauVirginData[i].u = (int32_t)RT_MAKE_U32_FROM_U8(pbBits[off], pbBits[off + 1], pbBits[off + 2], pbBits[off + 3]); break; case 1: pauVirginData[i].u = (int16_t)RT_MAKE_U16(pbBits[off], pbBits[off + 1]); break; case 0: pauVirginData[i].u = (int8_t)pbBits[off]; break; default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDR_BAD_FIXUP); } #ifdef LOG_ENABLED if (LogIs5Enabled()) { if (!paFixups[i].s.r_scattered) { Log5((" #%06x: %#08x LB %u: t=%#x pc=%u ex=%u sym=%#010x add=%#RX64\n", i, off, RT_BIT_32(cShift), paFixups[i].r.r_type, paFixups[i].r.r_pcrel, paFixups[i].r.r_extern, paFixups[i].r.r_symbolnum, pauVirginData[i].u)); if (paFixups[i].r.r_symbolnum < pThis->cSymbols) { if (pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE) { macho_nlist_64_t const *pSym = (macho_nlist_64_t const *)pThis->pvaSymbols + paFixups[i].r.r_symbolnum; Log5((" sym: %#04x:%#018RX64 t=%#04x d=%#06x %s\n", pSym->n_sect, pSym->n_value, pSym->n_type, pSym->n_desc, pSym->n_un.n_strx < pThis->cchStrings ? &pThis->pchStrings[pSym->n_un.n_strx] : "")); } else { macho_nlist_32_t const *pSym = (macho_nlist_32_t const *)pThis->pvaSymbols + paFixups[i].r.r_symbolnum; Log5((" sym: %#04x:%#010RX32 t=%#04x d=%#06x %s\n", pSym->n_sect, pSym->n_value, pSym->n_type, pSym->n_desc, (uint32_t)pSym->n_un.n_strx < pThis->cchStrings ? &pThis->pchStrings[pSym->n_un.n_strx] : "")); } } } else Log5((" #%06x: %#08x LB %u: t=%#x pc=%u val=%#010x add=%#RX64\n", i, off, RT_BIT_32(cShift), paFixups[i].s.r_type, paFixups[i].s.r_pcrel, paFixups[i].s.r_value, pauVirginData[i].u)); } #endif } return VINF_SUCCESS; } RT_NOREF(pThis, pszName); return VERR_NO_MEMORY; } /** * MH_OBJECT: Loads fixups and addends for each section. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pbBits The image bits. First time we're called, these are * ASSUMED to be in virgin state and suitable for * saving addends. */ static int rtldrMachOObjLoadFixupsAndVirginData(PRTLDRMODMACHO pThis, uint8_t const *pbBits) { PRTLDRMODMACHOSECT pSect = pThis->paSections; for (uint32_t i = 0; i < pThis->cSections; i++, pSect++) if ( !pSect->paFixups && pSect->cFixups > 0) { /* * Load and endian convert the fixups. */ int rc = kldrModMachOLoadFixups(pThis, pSect->offFixups, pSect->cFixups, &pSect->paFixups); if (RT_SUCCESS(rc)) { /* * Save virgin data (addends) for each fixup. */ rc = rtldrMachOLoadVirginData(pThis, &pbBits[(size_t)pSect->RVA], (size_t)pSect->cb, pSect->paFixups, pSect->cFixups, pThis->aSegments[pSect->iSegment].SegInfo.pszName, &pSect->pauFixupVirginData); if (RT_SUCCESS(rc)) continue; RTMemFree(pSect->pauFixupVirginData); pSect->pauFixupVirginData = NULL; RTMemFree(pSect->paFixups); pSect->paFixups = NULL; } return rc; } return VINF_SUCCESS; } /** * Dylib: Loads fixups and addends. * * @returns IPRT status code. * @param pThis The Mach-O module interpreter instance. * @param pbBits The image bits. First time we're called, these are * ASSUMED to be in virgin state and suitable for * saving addends. */ static int rtldrMachODylibLoadFixupsAndVirginData(PRTLDRMODMACHO pThis, uint8_t const *pbBits) { /* * Don't do it again if we already loaded them. */ if (pThis->paRelocations) { Assert(pThis->pauRelocationsVirginData); return VINF_SUCCESS; } /* * There must be a LC_DYSYMTAB. Fixups are optionals. */ dysymtab_command_t const *pDySymTab = pThis->pDySymTab; AssertReturn(pDySymTab, VERR_INTERNAL_ERROR_2); uint32_t cRelocations = pDySymTab->nlocrel + pDySymTab->nextrel; if (cRelocations == 0) return VINF_SUCCESS; /* * Load fixups. */ int rc = VINF_SUCCESS; uint32_t *paRawRelocs = (uint32_t *)RTMemAlloc(cRelocations * sizeof(macho_relocation_union_t)); if (paRawRelocs) { pThis->paRelocations = (macho_relocation_union_t *)paRawRelocs; if (pDySymTab->nextrel) rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, paRawRelocs, pDySymTab->nextrel * sizeof(macho_relocation_union_t), pDySymTab->extreloff); if (pDySymTab->nlocrel && RT_SUCCESS(rc)) rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, (uint8_t *)paRawRelocs + pDySymTab->nextrel * sizeof(macho_relocation_union_t), pDySymTab->nlocrel * sizeof(macho_relocation_union_t), pDySymTab->locreloff); if (RT_SUCCESS(rc)) { /* Byte swap if needed. */ if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE || pThis->Hdr.magic == IMAGE_MACHO64_SIGNATURE_OE) { for (uint32_t i = 0; i < cRelocations; i++) { paRawRelocs[i * 2] = RT_BSWAP_U32(paRawRelocs[i * 2]); paRawRelocs[i * 2 + 1] = RT_BSWAP_U32(paRawRelocs[i * 2 + 1]); } ASMCompilerBarrier(); } /* * Load virgin data (addends). */ rc = rtldrMachOLoadVirginData(pThis, pbBits, (size_t)pThis->cbImage, pThis->paRelocations, cRelocations, "whole-image", &pThis->pauRelocationsVirginData); if (RT_SUCCESS(rc)) return VINF_SUCCESS; RTMemFree(pThis->pauRelocationsVirginData); pThis->pauRelocationsVirginData = NULL; } RTMemFree(pThis->paRelocations); pThis->paRelocations = NULL; } else rc = VERR_NO_MEMORY; return rc; } #if 0 /** @copydoc kLdrModCallInit */ static int kldrModMachOCallInit(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle) { /* later */ RT_NOREF(pMod); RT_NOREF(pvMapping); RT_NOREF(uHandle); return VINF_SUCCESS; } /** @copydoc kLdrModCallTerm */ static int kldrModMachOCallTerm(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle) { /* later */ RT_NOREF(pMod); RT_NOREF(pvMapping); RT_NOREF(uHandle); return VINF_SUCCESS; } /** @copydoc kLdrModCallThread */ static int kldrModMachOCallThread(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle, unsigned fAttachingOrDetaching) { /* Relevant for Mach-O? */ RT_NOREF(pMod); RT_NOREF(pvMapping); RT_NOREF(uHandle); RT_NOREF(fAttachingOrDetaching); return VINF_SUCCESS; } #endif /** * @interface_method_impl{RTLDROPS,pfnGetImageSize} */ static DECLCALLBACK(size_t) rtldrMachO_GetImageSize(PRTLDRMODINTERNAL pMod) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); return pThis->cbImage; } /** * @interface_method_impl{RTLDROPS,pfnGetBits} */ static DECLCALLBACK(int) rtldrMachO_GetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); if (!pThis->fCanLoad) return VERR_LDRMACHO_TODO; /* * Zero the entire buffer first to simplify things. */ memset(pvBits, 0, (size_t)pThis->cbImage); /* * When possible use the segment table to load the data. */ for (uint32_t i = 0; i < pThis->cSegments; i++) { /* skip it? */ if ( pThis->aSegments[i].SegInfo.cbFile == -1 || pThis->aSegments[i].SegInfo.offFile == -1 || pThis->aSegments[i].SegInfo.RVA == NIL_RTLDRADDR || pThis->aSegments[i].SegInfo.cbMapped == 0 || !pThis->aSegments[i].SegInfo.Alignment) continue; int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, (uint8_t *)pvBits + pThis->aSegments[i].SegInfo.RVA, pThis->aSegments[i].SegInfo.cbFile, pThis->aSegments[i].SegInfo.offFile); if (RT_FAILURE(rc)) return rc; } /* * Perform relocations. */ return rtldrMachO_RelocateBits(pMod, pvBits, BaseAddress, pThis->LinkAddress, pfnGetImport, pvUser); } /** * @interface_method_impl{RTLDROPS,pfnRelocate} */ static DECLCALLBACK(int) rtldrMachO_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); int rc; /* * Call workers to do the jobs. */ if (pThis->Hdr.filetype == MH_OBJECT) { rc = rtldrMachOObjLoadFixupsAndVirginData(pThis, (uint8_t const *)pvBits); if (RT_SUCCESS(rc)) rc = kldrModMachOObjDoImports(pThis, NewBaseAddress, pfnGetImport, pvUser); if (RT_SUCCESS(rc)) rc = kldrModMachOObjDoFixups(pThis, pvBits, NewBaseAddress); } else { rc = rtldrMachODylibLoadFixupsAndVirginData(pThis, (uint8_t const *)pvBits); if (RT_SUCCESS(rc)) rc = kldrModMachODylibDoImports(pThis, NewBaseAddress, pfnGetImport, pvUser); if (RT_SUCCESS(rc)) rc = kldrModMachODylibDoIndirectSymbols(pThis, pvBits, NewBaseAddress - OldBaseAddress); if (RT_SUCCESS(rc)) rc = kldrModMachODylibDoFixups(pThis, pvBits, NewBaseAddress); } /* * Construct the global offset table if necessary, it's always the last * segment when present. */ if (RT_SUCCESS(rc) && pThis->fMakeGot) rc = kldrModMachOMakeGOT(pThis, pvBits, NewBaseAddress); return rc; } /** * Builds the GOT. * * Assumes the symbol table has all external symbols resolved correctly and that * the bits has been cleared up front. */ static int kldrModMachOMakeGOT(PRTLDRMODMACHO pThis, void *pvBits, RTLDRADDR NewBaseAddress) { uint32_t iSym = pThis->cSymbols; if ( pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE || pThis->Hdr.magic == IMAGE_MACHO32_SIGNATURE_OE) { macho_nlist_32_t const *paSyms = (macho_nlist_32_t const *)pThis->pvaSymbols; uint32_t *paGOT = (uint32_t *)((uint8_t *)pvBits + pThis->GotRVA); while (iSym-- > 0) switch (paSyms[iSym].n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSymSect = &pThis->paSections[paSyms[iSym].n_sect - 1]; paGOT[iSym] = (uint32_t)(paSyms[iSym].n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress); break; } case MACHO_N_UNDF: case MACHO_N_ABS: paGOT[iSym] = paSyms[iSym].n_value; break; } } else { macho_nlist_64_t const *paSyms = (macho_nlist_64_t const *)pThis->pvaSymbols; uint64_t *paGOT = (uint64_t *)((uint8_t *)pvBits + pThis->GotRVA); while (iSym-- > 0) { switch (paSyms[iSym].n_type & MACHO_N_TYPE) { case MACHO_N_SECT: { PRTLDRMODMACHOSECT pSymSect; RTLDRMODMACHO_CHECK_RETURN((uint32_t)paSyms[iSym].n_sect - 1 <= pThis->cSections, VERR_LDRMACHO_BAD_SYMBOL); pSymSect = &pThis->paSections[paSyms[iSym].n_sect - 1]; paGOT[iSym] = paSyms[iSym].n_value - pSymSect->LinkAddress + pSymSect->RVA + NewBaseAddress; break; } case MACHO_N_UNDF: case MACHO_N_ABS: paGOT[iSym] = paSyms[iSym].n_value; break; } } if (pThis->JmpStubsRVA != NIL_RTLDRADDR) { iSym = pThis->cSymbols; switch (pThis->Hdr.cputype) { /* * AMD64 is simple since the GOT and the indirect jmps are parallel * arrays with entries of the same size. The relative offset will * be the the same for each entry, kind of nice. :-) */ case CPU_TYPE_X86_64: { uint64_t *paJmps = (uint64_t *)((uint8_t *)pvBits + pThis->JmpStubsRVA); int32_t off; uint64_t u64Tmpl; union { uint8_t ab[8]; uint64_t u64; } Tmpl; /* create the template. */ off = (int32_t)(pThis->GotRVA - (pThis->JmpStubsRVA + 6)); Tmpl.ab[0] = 0xff; /* jmp [GOT-entry wrt RIP] */ Tmpl.ab[1] = 0x25; Tmpl.ab[2] = off & 0xff; Tmpl.ab[3] = (off >> 8) & 0xff; Tmpl.ab[4] = (off >> 16) & 0xff; Tmpl.ab[5] = (off >> 24) & 0xff; Tmpl.ab[6] = 0xcc; Tmpl.ab[7] = 0xcc; u64Tmpl = Tmpl.u64; /* copy the template to every jmp table entry. */ while (iSym-- > 0) paJmps[iSym] = u64Tmpl; break; } default: RTLDRMODMACHO_FAILED_RETURN(VERR_LDRMACHO_TODO); } } } return VINF_SUCCESS; } /** * @interface_method_impl{RTLDROPS,pfnEnumSegments} */ static DECLCALLBACK(int) rtldrMachO_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); uint32_t const cSegments = pThis->cSegments; for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) { int rc = pfnCallback(pMod, &pThis->aSegments[iSeg].SegInfo, pvUser); if (rc != VINF_SUCCESS) return rc; } return VINF_SUCCESS; } /** * @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset} */ static DECLCALLBACK(int) rtldrMachO_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, uint32_t *piSeg, PRTLDRADDR poffSeg) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); uint32_t const cSegments = pThis->cSegments; for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) { RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].SegInfo.LinkAddress; if ( offSeg < pThis->aSegments[iSeg].SegInfo.cbMapped || offSeg < pThis->aSegments[iSeg].SegInfo.cb) { *piSeg = iSeg; *poffSeg = offSeg; return VINF_SUCCESS; } } return VERR_LDR_INVALID_LINK_ADDRESS; } /** * @interface_method_impl{RTLDROPS,pfnLinkAddressToRva} */ static DECLCALLBACK(int) rtldrMachO_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); uint32_t const cSegments = pThis->cSegments; for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) { RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].SegInfo.LinkAddress; if ( offSeg < pThis->aSegments[iSeg].SegInfo.cbMapped || offSeg < pThis->aSegments[iSeg].SegInfo.cb) { *pRva = pThis->aSegments[iSeg].SegInfo.RVA + offSeg; return VINF_SUCCESS; } } return VERR_LDR_INVALID_RVA; } /** * @interface_method_impl{RTLDROPS,pfnSegOffsetToRva} */ static DECLCALLBACK(int) rtldrMachO_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg, PRTLDRADDR pRva) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); if (iSeg >= pThis->cSegments) return VERR_LDR_INVALID_SEG_OFFSET; RTLDRMODMACHOSEG const *pSegment = &pThis->aSegments[iSeg]; if ( offSeg > pSegment->SegInfo.cbMapped && offSeg > pSegment->SegInfo.cb && ( pSegment->SegInfo.cbFile < 0 || offSeg > (uint64_t)pSegment->SegInfo.cbFile)) return VERR_LDR_INVALID_SEG_OFFSET; *pRva = pSegment->SegInfo.RVA + offSeg; return VINF_SUCCESS; } /** * @interface_method_impl{RTLDROPS,pfnRvaToSegOffset} */ static DECLCALLBACK(int) rtldrMachO_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva, uint32_t *piSeg, PRTLDRADDR poffSeg) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); uint32_t const cSegments = pThis->cSegments; for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) { RTLDRADDR offSeg = Rva - pThis->aSegments[iSeg].SegInfo.RVA; if ( offSeg < pThis->aSegments[iSeg].SegInfo.cbMapped || offSeg < pThis->aSegments[iSeg].SegInfo.cb) { *piSeg = iSeg; *poffSeg = offSeg; return VINF_SUCCESS; } } return VERR_LDR_INVALID_RVA; } /** * @interface_method_impl{RTLDROPS,pfnReadDbgInfo} */ static DECLCALLBACK(int) rtldrMachO_ReadDbgInfo(PRTLDRMODINTERNAL pMod, uint32_t iDbgInfo, RTFOFF off, size_t cb, void *pvBuf) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); /** @todo May have to apply fixups here. */ if (iDbgInfo < pThis->cSections) return pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvBuf, cb, off); return VERR_OUT_OF_RANGE; } /** * Loads the code signing blob if necessary (RTLDRMODMACHO::PtrCodeSignature). * * @returns IPRT status code. * @param pThis The mach-o instance. */ static int rtldrMachO_LoadSignatureBlob(PRTLDRMODMACHO pThis) { Assert(pThis->cbCodeSignature > 0); if (pThis->PtrCodeSignature.pb != NULL) return VINF_SUCCESS; if ( pThis->cbCodeSignature > sizeof(RTCRAPLCSHDR) && pThis->cbCodeSignature <= _1M) { /* Allocate and read. */ void *pv = RTMemAllocZ(RT_ALIGN_Z(pThis->cbCodeSignature, 16)); AssertReturn(pv, VERR_NO_MEMORY); int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pv, pThis->cbCodeSignature, pThis->offImage + pThis->offCodeSignature); if (RT_SUCCESS(rc)) { /* Check blob signature. */ PCRTCRAPLCSSUPERBLOB pSuper = (PCRTCRAPLCSSUPERBLOB)pv; if (pSuper->Hdr.uMagic == RTCRAPLCS_MAGIC_EMBEDDED_SIGNATURE) { uint32_t cbHdr = RT_BE2H_U32(pSuper->Hdr.cb); uint32_t cSlots = RT_BE2H_U32(pSuper->cSlots); if ( cbHdr <= pThis->cbCodeSignature && cbHdr > RT_UOFFSETOF(RTCRAPLCSSUPERBLOB, aSlots) && cSlots > 0 && cSlots < 128 && RT_UOFFSETOF_DYN(RTCRAPLCSSUPERBLOB, aSlots[cSlots]) <= cbHdr) { pThis->PtrCodeSignature.pSuper = pSuper; return VINF_SUCCESS; } rc = VERR_LDRVI_BAD_CERT_HDR_LENGTH; } else rc = VERR_LDRVI_BAD_CERT_HDR_TYPE; } RTMemFree(pv); return rc; } return VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY; } /** * Handles a RTLDRPROP_PKCS7_SIGNED_DATA query. */ static int rtldrMachO_QueryPkcs7SignedData(PRTLDRMODMACHO pThis, void *pvBuf, size_t cbBuf, size_t *pcbRet) { int rc = rtldrMachO_LoadSignatureBlob(pThis); if (RT_SUCCESS(rc)) { /* * Locate the signature slot. */ uint32_t iSlot = RT_BE2H_U32(pThis->PtrCodeSignature.pSuper->cSlots); PCRTCRAPLCSBLOBSLOT pSlot = &pThis->PtrCodeSignature.pSuper->aSlots[iSlot]; while (iSlot-- > 0) { pSlot--; if (pSlot->uType == RTCRAPLCS_SLOT_SIGNATURE) { /* * Validate the data offset. */ uint32_t offData = RT_BE2H_U32(pSlot->offData); if ( offData < pThis->cbCodeSignature - sizeof(RTCRAPLCSHDR) || !(offData & 3) ) { /* * The data is prefixed by a header with magic set to blob wrapper. * Check that the size is within the bounds of the code signing blob. */ PCRTCRAPLCSHDR pHdr = (PCRTCRAPLCSHDR)&pThis->PtrCodeSignature.pb[offData]; if (pHdr->uMagic == RTCRAPLCS_MAGIC_BLOBWRAPPER) { uint32_t cbData = RT_BE2H_U32(pHdr->cb); uint32_t cbMax = pThis->cbCodeSignature - offData ; if ( cbData <= cbMax && cbData > sizeof(RTCRAPLCSHDR)) { /* * Copy out the requested data. */ *pcbRet = cbData; if (cbData <= cbBuf) { memcpy(pvBuf, pHdr + 1, cbData); return VINF_SUCCESS; } memcpy(pvBuf, pHdr + 1, cbBuf); return VERR_BUFFER_OVERFLOW; } } } return VERR_LDRVI_BAD_CERT_FORMAT; } } rc = VERR_NOT_FOUND; } return rc; } /** @interface_method_impl{RTLDROPS,pfnQueryProp} */ static DECLCALLBACK(int) rtldrMachO_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet) { PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); int rc = VERR_NOT_FOUND; switch (enmProp) { case RTLDRPROP_UUID: Assert(cbBuf >= sizeof(pThis->abImageUuid)); if (!ASMMemIsZero(pThis->abImageUuid, sizeof(pThis->abImageUuid))) { *pcbRet = sizeof(pThis->abImageUuid); memcpy(pvBuf, pThis->abImageUuid, sizeof(pThis->abImageUuid)); return VINF_SUCCESS; } break; case RTLDRPROP_FILE_OFF_HEADER: Assert(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t)); if (cbBuf == sizeof(uint32_t)) *(uint32_t *)pvBuf = pThis->offImage; else *(uint64_t *)pvBuf = pThis->offImage; return VINF_SUCCESS; case RTLDRPROP_IS_SIGNED: Assert(cbBuf == sizeof(bool)); Assert(*pcbRet == cbBuf); *(bool *)pvBuf = pThis->cbCodeSignature > 0; return VINF_SUCCESS; case RTLDRPROP_PKCS7_SIGNED_DATA: if (pThis->cbCodeSignature > 0) return rtldrMachO_QueryPkcs7SignedData(pThis, pvBuf, cbBuf, pcbRet); break; #if 0 /** @todo return LC_ID_DYLIB */ case RTLDRPROP_INTERNAL_NAME: #endif default: break; } NOREF(cbBuf); RT_NOREF_PV(pvBits); return rc; } #ifndef IPRT_WITHOUT_LDR_VERIFY /** * Decodes the signature blob at RTLDRMODMACHO::PtrCodeSignature. * * @returns IPRT status code. * @param pThis The Mach-O module instance. * @param ppSignature Where to return the decoded signature data. * @param pErrInfo Where to supply extra error details. Optional. */ static int rtldrMachO_VerifySignatureDecode(PRTLDRMODMACHO pThis, PRTLDRMACHOSIGNATURE *ppSignature, PRTERRINFO pErrInfo) { Assert(pThis->PtrCodeSignature.pSuper != NULL); /* * Allocate and init decoded signature data structure. */ PRTLDRMACHOSIGNATURE pSignature = (PRTLDRMACHOSIGNATURE)RTMemTmpAllocZ(sizeof(*pSignature)); *ppSignature = pSignature; if (!pSignature) return VERR_NO_TMP_MEMORY; pSignature->idxPkcs7 = UINT32_MAX; /* * Parse the slots, validating the slot headers. */ PCRTCRAPLCSSUPERBLOB pSuper = pThis->PtrCodeSignature.pSuper; uint32_t const cSlots = RT_BE2H_U32(pSuper->cSlots); uint32_t const offFirst = RT_UOFFSETOF_DYN(RTCRAPLCSSUPERBLOB, aSlots[cSlots]); uint32_t const cbBlob = RT_BE2H_U32(pSuper->Hdr.cb); for (uint32_t iSlot = 0; iSlot < cSlots; iSlot++) { /* * Check that the data offset is valid. There appears to be no alignment * requirements here, which is a little weird consindering the PPC heritage. */ uint32_t const offData = RT_BE2H_U32(pSuper->aSlots[iSlot].offData); if ( offData < offFirst || offData > cbBlob - sizeof(RTCRAPLCSHDR)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u has an invalid data offset: %#x (min %#x, max %#x-4)", iSlot, offData, offFirst, cbBlob); uint32_t const cbMaxData = cbBlob - offData; /* * PKCS#7/CMS signature. */ if (pSuper->aSlots[iSlot].uType == RTCRAPLCS_SLOT_SIGNATURE) { if (pSignature->idxPkcs7 != UINT32_MAX) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Already have PKCS#7 data in slot %#u", iSlot, pSignature->idxPkcs7); PCRTCRAPLCSHDR pHdr = (PCRTCRAPLCSHDR)&pThis->PtrCodeSignature.pb[offData]; if (pHdr->uMagic != RTCRAPLCS_MAGIC_BLOBWRAPPER) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Invalid PKCS#7 wrapper magic: %#x", iSlot, RT_BE2H_U32(pHdr->uMagic)); uint32_t const cb = RT_BE2H_U32(pHdr->cb); if (cb > cbMaxData || cb < sizeof(*pHdr) + 2U) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Invalid PKCS#7 size is out of bound: %#x (min %#x, max %#x)", iSlot, cb, sizeof(*pHdr) + 2, cbMaxData); pSignature->idxPkcs7 = iSlot; pSignature->pbPkcs7 = (uint8_t const *)(pHdr + 1); pSignature->cbPkcs7 = cb - sizeof(*pHdr); } /* * Code directories. */ else if ( pSuper->aSlots[iSlot].uType == RTCRAPLCS_SLOT_CODEDIRECTORY || ( RT_BE2H_U32(pSuper->aSlots[iSlot].uType) - RT_BE2H_U32_C(RTCRAPLCS_SLOT_ALTERNATE_CODEDIRECTORIES) < RTCRAPLCS_SLOT_ALTERNATE_CODEDIRECTORIES_COUNT)) { /* Make sure we don't get too many code directories and that the first one is a regular one. */ if (pSignature->cCodeDirs >= RT_ELEMENTS(pSignature->aCodeDirs)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Too many code directory slots (%u found thus far)", iSlot, pSignature->cCodeDirs + 1); if ( pSuper->aSlots[iSlot].uType == RTCRAPLCS_SLOT_CODEDIRECTORY && pSignature->cCodeDirs > 0) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Already have primary code directory in slot #%u", iSlot, pSignature->aCodeDirs[0].uSlot); if ( pSuper->aSlots[iSlot].uType != RTCRAPLCS_SLOT_CODEDIRECTORY /* lazy bird */ && pSignature->cCodeDirs == 0) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Expected alternative code directory after the primary one", iSlot); /* Check data header: */ if (cbMaxData < RT_UOFFSETOF(RTCRAPLCSCODEDIRECTORY, uUnused1)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Insufficient data vailable for code directory (max %#x)", iSlot, cbMaxData); PCRTCRAPLCSCODEDIRECTORY pCodeDir = (PCRTCRAPLCSCODEDIRECTORY)&pThis->PtrCodeSignature.pb[offData]; if (pCodeDir->Hdr.uMagic != RTCRAPLCS_MAGIC_CODEDIRECTORY) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Invalid code directory magic: %#x", iSlot, RT_BE2H_U32(pCodeDir->Hdr.uMagic)); uint32_t const cbCodeDir = RT_BE2H_U32(pCodeDir->Hdr.cb); if (cbCodeDir > cbMaxData || cbCodeDir < RT_UOFFSETOF(RTCRAPLCSCODEDIRECTORY, offScatter)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Code directory size is out of bound: %#x (min %#x, max %#x)", iSlot, cbCodeDir, RT_UOFFSETOF(RTCRAPLCSCODEDIRECTORY, offScatter), cbMaxData); pSignature->aCodeDirs[pSignature->cCodeDirs].pCodeDir = pCodeDir; pSignature->aCodeDirs[pSignature->cCodeDirs].cb = cbCodeDir; /* Check Version: */ uint32_t const uVersion = RT_BE2H_U32(pCodeDir->uVersion); if ( uVersion < RTCRAPLCS_VER_2_0 || uVersion >= RT_MAKE_U32(0, 3)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Code directory version is out of bounds: %#07x", iSlot, uVersion); uint32_t cbSelf = uVersion >= RTCRAPLCS_VER_SUPPORTS_EXEC_SEG ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, fExecSeg) : uVersion >= RTCRAPLCS_VER_SUPPORTS_CODE_LIMIT_64 ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, cbCodeLimit64) : uVersion >= RTCRAPLCS_VER_SUPPORTS_TEAMID ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offTeamId) : uVersion >= RTCRAPLCS_VER_SUPPORTS_SCATTER ? RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offScatter) : RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, uUnused1); if (cbSelf > cbCodeDir) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Code directory size is out of bound: %#x (min %#x, max %#x)", iSlot, cbCodeDir, cbSelf, cbCodeDir); /* hash type and size. */ uint8_t cbHash; RTDIGESTTYPE enmDigest; switch (pCodeDir->bHashType) { case RTCRAPLCS_HASHTYPE_SHA1: enmDigest = RTDIGESTTYPE_SHA1; cbHash = RTSHA1_HASH_SIZE; break; case RTCRAPLCS_HASHTYPE_SHA256: enmDigest = RTDIGESTTYPE_SHA256; cbHash = RTSHA256_HASH_SIZE; break; case RTCRAPLCS_HASHTYPE_SHA256_TRUNCATED: enmDigest = RTDIGESTTYPE_SHA256; cbHash = RTSHA1_HASH_SIZE; /* truncated to SHA-1 size. */ break; case RTCRAPLCS_HASHTYPE_SHA384: enmDigest = RTDIGESTTYPE_SHA384; cbHash = RTSHA384_HASH_SIZE; break; default: return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unknown hash type %#x (LB %#x)", iSlot, pCodeDir->bHashType, pCodeDir->cbHash); } pSignature->aCodeDirs[pSignature->cCodeDirs].enmDigest = enmDigest; if (pCodeDir->cbHash != cbHash) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unexpected hash size for %s: %#x, expected %#x", iSlot, RTCrDigestTypeToName(enmDigest), pCodeDir->cbHash, cbHash); /* Hash slot offset and counts. Special slots are counted backwards from offHashSlots. */ uint32_t const cSpecialSlots = RT_BE2H_U32(pCodeDir->cSpecialSlots); if (cSpecialSlots > 256) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Too many special slots: %#x", iSlot, cSpecialSlots); uint32_t const cCodeSlots = RT_BE2H_U32(pCodeDir->cCodeSlots); if ( cCodeSlots >= UINT32_MAX / 2 || cCodeSlots + cSpecialSlots > (cbCodeDir - cbHash) / cbHash) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Too many code slots: %#x + %#x (max %#x)", iSlot, cCodeSlots, cSpecialSlots, (cbCodeDir - cbHash) / cbHash); uint32_t const offHashSlots = RT_BE2H_U32(pCodeDir->offHashSlots); if ( offHashSlots > cbCodeDir - cCodeSlots * cbHash || offHashSlots < cbSelf + cSpecialSlots * cbHash) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Code directory hash offset is out of bounds: %#x (min: %#x, max: %#x)", iSlot, offHashSlots, cbSelf + cSpecialSlots * cbHash, cbCodeDir - cCodeSlots * cbHash); /* page shift */ if (pCodeDir->cPageShift == 0) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unsupported page shift of zero in code directory", iSlot); uint32_t cMaxPageShift; if ( pThis->Core.enmArch == RTLDRARCH_AMD64 || pThis->Core.enmArch == RTLDRARCH_X86_32 || pThis->Core.enmArch == RTLDRARCH_ARM32) cMaxPageShift = 12; else if (pThis->Core.enmArch == RTLDRARCH_ARM64) cMaxPageShift = 16; /* 16KB */ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Unsupported architecture: %d", pThis->Core.enmArch); if ( pCodeDir->cPageShift < 12 /* */ || pCodeDir->cPageShift > cMaxPageShift) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Page shift in code directory is out of range: %d (min: 12, max: %d)", iSlot, pCodeDir->cPageShift, cMaxPageShift); /* code limit vs page shift and code hash slots */ uint32_t const cbCodeLimit32 = RT_BE2H_U32(pCodeDir->cbCodeLimit32); uint32_t const cExpectedCodeHashes = pCodeDir->cPageShift == 0 ? 1 : (cbCodeLimit32 + RT_BIT_32(pCodeDir->cPageShift) - 1) >> pCodeDir->cPageShift; if (cExpectedCodeHashes != cCodeSlots) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Code limit and page shift value does not match code hash slots: cbCodeLimit32=%#x cPageShift=%u -> %#x; cCodeSlots=%#x", iSlot, cbCodeLimit32, pCodeDir->cPageShift, cExpectedCodeHashes, cCodeSlots); /* Identifier offset: */ if (pCodeDir->offIdentifier) { uint32_t const offIdentifier = RT_BE2H_U32(pCodeDir->offIdentifier); if ( offIdentifier < cbSelf || offIdentifier >= cbCodeDir) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Identifier offset is out of bounds: %#x (min: %#x, max: %#x)", iSlot, offIdentifier, cbSelf, cbCodeDir - 1); int rc = RTStrValidateEncodingEx((char const *)pCodeDir + offIdentifier, cbCodeDir - offIdentifier, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Malformed identifier string: %Rrc", iSlot, rc); } /* Team identifier: */ if (cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offTeamId) && pCodeDir->offTeamId) { uint32_t const offTeamId = RT_BE2H_U32(pCodeDir->offTeamId); if ( offTeamId < cbSelf || offTeamId >= cbCodeDir) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Team identifier offset is out of bounds: %#x (min: %#x, max: %#x)", iSlot, offTeamId, cbSelf, cbCodeDir - 1); int rc = RTStrValidateEncodingEx((char const *)pCodeDir + offTeamId, cbCodeDir - offTeamId, RTSTR_VALIDATE_ENCODING_ZERO_TERMINATED); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Malformed team identifier string: %Rrc", iSlot, rc); } /* We don't support scatter. */ if (cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, offScatter) && pCodeDir->offScatter) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Scatter not supported.", iSlot); /* We don't really support the 64-bit code limit either: */ if ( cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, cbCodeLimit64) && pCodeDir->cbCodeLimit64 && RT_BE2H_U64(pCodeDir->cbCodeLimit64) != cbCodeLimit32) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: 64-bit code limit does not match 32-bit: %#RX64 vs %#RX32", iSlot, RT_BE2H_U64(pCodeDir->cbCodeLimit64), cbCodeLimit32); /* Check executable segment info if present: */ if ( cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, fExecSeg) && ( pThis->offSeg0ForCodeSign != RT_BE2H_U64(pCodeDir->offExecSeg) || pThis->cbSeg0ForCodeSign != RT_BE2H_U64(pCodeDir->cbExecSeg) || pThis->fSeg0ForCodeSign != RT_BE2H_U64(pCodeDir->fExecSeg)) ) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Segment #0 info mismatch: @%#RX64 LB %#RX64 flags=%#RX64; expected @%#RX64 LB %#RX64 flags=%#RX64", iSlot, RT_BE2H_U64(pCodeDir->offExecSeg), RT_BE2H_U64(pCodeDir->cbExecSeg), RT_BE2H_U64(pCodeDir->fExecSeg), pThis->offSeg0ForCodeSign, pThis->cbSeg0ForCodeSign, pThis->fSeg0ForCodeSign); /* Check fields that must be zero (don't want anyone to use them to counter changes): */ if (pCodeDir->uUnused1 != 0) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unused field #1 is non-zero: %#x", iSlot, RT_BE2H_U32(pCodeDir->uUnused1)); if ( cbSelf >= RT_UOFFSET_AFTER(RTCRAPLCSCODEDIRECTORY, uUnused2) && pCodeDir->uUnused2 != 0) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unused field #2 is non-zero: %#x", iSlot, RT_BE2H_U32(pCodeDir->uUnused2)); /** @todo idPlatform values. */ /** @todo Check for gaps if we know the version number? Alignment? */ /* If first code directory, check that the code limit covers the whole image up to the signature data. */ if (pSignature->cCodeDirs == 0) { /** @todo verify the that the signature data is at the very end... */ if (cbCodeLimit32 != pThis->offCodeSignature) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Unexpected code limit: %#x, expected %#x", iSlot, cbCodeLimit32, pThis->offCodeSignature); } /* Otherwise, check that the code limit matches the previous directories. */ else for (uint32_t i = 0; i < pSignature->cCodeDirs; i++) if (pSignature->aCodeDirs[i].pCodeDir->cbCodeLimit32 != pCodeDir->cbCodeLimit32) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Slot #%u: Code limit differs from previous directory: %#x, expected %#x", iSlot, cbCodeLimit32, RT_BE2H_U32(pSignature->aCodeDirs[i].pCodeDir->cbCodeLimit32)); /* Commit the code dir entry: */ pSignature->aCodeDirs[pSignature->cCodeDirs++].uSlot = iSlot; } } /* * Check that we've got at least one code directory and one PKCS#7 signature. */ if (pSignature->cCodeDirs == 0) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "No code directory slot in the code signature"); if (pSignature->idxPkcs7 == UINT32_MAX) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "No PKCS#7 slot in the code signature"); /* * Decode the PKCS#7 signature. */ RTASN1CURSORPRIMARY PrimaryCursor; RTAsn1CursorInitPrimary(&PrimaryCursor, pSignature->pbPkcs7, pSignature->cbPkcs7, pErrInfo, &g_RTAsn1DefaultAllocator, 0, "Mach-O-BLOB"); int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, &pSignature->ContentInfo, "CI"); if (RT_SUCCESS(rc)) { if (RTCrPkcs7ContentInfo_IsSignedData(&pSignature->ContentInfo)) { pSignature->pSignedData = pSignature->ContentInfo.u.pSignedData; /* * Check that the signedData stuff adds up. */ if (!strcmp(pSignature->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID)) { rc = RTCrPkcs7SignedData_CheckSanity(pSignature->pSignedData, RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE /** @todo consider not piggy-backing on auth-code */ | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT, pErrInfo, "SD"); if (RT_SUCCESS(rc)) return VINF_SUCCESS; } else rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, "Unexpected pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)", pSignature->pSignedData->ContentInfo.ContentType.szObjId, RTCR_PKCS7_DATA_OID); } else rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/ "PKCS#7 is not 'signedData': %s", pSignature->ContentInfo.ContentType.szObjId); } return rc; } /** * Destroys the decoded signature data structure. * * @param pSignature The decoded signature data. Can be NULL. */ static void rtldrMachO_VerifySignatureDestroy(PRTLDRMACHOSIGNATURE pSignature) { if (pSignature) { RTCrPkcs7ContentInfo_Delete(&pSignature->ContentInfo); RTMemTmpFree(pSignature); } } /** * Worker for rtldrMachO_VerifySignatureValidatePkcs7Hashes that handles plists * with code directory hashes inside them. * * It is assumed that these plist files was invented to handle alternative code * directories. * * @note Putting an XML plist into the authenticated attribute list was * probably not such a great idea, given all the optional and * adjustable white-space padding. We should probably validate * everything very strictly, limiting the elements, require certain * attribute lists and even have strict expectations about the * white-space, but right now let just make sure it's xml and get the * data in the cdhashes array. * * @todo The code here is a little braindead and bulky. It should be * possible to describe the expected XML structure using a tables. */ static int rtldrMachO_VerifySignatureValidateCdHashesPlist(PRTLDRMACHOSIGNATURE pSignature, char *pszPlist, uint8_t *pbHash, uint32_t cbHash, PRTERRINFO pErrInfo) { const char * const pszStart = pszPlist; #define CHECK_ISTR_AND_SKIP_OR_RETURN(a_szLead) \ do { \ if (!RTStrNICmp(pszPlist, RT_STR_TUPLE(a_szLead))) \ pszPlist += sizeof(a_szLead) - 1; \ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \ "Expected '%s' found '%.16s...' at %#zu in plist", a_szLead, pszPlist, pszPlist - pszStart); \ } while (0) #define CHECK_STR_AND_SKIP_OR_RETURN(a_szLead) \ do { \ if (!RTStrNCmp(pszPlist, RT_STR_TUPLE(a_szLead))) \ pszPlist += sizeof(a_szLead) - 1; \ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \ "Expected '%s' found '%.16s...' at %#zu in plist", a_szLead, pszPlist, pszPlist - pszStart); \ } while (0) #define SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN() \ do { /* currently only permitting spaces, tabs and newline, following char must be '<'. */ \ char chMacro; \ while ((chMacro = *pszPlist) == ' ' || chMacro == '\n' || chMacro == '\t') \ pszPlist++; \ if (chMacro == '<') { /* likely */ } \ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \ "Expected '<' found '%.16s...' at %#zu in plist", pszPlist, pszPlist - pszStart); \ } while (0) #define SKIP_SPACE_BEFORE_VALUE() \ do { /* currently only permitting spaces, tabs and newline. */ \ char chMacro; \ while ((chMacro = *pszPlist) == ' ' || chMacro == '\n' || chMacro == '\t') \ pszPlist++; \ } while (0) #define SKIP_REQUIRED_SPACE_BETWEEN_ATTRIBUTES_OR_RETURN() \ do { /* currently only permitting a single space */ \ if (pszPlist[0] == ' ' && pszPlist[1] != ' ') \ pszPlist++; \ else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, \ "Expected ' ' found '%.16s...' at %#zu in plist", pszPlist, pszPlist - pszStart); \ } while (0) /* Example: cdhashes hul2SSkDQFRXbGlt3AmCp25MU0Y= N0kvxg0CJBNuZTq135PntAaRczw= */ /* */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* cdhashes */ CHECK_STR_AND_SKIP_OR_RETURN("cdhashes"); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* * Repeated: hul2SSkDQFRXbGlt3AmCp25MU0Y= */ uint32_t iCodeDir = 0; for (;;) { /* Decode the binary data (base64) and skip it. */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BEFORE_VALUE(); char ch; size_t cchBase64 = 0; while (RT_C_IS_ALNUM(ch = pszPlist[cchBase64]) || ch == '+' || ch == '/' || ch == '=') cchBase64++; size_t cbActualHash = cbHash; char *pszEnd = NULL; int rc = RTBase64DecodeEx(pszPlist, cchBase64, pbHash, cbHash, &cbActualHash, &pszEnd); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Failed to decode hash #%u in authenticated plist attribute: %Rrc (%.*s)", iCodeDir, rc, cchBase64, pszPlist); pszPlist += cchBase64; AssertReturn(pszPlist == pszEnd, VERR_INTERNAL_ERROR_2); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* The binary hash data must be exactly the size of SHA1, larger hash like SHA-256 and SHA-384 are truncated for some reason. */ if (cbActualHash != RTSHA1_HASH_SIZE) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Hash #%u in authenticated plist attribute has the wrong length: %u, exepcted %u", iCodeDir, cbActualHash, RTSHA1_HASH_SIZE); /* Skip closing tag. */ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /* Calculate the hash and compare. */ RTCRDIGEST hDigest; rc = RTCrDigestCreateByType(&hDigest, pSignature->aCodeDirs[iCodeDir].enmDigest); if (RT_SUCCESS(rc)) { rc = RTCrDigestUpdate(hDigest, pSignature->aCodeDirs[iCodeDir].pCodeDir, pSignature->aCodeDirs[iCodeDir].cb); if (RT_SUCCESS(rc)) { if (memcmp(pbHash, RTCrDigestGetHash(hDigest), cbActualHash) == 0) rc = VINF_SUCCESS; else rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH, "Code directory #%u hash mismatch (plist):\n" "signed: %.*Rhxs\n" "our: %.*Rhxs\n", iCodeDir, cbActualHash, pbHash, RTCrDigestGetHashSize(hDigest), RTCrDigestGetHash(hDigest)); } else rc = RTErrInfoSetF(pErrInfo, rc, "RTCrDigestUpdate failed: %Rrc", rc); RTCrDigestRelease(hDigest); } else rc = RTErrInfoSetF(pErrInfo, rc, "Failed to create a digest of type %u verifying code dir #%u: %Rrc", pSignature->aCodeDirs[iCodeDir].enmDigest, iCodeDir, rc); if (RT_FAILURE(rc)) return rc; /* * Advance. */ iCodeDir++; SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); if (RTStrNCmp(pszPlist, RT_STR_TUPLE("")) == 0) { if (iCodeDir >= pSignature->cCodeDirs) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Authenticated plist attribute has too many code directories (%u in blob)", pSignature->cCodeDirs); } else if (iCodeDir == pSignature->cCodeDirs) break; else return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Authenticated plist attribute does not include all code directors: %u out of %u", iCodeDir, pSignature->cCodeDirs); } /**/ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /**/ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BETWEEN_ELEMENTS_OR_RETURN(); /**/ CHECK_STR_AND_SKIP_OR_RETURN(""); SKIP_SPACE_BEFORE_VALUE(); if (*pszPlist == '\0') return VINF_SUCCESS; return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Authenticated plist attribute has unexpected trailing content: %.32s", pszPlist); } /** * Verifies the code directory hashes embedded in the PKCS\#7 data. * * @returns IPRT status code. * @param pSignature The decoded signature data. * @param pErrInfo Where to supply extra error details. Optional. */ static int rtldrMachO_VerifySignatureValidatePkcs7Hashes(PRTLDRMACHOSIGNATURE pSignature, PRTERRINFO pErrInfo) { /* * Look thru the authenticated attributes in the signer info array. */ PRTCRPKCS7SIGNEDDATA pSignedData = pSignature->pSignedData; for (uint32_t iSignerInfo = 0; iSignerInfo < pSignedData->SignerInfos.cItems; iSignerInfo++) { PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignedData->SignerInfos.papItems[iSignerInfo]; bool fMsgDigest = false; bool fPlist = false; for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->AuthenticatedAttributes.cItems; iAttrib++) { PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->AuthenticatedAttributes.papItems[iAttrib]; if (RTAsn1ObjId_CompareWithString(&pAttrib->Type, RTCR_PKCS9_ID_MESSAGE_DIGEST_OID) == 0) { /* * Validate the message digest while we're here. */ AssertReturn(pAttrib->uValues.pOctetStrings && pAttrib->uValues.pOctetStrings->cItems == 1, VERR_INTERNAL_ERROR_5); RTCRDIGEST hDigest; int rc = RTCrDigestCreateByObjId(&hDigest, &pSignerInfo->DigestAlgorithm.Algorithm); if (RT_SUCCESS(rc)) { rc = RTCrDigestUpdate(hDigest, pSignature->aCodeDirs[0].pCodeDir, pSignature->aCodeDirs[0].cb); if (RT_SUCCESS(rc)) { if (!RTCrDigestMatch(hDigest, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb)) rc = RTErrInfoSetF(pErrInfo, VERR_CR_PKCS7_MESSAGE_DIGEST_ATTRIB_MISMATCH, "Authenticated message-digest attribute mismatch:\n" "signed: %.*Rhxs\n" "our: %.*Rhxs\n", pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb, pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pv, RTCrDigestGetHashSize(hDigest), RTCrDigestGetHash(hDigest)); } else rc = RTErrInfoSetF(pErrInfo, rc, "RTCrDigestUpdate failed: %Rrc", rc); RTCrDigestRelease(hDigest); } else rc = RTErrInfoSetF(pErrInfo, rc, "Failed to create a digest for OID %s: %Rrc", pSignerInfo->DigestAlgorithm.Algorithm.szObjId, rc); if (RT_FAILURE(rc)) return rc; fMsgDigest = true; } else if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_APPLE_MULTI_CD_PLIST) { /* * An XML (better be) property list with code directory hashes in it. */ if (!pAttrib->uValues.pOctetStrings || pAttrib->uValues.pOctetStrings->cItems != 1) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Bad authenticated plist attribute"); uint32_t cch = pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.cb; char const *pch = pAttrib->uValues.pOctetStrings->papItems[0]->Asn1Core.uData.pch; int rc = RTStrValidateEncodingEx(pch, cch, RTSTR_VALIDATE_ENCODING_EXACT_LENGTH); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Authenticated plist attribute is not valid UTF-8: %Rrc", rc); uint32_t const cchMin = sizeof("cdhasheshul2SSkDQFRXbGlt3AmCp25MU0Y=") - 1; if (cch < cchMin) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Authenticated plist attribute is too short: %#x, min: %#x", cch, cchMin); if (cch > _64K) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Authenticated plist attribute is too long: %#x, max: 64KB", cch); /* Copy the plist into a buffer and zero terminate it. Also allocate room for decoding a hash. */ const uint32_t cbMaxHash = 128; char *pszTmp = (char *)RTMemTmpAlloc(cbMaxHash + cch + 3); if (!pszTmp) return VERR_NO_TMP_MEMORY; pszTmp[cbMaxHash + cch] = '\0'; pszTmp[cbMaxHash + cch + 1] = '\0'; pszTmp[cbMaxHash + cch + 2] = '\0'; rc = rtldrMachO_VerifySignatureValidateCdHashesPlist(pSignature, (char *)memcpy(pszTmp + cbMaxHash, pch, cch), (uint8_t *)pszTmp, cbMaxHash, pErrInfo); RTMemTmpFree(pszTmp); if (RT_FAILURE(rc)) return rc; fPlist = true; } } if (!fMsgDigest && pSignature->cCodeDirs > 1) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Missing authenticated message-digest attribute"); if (!fPlist && pSignature->cCodeDirs > 1) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "Missing authenticated code directory hash plist attribute"); } if (pSignedData->SignerInfos.cItems < 1) return RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_FORMAT, "PKCS#7 signed data contains no signatures"); return VINF_SUCCESS; } /** * Verifies the page hashes of the given code directory. * * @returns IPRT status code. * @param pThis The Mach-O module instance. * @param pEntry The data entry for the code directory to validate. * @param pbBuf Read buffer. * @param cbBuf Buffer size. * @param pErrInfo Where to supply extra error details. Optional. */ static int rtldrMachO_VerifySignatureValidateCodeDir(PRTLDRMODMACHO pThis, PRTLDRMACHCODEDIR pEntry, uint8_t *pbBuf, uint32_t cbBuf, PRTERRINFO pErrInfo) { RTCRDIGEST hDigest; int rc = RTCrDigestCreateByType(&hDigest, pEntry->enmDigest); if (RT_SUCCESS(rc)) { PCRTCRAPLCSCODEDIRECTORY pCodeDir = pEntry->pCodeDir; PRTLDRREADER const pRdr = pThis->Core.pReader; uint32_t cbCodeLimit = RT_BE2H_U32(pCodeDir->cbCodeLimit32); uint32_t const cbPage = RT_BIT_32(pCodeDir->cPageShift); uint32_t const cHashes = RT_BE2H_U32(pCodeDir->cCodeSlots); uint8_t const cbHash = pCodeDir->cbHash; uint8_t const *pbHash = (uint8_t const *)pCodeDir + RT_BE2H_U32(pCodeDir->offHashSlots); RTFOFF offFile = pThis->offImage; if ( RT_BE2H_U32(pCodeDir->uVersion) < RTCRAPLCS_VER_SUPPORTS_SCATTER || pCodeDir->offScatter == 0) { /* * Work the image in linear fashion. */ for (uint32_t iHash = 0; iHash < cHashes; iHash++, pbHash += cbHash, cbCodeLimit -= cbPage) { RTFOFF const offPage = offFile; /* * Read and digest the data for the current hash page. */ rc = RTCrDigestReset(hDigest); AssertRCBreak(rc); Assert(cbCodeLimit > cbPage || iHash + 1 == cHashes); uint32_t cbLeft = iHash + 1 < cHashes ? cbPage : cbCodeLimit; while (cbLeft > 0) { uint32_t const cbToRead = RT_MIN(cbBuf, cbLeft); rc = pRdr->pfnRead(pRdr, pbBuf, cbToRead, offFile); AssertRCBreak(rc); rc = RTCrDigestUpdate(hDigest, pbBuf, cbToRead); AssertRCBreak(rc); offFile += cbToRead; cbLeft -= cbToRead; } AssertRCBreak(rc); rc = RTCrDigestFinal(hDigest, NULL, 0); AssertRCBreak(rc); /* * Compare it. * Note! Don't use RTCrDigestMatch here as there is a truncated SHA-256 variant. */ if (memcmp(pbHash, RTCrDigestGetHash(hDigest), cbHash) != 0) { rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_MISMATCH, "Hash #%u (@%RX64 LB %#x) mismatch in code dir #%u: %.*Rhxs, expected %.*Rhxs", iHash, offPage, cbPage, pEntry->uSlot, (int)cbHash, pbHash, (int)cbHash, RTCrDigestGetHash(hDigest)); break; } } } /* * Work the image in scattered fashion. */ else rc = VERR_INTERNAL_ERROR_4; RTCrDigestRelease(hDigest); } return rc; } /** * Verifies the page hashes of all the code directories * * @returns IPRT status code. * @param pThis The Mach-O module instance. * @param pSignature The decoded signature data. * @param pErrInfo Where to supply extra error details. Optional. */ static int rtldrMachO_VerifySignatureValidateCodeDirs(PRTLDRMODMACHO pThis, PRTLDRMACHOSIGNATURE pSignature, PRTERRINFO pErrInfo) { void *pvBuf = RTMemTmpAllocZ(_4K); if (pvBuf) { int rc = VERR_INTERNAL_ERROR_3; for (uint32_t i = 0; i < pSignature->cCodeDirs; i++) { rc = rtldrMachO_VerifySignatureValidateCodeDir(pThis, &pSignature->aCodeDirs[i], (uint8_t *)pvBuf, _4K, pErrInfo); if (RT_FAILURE(rc)) break; } RTMemTmpFree(pvBuf); return rc; } return VERR_NO_TMP_MEMORY; } #endif /* !IPRT_WITHOUT_LDR_VERIFY*/ /** * @interface_method_impl{RTLDROPS,pfnVerifySignature} */ static DECLCALLBACK(int) rtldrMachO_VerifySignature(PRTLDRMODINTERNAL pMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser, PRTERRINFO pErrInfo) { #ifndef IPRT_WITHOUT_LDR_VERIFY PRTLDRMODMACHO pThis = RT_FROM_MEMBER(pMod, RTLDRMODMACHO, Core); int rc = rtldrMachO_LoadSignatureBlob(pThis); if (RT_SUCCESS(rc)) { PRTLDRMACHOSIGNATURE pSignature = NULL; rc = rtldrMachO_VerifySignatureDecode(pThis, &pSignature, pErrInfo); if (RT_SUCCESS(rc)) { rc = rtldrMachO_VerifySignatureValidatePkcs7Hashes(pSignature, pErrInfo); if (RT_SUCCESS(rc)) { rc = rtldrMachO_VerifySignatureValidateCodeDirs(pThis, pSignature, pErrInfo); if (RT_SUCCESS(rc)) { /* * Finally, let the caller verify the certificate chain for the PKCS#7 bit. */ rc = pfnCallback(&pThis->Core, RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA, &pSignature->ContentInfo, sizeof(pSignature->ContentInfo), pSignature->aCodeDirs[0].pCodeDir, pSignature->aCodeDirs[0].cb, pErrInfo, pvUser); } } } rtldrMachO_VerifySignatureDestroy(pSignature); } return rc; #else RT_NOREF_PV(pMod); RT_NOREF_PV(pfnCallback); RT_NOREF_PV(pvUser); RT_NOREF_PV(pErrInfo); return VERR_NOT_SUPPORTED; #endif } /** * Operations for a Mach-O module interpreter. */ static const RTLDROPS s_rtldrMachOOps= { "mach-o", rtldrMachO_Close, NULL, NULL /*pfnDone*/, rtldrMachO_EnumSymbols, /* ext */ rtldrMachO_GetImageSize, rtldrMachO_GetBits, rtldrMachO_RelocateBits, rtldrMachO_GetSymbolEx, NULL /*pfnQueryForwarderInfo*/, rtldrMachO_EnumDbgInfo, rtldrMachO_EnumSegments, rtldrMachO_LinkAddressToSegOffset, rtldrMachO_LinkAddressToRva, rtldrMachO_SegOffsetToRva, rtldrMachO_RvaToSegOffset, rtldrMachO_ReadDbgInfo, rtldrMachO_QueryProp, rtldrMachO_VerifySignature, NULL /*pfnHashImage*/, NULL /*pfnUnwindFrame*/, 42 }; /** * Handles opening Mach-O images (non-fat). */ DECLHIDDEN(int) rtldrMachOOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offImage, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) { /* * Create the instance data and do a minimal header validation. */ PRTLDRMODMACHO pThis = NULL; int rc = kldrModMachODoCreate(pReader, offImage, fFlags, &pThis, pErrInfo); if (RT_SUCCESS(rc)) { /* * Match up against the requested CPU architecture. */ if ( enmArch == RTLDRARCH_WHATEVER || pThis->Core.enmArch == enmArch) { pThis->Core.pOps = &s_rtldrMachOOps; pThis->Core.u32Magic = RTLDRMOD_MAGIC; *phLdrMod = &pThis->Core; return VINF_SUCCESS; } rc = VERR_LDR_ARCH_MISMATCH; } if (pThis) { RTMemFree(pThis->pbLoadCommands); RTMemFree(pThis); } return rc; } /** * Handles opening FAT Mach-O image. */ DECLHIDDEN(int) rtldrFatOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) { fat_header_t FatHdr; int rc = pReader->pfnRead(pReader, &FatHdr, sizeof(FatHdr), 0); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, rc, "Read error at offset 0: %Rrc", rc); if (FatHdr.magic == IMAGE_FAT_SIGNATURE) { /* likely */ } else if (FatHdr.magic == IMAGE_FAT_SIGNATURE_OE) FatHdr.nfat_arch = RT_BSWAP_U32(FatHdr.nfat_arch); else return RTErrInfoSetF(pErrInfo, VERR_INVALID_EXE_SIGNATURE, "magic=%#x", FatHdr.magic); if (FatHdr.nfat_arch < 64) return RTErrInfoSetF(pErrInfo, VERR_INVALID_EXE_SIGNATURE, "Bad nfat_arch value: %#x", FatHdr.nfat_arch); uint32_t offEntry = sizeof(FatHdr); for (uint32_t i = 0; i < FatHdr.nfat_arch; i++, offEntry += sizeof(fat_arch_t)) { fat_arch_t FatEntry; rc = pReader->pfnRead(pReader, &FatEntry, sizeof(FatEntry), offEntry); if (RT_FAILURE(rc)) return RTErrInfoSetF(pErrInfo, rc, "Read error at offset 0: %Rrc", rc); if (FatHdr.magic == IMAGE_FAT_SIGNATURE_OE) { FatEntry.cputype = (int32_t)RT_BSWAP_U32((uint32_t)FatEntry.cputype); //FatEntry.cpusubtype = (int32_t)RT_BSWAP_U32((uint32_t)FatEntry.cpusubtype); FatEntry.offset = RT_BSWAP_U32(FatEntry.offset); //FatEntry.size = RT_BSWAP_U32(FatEntry.size); //FatEntry.align = RT_BSWAP_U32(FatEntry.align); } /* * Match enmArch. */ bool fMatch = false; switch (enmArch) { case RTLDRARCH_WHATEVER: fMatch = true; break; case RTLDRARCH_X86_32: fMatch = FatEntry.cputype == CPU_TYPE_X86; break; case RTLDRARCH_AMD64: fMatch = FatEntry.cputype == CPU_TYPE_X86_64; break; case RTLDRARCH_ARM32: case RTLDRARCH_ARM64: case RTLDRARCH_X86_16: fMatch = false; break; case RTLDRARCH_INVALID: case RTLDRARCH_HOST: case RTLDRARCH_END: case RTLDRARCH_32BIT_HACK: AssertFailedReturn(VERR_INVALID_PARAMETER); } if (fMatch) return rtldrMachOOpen(pReader, fFlags, enmArch, FatEntry.offset, phLdrMod, pErrInfo); } return VERR_LDR_ARCH_MISMATCH; }