From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Runtime/common/ldr/ldrMachO.cpp | 5734 ++++++++++++++++++++++++++++++ 1 file changed, 5734 insertions(+) create mode 100644 src/VBox/Runtime/common/ldr/ldrMachO.cpp (limited to 'src/VBox/Runtime/common/ldr/ldrMachO.cpp') diff --git a/src/VBox/Runtime/common/ldr/ldrMachO.cpp b/src/VBox/Runtime/common/ldr/ldrMachO.cpp new file mode 100644 index 00000000..7d5c655a --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrMachO.cpp @@ -0,0 +1,5734 @@ +/* $Id: ldrMachO.cpp $ */ +/** @file + * kLdr - The Module Interpreter for the MACH-O format. + */ + +/* + * Copyright (C) 2018-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + * -------------------------------------------------------------------- + * + * This code is based on: kLdr/kLdrModMachO.c from kStuff r113. + * + * 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_MSG_RETURN + * Checks that an expression is true and return if it isn't. + * This is a debug aid. + */ +#ifdef RTLDRMODMACHO_STRICT2 +# define RTLDRMODMACHO_CHECK_MSG_RETURN(expr, msgargs, rc) AssertMsgReturn(expr, msgargs, rc) +#else +# define RTLDRMODMACHO_CHECK_MSG_RETURN(expr, msgargs, 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; + case CPU_TYPE_ARM64: + fMakeGot = s.Hdr32.filetype == MH_OBJECT || s.Hdr32.filetype == MH_KEXT_BUNDLE; + cbJmpStub = fMakeGot ? 8 : 0; /** @todo Not sure if this is right. Need to expore ARM64/MachO a bit more... */ + 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; + + case CPU_TYPE_ARM64: + pThis->Core.enmArch = RTLDRARCH_ARM64; + pThis->Core.enmEndian = RTLDRENDIAN_LITTLE; + switch (s.Hdr32.cpusubtype & ~CPU_SUBTYPE_MASK) + { + case CPU_SUBTYPE_ARM64_ALL: pThis->enmCpu = RTLDRCPU_ARM64_BLEND; break; + case CPU_SUBTYPE_ARM64_V8: pThis->enmCpu = RTLDRCPU_ARM64_V8; break; + case CPU_SUBTYPE_ARM64E: pThis->enmCpu = RTLDRCPU_ARM64E; 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_MSG_RETURN(!(pSrcSeg->flags & ~(SG_HIGHVM | SG_FVMLIB | SG_NORELOC | SG_PROTECTED_VERSION_1 | SG_READ_ONLY)), \ + ("flags=%#x %s\n", pSrcSeg->flags, pSrcSeg->segname), \ + 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_BUILD_VERSION: /* Harmless. It just gives a clue regarding the tool/sdk versions. */ + 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 = RT_BOOL(pThis->fOpenFlags & RTLDR_O_MACHO_LOAD_LINKEDIT); + 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 = NIL_RTLDRADDR; + 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 (typcially is, but not always for mach_kernel). */ + 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 = NIL_RTLDRADDR; + 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 + (uintptr_t)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 + (uintptr_t)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++) + if (pThis->aSegments[iSeg].SegInfo.RVA != NIL_RTLDRADDR) + { + Assert(pThis->aSegments[iSeg].SegInfo.cbMapped != NIL_RTLDRADDR); + 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++) + if (pThis->aSegments[iSeg].SegInfo.RVA != NIL_RTLDRADDR) + { + Assert(pThis->aSegments[iSeg].SegInfo.cbMapped != NIL_RTLDRADDR); + 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 (pSegment->SegInfo.RVA == NIL_RTLDRADDR) + return VERR_LDR_INVALID_SEG_OFFSET; + + 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++) + if (pThis->aSegments[iSeg].SegInfo.RVA != NIL_RTLDRADDR) + { + Assert(pThis->aSegments[iSeg].SegInfo.cbMapped != NIL_RTLDRADDR); + 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. + */ + RTLDRSIGNATUREINFO Info; + Info.iSignature = 0; + Info.cSignatures = 1; + Info.enmType = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA; + Info.pvSignature = &pSignature->ContentInfo; + Info.cbSignature = sizeof(pSignature->ContentInfo); + Info.pvExternalData = pSignature->aCodeDirs[0].pCodeDir; + Info.cbExternalData = pSignature->aCodeDirs[0].cb; + rc = pfnCallback(&pThis->Core, &Info, 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: + fMatch = FatEntry.cputype == CPU_TYPE_ARM32; + break; + + case RTLDRARCH_ARM64: + fMatch = FatEntry.cputype == CPU_TYPE_ARM64; + break; + + 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; + +} + -- cgit v1.2.3