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/Makefile.kup | 0 src/VBox/Runtime/common/ldr/ldr.cpp | 186 + src/VBox/Runtime/common/ldr/ldrELF.cpp | 381 ++ .../Runtime/common/ldr/ldrELFRelocatable.cpp.h | 3163 +++++++++++ src/VBox/Runtime/common/ldr/ldrEx.cpp | 773 +++ src/VBox/Runtime/common/ldr/ldrFile.cpp | 317 ++ src/VBox/Runtime/common/ldr/ldrLX.cpp | 3121 +++++++++++ src/VBox/Runtime/common/ldr/ldrMachO.cpp | 5734 ++++++++++++++++++++ src/VBox/Runtime/common/ldr/ldrMemory.cpp | 336 ++ src/VBox/Runtime/common/ldr/ldrNative.cpp | 336 ++ src/VBox/Runtime/common/ldr/ldrPE.cpp | 5194 ++++++++++++++++++ src/VBox/Runtime/common/ldr/ldrVfsFile.cpp | 293 + 12 files changed, 19834 insertions(+) create mode 100644 src/VBox/Runtime/common/ldr/Makefile.kup create mode 100644 src/VBox/Runtime/common/ldr/ldr.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrELF.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h create mode 100644 src/VBox/Runtime/common/ldr/ldrEx.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrFile.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrLX.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrMachO.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrMemory.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrNative.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrPE.cpp create mode 100644 src/VBox/Runtime/common/ldr/ldrVfsFile.cpp (limited to 'src/VBox/Runtime/common/ldr') diff --git a/src/VBox/Runtime/common/ldr/Makefile.kup b/src/VBox/Runtime/common/ldr/Makefile.kup new file mode 100644 index 00000000..e69de29b diff --git a/src/VBox/Runtime/common/ldr/ldr.cpp b/src/VBox/Runtime/common/ldr/ldr.cpp new file mode 100644 index 00000000..0d697974 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldr.cpp @@ -0,0 +1,186 @@ +/* $Id: ldr.cpp $ */ +/** @file + * IPRT - Binary Image Loader. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include "internal/ldr.h" + + +RTDECL(int) RTLdrGetSymbol(RTLDRMOD hLdrMod, const char *pszSymbol, void **ppvValue) +{ + LogFlow(("RTLdrGetSymbol: hLdrMod=%RTldrm pszSymbol=%p:{%s} ppvValue=%p\n", + hLdrMod, pszSymbol, pszSymbol, ppvValue)); + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertMsgReturn(pszSymbol, ("pszSymbol=%p\n", pszSymbol), VERR_INVALID_PARAMETER); + AssertPtrReturn(ppvValue, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnGetSymbol) + rc = pMod->pOps->pfnGetSymbol(pMod, pszSymbol, ppvValue); + else + { + RTUINTPTR Value = 0; + rc = pMod->pOps->pfnGetSymbolEx(pMod, NULL, 0, UINT32_MAX, pszSymbol, &Value); + if (RT_SUCCESS(rc)) + { + *ppvValue = (void *)(uintptr_t)Value; + if ((uintptr_t)*ppvValue != Value) + rc = VERR_BUFFER_OVERFLOW; + } + } + LogFlow(("RTLdrGetSymbol: return %Rrc *ppvValue=%p\n", rc, *ppvValue)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrGetSymbol); + + +RTDECL(PFNRT) RTLdrGetFunction(RTLDRMOD hLdrMod, const char *pszSymbol) +{ + PFNRT pfn; + int rc = RTLdrGetSymbol(hLdrMod, pszSymbol, (void **)&pfn); + if (RT_SUCCESS(rc)) + return pfn; + return NULL; +} +RT_EXPORT_SYMBOL(RTLdrGetFunction); + + +RTDECL(RTLDRFMT) RTLdrGetFormat(RTLDRMOD hLdrMod) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRFMT_INVALID); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + return pMod->enmFormat; +} +RT_EXPORT_SYMBOL(RTLdrGetFormat); + + +RTDECL(RTLDRTYPE) RTLdrGetType(RTLDRMOD hLdrMod) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRTYPE_INVALID); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + return pMod->enmType; +} +RT_EXPORT_SYMBOL(RTLdrGetType); + + +RTDECL(RTLDRENDIAN) RTLdrGetEndian(RTLDRMOD hLdrMod) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRENDIAN_INVALID); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + return pMod->enmEndian; +} +RT_EXPORT_SYMBOL(RTLdrGetEndian); + + +RTDECL(RTLDRARCH) RTLdrGetArch(RTLDRMOD hLdrMod) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRARCH_INVALID); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + return pMod->enmArch; +} +RT_EXPORT_SYMBOL(RTLdrGetArch); + + +RTDECL(int) RTLdrClose(RTLDRMOD hLdrMod) +{ + LogFlow(("RTLdrClose: hLdrMod=%RTldrm\n", hLdrMod)); + + /* + * Validate input. + */ + if (hLdrMod == NIL_RTLDRMOD) + return VINF_SUCCESS; + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc = pMod->pOps->pfnClose(pMod); + AssertRC(rc); + pMod->eState = LDR_STATE_INVALID; + pMod->u32Magic++; + if (pMod->pReader) + { + rc = pMod->pReader->pfnDestroy(pMod->pReader); + AssertRC(rc); + pMod->pReader = NULL; + } + RTMemFree(pMod); + + LogFlow(("RTLdrClose: returns VINF_SUCCESS\n")); + return VINF_SUCCESS; +} +RT_EXPORT_SYMBOL(RTLdrClose); + + +RTDECL(RTLDRARCH) RTLdrGetHostArch(void) +{ +#if defined(RT_ARCH_AMD64) + RTLDRARCH enmArch = RTLDRARCH_AMD64; +#elif defined(RT_ARCH_X86) + RTLDRARCH enmArch = RTLDRARCH_X86_32; +#elif defined(RT_ARCH_ARM) || defined(RT_ARCH_ARM32) + RTLDRARCH enmArch = RTLDRARCH_ARM32; +#elif defined(RT_ARCH_ARM64) + RTLDRARCH enmArch = RTLDRARCH_ARM64; +#else + RTLDRARCH enmArch = RTLDRARCH_WHATEVER; +#endif + return enmArch; +} +RT_EXPORT_SYMBOL(RTLdrGetHostArch); + diff --git a/src/VBox/Runtime/common/ldr/ldrELF.cpp b/src/VBox/Runtime/common/ldr/ldrELF.cpp new file mode 100644 index 00000000..ad380088 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrELF.cpp @@ -0,0 +1,381 @@ +/* $Id: ldrELF.cpp $ */ +/** @file + * IPRT - Binary Image Loader, Executable and Linker Format (ELF). + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* 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 +#include "internal/ldr.h" +#include "internal/dbgmod.h" + + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Finds an ELF symbol table string. */ +#define ELF_STR(pHdrs, iStr) ((pHdrs)->Rel.pStr + (iStr)) +/** Finds an ELF symbol table string. */ +#define ELF_DYN_STR(pHdrs, iStr) ((pHdrs)->Dyn.pStr + (iStr)) +/** Finds an ELF section header string. */ +#define ELF_SH_STR(pHdrs, iStr) ((pHdrs)->pShStr + (iStr)) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Magic string for RTLDRLNXMODSIG::achMagic */ +#define RTLDRLNXMODSIG_MAGIC "~Module signature appended~\n" +AssertCompile(sizeof(RTLDRLNXMODSIG_MAGIC) == 29); + +/** + * Linux kernel module signature footer - found at the end of the file. + */ +typedef struct RTLDRLNXMODSIG +{ + /** Zero. */ + uint8_t bAlgo; + /** Zero. */ + uint8_t bHash; + /** Signature type (RTLDRLNXMODSIG_TYPE_PKCS7). */ + uint8_t bType; + /** Zero. */ + uint8_t cbSignerName; + /** Zero. */ + uint8_t cbKeyId; + /** Zero padding. */ + uint8_t abReserved[3]; + /** The length of the signature preceeding this footer structure. */ + uint32_t cbSignature; + /** Magic value identifying this structure. */ + char achMagic[sizeof(RTLDRLNXMODSIG_MAGIC) - 1]; +} RTLDRLNXMODSIG; +typedef RTLDRLNXMODSIG *PRTLDRLNXMODSIG; +typedef RTLDRLNXMODSIG const *PCRTLDRLNXMODSIG; +/** Signature type. */ +#define RTLDRLNXMODSIG_TYPE_PKCS7 2 + + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int rtLdrELFLnxKModQueryPropIsSigned(PRTLDRREADER pReader, bool *pfRet); +static int rtLdrELFLnxKModQueryPropPkcs7SignedData(PRTLDRREADER pReader, void *pvBuf, size_t cbBuf, size_t *pcbRet); +static DECLCALLBACK(int) rtldrELFLnxKModHashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash); +#ifdef LOG_ENABLED +static const char *rtldrElfGetShdrType(uint32_t iType); +static const char *rtldrElfGetPhdrType(uint32_t iType); +#endif + + +/* Select ELF mode and include the template. */ +#define ELF_MODE 32 +#define Elf_Reloc Elf_Rel +#include "ldrELFRelocatable.cpp.h" +#undef ELF_MODE +#undef Elf_Reloc + + +#define ELF_MODE 64 +#define Elf_Reloc Elf_Rela +#include "ldrELFRelocatable.cpp.h" +#undef ELF_MODE +#undef Elf_Reloc + + +#ifdef LOG_ENABLED + +/** + * Gets the section type. + * + * @returns Pointer to read only string. + * @param iType The section type index. + */ +static const char *rtldrElfGetShdrType(uint32_t iType) +{ + switch (iType) + { + RT_CASE_RET_STR(SHT_NULL); + RT_CASE_RET_STR(SHT_PROGBITS); + RT_CASE_RET_STR(SHT_SYMTAB); + RT_CASE_RET_STR(SHT_STRTAB); + RT_CASE_RET_STR(SHT_RELA); + RT_CASE_RET_STR(SHT_HASH); + RT_CASE_RET_STR(SHT_DYNAMIC); + RT_CASE_RET_STR(SHT_NOTE); + RT_CASE_RET_STR(SHT_NOBITS); + RT_CASE_RET_STR(SHT_REL); + RT_CASE_RET_STR(SHT_SHLIB); + RT_CASE_RET_STR(SHT_DYNSYM); + default: + return ""; + } +} + +/** + * Gets the program header type. + * + * @returns Pointer to read only string. + * @param iType The section type index. + */ +static const char *rtldrElfGetPhdrType(uint32_t iType) +{ + switch (iType) + { + RT_CASE_RET_STR(PT_NULL); + RT_CASE_RET_STR(PT_LOAD); + RT_CASE_RET_STR(PT_DYNAMIC); + RT_CASE_RET_STR(PT_INTERP); + RT_CASE_RET_STR(PT_NOTE); + RT_CASE_RET_STR(PT_SHLIB); + RT_CASE_RET_STR(PT_PHDR); + RT_CASE_RET_STR(PT_TLS); + RT_CASE_RET_STR(PT_GNU_EH_FRAME); + RT_CASE_RET_STR(PT_GNU_STACK); + RT_CASE_RET_STR(PT_GNU_RELRO); + RT_CASE_RET_STR(PT_GNU_PROPERTY); + default: + return ""; + } +} + +#endif /* LOG_ENABLED*/ + +/** + * Reads in what migt be a linux kernel module signature footer. + */ +static int rtLdrELFLnxKModReadFooter(PRTLDRREADER pReader, PRTLDRLNXMODSIG pSigFooter, uint64_t *pcbFile) +{ + /* + * Look for the linux module signature at the end of the file. + * This should be safe to read w/o any size checking as it is smaller than the elf header. + */ + uint64_t cbFile = pReader->pfnSize(pReader); + *pcbFile = cbFile; + + AssertCompile(sizeof(*pSigFooter) <= sizeof(Elf32_Ehdr)); + return pReader->pfnRead(pReader, pSigFooter, sizeof(*pSigFooter), cbFile - sizeof(*pSigFooter)); +} + + +/** + * Check that a linux kernel module signature footer is valid. + */ +static bool rtLdrELFLnxKModIsFooterValid(PCRTLDRLNXMODSIG pSigFooter, uint64_t cbFile) +{ + if (memcmp(pSigFooter->achMagic, RTLDRLNXMODSIG_MAGIC, sizeof(pSigFooter->achMagic)) == 0) + { + uint32_t const cbSignature = RT_N2H_U32(pSigFooter->cbSignature); + if (cbSignature > 32 && cbSignature + sizeof(*pSigFooter) < cbFile) + return pSigFooter->bAlgo == 0 + && pSigFooter->bHash == 0 + && pSigFooter->cbSignerName == 0 + && pSigFooter->cbKeyId == 0; + } + return false; +} + + +/** + * Handles the linux kernel module signature part of RTLDRPROP_IS_SIGNED + * queries. + */ +static int rtLdrELFLnxKModQueryPropIsSigned(PRTLDRREADER pReader, bool *pfRet) +{ + *pfRet = false; + AssertReturn(pReader, VERR_INVALID_STATE); + + uint64_t cbFile; + RTLDRLNXMODSIG SigFooter; + int rc = rtLdrELFLnxKModReadFooter(pReader, &SigFooter, &cbFile); + if (RT_SUCCESS(rc)) + *pfRet = rtLdrELFLnxKModIsFooterValid(&SigFooter, cbFile); + return rc; +} + + +/** + * Handles the linux kernel module signature part of RTLDRPROP_IS_SIGNED + * queries. + */ +static int rtLdrELFLnxKModQueryPropPkcs7SignedData(PRTLDRREADER pReader, void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + AssertReturn(pReader, VERR_INVALID_STATE); + + uint64_t cbFile; + RTLDRLNXMODSIG SigFooter; + int rc = rtLdrELFLnxKModReadFooter(pReader, &SigFooter, &cbFile); + if (RT_SUCCESS(rc)) + { + if ( rtLdrELFLnxKModIsFooterValid(&SigFooter, cbFile) + && SigFooter.bType == RTLDRLNXMODSIG_TYPE_PKCS7) + { + uint32_t const cbSignature = RT_N2H_U32(SigFooter.cbSignature); + *pcbRet = cbSignature; + if (cbSignature <= cbBuf) + rc = pReader->pfnRead(pReader, pvBuf, cbSignature, cbFile - sizeof(SigFooter) - cbSignature); + else + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_NOT_FOUND; + } + return rc; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnHashImage, + * Handles the linux kernel module signatures.} + */ +static DECLCALLBACK(int) rtldrELFLnxKModHashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash) +{ + PRTLDRREADER pReader = pMod->pReader; + AssertReturn(pReader, VERR_INVALID_STATE); + + /* + * Get the file size and subtract any linux kernel module signature from it + * since it's not part of the hash. + */ + uint64_t cbFile; + RTLDRLNXMODSIG SigFooter; + int rc = rtLdrELFLnxKModReadFooter(pReader, &SigFooter, &cbFile); + if (RT_SUCCESS(rc)) + { + if (rtLdrELFLnxKModIsFooterValid(&SigFooter, cbFile)) + cbFile -= sizeof(SigFooter) + RT_N2H_U32(SigFooter.cbSignature); + + /* + * Now hash the file. + */ + RTCRDIGEST hDigest; + rc = RTCrDigestCreateByType(&hDigest, enmDigest); + if (RT_SUCCESS(rc)) + { + uint32_t cbBuf = _64K; + void *pvBuf = RTMemTmpAlloc(_64K); + void *pvBufFree = pvBuf; + if (!pvBuf) + { + cbBuf = _4K; + pvBuf = alloca(_4K); + } + + for (uint64_t offFile = 0; offFile < cbFile; ) + { + uint64_t cbLeft = cbFile - offFile; + uint32_t cbToRead = cbLeft >= cbBuf ? cbBuf : (uint32_t)cbLeft; + rc = pReader->pfnRead(pReader, pvBuf, cbToRead, offFile); + AssertRCBreak(rc); + + rc = RTCrDigestUpdate(hDigest, pvBuf, cbToRead); + offFile += cbToRead; + AssertRCBreak(rc); + } + + RTMemTmpFree(pvBufFree); + + if (RT_SUCCESS(rc)) + rc = RTCrDigestFinal(hDigest, pabHash, cbHash); + RTCrDigestRelease(hDigest); + } + } + return rc; +} + + +/** + * Open an ELF image. + * + * @returns iprt status code. + * @param pReader The loader reader instance which will provide the raw image bits. + * @param fFlags Reserved, MBZ. + * @param enmArch Architecture specifier. + * @param phLdrMod Where to store the handle. + * @param pErrInfo Where to return extended error information. Optional. + */ +DECLHIDDEN(int) rtldrELFOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + const char *pszLogName = pReader->pfnLogName(pReader); NOREF(pszLogName); + + /* + * Read the ident to decide if this is 32-bit or 64-bit + * and worth dealing with. + */ + uint8_t e_ident[EI_NIDENT]; + int rc = pReader->pfnRead(pReader, &e_ident, sizeof(e_ident), 0); + if (RT_FAILURE(rc)) + return rc; + + if ( e_ident[EI_MAG0] != ELFMAG0 + || e_ident[EI_MAG1] != ELFMAG1 + || e_ident[EI_MAG2] != ELFMAG2 + || e_ident[EI_MAG3] != ELFMAG3 + || ( e_ident[EI_CLASS] != ELFCLASS32 + && e_ident[EI_CLASS] != ELFCLASS64) + ) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Unsupported/invalid ident %.*Rhxs", pszLogName, sizeof(e_ident), e_ident); + + if (e_ident[EI_DATA] != ELFDATA2LSB) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_ODD_ENDIAN, + "%s: ELF endian %x is unsupported", pszLogName, e_ident[EI_DATA]); + + if (e_ident[EI_CLASS] == ELFCLASS32) + rc = rtldrELF32Open(pReader, fFlags, enmArch, phLdrMod, pErrInfo); + else + rc = rtldrELF64Open(pReader, fFlags, enmArch, phLdrMod, pErrInfo); + return rc; +} + diff --git a/src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h b/src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h new file mode 100644 index 00000000..1eb33be8 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrELFRelocatable.cpp.h @@ -0,0 +1,3163 @@ +/* $Id: ldrELFRelocatable.cpp.h $ */ +/** @file + * IPRT - Binary Image Loader, Template for ELF Relocatable Images. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/******************************************************************************* +* Defined Constants And Macros * +*******************************************************************************/ +#if ELF_MODE == 32 +# define RTLDRELF_NAME(name) rtldrELF32##name +# define RTLDRELF_SUFF(name) name##32 +# define RTLDRELF_MID(pre,suff) pre##32##suff +# define FMT_ELF_ADDR "%08RX32" +# define FMT_ELF_ADDR7 "%07RX32" +# define FMT_ELF_HALF "%04RX16" +# define FMT_ELF_OFF "%08RX32" +# define FMT_ELF_SIZE "%08RX32" +# define FMT_ELF_SWORD "%RI32" +# define FMT_ELF_WORD "%08RX32" +# define FMT_ELF_XWORD "%08RX32" +# define FMT_ELF_SXWORD "%RI32" +# define Elf_Xword Elf32_Word +# define Elf_Sxword Elf32_Sword + +#elif ELF_MODE == 64 +# define RTLDRELF_NAME(name) rtldrELF64##name +# define RTLDRELF_SUFF(name) name##64 +# define RTLDRELF_MID(pre,suff) pre##64##suff +# define FMT_ELF_ADDR "%016RX64" +# define FMT_ELF_ADDR7 "%08RX64" +# define FMT_ELF_HALF "%04RX16" +# define FMT_ELF_SHALF "%RI16" +# define FMT_ELF_OFF "%016RX64" +# define FMT_ELF_SIZE "%016RX64" +# define FMT_ELF_SWORD "%RI32" +# define FMT_ELF_WORD "%08RX32" +# define FMT_ELF_XWORD "%016RX64" +# define FMT_ELF_SXWORD "%RI64" +# define Elf_Xword Elf64_Xword +# define Elf_Sxword Elf64_Sxword +#endif + +#define Elf_Ehdr RTLDRELF_MID(Elf,_Ehdr) +#define Elf_Phdr RTLDRELF_MID(Elf,_Phdr) +#define Elf_Shdr RTLDRELF_MID(Elf,_Shdr) +#define Elf_Sym RTLDRELF_MID(Elf,_Sym) +#define Elf_Rel RTLDRELF_MID(Elf,_Rel) +#define Elf_Rela RTLDRELF_MID(Elf,_Rela) +#define Elf_Nhdr RTLDRELF_MID(Elf,_Nhdr) +#define Elf_Dyn RTLDRELF_MID(Elf,_Dyn) +#define Elf_Addr RTLDRELF_MID(Elf,_Addr) +#define Elf_Half RTLDRELF_MID(Elf,_Half) +#define Elf_Off RTLDRELF_MID(Elf,_Off) +#define Elf_Size RTLDRELF_MID(Elf,_Size) +#define Elf_Sword RTLDRELF_MID(Elf,_Sword) +#define Elf_Word RTLDRELF_MID(Elf,_Word) + +#define RTLDRMODELF RTLDRELF_MID(RTLDRMODELF,RT_NOTHING) +#define PRTLDRMODELF RTLDRELF_MID(PRTLDRMODELF,RT_NOTHING) + +#define RTLDRMODELFSHX RTLDRELF_MID(RTLDRMODELFSHX,RT_NOTHING) +#define PRTLDRMODELFSHX RTLDRELF_MID(PRTLDRMODELFSHX,RT_NOTHING) + +#define ELF_R_SYM(info) RTLDRELF_MID(ELF,_R_SYM)(info) +#define ELF_R_TYPE(info) RTLDRELF_MID(ELF,_R_TYPE)(info) +#define ELF_R_INFO(sym, type) RTLDRELF_MID(ELF,_R_INFO)(sym, type) + +#define ELF_ST_BIND(info) RTLDRELF_MID(ELF,_ST_BIND)(info) + + + +/******************************************************************************* +* Structures and Typedefs * +*******************************************************************************/ +/** + * Extra section info. + */ +typedef struct RTLDRMODELFSHX +{ + /** The corresponding program header. */ + uint16_t idxPhdr; + /** The corresponding dynamic section entry (address). */ + uint16_t idxDt; + /** The DT tag. */ + uint32_t uDtTag; +} RTLDRMODELFSHX; +typedef RTLDRMODELFSHX *PRTLDRMODELFSHX; + +/** + * The ELF loader structure. + */ +typedef struct RTLDRMODELF +{ + /** Core module structure. */ + RTLDRMODINTERNAL Core; + /** Pointer to readonly mapping of the image bits. + * This mapping is provided by the pReader. */ + const void *pvBits; + + /** The ELF header. */ + Elf_Ehdr Ehdr; + /** Pointer to our copy of the section headers with sh_addr as RVAs. + * The virtual addresses in this array is the 0 based assignments we've given the image. + * Not valid if the image is DONE. */ + Elf_Shdr *paShdrs; + /** Unmodified section headers (allocated after paShdrs, so no need to free). + * Not valid if the image is DONE. */ + Elf_Shdr const *paOrgShdrs; + /** Runs parallel to paShdrs and is part of the same allocation. */ + PRTLDRMODELFSHX paShdrExtras; + /** Base section number, either 1 or zero depending on whether we've + * re-used the NULL entry for .elf.headers in ET_EXEC/ET_DYN. */ + unsigned iFirstSect; + /** Set if the SHF_ALLOC section headers are in order of sh_addr. */ + bool fShdrInOrder; + /** The size of the loaded image. */ + size_t cbImage; + + /** The image base address if it's an EXEC or DYN image. */ + Elf_Addr LinkAddress; + + struct + { + /** The symbol section index. */ + unsigned iSymSh; + /** Number of symbols in the table. */ + unsigned cSyms; + /** Pointer to symbol table within RTLDRMODELF::pvBits. */ + const Elf_Sym *paSyms; + + /** The string section index. */ + unsigned iStrSh; + /** Size of the string table. */ + unsigned cbStr; + /** Pointer to string table within RTLDRMODELF::pvBits. */ + const char *pStr; + } Rel /**< Regular symbols and strings. */ + , Dyn /**< Dynamic symbols and strings. */; + + /** Pointer to section header string table within RTLDRMODELF::pvBits. */ + const char *pShStr; + /** Size of the section header string table. */ + unsigned cbShStr; + + /** The '.eh_frame' section index. Zero if not searched for, ~0U if not found. */ + unsigned iShEhFrame; + /** The '.eh_frame_hdr' section index. Zero if not searched for, ~0U if not found. */ + unsigned iShEhFrameHdr; + + /** The '.dynamic' / SHT_DYNAMIC section index. ~0U if not present. */ + unsigned iShDynamic; + /** Number of entries in paDynamic. */ + unsigned cDynamic; + /** The dynamic section (NULL for ET_REL). */ + Elf_Dyn *paDynamic; + /** Program headers (NULL for ET_REL). */ + Elf_Phdr *paPhdrs; + + /** Info extracted from PT_DYNAMIC and the program headers. */ + struct + { + /** DT_RELA/DT_REL. */ + Elf_Addr uPtrRelocs; + /** DT_RELASZ/DT_RELSZ. */ + Elf_Xword cbRelocs; + /** Non-zero if we've seen DT_RELAENT/DT_RELENT. */ + unsigned cbRelocEntry; + /** DT_RELA or DT_REL. */ + unsigned uRelocType; + /** The index of the section header matching DT_RELA/DT_REL. */ + unsigned idxShRelocs; + + /** DT_JMPREL. */ + Elf_Addr uPtrJmpRelocs; + /** DT_PLTRELSZ. */ + Elf_Xword cbJmpRelocs; + /** DT_RELA or DT_REL (if we've seen DT_PLTREL). */ + unsigned uJmpRelocType; + /** The index of the section header matching DT_JMPREL. */ + unsigned idxShJmpRelocs; + } DynInfo; +} RTLDRMODELF; +/** Pointer to an ELF module instance. */ +typedef RTLDRMODELF *PRTLDRMODELF; + + +/** + * Maps the image bits into memory and resolve pointers into it. + * + * @returns iprt status code. + * @param pModElf The ELF loader module instance data. + * @param fNeedsBits Set if we actually need the pvBits member. + * If we don't, we can simply read the string and symbol sections, thus saving memory. + */ +static int RTLDRELF_NAME(MapBits)(PRTLDRMODELF pModElf, bool fNeedsBits) +{ + NOREF(fNeedsBits); + if (pModElf->pvBits) + return VINF_SUCCESS; + int rc = pModElf->Core.pReader->pfnMap(pModElf->Core.pReader, &pModElf->pvBits); + if (RT_SUCCESS(rc)) + { + const uint8_t *pu8 = (const uint8_t *)pModElf->pvBits; + if (pModElf->Rel.iSymSh != ~0U) + pModElf->Rel.paSyms = (const Elf_Sym *)(pu8 + pModElf->paShdrs[pModElf->Rel.iSymSh].sh_offset); + if (pModElf->Rel.iStrSh != ~0U) + pModElf->Rel.pStr = (const char *)(pu8 + pModElf->paShdrs[pModElf->Rel.iStrSh].sh_offset); + if (pModElf->Dyn.iSymSh != ~0U) + pModElf->Dyn.paSyms = (const Elf_Sym *)(pu8 + pModElf->paShdrs[pModElf->Dyn.iSymSh].sh_offset); + if (pModElf->Dyn.iStrSh != ~0U) + pModElf->Dyn.pStr = (const char *)(pu8 + pModElf->paShdrs[pModElf->Dyn.iStrSh].sh_offset); + pModElf->pShStr = (const char *)(pu8 + pModElf->paShdrs[pModElf->Ehdr.e_shstrndx].sh_offset); + + /* + * Verify that the ends of the string tables have a zero terminator + * (this avoids duplicating the appropriate checks later in the code accessing the string tables). + * + * sh_offset and sh_size were verfied in RTLDRELF_NAME(ValidateSectionHeader)() already so they + * are safe to use. + */ + AssertMsgStmt( pModElf->Rel.iStrSh == ~0U + || pModElf->Rel.pStr[pModElf->paShdrs[pModElf->Rel.iStrSh].sh_size - 1] == '\0', + ("The string table is not zero terminated!\n"), + rc = VERR_LDRELF_UNTERMINATED_STRING_TAB); + AssertMsgStmt( pModElf->Dyn.iStrSh == ~0U + || pModElf->Dyn.pStr[pModElf->paShdrs[pModElf->Dyn.iStrSh].sh_size - 1] == '\0', + ("The string table is not zero terminated!\n"), + rc = VERR_LDRELF_UNTERMINATED_STRING_TAB); + AssertMsgStmt(pModElf->pShStr[pModElf->paShdrs[pModElf->Ehdr.e_shstrndx].sh_size - 1] == '\0', + ("The section header string table is not zero terminated!\n"), + rc = VERR_LDRELF_UNTERMINATED_STRING_TAB); + + if (RT_FAILURE(rc)) + { + /* Unmap. */ + int rc2 = pModElf->Core.pReader->pfnUnmap(pModElf->Core.pReader, pModElf->pvBits); + AssertRC(rc2); + pModElf->pvBits = NULL; + pModElf->Rel.paSyms = NULL; + pModElf->Rel.pStr = NULL; + pModElf->Dyn.paSyms = NULL; + pModElf->Dyn.pStr = NULL; + pModElf->pShStr = NULL; + } + } + return rc; +} + + +/* + * + * EXEC & DYN. + * EXEC & DYN. + * EXEC & DYN. + * EXEC & DYN. + * EXEC & DYN. + * + */ + +/** + * Get the symbol and symbol value. + * + * @returns iprt status code. + * @param pModElf The ELF loader module instance data. + * @param BaseAddr The base address which the module is being fixedup to. + * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals). + * @param pvUser User argument to pass to the callback. + * @param iSym The symbol to get. + * @param ppSym Where to store the symbol pointer on success. (read only) + * @param pSymValue Where to store the symbol value on success. + */ +static int RTLDRELF_NAME(SymbolExecDyn)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, PFNRTLDRIMPORT pfnGetImport, void *pvUser, + Elf_Size iSym, const Elf_Sym **ppSym, Elf_Addr *pSymValue) +{ + /* + * Validate and find the symbol. + */ + AssertMsgReturn(iSym < pModElf->Dyn.cSyms, ("iSym=%d is an invalid symbol index!\n", iSym), VERR_LDRELF_INVALID_SYMBOL_INDEX); + const Elf_Sym *pSym = &pModElf->Dyn.paSyms[iSym]; + *ppSym = pSym; + + AssertMsgReturn(pSym->st_name < pModElf->Dyn.cbStr, + ("iSym=%d st_name=%d str sh_size=%d\n", iSym, pSym->st_name, pModElf->Dyn.cbStr), + VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET); + const char * const pszName = pModElf->Dyn.pStr + pSym->st_name; + + /* + * Determine the symbol value. + * + * Symbols needs different treatment depending on which section their are in. + * Undefined and absolute symbols goes into special non-existing sections. + */ + switch (pSym->st_shndx) + { + /* + * Undefined symbol, needs resolving. + * + * Since ELF has no generic concept of importing from specific module (the OS/2 ELF format + * has but that's an OS extension and only applies to programs and dlls), we'll have to ask + * the resolver callback to do a global search. + */ + case SHN_UNDEF: + { + /* Try to resolve the symbol. */ + RTUINTPTR Value; + int rc = pfnGetImport(&pModElf->Core, "", pszName, ~0U, &Value, pvUser); + AssertMsgRCReturn(rc, ("Failed to resolve '%s' (iSym=" FMT_ELF_SIZE " rc=%Rrc\n", pszName, iSym, rc), rc); + + *pSymValue = (Elf_Addr)Value; + AssertMsgReturn((RTUINTPTR)*pSymValue == Value, + ("Symbol value overflowed! '%s' (iSym=" FMT_ELF_SIZE "\n", pszName, iSym), VERR_SYMBOL_VALUE_TOO_BIG); + + Log2(("rtldrELF: #%-3d - UNDEF " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName)); + break; + } + + /* + * Absolute symbols needs no fixing since they are, well, absolute. + */ + case SHN_ABS: + *pSymValue = pSym->st_value; + Log2(("rtldrELF: #%-3d - ABS " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName)); + break; + + /* + * All other symbols are addressed relative the image base in DYN and EXEC binaries. + */ + default: + AssertMsgReturn(pSym->st_shndx < pModElf->Ehdr.e_shnum, + ("iSym=%d st_shndx=%d e_shnum=%d pszName=%s\n", iSym, pSym->st_shndx, pModElf->Ehdr.e_shnum, pszName), + VERR_BAD_EXE_FORMAT); + *pSymValue = pSym->st_value + BaseAddr; + Log2(("rtldrELF: #%-3d - %5d " FMT_ELF_ADDR " '%s'\n", iSym, pSym->st_shndx, *pSymValue, pszName)); + break; + } + + return VINF_SUCCESS; +} + + +#if ELF_MODE == 32 +/** Helper for RelocateSectionExecDyn. */ +DECLINLINE(const Elf_Shdr *) RTLDRELF_NAME(RvaToSectionHeader)(PRTLDRMODELF pModElf, Elf_Addr uRva) +{ + const Elf_Shdr * const pShdrFirst = pModElf->paShdrs; + const Elf_Shdr *pShdr = pShdrFirst + pModElf->Ehdr.e_shnum; + while (--pShdr != pShdrFirst) + if (uRva - pShdr->sh_addr /*rva*/ < pShdr->sh_size) + return pShdr; + AssertFailed(); + return pShdr; +} +#endif + + +/** + * Applies the fixups for a section in an executable image. + * + * @returns iprt status code. + * @param pModElf The ELF loader module instance data. + * @param BaseAddr The base address which the module is being fixedup to. + * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals). + * @param pvUser User argument to pass to the callback. + * @param SecAddr The section address. This is the address the relocations are relative to. + * @param cbSec The section size. The relocations must be inside this. + * @param pu8SecBaseR Where we read section bits from. + * @param pu8SecBaseW Where we write section bits to. + * @param pvRelocs Pointer to where we read the relocations from. + * @param cbRelocs Size of the relocations. + */ +static int RTLDRELF_NAME(RelocateSectionExecDyn)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, + PFNRTLDRIMPORT pfnGetImport, void *pvUser, + const Elf_Addr SecAddr, Elf_Size cbSec, + const uint8_t *pu8SecBaseR, uint8_t *pu8SecBaseW, + const void *pvRelocs, Elf_Size cbRelocs) +{ +#if ELF_MODE != 32 + NOREF(pu8SecBaseR); +#endif + + /* + * Iterate the relocations. + * The relocations are stored in an array of Elf32_Rel records and covers the entire relocation section. + */ +#if ELF_MODE == 32 + const Elf_Shdr *pShdr = pModElf->paShdrs; + const Elf_Addr offDelta = BaseAddr - pModElf->LinkAddress; +#endif + const Elf_Reloc *paRels = (const Elf_Reloc *)pvRelocs; + const unsigned iRelMax = (unsigned)(cbRelocs / sizeof(paRels[0])); + AssertMsgReturn(iRelMax == cbRelocs / sizeof(paRels[0]), (FMT_ELF_SIZE "\n", cbRelocs / sizeof(paRels[0])), + VERR_IMAGE_TOO_BIG); + for (unsigned iRel = 0; iRel < iRelMax; iRel++) + { + /* + * Apply fixups not taking a symbol (will 'continue' rather than 'break'). + */ + AssertMsgReturn(paRels[iRel].r_offset < cbSec, (FMT_ELF_ADDR " " FMT_ELF_SIZE "\n", paRels[iRel].r_offset, cbSec), + VERR_LDRELF_INVALID_RELOCATION_OFFSET); +#if ELF_MODE == 32 + if (paRels[iRel].r_offset - pShdr->sh_addr /*rva*/ >= pShdr->sh_size) + pShdr = RTLDRELF_NAME(RvaToSectionHeader)(pModElf, paRels[iRel].r_offset); + static const Elf_Addr s_uZero = 0; + const Elf_Addr *pAddrR = RT_LIKELY(pShdr->sh_type != SHT_NOBITS) /* Where to read the addend. */ + ? (const Elf_Addr *)(pu8SecBaseR + paRels[iRel].r_offset - pShdr->sh_addr /*rva*/ + + pShdr->sh_offset) + : &s_uZero; +#endif + Elf_Addr *pAddrW = (Elf_Addr *)(pu8SecBaseW + paRels[iRel].r_offset); /* Where to write the fixup. */ + switch (ELF_R_TYPE(paRels[iRel].r_info)) + { + /* + * Image relative (addend + base). + */ +#if ELF_MODE == 32 + case R_386_RELATIVE: + { + const Elf_Addr Value = *pAddrR + BaseAddr; + *(uint32_t *)pAddrW = Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_RELATIVE Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value)); + AssertCompile(sizeof(Value) == sizeof(uint32_t)); + continue; + } +#elif ELF_MODE == 64 + case R_X86_64_RELATIVE: + { + const Elf_Addr Value = paRels[iRel].r_addend + BaseAddr; + *(uint64_t *)pAddrW = (uint64_t)Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_RELATIVE Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value)); + AssertCompile(sizeof(Value) == sizeof(uint64_t)); + continue; + } +#endif + + /* + * R_XXX_NONE. + */ +#if ELF_MODE == 32 + case R_386_NONE: +#elif ELF_MODE == 64 + case R_X86_64_NONE: +#endif + continue; + } + + /* + * Validate and find the symbol, resolve undefined ones. + */ + const Elf_Sym *pSym = NULL; /* shut up gcc */ + Elf_Addr SymValue = 0; /* shut up gcc-4 */ + int rc = RTLDRELF_NAME(SymbolExecDyn)(pModElf, BaseAddr, pfnGetImport, pvUser, ELF_R_SYM(paRels[iRel].r_info), &pSym, &SymValue); + if (RT_FAILURE(rc)) + return rc; + + /* + * Apply the fixup. + */ + switch (ELF_R_TYPE(paRels[iRel].r_info)) + { +#if ELF_MODE == 32 + /* + * GOT/PLT. + */ + case R_386_GLOB_DAT: + { + *(uint32_t *)pAddrW = (uint32_t)SymValue; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_GLOB_DAT Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue)); + AssertCompile(sizeof(SymValue) == sizeof(uint32_t)); + break; + } + + case R_386_JMP_SLOT: + { + *(uint32_t *)pAddrW = (uint32_t)SymValue; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_JMP_SLOT Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue)); + AssertCompile(sizeof(SymValue) == sizeof(uint32_t)); + break; + } + + /* + * Absolute addressing. + */ + case R_386_32: + { + Elf_Addr Value; + if (pSym->st_shndx < pModElf->Ehdr.e_shnum) + Value = *pAddrR + offDelta; /* Simplified. */ + else if (pSym->st_shndx == SHN_ABS) + continue; /* Internal fixup, no need to apply it. */ + else if (pSym->st_shndx == SHN_UNDEF) + Value = SymValue + *pAddrR; + else + AssertFailedReturn(VERR_LDR_GENERAL_FAILURE); /** @todo SHN_COMMON */ + *(uint32_t *)pAddrW = Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_32 Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value)); + break; + } + + /* + * PC relative addressing. + */ + case R_386_PC32: + { + Elf_Addr Value; + if (pSym->st_shndx < pModElf->Ehdr.e_shnum) + continue; /* Internal fixup, no need to apply it. */ + else if (pSym->st_shndx == SHN_ABS) + Value = *pAddrR + offDelta; /* Simplified. */ + else if (pSym->st_shndx == SHN_UNDEF) + { + const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */ + Value = SymValue + *(uint32_t *)pAddrR - SourceAddr; + *(uint32_t *)pAddrW = Value; + } + else + AssertFailedReturn(VERR_LDR_GENERAL_FAILURE); /** @todo SHN_COMMON */ + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_386_PC32 Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value)); + break; + } + +#elif ELF_MODE == 64 + /* + * GOT/PLT. + */ + case R_X86_64_GLOB_DAT: + { + *(uint64_t *)pAddrW = (uint64_t)SymValue; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_GLOB_DAT Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue)); + AssertCompile(sizeof(SymValue) == sizeof(uint64_t)); + break; + } + + case R_X86_64_JMP_SLOT: + { + *(uint64_t *)pAddrW = (uint64_t)SymValue; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_JMP_SLOT Value=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, SymValue)); + AssertCompile(sizeof(SymValue) == sizeof(uint64_t)); + break; + } + + /* + * Absolute addressing. + */ + case R_X86_64_64: + { + const Elf_Addr Value = SymValue + paRels[iRel].r_addend; + *(uint64_t *)pAddrW = Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_64 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value, SymValue)); + break; + } + + /* + * Truncated 32-bit value (zero-extendedable to the 64-bit value). + */ + case R_X86_64_32: + { + const Elf_Addr Value = SymValue + paRels[iRel].r_addend; + *(uint32_t *)pAddrW = (uint32_t)Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value, SymValue)); + AssertMsgReturn((Elf_Addr)*(uint32_t *)pAddrW == SymValue, ("Value=" FMT_ELF_ADDR "\n", SymValue), + VERR_SYMBOL_VALUE_TOO_BIG); + break; + } + + /* + * Truncated 32-bit value (sign-extendedable to the 64-bit value). + */ + case R_X86_64_32S: + { + const Elf_Addr Value = SymValue + paRels[iRel].r_addend; + *(int32_t *)pAddrW = (int32_t)Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_32S Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, paRels[iRel].r_offset, Value, SymValue)); + AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */ + break; + } + + /* + * PC relative addressing. + */ + case R_X86_64_PC32: + { + const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */ + const Elf_Addr Value = SymValue + paRels[iRel].r_addend - SourceAddr; + *(int32_t *)pAddrW = (int32_t)Value; + Log4((FMT_ELF_ADDR "/" FMT_ELF_ADDR7 ": R_X86_64_PC32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SourceAddr, paRels[iRel].r_offset, Value, SymValue)); + AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */ + break; + } + +#endif + default: + AssertMsgFailed(("Unknown relocation type: %d (iRel=%d iRelMax=%d)\n", + ELF_R_TYPE(paRels[iRel].r_info), iRel, iRelMax)); + return VERR_LDRELF_RELOCATION_NOT_SUPPORTED; + } + } + + return VINF_SUCCESS; +} + + + +/* + * + * REL + * REL + * REL + * REL + * REL + * + */ + +/** + * Get the symbol and symbol value. + * + * @returns iprt status code. + * @param pModElf The ELF loader module instance data. + * @param BaseAddr The base address which the module is being fixedup to. + * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals). + * @param pvUser User argument to pass to the callback. + * @param iSym The symbol to get. + * @param ppSym Where to store the symbol pointer on success. (read only) + * @param pSymValue Where to store the symbol value on success. + */ +static int RTLDRELF_NAME(Symbol)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, PFNRTLDRIMPORT pfnGetImport, void *pvUser, + Elf_Size iSym, const Elf_Sym **ppSym, Elf_Addr *pSymValue) +{ + /* + * Validate and find the symbol. + */ + AssertMsgReturn(iSym < pModElf->Rel.cSyms, ("iSym=%d is an invalid symbol index!\n", iSym), VERR_LDRELF_INVALID_SYMBOL_INDEX); + const Elf_Sym *pSym = &pModElf->Rel.paSyms[iSym]; + *ppSym = pSym; + + AssertMsgReturn(pSym->st_name < pModElf->Rel.cbStr, + ("iSym=%d st_name=%d str sh_size=%d\n", iSym, pSym->st_name, pModElf->Rel.cbStr), + VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET); + const char *pszName = ELF_STR(pModElf, pSym->st_name); + + /* + * Determine the symbol value. + * + * Symbols needs different treatment depending on which section their are in. + * Undefined and absolute symbols goes into special non-existing sections. + */ + switch (pSym->st_shndx) + { + /* + * Undefined symbol, needs resolving. + * + * Since ELF has no generic concept of importing from specific module (the OS/2 ELF format + * has but that's an OS extension and only applies to programs and dlls), we'll have to ask + * the resolver callback to do a global search. + */ + case SHN_UNDEF: + { + /* Try to resolve the symbol. */ + RTUINTPTR Value; + int rc = pfnGetImport(&pModElf->Core, "", pszName, ~0U, &Value, pvUser); + AssertMsgRCReturn(rc, ("Failed to resolve '%s' (iSym=" FMT_ELF_SIZE " rc=%Rrc\n", pszName, iSym, rc), rc); + *pSymValue = (Elf_Addr)Value; + + AssertMsgReturn((RTUINTPTR)*pSymValue == Value, + ("Symbol value overflowed! '%s' (iSym=" FMT_ELF_SIZE ")\n", pszName, iSym), + VERR_SYMBOL_VALUE_TOO_BIG); + + Log2(("rtldrELF: #%-3d - UNDEF " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName)); + break; + } + + /* + * Absolute symbols needs no fixing since they are, well, absolute. + */ + case SHN_ABS: + *pSymValue = pSym->st_value; + Log2(("rtldrELF: #%-3d - ABS " FMT_ELF_ADDR " '%s'\n", iSym, *pSymValue, pszName)); + break; + + /* + * All other symbols are addressed relative to their section and need to be fixed up. + */ + default: + if (pSym->st_shndx >= pModElf->Ehdr.e_shnum) + { + /* what about common symbols? */ + AssertMsg(pSym->st_shndx < pModElf->Ehdr.e_shnum, + ("iSym=%d st_shndx=%d e_shnum=%d pszName=%s\n", iSym, pSym->st_shndx, pModElf->Ehdr.e_shnum, pszName)); + return VERR_BAD_EXE_FORMAT; + } + *pSymValue = pSym->st_value + pModElf->paShdrs[pSym->st_shndx].sh_addr + BaseAddr; + Log2(("rtldrELF: #%-3d - %5d " FMT_ELF_ADDR " '%s'\n", iSym, pSym->st_shndx, *pSymValue, pszName)); + break; + } + + return VINF_SUCCESS; +} + + +/** + * Applies the fixups for a sections. + * + * @returns iprt status code. + * @param pModElf The ELF loader module instance data. + * @param BaseAddr The base address which the module is being fixedup to. + * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals). + * @param pvUser User argument to pass to the callback. + * @param SecAddr The section address. This is the address the relocations are relative to. + * @param cbSec The section size. The relocations must be inside this. + * @param pu8SecBaseR Where we read section bits from. + * @param pu8SecBaseW Where we write section bits to. + * @param pvRelocs Pointer to where we read the relocations from. + * @param cbRelocs Size of the relocations. + */ +static int RTLDRELF_NAME(RelocateSectionRel)(PRTLDRMODELF pModElf, Elf_Addr BaseAddr, PFNRTLDRIMPORT pfnGetImport, void *pvUser, + const Elf_Addr SecAddr, Elf_Size cbSec, const uint8_t *pu8SecBaseR, + uint8_t *pu8SecBaseW, const void *pvRelocs, Elf_Size cbRelocs) +{ +#if ELF_MODE != 32 + NOREF(pu8SecBaseR); +#endif + + /* + * Iterate the relocations. + * The relocations are stored in an array of Elf32_Rel records and covers the entire relocation section. + */ + const Elf_Reloc *paRels = (const Elf_Reloc *)pvRelocs; + const unsigned iRelMax = (unsigned)(cbRelocs / sizeof(paRels[0])); + AssertMsgReturn(iRelMax == cbRelocs / sizeof(paRels[0]), (FMT_ELF_SIZE "\n", cbRelocs / sizeof(paRels[0])), VERR_IMAGE_TOO_BIG); + for (unsigned iRel = 0; iRel < iRelMax; iRel++) + { + /* + * Skip R_XXX_NONE entries early to avoid confusion in the symbol + * getter code. + */ +#if ELF_MODE == 32 + if (ELF_R_TYPE(paRels[iRel].r_info) == R_386_NONE) + continue; +#elif ELF_MODE == 64 + if (ELF_R_TYPE(paRels[iRel].r_info) == R_X86_64_NONE) + continue; +#endif + + + /* + * Get the symbol. + */ + const Elf_Sym *pSym = NULL; /* shut up gcc */ + Elf_Addr SymValue = 0; /* shut up gcc-4 */ + int rc = RTLDRELF_NAME(Symbol)(pModElf, BaseAddr, pfnGetImport, pvUser, ELF_R_SYM(paRels[iRel].r_info), &pSym, &SymValue); + if (RT_FAILURE(rc)) + return rc; + + Log3(("rtldrELF: " FMT_ELF_ADDR " %02x %06x - " FMT_ELF_ADDR " %3d %02x %s\n", + paRels[iRel].r_offset, ELF_R_TYPE(paRels[iRel].r_info), (unsigned)ELF_R_SYM(paRels[iRel].r_info), + SymValue, (unsigned)pSym->st_shndx, pSym->st_info, ELF_STR(pModElf, pSym->st_name))); + + /* + * Apply the fixup. + */ + AssertMsgReturn(paRels[iRel].r_offset < cbSec, (FMT_ELF_ADDR " " FMT_ELF_SIZE "\n", paRels[iRel].r_offset, cbSec), VERR_LDRELF_INVALID_RELOCATION_OFFSET); +#if ELF_MODE == 32 + const Elf_Addr *pAddrR = (const Elf_Addr *)(pu8SecBaseR + paRels[iRel].r_offset); /* Where to read the addend. */ +#endif + Elf_Addr *pAddrW = (Elf_Addr *)(pu8SecBaseW + paRels[iRel].r_offset); /* Where to write the fixup. */ + switch (ELF_R_TYPE(paRels[iRel].r_info)) + { +#if ELF_MODE == 32 + /* + * Absolute addressing. + */ + case R_386_32: + { + const Elf_Addr Value = SymValue + *pAddrR; + *(uint32_t *)pAddrW = Value; + Log4((FMT_ELF_ADDR": R_386_32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue)); + break; + } + + /* + * PC relative addressing. + */ + case R_386_PC32: + { + const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */ + const Elf_Addr Value = SymValue + *(uint32_t *)pAddrR - SourceAddr; + *(uint32_t *)pAddrW = Value; + Log4((FMT_ELF_ADDR": R_386_PC32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SourceAddr, Value, SymValue)); + break; + } + + /* ignore */ + case R_386_NONE: + break; + +#elif ELF_MODE == 64 + + /* + * Absolute addressing + */ + case R_X86_64_64: + { + const Elf_Addr Value = SymValue + paRels[iRel].r_addend; + *(uint64_t *)pAddrW = Value; + Log4((FMT_ELF_ADDR": R_X86_64_64 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue)); + break; + } + + /* + * Truncated 32-bit value (zero-extendedable to the 64-bit value). + */ + case R_X86_64_32: + { + const Elf_Addr Value = SymValue + paRels[iRel].r_addend; + *(uint32_t *)pAddrW = (uint32_t)Value; + Log4((FMT_ELF_ADDR": R_X86_64_32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue)); + AssertMsgReturn((Elf_Addr)*(uint32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); + break; + } + + /* + * Truncated 32-bit value (sign-extendedable to the 64-bit value). + */ + case R_X86_64_32S: + { + const Elf_Addr Value = SymValue + paRels[iRel].r_addend; + *(int32_t *)pAddrW = (int32_t)Value; + Log4((FMT_ELF_ADDR": R_X86_64_32S Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SecAddr + paRels[iRel].r_offset + BaseAddr, Value, SymValue)); + AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */ + break; + } + + /* + * PC relative addressing. + */ + case R_X86_64_PC32: + case R_X86_64_PLT32: /* binutils commit 451875b4f976a527395e9303224c7881b65e12ed feature/regression. */ + { + const Elf_Addr SourceAddr = SecAddr + paRels[iRel].r_offset + BaseAddr; /* Where the source really is. */ + const Elf_Addr Value = SymValue + paRels[iRel].r_addend - SourceAddr; + *(int32_t *)pAddrW = (int32_t)Value; + Log4((FMT_ELF_ADDR": R_X86_64_PC32 Value=" FMT_ELF_ADDR " SymValue=" FMT_ELF_ADDR "\n", + SourceAddr, Value, SymValue)); + AssertMsgReturn((Elf_Addr)*(int32_t *)pAddrW == Value, ("Value=" FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); /** @todo check the sign-extending here. */ + break; + } + + /* ignore */ + case R_X86_64_NONE: + break; +#endif + + default: + AssertMsgFailed(("Unknown relocation type: %d (iRel=%d iRelMax=%d)\n", + ELF_R_TYPE(paRels[iRel].r_info), iRel, iRelMax)); + return VERR_LDRELF_RELOCATION_NOT_SUPPORTED; + } + } + + return VINF_SUCCESS; +} + + + +/** @copydoc RTLDROPS::pfnClose */ +static DECLCALLBACK(int) RTLDRELF_NAME(Close)(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + + if (pModElf->paShdrs) + { + RTMemFree(pModElf->paShdrs); + pModElf->paShdrs = NULL; + } + + if (pModElf->paPhdrs) + { + RTMemFree(pModElf->paPhdrs); + pModElf->paPhdrs = NULL; + } + + if (pModElf->paDynamic) + { + RTMemFree(pModElf->paDynamic); + pModElf->paDynamic = NULL; + } + + if (pModElf->pvBits) + { + pModElf->Core.pReader->pfnUnmap(pModElf->Core.pReader, pModElf->pvBits); + pModElf->pvBits = NULL; + } + + return VINF_SUCCESS; +} + + +/** @copydoc RTLDROPS::Done */ +static DECLCALLBACK(int) RTLDRELF_NAME(Done)(PRTLDRMODINTERNAL pMod) +{ + NOREF(pMod); /*PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod;*/ + /** @todo Have to think more about this .... */ + return -1; +} + + +/** @copydoc RTLDROPS::pfnEnumSymbols */ +static DECLCALLBACK(int) RTLDRELF_NAME(EnumSymbols)(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, + RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + NOREF(pvBits); + + /* + * Validate the input. + */ + Elf_Addr BaseAddr = (Elf_Addr)BaseAddress; + AssertMsgReturn((RTUINTPTR)BaseAddr == BaseAddress, ("%RTptr", BaseAddress), VERR_IMAGE_BASE_TOO_HIGH); + + /* + * Make sure we've got the string and symbol tables. (We don't need the pvBits.) + */ + int rc = RTLDRELF_NAME(MapBits)(pModElf, false); + if (RT_FAILURE(rc)) + return rc; + + /* + * Enumerate the symbol table. + */ + const Elf_Sym *paSyms = pModElf->Rel.paSyms; + unsigned cSyms = pModElf->Rel.cSyms; + const char *pszzStr = pModElf->Rel.pStr; + unsigned cbStr = pModElf->Rel.cbStr; + if ( ( !(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL) + && pModElf->Dyn.cSyms > 0) + || cSyms == 0) + { + paSyms = pModElf->Dyn.paSyms; + cSyms = pModElf->Dyn.cSyms; + pszzStr = pModElf->Dyn.pStr; + cbStr = pModElf->Dyn.cbStr; + } + + for (unsigned iSym = 1; iSym < cSyms; iSym++) + { + /* + * Skip imports (undefined). + */ + if (paSyms[iSym].st_shndx != SHN_UNDEF) + { + /* + * Calc value and get name. + */ + Elf_Addr Value; + if (paSyms[iSym].st_shndx == SHN_ABS) + /* absolute symbols are not subject to any relocation. */ + Value = paSyms[iSym].st_value; + else if (paSyms[iSym].st_shndx < pModElf->Ehdr.e_shnum) + { + if (pModElf->Ehdr.e_type == ET_REL) + /* relative to the section. */ + Value = BaseAddr + paSyms[iSym].st_value + pModElf->paShdrs[paSyms[iSym].st_shndx].sh_addr; + else /* Fixed up for link address. */ + Value = BaseAddr + paSyms[iSym].st_value - pModElf->LinkAddress; + } + else + { + AssertMsgFailed(("Arg! paSyms[%u].st_shndx=" FMT_ELF_HALF "\n", iSym, paSyms[iSym].st_shndx)); + return VERR_BAD_EXE_FORMAT; + } + + AssertMsgReturn(paSyms[iSym].st_name < cbStr, + ("String outside string table! iSym=%d paSyms[iSym].st_name=%#x\n", iSym, paSyms[iSym].st_name), + VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET); + const char * const pszName = pszzStr + paSyms[iSym].st_name; + + /* String termination was already checked when the string table was mapped. */ + if ( *pszName != '\0' + && ( (fFlags & RTLDR_ENUM_SYMBOL_FLAGS_ALL) + || ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL) ) + { + /* + * Call back. + */ + AssertMsgReturn(Value == (RTUINTPTR)Value, (FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); + rc = pfnCallback(pMod, pszName, iSym, (RTUINTPTR)Value, pvUser); + if (rc) + return rc; + } + } + } + + return VINF_SUCCESS; +} + + +/** @copydoc RTLDROPS::GetImageSize */ +static DECLCALLBACK(size_t) RTLDRELF_NAME(GetImageSize)(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + + return pModElf->cbImage; +} + + +/** @copydoc RTLDROPS::GetBits */ +static DECLCALLBACK(int) RTLDRELF_NAME(GetBits)(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + + /* + * This operation is currently only available on relocatable images. + */ + switch (pModElf->Ehdr.e_type) + { + case ET_REL: + case ET_DYN: + break; + case ET_EXEC: + Log(("RTLdrELF: %s: Executable images are not supported yet!\n", pModElf->Core.pReader->pfnLogName(pModElf->Core.pReader))); + return VERR_LDRELF_EXEC; + default: AssertFailedReturn(VERR_BAD_EXE_FORMAT); + } + + /* + * Load the bits into pvBits. + */ + const Elf_Shdr *paShdrs = pModElf->paShdrs; + for (unsigned iShdr = 0; iShdr < pModElf->Ehdr.e_shnum; iShdr++) + { + if (paShdrs[iShdr].sh_flags & SHF_ALLOC) + { + AssertMsgReturn((size_t)paShdrs[iShdr].sh_size == (size_t)paShdrs[iShdr].sh_size, (FMT_ELF_SIZE "\n", paShdrs[iShdr].sh_size), VERR_IMAGE_TOO_BIG); + switch (paShdrs[iShdr].sh_type) + { + case SHT_NOBITS: + memset((uint8_t *)pvBits + paShdrs[iShdr].sh_addr, 0, (size_t)paShdrs[iShdr].sh_size); + break; + + case SHT_PROGBITS: + default: + { + int rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, (uint8_t *)pvBits + paShdrs[iShdr].sh_addr, + (size_t)paShdrs[iShdr].sh_size, paShdrs[iShdr].sh_offset); + if (RT_FAILURE(rc)) + { + Log(("RTLdrELF: %s: Read error when reading " FMT_ELF_SIZE " bytes at " FMT_ELF_OFF ", iShdr=%d\n", + pModElf->Core.pReader->pfnLogName(pModElf->Core.pReader), + paShdrs[iShdr].sh_size, paShdrs[iShdr].sh_offset, iShdr)); + return rc; + } + } + } + } + } + + /* + * Relocate the image. + */ + return pModElf->Core.pOps->pfnRelocate(pMod, pvBits, BaseAddress, ~(RTUINTPTR)0, pfnGetImport, pvUser); +} + + +/** @copydoc RTLDROPS::Relocate */ +static DECLCALLBACK(int) RTLDRELF_NAME(Relocate)(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, + RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; +#ifdef LOG_ENABLED + const char *pszLogName = pModElf->Core.pReader->pfnLogName(pModElf->Core.pReader); +#endif + NOREF(OldBaseAddress); + + /* + * This operation is currently only available on relocatable images. + */ + switch (pModElf->Ehdr.e_type) + { + case ET_REL: + case ET_DYN: + break; + case ET_EXEC: + Log(("RTLdrELF: %s: Executable images are not supported yet!\n", pszLogName)); + return VERR_LDRELF_EXEC; + default: AssertFailedReturn(VERR_BAD_EXE_FORMAT); + } + + /* + * Validate the input. + */ + Elf_Addr BaseAddr = (Elf_Addr)NewBaseAddress; + AssertMsgReturn((RTUINTPTR)BaseAddr == NewBaseAddress, ("%RTptr", NewBaseAddress), VERR_IMAGE_BASE_TOO_HIGH); + + /* + * Map the image bits if not already done and setup pointer into it. + */ + int rc = RTLDRELF_NAME(MapBits)(pModElf, true); + if (RT_FAILURE(rc)) + return rc; + + /* + * Iterate the sections looking for interesting SHT_REL[A] sections. + * + * In ET_REL files the SHT_REL[A] sections have the section index of + * the section they contain fixups for in the sh_info member. + */ + const Elf_Shdr *paShdrs = pModElf->paShdrs; + Log2(("rtLdrElf: %s: Fixing up image\n", pszLogName)); + for (unsigned iShdr = 0; iShdr < pModElf->Ehdr.e_shnum; iShdr++) + { + const Elf_Shdr *pShdrRel = &paShdrs[iShdr]; + + /* + * Skip sections without interest to us. + */ +#if ELF_MODE == 32 + if (pShdrRel->sh_type != SHT_REL) +#else + if (pShdrRel->sh_type != SHT_RELA) +#endif + continue; + if (pModElf->Ehdr.e_type == ET_REL) + { + if (pShdrRel->sh_info >= pModElf->Ehdr.e_shnum) + continue; + const Elf_Shdr *pShdr = &paShdrs[pShdrRel->sh_info]; /* the section to fixup. */ + if (!(pShdr->sh_flags & SHF_ALLOC)) + continue; + + /* + * Relocate the section. + */ + Log2(("rtldrELF: %s: Relocation records for #%d [%s] (sh_info=%d sh_link=%d) found in #%d [%s] (sh_info=%d sh_link=%d)\n", + pszLogName, (int)pShdrRel->sh_info, ELF_SH_STR(pModElf, pShdr->sh_name), (int)pShdr->sh_info, (int)pShdr->sh_link, + iShdr, ELF_SH_STR(pModElf, pShdrRel->sh_name), (int)pShdrRel->sh_info, (int)pShdrRel->sh_link)); + + rc = RTLDRELF_NAME(RelocateSectionRel)(pModElf, BaseAddr, pfnGetImport, pvUser, + pShdr->sh_addr, + pShdr->sh_size, + (const uint8_t *)pModElf->pvBits + pShdr->sh_offset, + (uint8_t *)pvBits + pShdr->sh_addr, + (const uint8_t *)pModElf->pvBits + pShdrRel->sh_offset, + pShdrRel->sh_size); + } + else + rc = RTLDRELF_NAME(RelocateSectionExecDyn)(pModElf, BaseAddr, pfnGetImport, pvUser, + 0, (Elf_Size)pModElf->cbImage, + (const uint8_t *)pModElf->pvBits /** @todo file offset ?? */, + (uint8_t *)pvBits, + (const uint8_t *)pModElf->pvBits + pShdrRel->sh_offset, + pShdrRel->sh_size); + + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** + * Worker for pfnGetSymbolEx. + */ +static int RTLDRELF_NAME(ReturnSymbol)(PRTLDRMODELF pThis, const Elf_Sym *pSym, Elf_Addr uBaseAddr, PRTUINTPTR pValue) +{ + Elf_Addr Value; + if (pSym->st_shndx == SHN_ABS) + /* absolute symbols are not subject to any relocation. */ + Value = pSym->st_value; + else if (pSym->st_shndx < pThis->Ehdr.e_shnum) + { + if (pThis->Ehdr.e_type == ET_REL) + /* relative to the section. */ + Value = uBaseAddr + pSym->st_value + pThis->paShdrs[pSym->st_shndx].sh_addr; + else /* Fixed up for link address. */ + Value = uBaseAddr + pSym->st_value - pThis->LinkAddress; + } + else + { + AssertMsgFailed(("Arg! pSym->st_shndx=%d\n", pSym->st_shndx)); + return VERR_BAD_EXE_FORMAT; + } + AssertMsgReturn(Value == (RTUINTPTR)Value, (FMT_ELF_ADDR "\n", Value), VERR_SYMBOL_VALUE_TOO_BIG); + *pValue = (RTUINTPTR)Value; + return VINF_SUCCESS; +} + + +/** @copydoc RTLDROPS::pfnGetSymbolEx */ +static DECLCALLBACK(int) RTLDRELF_NAME(GetSymbolEx)(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress, + uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + NOREF(pvBits); + + /* + * Validate the input. + */ + Elf_Addr uBaseAddr = (Elf_Addr)BaseAddress; + AssertMsgReturn((RTUINTPTR)uBaseAddr == BaseAddress, ("%RTptr", BaseAddress), VERR_IMAGE_BASE_TOO_HIGH); + + /* + * Map the image bits if not already done and setup pointer into it. + */ + int rc = RTLDRELF_NAME(MapBits)(pModElf, true); + if (RT_FAILURE(rc)) + return rc; + + /* + * Calc all kinds of pointers before we start iterating the symbol table. + */ + const Elf_Sym *paSyms = pModElf->Rel.paSyms; + unsigned cSyms = pModElf->Rel.cSyms; + const char *pszzStr = pModElf->Rel.pStr; + unsigned cbStr = pModElf->Rel.cbStr; + if (pModElf->Dyn.cSyms > 0) + { + paSyms = pModElf->Dyn.paSyms; + cSyms = pModElf->Dyn.cSyms; + pszzStr = pModElf->Dyn.pStr; + cbStr = pModElf->Dyn.cbStr; + } + + if (iOrdinal == UINT32_MAX) + { + for (unsigned iSym = 1; iSym < cSyms; iSym++) + { + /* Undefined symbols are not exports, they are imports. */ + if ( paSyms[iSym].st_shndx != SHN_UNDEF + && ( ELF_ST_BIND(paSyms[iSym].st_info) == STB_GLOBAL + || ELF_ST_BIND(paSyms[iSym].st_info) == STB_WEAK)) + { + /* Validate the name string and try match with it. */ + AssertMsgReturn(paSyms[iSym].st_name < cbStr, + ("String outside string table! iSym=%d paSyms[iSym].st_name=%#x\n", iSym, paSyms[iSym].st_name), + VERR_LDRELF_INVALID_SYMBOL_NAME_OFFSET); + if (!strcmp(pszSymbol, pszzStr + paSyms[iSym].st_name)) + { + /* matched! */ + return RTLDRELF_NAME(ReturnSymbol)(pModElf, &paSyms[iSym], uBaseAddr, pValue); + } + } + } + } + else if (iOrdinal < cSyms) + { + if ( paSyms[iOrdinal].st_shndx != SHN_UNDEF + && ( ELF_ST_BIND(paSyms[iOrdinal].st_info) == STB_GLOBAL + || ELF_ST_BIND(paSyms[iOrdinal].st_info) == STB_WEAK)) + return RTLDRELF_NAME(ReturnSymbol)(pModElf, &paSyms[iOrdinal], uBaseAddr, pValue); + } + + return VERR_SYMBOL_NOT_FOUND; +} + + +/** @copydoc RTLDROPS::pfnEnumDbgInfo */ +static DECLCALLBACK(int) RTLDRELF_NAME(EnumDbgInfo)(PRTLDRMODINTERNAL pMod, const void *pvBits, + PFNRTLDRENUMDBG pfnCallback, void *pvUser) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + RT_NOREF_PV(pvBits); + + /* + * Map the image bits if not already done and setup pointer into it. + */ + int rc = RTLDRELF_NAME(MapBits)(pModElf, true); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do the enumeration. + */ + const Elf_Shdr *paShdrs = pModElf->paOrgShdrs; + for (unsigned iShdr = 0; iShdr < pModElf->Ehdr.e_shnum; iShdr++) + { + /* Debug sections are expected to be PROGBITS and not allocated. */ + if (paShdrs[iShdr].sh_type != SHT_PROGBITS) + continue; + if (paShdrs[iShdr].sh_flags & SHF_ALLOC) + continue; + + RTLDRDBGINFO DbgInfo; + const char *pszSectName = ELF_SH_STR(pModElf, paShdrs[iShdr].sh_name); + if ( !strncmp(pszSectName, RT_STR_TUPLE(".debug_")) + || !strcmp(pszSectName, ".WATCOM_references") ) + { + RT_ZERO(DbgInfo.u); + DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; + DbgInfo.pszExtFile = NULL; + DbgInfo.offFile = paShdrs[iShdr].sh_offset; + DbgInfo.cb = paShdrs[iShdr].sh_size; + DbgInfo.u.Dwarf.pszSection = pszSectName; + } + else if (!strcmp(pszSectName, ".gnu_debuglink")) + { + if ((paShdrs[iShdr].sh_size & 3) || paShdrs[iShdr].sh_size < 8) + return VERR_BAD_EXE_FORMAT; + + RT_ZERO(DbgInfo.u); + DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF_DWO; + DbgInfo.pszExtFile = (const char *)((uintptr_t)pModElf->pvBits + (uintptr_t)paShdrs[iShdr].sh_offset); + if (!RTStrEnd(DbgInfo.pszExtFile, paShdrs[iShdr].sh_size)) + return VERR_BAD_EXE_FORMAT; + DbgInfo.u.Dwo.uCrc32 = *(uint32_t *)((uintptr_t)DbgInfo.pszExtFile + (uintptr_t)paShdrs[iShdr].sh_size + - sizeof(uint32_t)); + DbgInfo.offFile = -1; + DbgInfo.cb = 0; + } + else + continue; + + DbgInfo.LinkAddress = NIL_RTLDRADDR; + DbgInfo.iDbgInfo = iShdr - 1; + + rc = pfnCallback(pMod, &DbgInfo, pvUser); + if (rc != VINF_SUCCESS) + return rc; + + } + + return VINF_SUCCESS; +} + + +/** + * Locate the next allocated section by RVA (sh_addr). + * + * This is a helper for EnumSegments and SegOffsetToRva. + * + * @returns Pointer to the section header if found, NULL if none. + * @param pModElf The module instance. + * @param iShdrCur The current section header. + */ +static const Elf_Shdr *RTLDRELF_NAME(GetNextAllocatedSection)(PRTLDRMODELF pModElf, unsigned iShdrCur) +{ + unsigned const cShdrs = pModElf->Ehdr.e_shnum; + const Elf_Shdr * const paShdrs = pModElf->paShdrs; + if (pModElf->fShdrInOrder) + { + for (unsigned iShdr = iShdrCur + 1; iShdr < cShdrs; iShdr++) + if (paShdrs[iShdr].sh_flags & SHF_ALLOC) + return &paShdrs[iShdr]; + } + else + { + Elf_Addr const uEndCur = paShdrs[iShdrCur].sh_addr + paShdrs[iShdrCur].sh_size; + Elf_Addr offBest = ~(Elf_Addr)0; + unsigned iBest = cShdrs; + for (unsigned iShdr = pModElf->iFirstSect; iShdr < cShdrs; iShdr++) + if ((paShdrs[iShdr].sh_flags & SHF_ALLOC) && iShdr != iShdrCur) + { + Elf_Addr const offDelta = paShdrs[iShdr].sh_addr - uEndCur; + if ( offDelta < offBest + && paShdrs[iShdr].sh_addr >= uEndCur) + { + offBest = offDelta; + iBest = iShdr; + } + } + if (iBest < cShdrs) + return &paShdrs[iBest]; + } + return NULL; +} + + +/** @copydoc RTLDROPS::pfnEnumSegments. */ +static DECLCALLBACK(int) RTLDRELF_NAME(EnumSegments)(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + + /* + * Map the image bits if not already done and setup pointer into it. + */ + int rc = RTLDRELF_NAME(MapBits)(pModElf, true); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do the enumeration. + */ + char szName[32]; + Elf_Addr uPrevMappedRva = 0; + const Elf_Shdr *paShdrs = pModElf->paShdrs; + const Elf_Shdr *paOrgShdrs = pModElf->paOrgShdrs; + for (unsigned iShdr = pModElf->iFirstSect; iShdr < pModElf->Ehdr.e_shnum; iShdr++) + { + RTLDRSEG Seg; + if (iShdr != 0) + { + Seg.pszName = ELF_SH_STR(pModElf, paShdrs[iShdr].sh_name); + Seg.cchName = (uint32_t)strlen(Seg.pszName); + if (Seg.cchName == 0) + { + Seg.pszName = szName; + Seg.cchName = (uint32_t)RTStrPrintf(szName, sizeof(szName), "UnamedSect%02u", iShdr); + } + } + else + { + Seg.pszName = ".elf.headers"; + Seg.cchName = 12; + } + Seg.SelFlat = 0; + Seg.Sel16bit = 0; + Seg.fFlags = 0; + Seg.fProt = RTMEM_PROT_READ; + if (paShdrs[iShdr].sh_flags & SHF_WRITE) + Seg.fProt |= RTMEM_PROT_WRITE; + if (paShdrs[iShdr].sh_flags & SHF_EXECINSTR) + Seg.fProt |= RTMEM_PROT_EXEC; + Seg.cb = paShdrs[iShdr].sh_size; + Seg.Alignment = paShdrs[iShdr].sh_addralign; + if (paShdrs[iShdr].sh_flags & SHF_ALLOC) + { + Seg.LinkAddress = paOrgShdrs[iShdr].sh_addr; + Seg.RVA = paShdrs[iShdr].sh_addr; + const Elf_Shdr *pShdr2 = RTLDRELF_NAME(GetNextAllocatedSection)(pModElf, iShdr); + if (pShdr2) + Seg.cbMapped = pShdr2->sh_addr - paShdrs[iShdr].sh_addr; + else + Seg.cbMapped = pModElf->cbImage - paShdrs[iShdr].sh_addr; + uPrevMappedRva = Seg.RVA; + } + else + { + Seg.LinkAddress = NIL_RTLDRADDR; + Seg.RVA = NIL_RTLDRADDR; + Seg.cbMapped = NIL_RTLDRADDR; + } + if (paShdrs[iShdr].sh_type != SHT_NOBITS) + { + Seg.offFile = paShdrs[iShdr].sh_offset; + Seg.cbFile = paShdrs[iShdr].sh_size; + } + else + { + Seg.offFile = -1; + Seg.cbFile = 0; + } + + rc = pfnCallback(pMod, &Seg, pvUser); + if (rc != VINF_SUCCESS) + return rc; + } + + return VINF_SUCCESS; +} + + +/** @copydoc RTLDROPS::pfnLinkAddressToSegOffset. */ +static DECLCALLBACK(int) RTLDRELF_NAME(LinkAddressToSegOffset)(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, + uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + + const Elf_Shdr *pShdrEnd = NULL; + unsigned cLeft = pModElf->Ehdr.e_shnum - pModElf->iFirstSect; + const Elf_Shdr *pShdr = &pModElf->paOrgShdrs[pModElf->Ehdr.e_shnum]; + while (cLeft-- > 0) + { + pShdr--; + if (pShdr->sh_flags & SHF_ALLOC) + { + RTLDRADDR offSeg = LinkAddress - pShdr->sh_addr; + if (offSeg < pShdr->sh_size) + { + *poffSeg = offSeg; + *piSeg = cLeft; + return VINF_SUCCESS; + } + if (offSeg == pShdr->sh_size) + pShdrEnd = pShdr; + } + } + + if (pShdrEnd) + { + *poffSeg = pShdrEnd->sh_size; + *piSeg = pShdrEnd - pModElf->paOrgShdrs - pModElf->iFirstSect; + return VINF_SUCCESS; + } + + return VERR_LDR_INVALID_LINK_ADDRESS; +} + + +/** @copydoc RTLDROPS::pfnLinkAddressToRva. */ +static DECLCALLBACK(int) RTLDRELF_NAME(LinkAddressToRva)(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + uint32_t iSeg; + RTLDRADDR offSeg; + int rc = RTLDRELF_NAME(LinkAddressToSegOffset)(pMod, LinkAddress, &iSeg, &offSeg); + if (RT_SUCCESS(rc)) + *pRva = pModElf->paShdrs[iSeg + pModElf->iFirstSect].sh_addr + offSeg; + return rc; +} + + +/** @copydoc RTLDROPS::pfnSegOffsetToRva. */ +static DECLCALLBACK(int) RTLDRELF_NAME(SegOffsetToRva)(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg, + PRTLDRADDR pRva) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + if (iSeg >= pModElf->Ehdr.e_shnum - pModElf->iFirstSect) + return VERR_LDR_INVALID_SEG_OFFSET; + + iSeg += pModElf->iFirstSect; /* skip section 0 if not used */ + if (offSeg > pModElf->paShdrs[iSeg].sh_size) + { + const Elf_Shdr *pShdr2 = RTLDRELF_NAME(GetNextAllocatedSection)(pModElf, iSeg); + if ( !pShdr2 + || offSeg > (pShdr2->sh_addr - pModElf->paShdrs[iSeg].sh_addr)) + return VERR_LDR_INVALID_SEG_OFFSET; + } + + if (!(pModElf->paShdrs[iSeg].sh_flags & SHF_ALLOC)) + return VERR_LDR_INVALID_SEG_OFFSET; + + *pRva = pModElf->paShdrs[iSeg].sh_addr; + return VINF_SUCCESS; +} + + +/** @copydoc RTLDROPS::pfnRvaToSegOffset. */ +static DECLCALLBACK(int) RTLDRELF_NAME(RvaToSegOffset)(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva, + uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + PRTLDRMODELF pModElf = (PRTLDRMODELF)pMod; + Elf_Addr PrevAddr = 0; + unsigned cLeft = pModElf->Ehdr.e_shnum - pModElf->iFirstSect; + const Elf_Shdr *pShdr = &pModElf->paShdrs[pModElf->Ehdr.e_shnum]; + while (cLeft-- > 0) + { + pShdr--; + if (pShdr->sh_flags & SHF_ALLOC) + { + Elf_Addr cbSeg = PrevAddr ? PrevAddr - pShdr->sh_addr : pShdr->sh_size; + RTLDRADDR offSeg = Rva - pShdr->sh_addr; + if (offSeg <= cbSeg) + { + *poffSeg = offSeg; + *piSeg = cLeft; + return VINF_SUCCESS; + } + PrevAddr = pShdr->sh_addr; + } + } + + return VERR_LDR_INVALID_RVA; +} + + +/** @callback_method_impl{FNRTLDRIMPORT, Stub used by ReadDbgInfo.} */ +static DECLCALLBACK(int) RTLDRELF_NAME(GetImportStubCallback)(RTLDRMOD hLdrMod, const char *pszModule, const char *pszSymbol, + unsigned uSymbol, PRTLDRADDR pValue, void *pvUser) +{ + RT_NOREF_PV(hLdrMod); RT_NOREF_PV(pszModule); RT_NOREF_PV(pszSymbol); + RT_NOREF_PV(uSymbol); RT_NOREF_PV(pValue); RT_NOREF_PV(pvUser); + return VERR_SYMBOL_NOT_FOUND; +} + + +/** @copydoc RTLDROPS::pfnReadDbgInfo. */ +static DECLCALLBACK(int) RTLDRELF_NAME(ReadDbgInfo)(PRTLDRMODINTERNAL pMod, uint32_t iDbgInfo, RTFOFF off, + size_t cb, void *pvBuf) +{ + PRTLDRMODELF pThis = (PRTLDRMODELF)pMod; + LogFlow(("%s: iDbgInfo=%#x off=%RTfoff cb=%#zu\n", __FUNCTION__, iDbgInfo, off, cb)); + + /* + * Input validation. + */ + AssertReturn(iDbgInfo < pThis->Ehdr.e_shnum && iDbgInfo + 1 < pThis->Ehdr.e_shnum, VERR_INVALID_PARAMETER); + iDbgInfo++; + AssertReturn(!(pThis->paShdrs[iDbgInfo].sh_flags & SHF_ALLOC), VERR_INVALID_PARAMETER); + AssertReturn(pThis->paShdrs[iDbgInfo].sh_type == SHT_PROGBITS, VERR_INVALID_PARAMETER); + AssertReturn(pThis->paShdrs[iDbgInfo].sh_offset == (uint64_t)off, VERR_INVALID_PARAMETER); + AssertReturn(pThis->paShdrs[iDbgInfo].sh_size == cb, VERR_INVALID_PARAMETER); + uint64_t cbRawImage = pThis->Core.pReader->pfnSize(pThis->Core.pReader); + AssertReturn(off >= 0 && cb <= cbRawImage && (uint64_t)off + cb <= cbRawImage, VERR_INVALID_PARAMETER); + + /* + * Read it from the file and look for fixup sections. + */ + int rc; + if (pThis->pvBits) + memcpy(pvBuf, (const uint8_t *)pThis->pvBits + (size_t)off, cb); + else + { + rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvBuf, cb, off); + if (RT_FAILURE(rc)) + return rc; + } + + uint32_t iRelocs = iDbgInfo + 1; + if ( iRelocs >= pThis->Ehdr.e_shnum + || pThis->paShdrs[iRelocs].sh_info != iDbgInfo + || ( pThis->paShdrs[iRelocs].sh_type != SHT_REL + && pThis->paShdrs[iRelocs].sh_type != SHT_RELA) ) + { + iRelocs = 0; + while ( iRelocs < pThis->Ehdr.e_shnum + && ( pThis->paShdrs[iRelocs].sh_info != iDbgInfo + || ( pThis->paShdrs[iRelocs].sh_type != SHT_REL + && pThis->paShdrs[iRelocs].sh_type != SHT_RELA)) ) + iRelocs++; + } + if ( iRelocs < pThis->Ehdr.e_shnum + && pThis->paShdrs[iRelocs].sh_size > 0) + { + /* + * Load the relocations. + */ + uint8_t *pbRelocsBuf = NULL; + const uint8_t *pbRelocs; + if (pThis->pvBits) + pbRelocs = (const uint8_t *)pThis->pvBits + pThis->paShdrs[iRelocs].sh_offset; + else + { + pbRelocs = pbRelocsBuf = (uint8_t *)RTMemTmpAlloc(pThis->paShdrs[iRelocs].sh_size); + if (!pbRelocsBuf) + return VERR_NO_TMP_MEMORY; + rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbRelocsBuf, + pThis->paShdrs[iRelocs].sh_size, + pThis->paShdrs[iRelocs].sh_offset); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pbRelocsBuf); + return rc; + } + } + + /* + * Apply the relocations. + */ + if (pThis->Ehdr.e_type == ET_REL) + rc = RTLDRELF_NAME(RelocateSectionRel)(pThis, pThis->LinkAddress, + RTLDRELF_NAME(GetImportStubCallback), NULL /*pvUser*/, + pThis->paShdrs[iDbgInfo].sh_addr, + pThis->paShdrs[iDbgInfo].sh_size, + (const uint8_t *)pvBuf, + (uint8_t *)pvBuf, + pbRelocs, + pThis->paShdrs[iRelocs].sh_size); + else + rc = RTLDRELF_NAME(RelocateSectionExecDyn)(pThis, pThis->LinkAddress, + RTLDRELF_NAME(GetImportStubCallback), NULL /*pvUser*/, + pThis->paShdrs[iDbgInfo].sh_addr, + pThis->paShdrs[iDbgInfo].sh_size, + (const uint8_t *)pvBuf, + (uint8_t *)pvBuf, + pbRelocs, + pThis->paShdrs[iRelocs].sh_size); + + RTMemTmpFree(pbRelocsBuf); + } + else + rc = VINF_SUCCESS; + return rc; +} + + +/** + * Handles RTLDRPROP_BUILDID queries. + */ +static int RTLDRELF_NAME(QueryPropBuildId)(PRTLDRMODELF pThis, void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + /* + * Map the image bits if not already done and setup pointer into it. + */ + int rc = RTLDRELF_NAME(MapBits)(pThis, true); + if (RT_FAILURE(rc)) + return rc; + + /* + * Search for the build ID. + */ + const Elf_Shdr *paShdrs = pThis->paOrgShdrs; + for (unsigned iShdr = 0; iShdr < pThis->Ehdr.e_shnum; iShdr++) + { + const char *pszSectName = ELF_SH_STR(pThis, paShdrs[iShdr].sh_name); + + if (!strcmp(pszSectName, ".note.gnu.build-id")) + { + if ((paShdrs[iShdr].sh_size & 3) || paShdrs[iShdr].sh_size < sizeof(Elf_Nhdr)) + return VERR_BAD_EXE_FORMAT; + + Elf_Nhdr *pNHdr = (Elf_Nhdr *)((uintptr_t)pThis->pvBits + (uintptr_t)paShdrs[iShdr].sh_offset); + if ( pNHdr->n_namesz > paShdrs[iShdr].sh_size + || pNHdr->n_descsz > paShdrs[iShdr].sh_size + || (paShdrs[iShdr].sh_size - pNHdr->n_descsz) < pNHdr->n_namesz + || pNHdr->n_type != NT_GNU_BUILD_ID) + return VERR_BAD_EXE_FORMAT; + + const char *pszOwner = (const char *)(pNHdr + 1); + if ( !RTStrEnd(pszOwner, pNHdr->n_namesz) + || strcmp(pszOwner, "GNU")) + return VERR_BAD_EXE_FORMAT; + + if (cbBuf < pNHdr->n_descsz) + return VERR_BUFFER_OVERFLOW; + + memcpy(pvBuf, pszOwner + pNHdr->n_namesz, pNHdr->n_descsz); + *pcbRet = pNHdr->n_descsz; + return VINF_SUCCESS; + } + } + + return VERR_NOT_FOUND; +} + + +/** @interface_method_impl{RTLDROPS,pfnQueryProp} */ +static DECLCALLBACK(int) RTLDRELF_NAME(QueryProp)(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits, + void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + PRTLDRMODELF pThis = (PRTLDRMODELF)pMod; + RT_NOREF(pvBits); + switch (enmProp) + { + case RTLDRPROP_BUILDID: + return RTLDRELF_NAME(QueryPropBuildId)(pThis, pvBuf, cbBuf, pcbRet); + + case RTLDRPROP_IS_SIGNED: + *pcbRet = sizeof(bool); + return rtLdrELFLnxKModQueryPropIsSigned(pThis->Core.pReader, (bool *)pvBuf); + + case RTLDRPROP_PKCS7_SIGNED_DATA: + *pcbRet = sizeof(bool); + return rtLdrELFLnxKModQueryPropPkcs7SignedData(pThis->Core.pReader, pvBuf, cbBuf, pcbRet); + + default: + return VERR_NOT_FOUND; + } +} + + +/** + * @interface_method_impl{RTLDROPS,pfnUnwindFrame} + */ +static DECLCALLBACK(int) +RTLDRELF_NAME(UnwindFrame)(PRTLDRMODINTERNAL pMod, void const *pvBits, uint32_t iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState) +{ + PRTLDRMODELF pThis = (PRTLDRMODELF)pMod; + LogFlow(("%s: iSeg=%#x off=%RTptr\n", __FUNCTION__, iSeg, off)); + + /* + * Process the input address, making us both RVA and proper seg:offset out of it. + */ + int rc; + RTLDRADDR uRva = off; + if (iSeg == UINT32_MAX) + rc = RTLDRELF_NAME(RvaToSegOffset)(pMod, uRva, &iSeg, &off); + else + rc = RTLDRELF_NAME(SegOffsetToRva)(pMod, iSeg, off, &uRva); + AssertRCReturn(rc, rc); + + /* + * Map the image bits if not already done and setup pointer into it. + */ + RT_NOREF(pvBits); /** @todo Try use passed in pvBits? */ + rc = RTLDRELF_NAME(MapBits)(pThis, true); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do we need to search for .eh_frame and .eh_frame_hdr? + */ + if (pThis->iShEhFrame == 0) + { + pThis->iShEhFrame = ~0U; + pThis->iShEhFrameHdr = ~0U; + unsigned cLeft = 2; + for (unsigned iShdr = 1; iShdr < pThis->Ehdr.e_shnum; iShdr++) + { + const char *pszName = ELF_SH_STR(pThis, pThis->paShdrs[iShdr].sh_name); + if ( pszName[0] == '.' + && pszName[1] == 'e' + && pszName[2] == 'h' + && pszName[3] == '_' + && pszName[4] == 'f' + && pszName[5] == 'r' + && pszName[6] == 'a' + && pszName[7] == 'm' + && pszName[8] == 'e') + { + if (pszName[9] == '\0') + pThis->iShEhFrame = iShdr; + else if ( pszName[9] == '_' + && pszName[10] == 'h' + && pszName[11] == 'd' + && pszName[12] == 'r' + && pszName[13] == '\0') + pThis->iShEhFrameHdr = iShdr; + else + continue; + if (--cLeft == 0) + break; + } + } + } + + /* + * Any info present? + */ + unsigned iShdr = pThis->iShEhFrame; + if ( iShdr != ~0U + && pThis->paShdrs[iShdr].sh_size > 0) + { + if (pThis->paShdrs[iShdr].sh_flags & SHF_ALLOC) + return rtDwarfUnwind_EhData((uint8_t const *)pThis->pvBits + pThis->paShdrs[iShdr].sh_addr, + pThis->paShdrs[iShdr].sh_size, pThis->paShdrs[iShdr].sh_addr, + iSeg, off, uRva, pState, pThis->Core.enmArch); + } + return VERR_DBG_NO_UNWIND_INFO; +} + + +/** + * The ELF module operations. + */ +static RTLDROPS RTLDRELF_MID(s_rtldrElf,Ops) = +{ +#if ELF_MODE == 32 + "elf32", +#elif ELF_MODE == 64 + "elf64", +#endif + RTLDRELF_NAME(Close), + NULL, /* Get Symbol */ + RTLDRELF_NAME(Done), + RTLDRELF_NAME(EnumSymbols), + /* ext: */ + RTLDRELF_NAME(GetImageSize), + RTLDRELF_NAME(GetBits), + RTLDRELF_NAME(Relocate), + RTLDRELF_NAME(GetSymbolEx), + NULL /*pfnQueryForwarderInfo*/, + RTLDRELF_NAME(EnumDbgInfo), + RTLDRELF_NAME(EnumSegments), + RTLDRELF_NAME(LinkAddressToSegOffset), + RTLDRELF_NAME(LinkAddressToRva), + RTLDRELF_NAME(SegOffsetToRva), + RTLDRELF_NAME(RvaToSegOffset), + RTLDRELF_NAME(ReadDbgInfo), + RTLDRELF_NAME(QueryProp), + NULL /*pfnVerifySignature*/, + rtldrELFLnxKModHashImage, + RTLDRELF_NAME(UnwindFrame), + 42 +}; + + + +/** + * Validates the ELF header. + * + * @returns iprt status code. + * @param pEhdr Pointer to the ELF header. + * @param cbRawImage The size of the raw image. + * @param pszLogName The log name. + * @param penmArch Where to return the architecture. + * @param pErrInfo Where to return extended error info. Optional. + */ +static int RTLDRELF_NAME(ValidateElfHeader)(const Elf_Ehdr *pEhdr, uint64_t cbRawImage, const char *pszLogName, + PRTLDRARCH penmArch, PRTERRINFO pErrInfo) +{ + Log3(("RTLdrELF: e_ident: %.*Rhxs\n" + "RTLdrELF: e_type: " FMT_ELF_HALF "\n" + "RTLdrELF: e_version: " FMT_ELF_HALF "\n" + "RTLdrELF: e_entry: " FMT_ELF_ADDR "\n" + "RTLdrELF: e_phoff: " FMT_ELF_OFF "\n" + "RTLdrELF: e_shoff: " FMT_ELF_OFF "\n" + "RTLdrELF: e_flags: " FMT_ELF_WORD "\n" + "RTLdrELF: e_ehsize: " FMT_ELF_HALF "\n" + "RTLdrELF: e_phentsize: " FMT_ELF_HALF "\n" + "RTLdrELF: e_phnum: " FMT_ELF_HALF "\n" + "RTLdrELF: e_shentsize: " FMT_ELF_HALF "\n" + "RTLdrELF: e_shnum: " FMT_ELF_HALF "\n" + "RTLdrELF: e_shstrndx: " FMT_ELF_HALF "\n", + RT_ELEMENTS(pEhdr->e_ident), &pEhdr->e_ident[0], pEhdr->e_type, pEhdr->e_version, + pEhdr->e_entry, pEhdr->e_phoff, pEhdr->e_shoff,pEhdr->e_flags, pEhdr->e_ehsize, pEhdr->e_phentsize, + pEhdr->e_phnum, pEhdr->e_shentsize, pEhdr->e_shnum, pEhdr->e_shstrndx)); + + if ( pEhdr->e_ident[EI_MAG0] != ELFMAG0 + || pEhdr->e_ident[EI_MAG1] != ELFMAG1 + || pEhdr->e_ident[EI_MAG2] != ELFMAG2 + || pEhdr->e_ident[EI_MAG3] != ELFMAG3) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Invalid ELF magic (%.*Rhxs)", pszLogName, sizeof(pEhdr->e_ident), pEhdr->e_ident); + if (pEhdr->e_ident[EI_CLASS] != RTLDRELF_SUFF(ELFCLASS)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Invalid ELF class (%.*Rhxs)", pszLogName, sizeof(pEhdr->e_ident), pEhdr->e_ident); + if (pEhdr->e_ident[EI_DATA] != ELFDATA2LSB) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_ODD_ENDIAN, + "%s: ELF endian %x is unsupported", pszLogName, pEhdr->e_ident[EI_DATA]); + if (pEhdr->e_version != EV_CURRENT) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_VERSION, + "%s: ELF version %x is unsupported", pszLogName, pEhdr->e_version); + + if (sizeof(Elf_Ehdr) != pEhdr->e_ehsize) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Elf header e_ehsize is %d expected %d!", pszLogName, pEhdr->e_ehsize, sizeof(Elf_Ehdr)); + if ( sizeof(Elf_Phdr) != pEhdr->e_phentsize + && ( pEhdr->e_phnum != 0 + || pEhdr->e_type == ET_DYN + || pEhdr->e_type == ET_EXEC)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Elf header e_phentsize is %d expected %d!", + pszLogName, pEhdr->e_phentsize, sizeof(Elf_Phdr)); + if (sizeof(Elf_Shdr) != pEhdr->e_shentsize) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Elf header e_shentsize is %d expected %d!", + pszLogName, pEhdr->e_shentsize, sizeof(Elf_Shdr)); + + switch (pEhdr->e_type) + { + case ET_REL: + case ET_EXEC: + case ET_DYN: + break; + default: + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: image type %#x is not supported!", + pszLogName, pEhdr->e_type); + } + + switch (pEhdr->e_machine) + { +#if ELF_MODE == 32 + case EM_386: + case EM_486: + *penmArch = RTLDRARCH_X86_32; + break; +#elif ELF_MODE == 64 + case EM_X86_64: + *penmArch = RTLDRARCH_AMD64; + break; +#endif + default: + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_MACHINE, + "%s: machine type %u is not supported!", pszLogName, pEhdr->e_machine); + } + + if ( pEhdr->e_phoff < pEhdr->e_ehsize + && !(pEhdr->e_phoff && pEhdr->e_phnum) + && pEhdr->e_phnum) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: The program headers overlap with the ELF header! e_phoff=" FMT_ELF_OFF, + pszLogName, pEhdr->e_phoff); + if ( pEhdr->e_phoff + pEhdr->e_phnum * pEhdr->e_phentsize > cbRawImage + || pEhdr->e_phoff + pEhdr->e_phnum * pEhdr->e_phentsize < pEhdr->e_phoff) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: The program headers extends beyond the file! e_phoff=" FMT_ELF_OFF " e_phnum=" FMT_ELF_HALF, + pszLogName, pEhdr->e_phoff, pEhdr->e_phnum); + + + if ( pEhdr->e_shoff < pEhdr->e_ehsize + && !(pEhdr->e_shoff && pEhdr->e_shnum)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: The section headers overlap with the ELF header! e_shoff=" FMT_ELF_OFF, + pszLogName, pEhdr->e_shoff); + if ( pEhdr->e_shoff + pEhdr->e_shnum * pEhdr->e_shentsize > cbRawImage + || pEhdr->e_shoff + pEhdr->e_shnum * pEhdr->e_shentsize < pEhdr->e_shoff) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: The section headers extends beyond the file! e_shoff=" FMT_ELF_OFF " e_shnum=" FMT_ELF_HALF, + pszLogName, pEhdr->e_shoff, pEhdr->e_shnum); + + if (pEhdr->e_shstrndx == 0 || pEhdr->e_shstrndx > pEhdr->e_shnum) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: The section headers string table is out of bounds! e_shstrndx=" FMT_ELF_HALF " e_shnum=" FMT_ELF_HALF, + pszLogName, pEhdr->e_shstrndx, pEhdr->e_shnum); + + return VINF_SUCCESS; +} + + +/** + * Gets the section header name. + * + * @returns pszName. + * @param pEhdr The elf header. + * @param offName The offset of the section header name. + * @param pszName Where to store the name. + * @param cbName The size of the buffer pointed to by pszName. + */ +const char *RTLDRELF_NAME(GetSHdrName)(PRTLDRMODELF pModElf, Elf_Word offName, char *pszName, size_t cbName) +{ + RTFOFF off = pModElf->paShdrs[pModElf->Ehdr.e_shstrndx].sh_offset + offName; + int rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, pszName, cbName - 1, off); + if (RT_FAILURE(rc)) + { + /* read by for byte. */ + for (unsigned i = 0; i < cbName; i++, off++) + { + rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, pszName + i, 1, off); + if (RT_FAILURE(rc)) + { + pszName[i] = '\0'; + break; + } + } + } + + pszName[cbName - 1] = '\0'; + return pszName; +} + + +/** + * Validates a section header. + * + * @returns iprt status code. + * @param pModElf Pointer to the module structure. + * @param iShdr The index of section header which should be validated. + * The section headers are found in the pModElf->paShdrs array. + * @param cbRawImage The size of the raw image. + * @param pszLogName The log name. + * @param pErrInfo Where to return extended error info. Optional. + */ +static int RTLDRELF_NAME(ValidateSectionHeader)(PRTLDRMODELF pModElf, unsigned iShdr, uint64_t cbRawImage, + const char *pszLogName, PRTERRINFO pErrInfo) +{ + const Elf_Shdr *pShdr = &pModElf->paShdrs[iShdr]; + char szSectionName[80]; NOREF(szSectionName); + Log3(("RTLdrELF: Section Header #%d:\n" + "RTLdrELF: sh_name: " FMT_ELF_WORD " - %s\n" + "RTLdrELF: sh_type: " FMT_ELF_WORD " (%s)\n" + "RTLdrELF: sh_flags: " FMT_ELF_XWORD "\n" + "RTLdrELF: sh_addr: " FMT_ELF_ADDR "\n" + "RTLdrELF: sh_offset: " FMT_ELF_OFF "\n" + "RTLdrELF: sh_size: " FMT_ELF_XWORD "\n" + "RTLdrELF: sh_link: " FMT_ELF_WORD "\n" + "RTLdrELF: sh_info: " FMT_ELF_WORD "\n" + "RTLdrELF: sh_addralign: " FMT_ELF_XWORD "\n" + "RTLdrELF: sh_entsize: " FMT_ELF_XWORD "\n", + iShdr, + pShdr->sh_name, RTLDRELF_NAME(GetSHdrName)(pModElf, pShdr->sh_name, szSectionName, sizeof(szSectionName)), + pShdr->sh_type, rtldrElfGetShdrType(pShdr->sh_type), pShdr->sh_flags, pShdr->sh_addr, + pShdr->sh_offset, pShdr->sh_size, pShdr->sh_link, pShdr->sh_info, pShdr->sh_addralign, + pShdr->sh_entsize)); + + if (iShdr == 0) + { + if ( pShdr->sh_name != 0 + || pShdr->sh_type != SHT_NULL + || pShdr->sh_flags != 0 + || pShdr->sh_addr != 0 + || pShdr->sh_size != 0 + || pShdr->sh_offset != 0 + || pShdr->sh_link != SHN_UNDEF + || pShdr->sh_addralign != 0 + || pShdr->sh_entsize != 0 ) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Bad #0 section: %.*Rhxs", pszLogName, sizeof(*pShdr), pShdr); + return VINF_SUCCESS; + } + + if (pShdr->sh_name >= pModElf->cbShStr) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Shdr #%d: sh_name (%d) is beyond the end of the section header string table (%d)!", + pszLogName, iShdr, pShdr->sh_name, pModElf->cbShStr); + + if (pShdr->sh_link >= pModElf->Ehdr.e_shnum) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Shdr #%d: sh_link (%d) is beyond the end of the section table (%d)!", + pszLogName, iShdr, pShdr->sh_link, pModElf->Ehdr.e_shnum); + + switch (pShdr->sh_type) + { + /** @todo find specs and check up which sh_info fields indicates section table entries */ + case 12301230: + if (pShdr->sh_info >= pModElf->Ehdr.e_shnum) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Shdr #%d: sh_info (%d) is beyond the end of the section table (%d)!", + pszLogName, iShdr, pShdr->sh_link, pModElf->Ehdr.e_shnum); + break; + + case SHT_NULL: + break; + case SHT_PROGBITS: + case SHT_SYMTAB: + case SHT_STRTAB: + case SHT_RELA: + case SHT_HASH: + case SHT_DYNAMIC: + case SHT_NOTE: + case SHT_NOBITS: + case SHT_REL: + case SHT_SHLIB: + case SHT_DYNSYM: + /* + * For these types sh_info doesn't have any special meaning, or anything which + * we need/can validate now. + */ + break; + + + default: + Log(("RTLdrELF: %s: Warning, unknown type %d!\n", pszLogName, pShdr->sh_type)); + break; + } + + if ( pShdr->sh_type != SHT_NOBITS + && pShdr->sh_size) + { + uint64_t offEnd = pShdr->sh_offset + pShdr->sh_size; + if ( offEnd > cbRawImage + || offEnd < (uint64_t)pShdr->sh_offset) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Shdr #%d: sh_offset (" FMT_ELF_OFF ") + sh_size (" FMT_ELF_XWORD " = %RX64) is beyond the end of the file (%RX64)!", + pszLogName, iShdr, pShdr->sh_offset, pShdr->sh_size, offEnd, cbRawImage); + if (pShdr->sh_offset < sizeof(Elf_Ehdr)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Shdr #%d: sh_offset (" FMT_ELF_OFF ") + sh_size (" FMT_ELF_XWORD ") is starting in the ELF header!", + pszLogName, iShdr, pShdr->sh_offset, pShdr->sh_size); + } + + return VINF_SUCCESS; +} + + +/** + * Process the section headers. + * + * @returns iprt status code. + * @param pModElf Pointer to the module structure. + * @param paShdrs The section headers. + * @param cbRawImage The size of the raw image. + * @param pszLogName The log name. + * @param pErrInfo Where to return extended error info. Optional. + */ +static int RTLDRELF_NAME(ValidateAndProcessSectionHeaders)(PRTLDRMODELF pModElf, Elf_Shdr *paShdrs, uint64_t cbRawImage, + const char *pszLogName, PRTERRINFO pErrInfo) +{ + Elf_Addr uNextAddr = 0; + for (unsigned i = 0; i < pModElf->Ehdr.e_shnum; i++) + { + int rc = RTLDRELF_NAME(ValidateSectionHeader)(pModElf, i, cbRawImage, pszLogName, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + + /* + * We're looking for symbol tables. + */ + if (paShdrs[i].sh_type == SHT_SYMTAB) + { + if (pModElf->Rel.iSymSh != ~0U) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_MULTIPLE_SYMTABS, + "%s: Multiple symbol tabs! iSymSh=%d i=%d", pszLogName, pModElf->Rel.iSymSh, i); + pModElf->Rel.iSymSh = i; + pModElf->Rel.cSyms = (unsigned)(paShdrs[i].sh_size / sizeof(Elf_Sym)); + AssertBreakStmt(pModElf->Rel.cSyms == paShdrs[i].sh_size / sizeof(Elf_Sym), rc = VERR_IMAGE_TOO_BIG); + pModElf->Rel.iStrSh = paShdrs[i].sh_link; + pModElf->Rel.cbStr = (unsigned)paShdrs[pModElf->Rel.iStrSh].sh_size; + AssertBreakStmt(pModElf->Rel.cbStr == paShdrs[pModElf->Rel.iStrSh].sh_size, rc = VERR_IMAGE_TOO_BIG); + } + else if (paShdrs[i].sh_type == SHT_DYNSYM) + { + if (pModElf->Dyn.iSymSh != ~0U) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRELF_MULTIPLE_SYMTABS, + "%s: Multiple dynamic symbol tabs! iSymSh=%d i=%d", pszLogName, pModElf->Dyn.iSymSh, i); + if (pModElf->Ehdr.e_type != ET_DYN && pModElf->Ehdr.e_type != ET_EXEC) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Unexpected SHT_DYNSYM (i=%d) for e_type=%d", pszLogName, i, pModElf->Ehdr.e_type); + pModElf->Dyn.iSymSh = i; + pModElf->Dyn.cSyms = (unsigned)(paShdrs[i].sh_size / sizeof(Elf_Sym)); + AssertBreakStmt(pModElf->Dyn.cSyms == paShdrs[i].sh_size / sizeof(Elf_Sym), rc = VERR_IMAGE_TOO_BIG); + pModElf->Dyn.iStrSh = paShdrs[i].sh_link; + pModElf->Dyn.cbStr = (unsigned)paShdrs[pModElf->Dyn.iStrSh].sh_size; + AssertBreakStmt(pModElf->Dyn.cbStr == paShdrs[pModElf->Dyn.iStrSh].sh_size, rc = VERR_IMAGE_TOO_BIG); + } + /* + * We're also look for the dynamic section. + */ + else if (paShdrs[i].sh_type == SHT_DYNAMIC) + { + if (pModElf->iShDynamic != ~0U) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Multiple dynamic sections! iShDynamic=%d i=%d", + pszLogName, pModElf->iShDynamic, i); + if (pModElf->Ehdr.e_type != ET_DYN && pModElf->Ehdr.e_type != ET_EXEC) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Unexpected SHT_DYNAMIC (i=%d) for e_type=%d", pszLogName, i, pModElf->Ehdr.e_type); + if (paShdrs[i].sh_entsize != sizeof(Elf_Dyn)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: SHT_DYNAMIC (i=%d) sh_entsize=" FMT_ELF_XWORD ", expected %#zx", + pszLogName, i, paShdrs[i].sh_entsize, sizeof(Elf_Dyn)); + pModElf->iShDynamic = i; + Elf_Xword const cDynamic = paShdrs[i].sh_size / sizeof(Elf_Dyn); + if (cDynamic > _64K || cDynamic < 2) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: SHT_DYNAMIC (i=%d) sh_size=" FMT_ELF_XWORD " is out of range (2..64K)", + pszLogName, i, paShdrs[i].sh_size); + pModElf->cDynamic = (unsigned)cDynamic; + } + + /* + * Special checks for the section string table. + */ + if (i == pModElf->Ehdr.e_shstrndx) + { + if (paShdrs[i].sh_type != SHT_STRTAB) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Section header string table is not a SHT_STRTAB: %#x", + pszLogName, paShdrs[i].sh_type); + if (paShdrs[i].sh_size == 0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Section header string table is empty", pszLogName); + } + + /* + * Kluge for the .data..percpu segment in 64-bit linux kernels. + */ + if (paShdrs[i].sh_flags & SHF_ALLOC) + { + if ( paShdrs[i].sh_addr == 0 + && paShdrs[i].sh_addr < uNextAddr) + { + Elf_Addr uAddr = RT_ALIGN_T(uNextAddr, paShdrs[i].sh_addralign, Elf_Addr); + Log(("RTLdrElf: Out of order section #%d; adjusting sh_addr from " FMT_ELF_ADDR " to " FMT_ELF_ADDR "\n", + i, paShdrs[i].sh_addr, uAddr)); + paShdrs[i].sh_addr = uAddr; + } + uNextAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size; + } + } /* for each section header */ + + return VINF_SUCCESS; +} + + +/** + * Process the section headers. + * + * @returns iprt status code. + * @param pModElf Pointer to the module structure. + * @param paShdrs The section headers. + * @param cbRawImage The size of the raw image. + * @param pszLogName The log name. + * @param pErrInfo Where to return extended error info. Optional. + */ +static int RTLDRELF_NAME(ValidateAndProcessDynamicInfo)(PRTLDRMODELF pModElf, uint64_t cbRawImage, uint32_t fFlags, + const char *pszLogName, PRTERRINFO pErrInfo) +{ + /* + * Check preconditions. + */ + AssertReturn(pModElf->Ehdr.e_type == ET_DYN || pModElf->Ehdr.e_type == ET_EXEC, VERR_INTERNAL_ERROR_2); + if (pModElf->Ehdr.e_phnum <= 1 || pModElf->Ehdr.e_phnum >= _32K) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: e_phnum=%u is out of bounds (2..32K)", pszLogName, pModElf->Ehdr.e_phnum); + if (pModElf->iShDynamic == ~0U) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: no .dynamic section", pszLogName); + AssertReturn(pModElf->cDynamic > 1 && pModElf->cDynamic <= _64K, VERR_INTERNAL_ERROR_3); + + /* ASSUME that the sections are ordered by address. That simplifies + validation code further down. */ + AssertReturn(pModElf->Ehdr.e_shnum >= 2, VERR_INTERNAL_ERROR_4); + Elf_Shdr const *paShdrs = pModElf->paShdrs; + Elf_Addr uPrevEnd = paShdrs[1].sh_addr + paShdrs[1].sh_size; + for (unsigned i = 2; i < pModElf->Ehdr.e_shnum; i++) + if (paShdrs[i].sh_flags & SHF_ALLOC) + { + if (uPrevEnd > paShdrs[i].sh_addr) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: section %u is out of order: uPrevEnd=" FMT_ELF_ADDR " sh_addr=" FMT_ELF_ADDR, + pszLogName, i, uPrevEnd, paShdrs[i].sh_addr); + uPrevEnd = paShdrs[i].sh_addr + paShdrs[i].sh_size; + } + + /* Must have string and symbol tables. */ + if (pModElf->Dyn.iStrSh == ~0U) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: No dynamic string table section", pszLogName); + if (pModElf->Dyn.iSymSh == ~0U) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: No dynamic symbol table section", pszLogName); + + /* + * Load the program headers. + */ + size_t const cbPhdrs = sizeof(pModElf->paPhdrs[0]) * pModElf->Ehdr.e_phnum; + Elf_Phdr *paPhdrs = (Elf_Phdr *)RTMemAllocZ(cbPhdrs); + pModElf->paPhdrs = paPhdrs; + AssertReturn(paPhdrs, VERR_NO_MEMORY); + + int rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, paPhdrs, cbPhdrs, pModElf->Ehdr.e_phoff); + if (RT_FAILURE(rc)) + return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: pfnRead(,,%#zx, " FMT_ELF_OFF ") -> %Rrc", + pszLogName, cbPhdrs, pModElf->Ehdr.e_phoff, rc); + + /* + * Validate them. + */ + unsigned cbPage = _4K; /** @todo generalize architecture specific stuff using its own code template header. */ + switch (pModElf->Core.enmArch) + { + case RTLDRARCH_AMD64: + case RTLDRARCH_X86_32: + break; + default: + AssertFailedBreak(/** @todo page size for got.plt hacks */); + } + unsigned iLoad = 0; + unsigned iLoadShdr = 1; /* ASSUMES ordered (checked above). */ + unsigned cDynamic = 0; + Elf_Addr cbImage = 0; + Elf_Addr uLinkAddress = ~(Elf_Addr)0; + for (unsigned i = 0; i < pModElf->Ehdr.e_phnum; i++) + { + const Elf_Phdr * const pPhdr = &paPhdrs[i]; + Log3(("RTLdrELF: Program Header #%d:\n" + "RTLdrELF: p_type: " FMT_ELF_WORD " (%s)\n" + "RTLdrELF: p_flags: " FMT_ELF_WORD "\n" + "RTLdrELF: p_offset: " FMT_ELF_OFF "\n" + "RTLdrELF: p_vaddr: " FMT_ELF_ADDR "\n" + "RTLdrELF: p_paddr: " FMT_ELF_ADDR "\n" + "RTLdrELF: p_filesz: " FMT_ELF_XWORD "\n" + "RTLdrELF: p_memsz: " FMT_ELF_XWORD "\n" + "RTLdrELF: p_align: " FMT_ELF_XWORD "\n", + i, + pPhdr->p_type, rtldrElfGetPhdrType(pPhdr->p_type), pPhdr->p_flags, pPhdr->p_offset, + pPhdr->p_vaddr, pPhdr->p_paddr, pPhdr->p_filesz, pPhdr->p_memsz, pPhdr->p_align)); + + if (pPhdr->p_type == DT_NULL) + continue; + + if ( pPhdr->p_filesz != 0 + && ( pPhdr->p_offset >= cbRawImage + || pPhdr->p_filesz > cbRawImage + || pPhdr->p_offset + pPhdr->p_filesz > cbRawImage)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u: bogus p_offset=" FMT_ELF_OFF " & p_filesz=" FMT_ELF_XWORD " (file size %#RX64)", + pszLogName, i, pPhdr->p_offset, pPhdr->p_filesz, cbRawImage); + + if (pPhdr->p_flags & ~(Elf64_Word)(PF_X | PF_R | PF_W)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Prog Hdr #%u: bogus p_flags=" FMT_ELF_WORD, + pszLogName, i, pPhdr->p_flags); + + if (!RT_IS_POWER_OF_TWO(pPhdr->p_align)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Prog Hdr #%u: bogus p_align=" FMT_ELF_XWORD, + pszLogName, i, pPhdr->p_align); + + if ( pPhdr->p_align > 1 + && pPhdr->p_memsz > 0 + && pPhdr->p_filesz > 0 + && (pPhdr->p_offset & (pPhdr->p_align - 1)) != (pPhdr->p_vaddr & (pPhdr->p_align - 1))) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u: misaligned p_offset=" FMT_ELF_OFF " p_vaddr=" FMT_ELF_ADDR " p_align=" FMT_ELF_XWORD, + pszLogName, i, pPhdr->p_offset, pPhdr->p_vaddr, pPhdr->p_align); + + /* Do some type specfic checks: */ + switch (pPhdr->p_type) + { + case PT_LOAD: + { + if (pPhdr->p_memsz < pPhdr->p_filesz) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u/LOAD#%u: bogus p_memsz=" FMT_ELF_XWORD " or p_filesz=" FMT_ELF_XWORD, + pszLogName, i, iLoad, pPhdr->p_memsz, pPhdr->p_filesz); + cbImage = pPhdr->p_vaddr + pPhdr->p_memsz; + if (iLoad == 0) + uLinkAddress = pPhdr->p_vaddr; + + /* Find the corresponding sections, checking their addresses and + file offsets since the rest of the code is still section based + rather than using program headers as it should... */ + Elf_Off off = pPhdr->p_offset; + Elf_Addr uAddr = pPhdr->p_vaddr; + Elf_Xword cbMem = pPhdr->p_memsz; + Elf_Xword cbFile = pPhdr->p_filesz; + + /* HACK to allow loading isolinux-debug.elf where program headers aren't + sorted by virtual address. */ + if ( (fFlags & RTLDR_O_FOR_DEBUG) + && uAddr != paShdrs[iLoadShdr].sh_addr) + { + for (unsigned iShdr = 1; iShdr < pModElf->Ehdr.e_shnum; iShdr++) + if (uAddr == paShdrs[iShdr].sh_addr) + { + iLoadShdr = iShdr; + break; + } + } + + while (cbMem > 0) + { + if (iLoadShdr < pModElf->Ehdr.e_shnum) + { /* likely */ } + else if (iLoadShdr == pModElf->Ehdr.e_shnum) + { + /** @todo anything else to check here? */ + iLoadShdr++; + break; + } + else + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u/LOAD#%u: Out of sections at " FMT_ELF_ADDR " LB " FMT_ELF_XWORD, + pszLogName, i, iLoad, uAddr, cbMem); + if (!(paShdrs[iLoadShdr].sh_flags & SHF_ALLOC)) + { + if ( paShdrs[iLoadShdr].sh_type != SHT_NOBITS + && paShdrs[iLoadShdr].sh_size > 0 + && off < paShdrs[iLoadShdr].sh_offset + paShdrs[iLoadShdr].sh_size + && paShdrs[iLoadShdr].sh_offset < off + cbMem) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u/LOAD#%u: Overlaps with !SHF_ALLOC section at " FMT_ELF_OFF " LB " FMT_ELF_XWORD, + pszLogName, i, iLoad, paShdrs[iLoadShdr].sh_offset, paShdrs[iLoadShdr].sh_size); + pModElf->paShdrExtras[iLoadShdr].idxPhdr = UINT16_MAX; + iLoadShdr++; + continue; + } + + if (uAddr != paShdrs[iLoadShdr].sh_addr) + { + /* Before the first section we expect headers to be loaded, so + that the file is simply mapped from file offset zero. */ + if ( iLoadShdr == 1 + && iLoad == 0 + && paShdrs[1].sh_addr == paShdrs[1].sh_offset + && cbFile >= paShdrs[1].sh_offset + && cbMem >= paShdrs[1].sh_offset) + { + /* Modify paShdrs[0] to describe the gap. ".elf.headers" */ + pModElf->iFirstSect = 0; + pModElf->paShdrs[0].sh_name = 0; + pModElf->paShdrs[0].sh_type = SHT_PROGBITS; + pModElf->paShdrs[0].sh_flags = SHF_ALLOC + | (pPhdr->p_flags & PF_W ? SHF_WRITE : 0) + | (pPhdr->p_flags & PF_X ? SHF_EXECINSTR : 0); + pModElf->paShdrs[0].sh_addr = uAddr; + pModElf->paShdrs[0].sh_offset = off; + pModElf->paShdrs[0].sh_size = paShdrs[1].sh_offset; + pModElf->paShdrs[0].sh_link = 0; + pModElf->paShdrs[0].sh_info = 0; + pModElf->paShdrs[0].sh_addralign = pPhdr->p_align; + pModElf->paShdrs[0].sh_entsize = 0; + *(Elf_Shdr *)pModElf->paOrgShdrs = pModElf->paShdrs[0]; /* (necessary for segment enumeration) */ + + uAddr += paShdrs[1].sh_offset; + cbMem -= paShdrs[1].sh_offset; + cbFile -= paShdrs[1].sh_offset; + off = paShdrs[1].sh_offset; + } + /* Alignment padding? Allow up to a page size. */ + else if ( paShdrs[iLoadShdr].sh_addr > uAddr + && paShdrs[iLoadShdr].sh_addr - uAddr + < RT_MAX(paShdrs[iLoadShdr].sh_addralign, cbPage /*got.plt hack*/)) + { + Elf_Xword cbAlignPadding = paShdrs[iLoadShdr].sh_addr - uAddr; + if (cbAlignPadding >= cbMem) + break; + cbMem -= cbAlignPadding; + uAddr += cbAlignPadding; + if (cbFile > cbAlignPadding) + { + off += cbAlignPadding; + cbFile -= cbAlignPadding; + } + else + { + off += cbFile; + cbFile = 0; + } + } + } + + if ( uAddr == paShdrs[iLoadShdr].sh_addr + && cbMem >= paShdrs[iLoadShdr].sh_size + && ( paShdrs[iLoadShdr].sh_type != SHT_NOBITS + ? off == paShdrs[iLoadShdr].sh_offset + && cbFile >= paShdrs[iLoadShdr].sh_size /* this might be too strict... */ + : cbFile == 0 + || cbMem > paShdrs[iLoadShdr].sh_size /* isolinux.elf: linker merge no-bits and progbits sections */) ) + { + if ( paShdrs[iLoadShdr].sh_type != SHT_NOBITS + || cbFile != 0) + { + off += paShdrs[iLoadShdr].sh_size; + cbFile -= paShdrs[iLoadShdr].sh_size; + } + uAddr += paShdrs[iLoadShdr].sh_size; + cbMem -= paShdrs[iLoadShdr].sh_size; + } + else + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u/LOAD#%u: Mismatch at " FMT_ELF_ADDR " LB " FMT_ELF_XWORD " (file " FMT_ELF_OFF " LB " FMT_ELF_XWORD ") with section #%u " FMT_ELF_ADDR " LB " FMT_ELF_XWORD " (file " FMT_ELF_OFF " sh_type=" FMT_ELF_WORD ")", + pszLogName, i, iLoad, uAddr, cbMem, off, cbFile, + iLoadShdr, paShdrs[iLoadShdr].sh_addr, paShdrs[iLoadShdr].sh_size, + paShdrs[iLoadShdr].sh_offset, paShdrs[iLoadShdr].sh_type); + + pModElf->paShdrExtras[iLoadShdr].idxPhdr = iLoad; + iLoadShdr++; + } /* section loop */ + + iLoad++; + break; + } + + case PT_DYNAMIC: + { + const Elf_Shdr *pShdr = &pModElf->paShdrs[pModElf->iShDynamic]; + if (pPhdr->p_offset != pShdr->sh_offset) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u/DYNAMIC: p_offset=" FMT_ELF_OFF " expected " FMT_ELF_OFF, + pszLogName, i, pPhdr->p_offset, pShdr->sh_offset); + if (RT_MAX(pPhdr->p_memsz, pPhdr->p_filesz) != pShdr->sh_size) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: Prog Hdr #%u/DYNAMIC: expected " FMT_ELF_XWORD " for RT_MAX(p_memsz=" FMT_ELF_XWORD ", p_filesz=" FMT_ELF_XWORD ")", + pszLogName, i, pShdr->sh_size, pPhdr->p_memsz, pPhdr->p_filesz); + cDynamic++; + break; + } + } + } + + if (iLoad == 0) + return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: No PT_LOAD program headers", pszLogName); + if (cDynamic != 1) + return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: No program header for the DYNAMIC section", pszLogName); + + cbImage -= uLinkAddress; + pModElf->cbImage = (uint64_t)cbImage; + pModElf->LinkAddress = uLinkAddress; + AssertReturn(pModElf->cbImage == cbImage, VERR_INTERNAL_ERROR_5); + Log3(("RTLdrELF: LinkAddress=" FMT_ELF_ADDR " cbImage=" FMT_ELF_ADDR " (from PT_LOAD)\n", uLinkAddress, cbImage)); + + for (; iLoadShdr < pModElf->Ehdr.e_shnum; iLoadShdr++) + if ( !(paShdrs[iLoadShdr].sh_flags & SHF_ALLOC) + || paShdrs[iLoadShdr].sh_size == 0) + pModElf->paShdrExtras[iLoadShdr].idxPhdr = UINT16_MAX; + else + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: No PT_LOAD for section #%u " FMT_ELF_ADDR " LB " FMT_ELF_XWORD " (file " FMT_ELF_OFF " sh_type=" FMT_ELF_WORD ")", + pszLogName, iLoadShdr, paShdrs[iLoadShdr].sh_addr, paShdrs[iLoadShdr].sh_size, + paShdrs[iLoadShdr].sh_offset, paShdrs[iLoadShdr].sh_type); + + /* + * Load and validate the dynamic table. We have got / will get most of the + * info we need from the section table, so we must make sure this matches up. + */ + Log3(("RTLdrELF: Dynamic section - %u entries\n", pModElf->cDynamic)); + size_t const cbDynamic = pModElf->cDynamic * sizeof(pModElf->paDynamic[0]); + Elf_Dyn * const paDynamic = (Elf_Dyn *)RTMemAlloc(cbDynamic); + AssertReturn(paDynamic, VERR_NO_MEMORY); + pModElf->paDynamic = paDynamic; + + rc = pModElf->Core.pReader->pfnRead(pModElf->Core.pReader, paDynamic, cbDynamic, paShdrs[pModElf->iShDynamic].sh_offset); + if (RT_FAILURE(rc)) + return RTERRINFO_LOG_SET_F(pErrInfo, rc, "%s: pfnRead(,,%#zx, " FMT_ELF_OFF ") -> %Rrc", + pszLogName, cbDynamic, paShdrs[pModElf->iShDynamic].sh_offset, rc); + + for (uint32_t i = 0; i < pModElf->cDynamic; i++) + { +#define LOG_VALIDATE_PTR_RET(szName) do { \ + Log3(("RTLdrELF: DT[%u]: %16s " FMT_ELF_ADDR "\n", i, szName, paDynamic[i].d_un.d_ptr)); \ + if ((uint64_t)paDynamic[i].d_un.d_ptr - uLinkAddress < cbImage) { /* likely */ } \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": Invalid address " FMT_ELF_ADDR " (valid range: " FMT_ELF_ADDR " LB " FMT_ELF_ADDR ")", \ + pszLogName, i, paDynamic[i].d_un.d_ptr, uLinkAddress, cbImage); \ + } while (0) +#define LOG_VALIDATE_PTR_VAL_RET(szName, uExpected) do { \ + Log3(("RTLdrELF: DT[%u]: %16s " FMT_ELF_ADDR "\n", i, szName, (uint64_t)paDynamic[i].d_un.d_ptr)); \ + if (paDynamic[i].d_un.d_ptr == (Elf_Addr)(uExpected)) { /* likely */ } \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": " FMT_ELF_ADDR ", expected " FMT_ELF_ADDR, \ + pszLogName, i, paDynamic[i].d_un.d_ptr, (Elf_Addr)(uExpected)); \ + } while (0) +#define LOG_VALIDATE_STR_RET(szName) do { \ + Log3(("RTLdrELF: DT[%u]: %16s %#RX64\n", i, szName, (uint64_t)paDynamic[i].d_un.d_val)); \ + if ((uint64_t)paDynamic[i].d_un.d_val < pModElf->Dyn.cbStr) { /* likely */ } \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": Invalid string table offset %#RX64 (max %#x)", \ + pszLogName, i, (uint64_t)paDynamic[i].d_un.d_val, pModElf->Dyn.cbStr); \ + } while (0) +#define LOG_VALIDATE_VAL_RET(szName, uExpected) do { \ + Log3(("RTLdrELF: DT[%u]: %16s %#RX64\n", i, szName, (uint64_t)paDynamic[i].d_un.d_val)); \ + if ((uint64_t)paDynamic[i].d_un.d_val == (uint64_t)(uExpected)) { /* likely */ } \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" szName ": %#RX64, expected %#RX64", \ + pszLogName, i, (uint64_t)paDynamic[i].d_un.d_val, (uint64_t)(uExpected)); \ + } while (0) +#define SET_RELOC_TYPE_RET(a_szName, a_uType) do { \ + if (pModElf->DynInfo.uRelocType == 0 || pModElf->DynInfo.uRelocType == (a_uType)) \ + pModElf->DynInfo.uRelocType = (a_uType); \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Mixing DT_RELA and DT_REL", pszLogName, i); \ + } while (0) +#define SET_INFO_FIELD_RET(a_szName, a_Field, a_Value, a_UnsetValue, a_szFmt) do { \ + if ((a_Field) == (a_UnsetValue) && (a_Value) != (a_UnsetValue)) \ + (a_Field) = (a_Value); /* likely */ \ + else if ((a_Field) != (a_UnsetValue)) \ + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Multiple entries (first value " a_szFmt ", second " a_szFmt ")", pszLogName, i, (a_Field), (a_Value)); \ + else if ((a_Value) != (a_UnsetValue)) \ + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Unexpected value " a_szFmt, pszLogName, i, (a_Value)); \ + } while (0) +#define FIND_MATCHING_SECTION_RET(a_szName, a_ExtraMatchExpr, a_idxShFieldToSet) do { \ + unsigned iSh; \ + for (iSh = 1; iSh < pModElf->Ehdr.e_shnum; iSh++) \ + if ( paShdrs[iSh].sh_addr == paDynamic[i].d_un.d_ptr \ + && (a_ExtraMatchExpr)) \ + { \ + (a_idxShFieldToSet) = iSh; \ + if (pModElf->paShdrExtras[iSh].idxDt != UINT16_MAX) \ + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, \ + "%s: DT[%u]/" a_szName ": section #%u (" FMT_ELF_ADDR ") already referenced by DT[%u]", \ + pszLogName, i, iSh, paShdrs[iSh].sh_addr, pModElf->paShdrExtras[iSh].idxDt); \ + pModElf->paShdrExtras[iSh].idxDt = i; \ + pModElf->paShdrExtras[iSh].uDtTag = (uint32_t)paDynamic[i].d_tag; \ + break; \ + } \ + if (iSh < pModElf->Ehdr.e_shnum) { /* likely */ } \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": No matching section for " FMT_ELF_ADDR, pszLogName, i, paDynamic[i].d_un.d_ptr); \ + } while (0) +#define ONLY_FOR_DEBUG_OR_VALIDATION_RET(a_szName) do { \ + if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) { /* likely */ } \ + else return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/" a_szName ": Not supported (" FMT_ELF_ADDR ")", pszLogName, i, paDynamic[i].d_un.d_ptr); \ + } while (0) +#define LOG_NON_VALUE_ENTRY(a_szName) Log3(("RTLdrELF: DT[%u]: %16s (%#RX64)\n", i, a_szName, (uint64_t)paDynamic[i].d_un.d_val)) + + switch (paDynamic[i].d_tag) + { + case DT_NULL: + LOG_NON_VALUE_ENTRY("DT_NULL"); + for (unsigned iNull = i + 1; iNull < pModElf->cDynamic; iNull++) + if (paDynamic[i].d_tag == DT_NULL) /* Not technically a bug, but let's try being extremely strict for now */ + LOG_NON_VALUE_ENTRY("DT_NULL"); + else if (!(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: DT[%u]/DT_NULL: Dynamic section isn't zero padded (extra #%u of #%u)", + pszLogName, i, iNull - i, pModElf->cDynamic - i); + i = pModElf->cDynamic; + break; + case DT_NEEDED: + LOG_VALIDATE_STR_RET("DT_NEEDED"); + break; + case DT_PLTRELSZ: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_PLTRELSZ", (uint64_t)paDynamic[i].d_un.d_val)); + SET_INFO_FIELD_RET("DT_PLTRELSZ", pModElf->DynInfo.cbJmpRelocs, (Elf_Xword)paDynamic[i].d_un.d_val, 0, FMT_ELF_XWORD); + break; + case DT_PLTGOT: + LOG_VALIDATE_PTR_RET("DT_PLTGOT"); + break; + case DT_HASH: + LOG_VALIDATE_PTR_RET("DT_HASH"); + break; + case DT_STRTAB: + LOG_VALIDATE_PTR_VAL_RET("DT_STRTAB", paShdrs[pModElf->Dyn.iStrSh].sh_addr); + pModElf->paShdrExtras[pModElf->Dyn.iStrSh].idxDt = i; + pModElf->paShdrExtras[pModElf->Dyn.iSymSh].uDtTag = DT_STRTAB; + break; + case DT_SYMTAB: + LOG_VALIDATE_PTR_VAL_RET("DT_SYMTAB", paShdrs[pModElf->Dyn.iSymSh].sh_addr); + pModElf->paShdrExtras[pModElf->Dyn.iSymSh].idxDt = i; + pModElf->paShdrExtras[pModElf->Dyn.iSymSh].uDtTag = DT_SYMTAB; + break; + case DT_RELA: + LOG_VALIDATE_PTR_RET("DT_RELA"); + SET_RELOC_TYPE_RET("DT_RELA", DT_RELA); + SET_INFO_FIELD_RET("DT_RELA", pModElf->DynInfo.uPtrRelocs, paDynamic[i].d_un.d_ptr, ~(Elf_Addr)0, FMT_ELF_ADDR); + FIND_MATCHING_SECTION_RET("DT_RELA", paShdrs[iSh].sh_type == SHT_RELA, pModElf->DynInfo.idxShRelocs); + break; + case DT_RELASZ: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_RELASZ", (uint64_t)paDynamic[i].d_un.d_val)); + SET_RELOC_TYPE_RET("DT_RELASZ", DT_RELA); + SET_INFO_FIELD_RET("DT_RELASZ", pModElf->DynInfo.cbRelocs, (Elf_Xword)paDynamic[i].d_un.d_val, 0, FMT_ELF_XWORD); + break; + case DT_RELAENT: + LOG_VALIDATE_VAL_RET("DT_RELAENT", sizeof(Elf_Rela)); + SET_RELOC_TYPE_RET("DT_RELAENT", DT_RELA); + SET_INFO_FIELD_RET("DT_RELAENT", pModElf->DynInfo.cbRelocEntry, (unsigned)sizeof(Elf_Rela), 0, "%u"); + break; + case DT_STRSZ: + LOG_VALIDATE_VAL_RET("DT_STRSZ", pModElf->Dyn.cbStr); + break; + case DT_SYMENT: + LOG_VALIDATE_VAL_RET("DT_SYMENT", sizeof(Elf_Sym)); + break; + case DT_INIT: + LOG_VALIDATE_PTR_RET("DT_INIT"); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_INIT"); + break; + case DT_FINI: + LOG_VALIDATE_PTR_RET("DT_FINI"); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_FINI"); + break; + case DT_SONAME: + LOG_VALIDATE_STR_RET("DT_SONAME"); + break; + case DT_RPATH: + LOG_VALIDATE_STR_RET("DT_RPATH"); + break; + case DT_SYMBOLIC: + LOG_NON_VALUE_ENTRY("DT_SYMBOLIC"); + break; + case DT_REL: + LOG_VALIDATE_PTR_RET("DT_REL"); + SET_RELOC_TYPE_RET("DT_REL", DT_REL); + SET_INFO_FIELD_RET("DT_REL", pModElf->DynInfo.uPtrRelocs, paDynamic[i].d_un.d_ptr, ~(Elf_Addr)0, FMT_ELF_ADDR); + FIND_MATCHING_SECTION_RET("DT_REL", paShdrs[iSh].sh_type == SHT_REL, pModElf->DynInfo.idxShRelocs); + break; + case DT_RELSZ: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_RELSZ", (uint64_t)paDynamic[i].d_un.d_val)); + SET_RELOC_TYPE_RET("DT_RELSZ", DT_REL); + SET_INFO_FIELD_RET("DT_RELSZ", pModElf->DynInfo.cbRelocs, (Elf_Xword)paDynamic[i].d_un.d_val, 0, FMT_ELF_XWORD); + break; + case DT_RELENT: + LOG_VALIDATE_VAL_RET("DT_RELENT", sizeof(Elf_Rel)); + SET_RELOC_TYPE_RET("DT_RELENT", DT_REL); + SET_INFO_FIELD_RET("DT_RELENT", pModElf->DynInfo.cbRelocEntry, (unsigned)sizeof(Elf_Rel), 0, "%u"); + break; + case DT_PLTREL: + if (paDynamic[i].d_un.d_val != DT_RELA && paDynamic[i].d_un.d_val != DT_REL) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT[%u]/DT_PLTREL: Invalid value %#RX64", + pszLogName, i, (uint64_t)paDynamic[i].d_un.d_val); + Log3(("RTLdrELF: DT[%u]: %16s DT_REL%s\n", i, "DT_PLTREL", paDynamic[i].d_un.d_val == DT_RELA ? "A" : "")); + SET_INFO_FIELD_RET("DT_PLTREL", pModElf->DynInfo.uJmpRelocType, (unsigned)paDynamic[i].d_un.d_val, 0, "%u"); + break; + case DT_DEBUG: + /* + * DT_DEBUG is filled in by the dynamic linker to point a debugger to the head of the link map, + * it can point anywhere in userspace. For binaries not being executed it will be 0, + * so there is nothing we can validate here (and it is not required as we don't use + * this dynamic section). See https://ypl.coffee/dl-resolve-full-relro/ for more information. + */ + break; + case DT_TEXTREL: + LOG_NON_VALUE_ENTRY("DT_TEXTREL"); + break; + case DT_JMPREL: + LOG_VALIDATE_PTR_RET("DT_JMPREL"); + SET_INFO_FIELD_RET("DT_JMPREL", pModElf->DynInfo.uPtrJmpRelocs, paDynamic[i].d_un.d_ptr, ~(Elf_Addr)0, FMT_ELF_ADDR); + FIND_MATCHING_SECTION_RET("DT_JMPREL", 1, pModElf->DynInfo.idxShJmpRelocs); + break; + case DT_BIND_NOW: + LOG_NON_VALUE_ENTRY("DT_BIND_NOW"); + break; + case DT_INIT_ARRAY: + LOG_VALIDATE_PTR_RET("DT_INIT_ARRAY"); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_INIT_ARRAY"); + break; + case DT_FINI_ARRAY: + LOG_VALIDATE_PTR_RET("DT_FINI_ARRAY"); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_FINI_ARRAY"); + break; + case DT_INIT_ARRAYSZ: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_INIT_ARRAYSZ", (uint64_t)paDynamic[i].d_un.d_val)); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_INIT_ARRAYSZ"); + break; + case DT_FINI_ARRAYSZ: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_FINI_ARRAYSZ", (uint64_t)paDynamic[i].d_un.d_val)); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_FINI_ARRAYSZ"); + break; + case DT_RUNPATH: + LOG_VALIDATE_STR_RET("DT_RUNPATH"); + break; + case DT_FLAGS: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64\n", i, "DT_FLAGS", (uint64_t)paDynamic[i].d_un.d_val)); + break; + case DT_PREINIT_ARRAY: + LOG_VALIDATE_PTR_RET("DT_PREINIT_ARRAY"); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_PREINIT_ARRAY"); + break; + case DT_PREINIT_ARRAYSZ: + Log3(("RTLdrELF: DT[%u]: %16s %#RX64 bytes\n", i, "DT_PREINIT_ARRAYSZ", (uint64_t)paDynamic[i].d_un.d_val)); + ONLY_FOR_DEBUG_OR_VALIDATION_RET("DT_PREINIT_ARRAYSZ"); + break; + default: + if ( paDynamic[i].d_tag < DT_ENCODING + || paDynamic[i].d_tag >= DT_LOOS + || (paDynamic[i].d_tag & 1)) + Log3(("RTLdrELF: DT[%u]: %#010RX64 %#RX64%s\n", i, (uint64_t)paDynamic[i].d_tag, + (uint64_t)paDynamic[i].d_un.d_val, paDynamic[i].d_un.d_val >= DT_ENCODING ? " (val)" : "")); + else + { + Log3(("RTLdrELF: DT[%u]: %#010RX64 " FMT_ELF_ADDR " (addr)\n", + i, (uint64_t)paDynamic[i].d_tag, paDynamic[i].d_un.d_ptr)); + if ((uint64_t)paDynamic[i].d_un.d_ptr - uLinkAddress >= cbImage) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: DT[%u]/%#RX64: Invalid address " FMT_ELF_ADDR " (valid range: " FMT_ELF_ADDR " LB " FMT_ELF_ADDR ")", + pszLogName, i, (uint64_t)paDynamic[i].d_tag, + paDynamic[i].d_un.d_ptr, uLinkAddress, cbImage); + } + break; + } +#undef LOG_VALIDATE_VAL_RET +#undef LOG_VALIDATE_STR_RET +#undef LOG_VALIDATE_PTR_VAL_RET +#undef LOG_VALIDATE_PTR_RET +#undef SET_RELOC_TYPE_RET +#undef SET_INFO_FIELD_RET +#undef FIND_MATCHING_SECTION_RET +#undef ONLY_FOR_DEBUG_OR_VALIDATION_RET + } + + /* + * Validate the relocation information we've gathered. + */ + Elf_Word uShTypeArch = SHT_RELA; /** @todo generalize architecture specific stuff using its own code template header. */ + switch (pModElf->Core.enmArch) + { + case RTLDRARCH_AMD64: + break; + case RTLDRARCH_X86_32: + uShTypeArch = SHT_REL; + break; + default: + AssertFailedBreak(/** @todo page size for got.plt hacks */); + + } + + if (pModElf->DynInfo.uRelocType != 0) + { + const char * const pszModifier = pModElf->DynInfo.uRelocType == DT_RELA ? "A" : ""; + if (pModElf->DynInfo.uPtrRelocs == ~(Elf_Addr)0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_REL%s", pszLogName, pszModifier); + if (pModElf->DynInfo.cbRelocs == 0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_REL%sSZ", pszLogName, pszModifier); + if (pModElf->DynInfo.cbRelocEntry == 0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_REL%sENT", pszLogName, pszModifier); + Elf_Shdr const *pShdrRelocs = &paShdrs[pModElf->DynInfo.idxShRelocs]; + Elf_Word const uShType = pModElf->DynInfo.uJmpRelocType == DT_RELA ? SHT_RELA : SHT_REL; + if (pShdrRelocs->sh_type != uShType) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_REL%s* does not match section type: %u vs %u", + pszLogName, pszModifier, pShdrRelocs->sh_type, uShType); + if (pShdrRelocs->sh_size != pModElf->DynInfo.cbRelocs) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_REL%sSZ does not match section size: %u vs %u", + pszLogName, pszModifier, pShdrRelocs->sh_size, pModElf->DynInfo.cbRelocs); + if (uShType != uShTypeArch) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_REL%s* does not match architecture: %u, arch wants %u", + pszLogName, pszModifier, uShType, uShTypeArch); + } + + if ( pModElf->DynInfo.uPtrJmpRelocs != ~(Elf_Addr)0 + || pModElf->DynInfo.cbJmpRelocs != 0 + || pModElf->DynInfo.uJmpRelocType != 0) + { + if (pModElf->DynInfo.uPtrJmpRelocs == ~(Elf_Addr)0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_JMPREL", pszLogName); + if (pModElf->DynInfo.cbJmpRelocs == 0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_PLTRELSZ", pszLogName); + if (pModElf->DynInfo.uJmpRelocType == 0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: Missing DT_PLTREL", pszLogName); + Elf_Shdr const *pShdrRelocs = &paShdrs[pModElf->DynInfo.idxShJmpRelocs]; + Elf_Word const uShType = pModElf->DynInfo.uJmpRelocType == DT_RELA ? SHT_RELA : SHT_REL; + if (pShdrRelocs->sh_type != uShType) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_PLTREL does not match section type: %u vs %u", + pszLogName, pShdrRelocs->sh_type, uShType); + if (pShdrRelocs->sh_size != pModElf->DynInfo.cbJmpRelocs) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_PLTRELSZ does not match section size: %u vs %u", + pszLogName, pShdrRelocs->sh_size, pModElf->DynInfo.cbJmpRelocs); + if (uShType != uShTypeArch) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "%s: DT_PLTREL does not match architecture: %u, arch wants %u", + pszLogName, uShType, uShTypeArch); + } + + /* + * Check that there aren't any other relocations hiding in the section table. + */ + for (uint32_t i = 1; i < pModElf->Ehdr.e_shnum; i++) + if ( (paShdrs[i].sh_type == SHT_REL || paShdrs[i].sh_type == SHT_RELA) + && pModElf->paShdrExtras[i].uDtTag != DT_REL + && pModElf->paShdrExtras[i].uDtTag != DT_RELA + && pModElf->paShdrExtras[i].uDtTag != DT_JMPREL) + { + char szSecHdrNm[80]; + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "%s: section header #%u (%s type=" FMT_ELF_WORD " size=" FMT_ELF_XWORD ") contains relocations not referenced by the dynamic section", + pszLogName, i, + RTLDRELF_NAME(GetSHdrName)(pModElf, paShdrs[i].sh_name, szSecHdrNm, sizeof(szSecHdrNm)), + paShdrs[i].sh_type, paShdrs[i].sh_size); + } + + return VINF_SUCCESS; +} + + + +/** + * Opens an ELF image, fixed bitness. + * + * @returns iprt status code. + * @param pReader The loader reader instance which will provide the raw image bits. + * @param fFlags Reserved, MBZ. + * @param enmArch Architecture specifier. + * @param phLdrMod Where to store the handle. + * @param pErrInfo Where to return extended error info. Optional. + */ +static int RTLDRELF_NAME(Open)(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + const char *pszLogName = pReader->pfnLogName(pReader); + uint64_t cbRawImage = pReader->pfnSize(pReader); + RT_NOREF_PV(fFlags); + + /* + * Create the loader module instance. + */ + PRTLDRMODELF pModElf = (PRTLDRMODELF)RTMemAllocZ(sizeof(*pModElf)); + if (!pModElf) + return VERR_NO_MEMORY; + + pModElf->Core.u32Magic = RTLDRMOD_MAGIC; + pModElf->Core.eState = LDR_STATE_INVALID; + pModElf->Core.pReader = pReader; + pModElf->Core.enmFormat = RTLDRFMT_ELF; + pModElf->Core.enmType = RTLDRTYPE_OBJECT; + pModElf->Core.enmEndian = RTLDRENDIAN_LITTLE; +#if ELF_MODE == 32 + pModElf->Core.enmArch = RTLDRARCH_X86_32; +#else + pModElf->Core.enmArch = RTLDRARCH_AMD64; +#endif + //pModElf->pvBits = NULL; + //pModElf->Ehdr = {0}; + //pModElf->paShdrs = NULL; + //pModElf->Rel.paSyms = NULL; + pModElf->Rel.iSymSh = ~0U; + //pModElf->Rel.cSyms = 0; + pModElf->Rel.iStrSh = ~0U; + //pModElf->Rel.cbStr = 0; + //pModElf->Rel.pStr = NULL; + //pModElf->Dyn.paSyms = NULL; + pModElf->Dyn.iSymSh = ~0U; + //pModElf->Dyn.cSyms = 0; + pModElf->Dyn.iStrSh = ~0U; + //pModElf->Dyn.cbStr = 0; + //pModElf->Dyn.pStr = NULL; + pModElf->iFirstSect = 1; + //pModElf->fShdrInOrder = false; + //pModElf->cbImage = 0; + pModElf->LinkAddress = ~(Elf_Addr)0; + //pModElf->cbShStr = 0; + //pModElf->pShStr = NULL; + //pModElf->iShEhFrame = 0; + //pModElf->iShEhFrameHdr= 0; + pModElf->iShDynamic = ~0U; + //pModElf->cDynamic = 0; + //pModElf->paDynamic = NULL; + //pModElf->paPhdrs = NULL; + pModElf->DynInfo.uPtrRelocs = ~(Elf_Addr)0; + //pModElf->DynInfo.cbRelocs = 0; + //pModElf->DynInfo.cbRelocEntry = 0; + //pModElf->DynInfo.uRelocType = 0; + //pModElf->DynInfo.idxShRelocs = 0; + pModElf->DynInfo.uPtrJmpRelocs = ~(Elf_Addr)0; + //pModElf->DynInfo.cbJmpRelocs = 0; + //pModElf->DynInfo.uJmpRelocType = 0; + //pModElf->DynInfo.idxShJmpRelocs = 0; + + /* + * Read and validate the ELF header and match up the CPU architecture. + */ + int rc = pReader->pfnRead(pReader, &pModElf->Ehdr, sizeof(pModElf->Ehdr), 0); + if (RT_SUCCESS(rc)) + { + RTLDRARCH enmArchImage = RTLDRARCH_INVALID; /* shut up gcc */ + rc = RTLDRELF_NAME(ValidateElfHeader)(&pModElf->Ehdr, cbRawImage, pszLogName, &enmArchImage, pErrInfo); + if (RT_SUCCESS(rc)) + { + if ( enmArch != RTLDRARCH_WHATEVER + && enmArch != enmArchImage) + rc = VERR_LDR_ARCH_MISMATCH; + } + } + if (RT_SUCCESS(rc)) + { + /* + * Read the section headers, keeping a prestine copy for the module + * introspection methods. + */ + size_t const cbShdrs = pModElf->Ehdr.e_shnum * sizeof(Elf_Shdr); + Elf_Shdr *paShdrs = (Elf_Shdr *)RTMemAlloc(cbShdrs * 2 + sizeof(RTLDRMODELFSHX) * pModElf->Ehdr.e_shnum); + if (paShdrs) + { + pModElf->paShdrs = paShdrs; + rc = pReader->pfnRead(pReader, paShdrs, cbShdrs, pModElf->Ehdr.e_shoff); + if (RT_SUCCESS(rc)) + { + memcpy(&paShdrs[pModElf->Ehdr.e_shnum], paShdrs, cbShdrs); + pModElf->paOrgShdrs = &paShdrs[pModElf->Ehdr.e_shnum]; + + pModElf->paShdrExtras = (PRTLDRMODELFSHX)&pModElf->paOrgShdrs[pModElf->Ehdr.e_shnum]; + memset(pModElf->paShdrExtras, 0xff, sizeof(RTLDRMODELFSHX) * pModElf->Ehdr.e_shnum); + + pModElf->cbShStr = paShdrs[pModElf->Ehdr.e_shstrndx].sh_size; + + /* + * Validate the section headers and find relevant sections. + */ + rc = RTLDRELF_NAME(ValidateAndProcessSectionHeaders)(pModElf, paShdrs, cbRawImage, pszLogName, pErrInfo); + + /* + * Read validate and process program headers if ET_DYN or ET_EXEC. + */ + if (RT_SUCCESS(rc) && (pModElf->Ehdr.e_type == ET_DYN || pModElf->Ehdr.e_type == ET_EXEC)) + rc = RTLDRELF_NAME(ValidateAndProcessDynamicInfo)(pModElf, cbRawImage, fFlags, pszLogName, pErrInfo); + + /* + * Massage the section headers. + */ + if (RT_SUCCESS(rc)) + { + if (pModElf->Ehdr.e_type == ET_REL) + { + /* Do allocations and figure the image size: */ + pModElf->LinkAddress = 0; + for (unsigned i = 1; i < pModElf->Ehdr.e_shnum; i++) + if (paShdrs[i].sh_flags & SHF_ALLOC) + { + paShdrs[i].sh_addr = paShdrs[i].sh_addralign + ? RT_ALIGN_T(pModElf->cbImage, paShdrs[i].sh_addralign, Elf_Addr) + : (Elf_Addr)pModElf->cbImage; + Elf_Addr EndAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size; + if (pModElf->cbImage < EndAddr) + { + pModElf->cbImage = (size_t)EndAddr; + AssertMsgBreakStmt(pModElf->cbImage == EndAddr, (FMT_ELF_ADDR "\n", EndAddr), rc = VERR_IMAGE_TOO_BIG); + } + Log2(("RTLdrElf: %s: Assigned " FMT_ELF_ADDR " to section #%d\n", pszLogName, paShdrs[i].sh_addr, i)); + } + } + else + { + /* Convert sh_addr to RVA: */ + Assert(pModElf->LinkAddress != ~(Elf_Addr)0); + for (unsigned i = 0 /*!*/; i < pModElf->Ehdr.e_shnum; i++) + if (paShdrs[i].sh_flags & SHF_ALLOC) + paShdrs[i].sh_addr -= pModElf->LinkAddress; + } + } + + /* + * Check if the sections are in order by address, as that will simplify + * enumeration and address translation. + */ + pModElf->fShdrInOrder = true; + Elf_Addr uEndAddr = 0; + for (unsigned i = pModElf->iFirstSect; i < pModElf->Ehdr.e_shnum; i++) + if (paShdrs[i].sh_flags & SHF_ALLOC) + { + if (uEndAddr <= paShdrs[i].sh_addr) + uEndAddr = paShdrs[i].sh_addr + paShdrs[i].sh_size; + else + { + pModElf->fShdrInOrder = false; + break; + } + } + + Log2(("RTLdrElf: iSymSh=%u cSyms=%u iStrSh=%u cbStr=%u rc=%Rrc cbImage=%#zx LinkAddress=" FMT_ELF_ADDR " fShdrInOrder=%RTbool\n", + pModElf->Rel.iSymSh, pModElf->Rel.cSyms, pModElf->Rel.iStrSh, pModElf->Rel.cbStr, rc, + pModElf->cbImage, pModElf->LinkAddress, pModElf->fShdrInOrder)); + if (RT_SUCCESS(rc)) + { + pModElf->Core.pOps = &RTLDRELF_MID(s_rtldrElf,Ops); + pModElf->Core.eState = LDR_STATE_OPENED; + *phLdrMod = &pModElf->Core; + + LogFlow(("%s: %s: returns VINF_SUCCESS *phLdrMod=%p\n", __FUNCTION__, pszLogName, *phLdrMod)); + return VINF_SUCCESS; + } + } + + RTMemFree(paShdrs); + } + else + rc = VERR_NO_MEMORY; + } + + RTMemFree(pModElf); + LogFlow(("%s: returns %Rrc\n", __FUNCTION__, rc)); + return rc; +} + + + + +/******************************************************************************* +* Cleanup Constants And Macros * +*******************************************************************************/ +#undef RTLDRELF_NAME +#undef RTLDRELF_SUFF +#undef RTLDRELF_MID + +#undef FMT_ELF_ADDR +#undef FMT_ELF_ADDR7 +#undef FMT_ELF_HALF +#undef FMT_ELF_SHALF +#undef FMT_ELF_OFF +#undef FMT_ELF_SIZE +#undef FMT_ELF_SWORD +#undef FMT_ELF_WORD +#undef FMT_ELF_XWORD +#undef FMT_ELF_SXWORD + +#undef Elf_Ehdr +#undef Elf_Phdr +#undef Elf_Shdr +#undef Elf_Sym +#undef Elf_Rel +#undef Elf_Rela +#undef Elf_Reloc +#undef Elf_Nhdr +#undef Elf_Dyn + +#undef Elf_Addr +#undef Elf_Half +#undef Elf_Off +#undef Elf_Size +#undef Elf_Sword +#undef Elf_Word +#undef Elf_Xword +#undef Elf_Sxword + +#undef RTLDRMODELF +#undef PRTLDRMODELF + +#undef ELF_R_SYM +#undef ELF_R_TYPE +#undef ELF_R_INFO + +#undef ELF_ST_BIND + diff --git a/src/VBox/Runtime/common/ldr/ldrEx.cpp b/src/VBox/Runtime/common/ldr/ldrEx.cpp new file mode 100644 index 00000000..d4a644ab --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrEx.cpp @@ -0,0 +1,773 @@ +/* $Id: ldrEx.cpp $ */ +/** @file + * IPRT - Binary Image Loader, Extended Features. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal/ldr.h" + +#if defined(LDR_ONLY_PE) || defined(LDR_ONLY_MACHO) +# undef LDR_WITH_PE +# undef LDR_WITH_ELF +# undef LDR_WITH_LX +# undef LDR_WITH_LE +# undef LDR_WITH_MACHO +# undef LDR_WITH_NE +# undef LDR_WITH_MZ +# undef LDR_WITH_AOUT +# ifdef LDR_ONLY_PE +# define LDR_WITH_PE +# endif +# ifdef LDR_ONLY_MACHO +# define LDR_WITH_MACHO +# endif +#endif + + +RTDECL(int) RTLdrOpenWithReader(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phMod, PRTERRINFO pErrInfo) +{ + /* + * Resolve RTLDRARCH_HOST. + */ + if (enmArch == RTLDRARCH_HOST) + enmArch = RTLdrGetHostArch(); + + /* + * Read and verify the file signature. + */ + union + { + char ach[4]; + uint16_t au16[2]; + uint32_t u32; + } uSign; + int rc = pReader->pfnRead(pReader, &uSign, sizeof(uSign), 0); + if (RT_FAILURE(rc)) + return rc; + if ( uSign.au16[0] != IMAGE_DOS_SIGNATURE + && uSign.u32 != IMAGE_NT_SIGNATURE + && uSign.u32 != IMAGE_ELF_SIGNATURE + && uSign.au16[0] != IMAGE_LX_SIGNATURE + && uSign.u32 != IMAGE_MACHO64_SIGNATURE + && uSign.u32 != IMAGE_MACHO64_SIGNATURE_OE + && uSign.u32 != IMAGE_MACHO32_SIGNATURE + && uSign.u32 != IMAGE_MACHO32_SIGNATURE_OE + && uSign.u32 != IMAGE_FAT_SIGNATURE + && uSign.u32 != IMAGE_FAT_SIGNATURE_OE ) + { + Log(("rtldrOpenWithReader: %s: unknown magic %#x / '%.4s\n", pReader->pfnLogName(pReader), uSign.u32, &uSign.ach[0])); + return VERR_INVALID_EXE_SIGNATURE; + } + uint32_t offHdr = 0; + if (uSign.au16[0] == IMAGE_DOS_SIGNATURE) + { + rc = pReader->pfnRead(pReader, &offHdr, sizeof(offHdr), RT_UOFFSETOF(IMAGE_DOS_HEADER, e_lfanew)); + if (RT_FAILURE(rc)) + return rc; + + if (offHdr <= sizeof(IMAGE_DOS_HEADER)) + { + Log(("rtldrOpenWithReader: %s: no new header / invalid offset %#RX32\n", pReader->pfnLogName(pReader), offHdr)); + return VERR_INVALID_EXE_SIGNATURE; + } + rc = pReader->pfnRead(pReader, &uSign, sizeof(uSign), offHdr); + if (RT_FAILURE(rc)) + return rc; + if ( uSign.u32 != IMAGE_NT_SIGNATURE + && uSign.au16[0] != IMAGE_LX_SIGNATURE + && uSign.au16[0] != IMAGE_LE_SIGNATURE + && uSign.au16[0] != IMAGE_NE_SIGNATURE) + { + Log(("rtldrOpenWithReader: %s: unknown new magic %#x / '%.4s\n", pReader->pfnLogName(pReader), uSign.u32, &uSign.ach[0])); + return VERR_INVALID_EXE_SIGNATURE; + } + } + + /* + * Create image interpreter instance depending on the signature. + */ + if (uSign.u32 == IMAGE_NT_SIGNATURE) +#ifdef LDR_WITH_PE + rc = rtldrPEOpen(pReader, fFlags, enmArch, offHdr, phMod, pErrInfo); +#else + rc = VERR_PE_EXE_NOT_SUPPORTED; +#endif + else if (uSign.u32 == IMAGE_ELF_SIGNATURE) +#if defined(LDR_WITH_ELF) + rc = rtldrELFOpen(pReader, fFlags, enmArch, phMod, pErrInfo); +#else + rc = VERR_ELF_EXE_NOT_SUPPORTED; +#endif + else if ( uSign.u32 == IMAGE_MACHO64_SIGNATURE + || uSign.u32 == IMAGE_MACHO64_SIGNATURE_OE + || uSign.u32 == IMAGE_MACHO32_SIGNATURE + || uSign.u32 == IMAGE_MACHO32_SIGNATURE_OE) +#if defined(LDR_WITH_MACHO) + rc = rtldrMachOOpen(pReader, fFlags, enmArch, offHdr, phMod, pErrInfo); +#else + rc = VERR_INVALID_EXE_SIGNATURE; +#endif + else if ( uSign.u32 == IMAGE_FAT_SIGNATURE + || uSign.u32 == IMAGE_FAT_SIGNATURE_OE) +#if defined(LDR_WITH_MACHO) + rc = rtldrFatOpen(pReader, fFlags, enmArch, phMod, pErrInfo); +#else + rc = VERR_INVALID_EXE_SIGNATURE; +#endif + else if (uSign.au16[0] == IMAGE_LX_SIGNATURE) +#ifdef LDR_WITH_LX + rc = rtldrLXOpen(pReader, fFlags, enmArch, offHdr, phMod, pErrInfo); +#else + rc = VERR_LX_EXE_NOT_SUPPORTED; +#endif + else if (uSign.au16[0] == IMAGE_LE_SIGNATURE) +#ifdef LDR_WITH_LE + rc = rtldrLEOpen(pReader, fFlags, enmArch, phMod, pErrInfo); +#else + rc = VERR_LE_EXE_NOT_SUPPORTED; +#endif + else if (uSign.au16[0] == IMAGE_NE_SIGNATURE) +#ifdef LDR_WITH_NE + rc = rtldrNEOpen(pReader, fFlags, enmArch, phMod, pErrInfo); +#else + rc = VERR_NE_EXE_NOT_SUPPORTED; +#endif + else if (uSign.au16[0] == IMAGE_DOS_SIGNATURE) +#ifdef LDR_WITH_MZ + rc = rtldrMZOpen(pReader, fFlags, enmArch, phMod, pErrInfo); +#else + rc = VERR_MZ_EXE_NOT_SUPPORTED; +#endif + else if (/* uSign.u32 == IMAGE_AOUT_A_SIGNATURE + || uSign.u32 == IMAGE_AOUT_Z_SIGNATURE*/ /** @todo find the aout magics in emx or binutils. */ + 0) +#ifdef LDR_WITH_AOUT + rc = rtldrAOUTOpen(pReader, fFlags, enmArch, phMod, pErrInfo); +#else + rc = VERR_AOUT_EXE_NOT_SUPPORTED; +#endif + else + { + Log(("rtldrOpenWithReader: %s: the format isn't implemented %#x / '%.4s\n", pReader->pfnLogName(pReader), uSign.u32, &uSign.ach[0])); + rc = VERR_INVALID_EXE_SIGNATURE; + } + + LogFlow(("rtldrOpenWithReader: %s: returns %Rrc *phMod=%p\n", pReader->pfnLogName(pReader), rc, *phMod)); + return rc; +} + + +RTDECL(size_t) RTLdrSize(RTLDRMOD hLdrMod) +{ + LogFlow(("RTLdrSize: hLdrMod=%RTldrm\n", hLdrMod)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), ~(size_t)0); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), ~(size_t)0); + + /* + * Do it. + */ + size_t cb = pMod->pOps->pfnGetImageSize(pMod); + LogFlow(("RTLdrSize: returns %zu\n", cb)); + return cb; +} +RT_EXPORT_SYMBOL(RTLdrSize); + + +RTDECL(int) RTLdrGetBits(RTLDRMOD hLdrMod, void *pvBits, RTLDRADDR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + LogFlow(("RTLdrGetBits: hLdrMod=%RTldrm pvBits=%p BaseAddress=%RTptr pfnGetImport=%p pvUser=%p\n", + hLdrMod, pvBits, BaseAddress, pfnGetImport, pvUser)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(pvBits, VERR_INVALID_POINTER); + AssertPtrNullReturn(pfnGetImport, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc = pMod->pOps->pfnGetBits(pMod, pvBits, BaseAddress, pfnGetImport, pvUser); + LogFlow(("RTLdrGetBits: returns %Rrc\n",rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrGetBits); + + +RTDECL(int) RTLdrRelocate(RTLDRMOD hLdrMod, void *pvBits, RTLDRADDR NewBaseAddress, RTLDRADDR OldBaseAddress, + PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + LogFlow(("RTLdrRelocate: hLdrMod=%RTldrm pvBits=%p NewBaseAddress=%RTptr OldBaseAddress=%RTptr pfnGetImport=%p pvUser=%p\n", + hLdrMod, pvBits, NewBaseAddress, OldBaseAddress, pfnGetImport, pvUser)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(pvBits, VERR_INVALID_POINTER); + AssertPtrReturn(pfnGetImport, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc = pMod->pOps->pfnRelocate(pMod, pvBits, NewBaseAddress, OldBaseAddress, pfnGetImport, pvUser); + LogFlow(("RTLdrRelocate: returns %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrRelocate); + + +RTDECL(int) RTLdrGetSymbolEx(RTLDRMOD hLdrMod, const void *pvBits, RTLDRADDR BaseAddress, + uint32_t iOrdinal, const char *pszSymbol, PRTLDRADDR pValue) +{ + LogFlow(("RTLdrGetSymbolEx: hLdrMod=%RTldrm pvBits=%p BaseAddress=%RTptr iOrdinal=%#x pszSymbol=%p:{%s} pValue=%p\n", + hLdrMod, pvBits, BaseAddress, iOrdinal, pszSymbol, pszSymbol, pValue)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER); + AssertPtrNullReturn(pszSymbol, VERR_INVALID_POINTER); + AssertReturn(pszSymbol || iOrdinal != UINT32_MAX, VERR_INVALID_PARAMETER); + AssertPtrReturn(pValue, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnGetSymbolEx) + rc = pMod->pOps->pfnGetSymbolEx(pMod, pvBits, BaseAddress, iOrdinal, pszSymbol, pValue); + else if (!BaseAddress && !pvBits && iOrdinal == UINT32_MAX) + { + void *pvValue; + rc = pMod->pOps->pfnGetSymbol(pMod, pszSymbol, &pvValue); + if (RT_SUCCESS(rc)) + *pValue = (uintptr_t)pvValue; + } + else + AssertMsgFailedReturn(("BaseAddress=%RTptr pvBits=%p\n", BaseAddress, pvBits), VERR_INVALID_FUNCTION); + LogFlow(("RTLdrGetSymbolEx: returns %Rrc *pValue=%p\n", rc, *pValue)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrGetSymbolEx); + + +RTDECL(int) RTLdrQueryForwarderInfo(RTLDRMOD hLdrMod, const void *pvBits, uint32_t iOrdinal, const char *pszSymbol, + PRTLDRIMPORTINFO pInfo, size_t cbInfo) +{ + LogFlow(("RTLdrQueryForwarderInfo: hLdrMod=%RTldrm pvBits=%p iOrdinal=%#x pszSymbol=%p:{%s} pInfo=%p cbInfo=%zu\n", + hLdrMod, pvBits, iOrdinal, pszSymbol, pszSymbol, pInfo, cbInfo)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER); + AssertMsgReturn(pszSymbol, ("pszSymbol=%p\n", pszSymbol), VERR_INVALID_PARAMETER); + AssertPtrReturn(pInfo, VERR_INVALID_PARAMETER); + AssertReturn(cbInfo >= sizeof(*pInfo), VERR_INVALID_PARAMETER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnQueryForwarderInfo) + { + rc = pMod->pOps->pfnQueryForwarderInfo(pMod, pvBits, iOrdinal, pszSymbol, pInfo, cbInfo); + if (RT_SUCCESS(rc)) + LogFlow(("RTLdrQueryForwarderInfo: returns %Rrc pInfo={%#x,%#x,%s,%s}\n", rc, + pInfo->iSelfOrdinal, pInfo->iOrdinal, pInfo->pszSymbol, pInfo->szModule)); + else + LogFlow(("RTLdrQueryForwarderInfo: returns %Rrc\n", rc)); + } + else + { + LogFlow(("RTLdrQueryForwarderInfo: returns VERR_NOT_SUPPORTED\n")); + rc = VERR_NOT_SUPPORTED; + } + return rc; + +} +RT_EXPORT_SYMBOL(RTLdrQueryForwarderInfo); + + +RTDECL(int) RTLdrEnumSymbols(RTLDRMOD hLdrMod, unsigned fFlags, const void *pvBits, RTLDRADDR BaseAddress, + PFNRTLDRENUMSYMS pfnCallback, void *pvUser) +{ + LogFlow(("RTLdrEnumSymbols: hLdrMod=%RTldrm fFlags=%#x pvBits=%p BaseAddress=%RTptr pfnCallback=%p pvUser=%p\n", + hLdrMod, fFlags, pvBits, BaseAddress, pfnCallback, pvUser)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER); + AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc = pMod->pOps->pfnEnumSymbols(pMod, fFlags, pvBits, BaseAddress, pfnCallback, pvUser); + LogFlow(("RTLdrEnumSymbols: returns %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrEnumSymbols); + + +RTDECL(int) RTLdrEnumDbgInfo(RTLDRMOD hLdrMod, const void *pvBits, PFNRTLDRENUMDBG pfnCallback, void *pvUser) +{ + LogFlow(("RTLdrEnumDbgInfo: hLdrMod=%RTldrm pvBits=%p pfnCallback=%p pvUser=%p\n", + hLdrMod, pvBits, pfnCallback, pvUser)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrNullReturn(pvBits, VERR_INVALID_POINTER); + AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnEnumDbgInfo) + rc = pMod->pOps->pfnEnumDbgInfo(pMod, pvBits, pfnCallback, pvUser); + else + rc = VERR_NOT_SUPPORTED; + + LogFlow(("RTLdrEnumDbgInfo: returns %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrEnumDbgInfo); + + +RTDECL(int) RTLdrEnumSegments(RTLDRMOD hLdrMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser) +{ + LogFlow(("RTLdrEnumSegments: hLdrMod=%RTldrm pfnCallback=%p pvUser=%p\n", + hLdrMod, pfnCallback, pvUser)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnEnumSegments) + rc = pMod->pOps->pfnEnumSegments(pMod, pfnCallback, pvUser); + else + rc = VERR_NOT_SUPPORTED; + + LogFlow(("RTLdrEnumSegments: returns %Rrc\n", rc)); + return rc; + +} +RT_EXPORT_SYMBOL(RTLdrEnumSegments); + + +RTDECL(int) RTLdrLinkAddressToSegOffset(RTLDRMOD hLdrMod, RTLDRADDR LinkAddress, uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + LogFlow(("RTLdrLinkAddressToSegOffset: hLdrMod=%RTldrm LinkAddress=%RTptr piSeg=%p poffSeg=%p\n", + hLdrMod, LinkAddress, piSeg, poffSeg)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(piSeg, VERR_INVALID_POINTER); + AssertPtrReturn(poffSeg, VERR_INVALID_POINTER); + + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + *piSeg = UINT32_MAX; + *poffSeg = ~(RTLDRADDR)0; + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnLinkAddressToSegOffset) + rc = pMod->pOps->pfnLinkAddressToSegOffset(pMod, LinkAddress, piSeg, poffSeg); + else + rc = VERR_NOT_SUPPORTED; + + LogFlow(("RTLdrLinkAddressToSegOffset: returns %Rrc %#x:%RTptr\n", rc, *piSeg, *poffSeg)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrLinkAddressToSegOffset); + + +RTDECL(int) RTLdrLinkAddressToRva(RTLDRMOD hLdrMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva) +{ + LogFlow(("RTLdrLinkAddressToRva: hLdrMod=%RTldrm LinkAddress=%RTptr pRva=%p\n", + hLdrMod, LinkAddress, pRva)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(pRva, VERR_INVALID_POINTER); + + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + *pRva = ~(RTLDRADDR)0; + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnLinkAddressToRva) + rc = pMod->pOps->pfnLinkAddressToRva(pMod, LinkAddress, pRva); + else + rc = VERR_NOT_SUPPORTED; + + LogFlow(("RTLdrLinkAddressToRva: returns %Rrc %RTptr\n", rc, *pRva)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrLinkAddressToRva); + + +RTDECL(int) RTLdrSegOffsetToRva(RTLDRMOD hLdrMod, uint32_t iSeg, RTLDRADDR offSeg, PRTLDRADDR pRva) +{ + LogFlow(("RTLdrSegOffsetToRva: hLdrMod=%RTldrm iSeg=%#x offSeg=%RTptr pRva=%p\n", hLdrMod, iSeg, offSeg, pRva)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(pRva, VERR_INVALID_POINTER); + + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + *pRva = ~(RTLDRADDR)0; + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnSegOffsetToRva) + rc = pMod->pOps->pfnSegOffsetToRva(pMod, iSeg, offSeg, pRva); + else + rc = VERR_NOT_SUPPORTED; + + LogFlow(("RTLdrSegOffsetToRva: returns %Rrc %RTptr\n", rc, *pRva)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrSegOffsetToRva); + +RTDECL(int) RTLdrRvaToSegOffset(RTLDRMOD hLdrMod, RTLDRADDR Rva, uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + LogFlow(("RTLdrRvaToSegOffset: hLdrMod=%RTldrm Rva=%RTptr piSeg=%p poffSeg=%p\n", + hLdrMod, Rva, piSeg, poffSeg)); + + /* + * Validate input. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + AssertPtrReturn(piSeg, VERR_INVALID_POINTER); + AssertPtrReturn(poffSeg, VERR_INVALID_POINTER); + + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + //AssertMsgReturn(pMod->eState == LDR_STATE_OPENED, ("eState=%d\n", pMod->eState), VERR_WRONG_ORDER); + + *piSeg = UINT32_MAX; + *poffSeg = ~(RTLDRADDR)0; + + /* + * Do it. + */ + int rc; + if (pMod->pOps->pfnRvaToSegOffset) + rc = pMod->pOps->pfnRvaToSegOffset(pMod, Rva, piSeg, poffSeg); + else + rc = VERR_NOT_SUPPORTED; + + LogFlow(("RTLdrRvaToSegOffset: returns %Rrc %#x:%RTptr\n", rc, *piSeg, *poffSeg)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrRvaToSegOffset); + + +RTDECL(int) RTLdrQueryProp(RTLDRMOD hLdrMod, RTLDRPROP enmProp, void *pvBuf, size_t cbBuf) +{ + return RTLdrQueryPropEx(hLdrMod, enmProp, NULL /*pvBits*/, pvBuf, cbBuf, NULL); +} +RT_EXPORT_SYMBOL(RTLdrQueryProp); + + +RTDECL(int) RTLdrQueryPropEx(RTLDRMOD hLdrMod, RTLDRPROP enmProp, void *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), RTLDRENDIAN_INVALID); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + + AssertPtrNullReturn(pcbRet, VERR_INVALID_POINTER); + size_t cbRet; + if (!pcbRet) + pcbRet = &cbRet; + + /* + * Do some pre screening of the input + */ + switch (enmProp) + { + case RTLDRPROP_UUID: + *pcbRet = sizeof(RTUUID); + AssertReturn(cbBuf == sizeof(RTUUID), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_TIMESTAMP_SECONDS: + *pcbRet = sizeof(int64_t); + AssertReturn(cbBuf == sizeof(int32_t) || cbBuf == sizeof(int64_t), VERR_INVALID_PARAMETER); + *pcbRet = cbBuf; + break; + case RTLDRPROP_IS_SIGNED: + *pcbRet = sizeof(bool); + AssertReturn(cbBuf == sizeof(bool), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_PKCS7_SIGNED_DATA: + case RTLDRPROP_SHA1_PAGE_HASHES: + case RTLDRPROP_SHA256_PAGE_HASHES: + *pcbRet = 0; + break; + case RTLDRPROP_HASHABLE_PAGES: + *pcbRet = sizeof(uint32_t); + AssertReturn(cbBuf >= sizeof(uint32_t), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_SIGNATURE_CHECKS_ENFORCED: + *pcbRet = sizeof(bool); + AssertReturn(cbBuf == sizeof(bool), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_IMPORT_COUNT: + *pcbRet = sizeof(uint32_t); + AssertReturn(cbBuf == sizeof(uint32_t), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_IMPORT_MODULE: + *pcbRet = sizeof(uint32_t); + AssertReturn(cbBuf >= sizeof(uint32_t), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_FILE_OFF_HEADER: + *pcbRet = sizeof(uint64_t); + AssertReturn(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t), VERR_INVALID_PARAMETER); + break; + case RTLDRPROP_INTERNAL_NAME: + case RTLDRPROP_UNWIND_TABLE: + *pcbRet = 0; + break; + + case RTLDRPROP_UNWIND_INFO: + AssertReturn(pvBuf, VERR_INVALID_POINTER); + AssertReturn(cbBuf >= sizeof(uint32_t), VERR_INVALID_PARAMETER); + *pcbRet = 0; + break; + + case RTLDRPROP_BUILDID: + *pcbRet = 0; + break; + + default: + AssertFailedReturn(VERR_INVALID_FUNCTION); + } + AssertPtrReturn(pvBuf, VERR_INVALID_POINTER); + + /* + * Call the image specific worker, if there is one. + */ + if (!pMod->pOps->pfnQueryProp) + return VERR_NOT_SUPPORTED; + return pMod->pOps->pfnQueryProp(pMod, enmProp, pvBits, pvBuf, cbBuf, pcbRet); +} +RT_EXPORT_SYMBOL(RTLdrQueryPropEx); + + +RTDECL(int) RTLdrVerifySignature(RTLDRMOD hLdrMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser, PRTERRINFO pErrInfo) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + AssertPtrReturn(pfnCallback, VERR_INVALID_POINTER); + + /* + * Call the image specific worker, if there is one. + */ + if (!pMod->pOps->pfnVerifySignature) + return VERR_NOT_SUPPORTED; + return pMod->pOps->pfnVerifySignature(pMod, pfnCallback, pvUser, pErrInfo); +} +RT_EXPORT_SYMBOL(RTLdrVerifySignature); + + +RTDECL(int) RTLdrHashImage(RTLDRMOD hLdrMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + + /* + * Make sure there is sufficient space for the wanted digest and that + * it's supported. + */ + switch (enmDigest) + { + case RTDIGESTTYPE_MD5: AssertReturn(cbHash >= RTMD5_HASH_SIZE, VERR_BUFFER_OVERFLOW); break; + case RTDIGESTTYPE_SHA1: AssertReturn(cbHash >= RTSHA1_HASH_SIZE, VERR_BUFFER_OVERFLOW); break; + case RTDIGESTTYPE_SHA256: AssertReturn(cbHash >= RTSHA256_HASH_SIZE, VERR_BUFFER_OVERFLOW); break; + case RTDIGESTTYPE_SHA512: AssertReturn(cbHash >= RTSHA512_HASH_SIZE, VERR_BUFFER_OVERFLOW); break; + default: + if (enmDigest > RTDIGESTTYPE_INVALID && enmDigest < RTDIGESTTYPE_END) + return VERR_NOT_SUPPORTED; + AssertFailedReturn(VERR_INVALID_PARAMETER); + } + AssertPtrReturn(pabHash, VERR_INVALID_POINTER); + + /* + * Call the image specific worker, if there is one. + */ + if (!pMod->pOps->pfnHashImage) + return VERR_NOT_SUPPORTED; + return pMod->pOps->pfnHashImage(pMod, enmDigest, pabHash, cbHash); +} +RT_EXPORT_SYMBOL(RTLdrHashImage); + + +RTDECL(int) RTLdrUnwindFrame(RTLDRMOD hLdrMod, void const *pvBits, uint32_t iSeg, RTLDRADDR off, PRTDBGUNWINDSTATE pState) +{ + /* + * Validate. + */ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + AssertPtr(pState); + AssertReturn(pState->u32Magic == RTDBGUNWINDSTATE_MAGIC, VERR_INVALID_MAGIC); + + /* + * Pass on the work. + */ + if (pMod->pOps->pfnUnwindFrame) + return pMod->pOps->pfnUnwindFrame(pMod, pvBits, iSeg, off, pState); + return VERR_DBG_NO_UNWIND_INFO; +} +RT_EXPORT_SYMBOL(RTLdrUnwindFrame); + + +/** + * Internal method used by the IPRT debug bits. + * + * @returns IPRT status code. + * @param hLdrMod The loader handle which executable we wish to + * read from. + * @param pvBuf The output buffer. + * @param iDbgInfo The debug info ordinal number if the request + * corresponds exactly to a debug info part from + * pfnEnumDbgInfo. Otherwise, pass UINT32_MAX. + * @param off Where in the executable file to start reading. + * @param cb The number of bytes to read. + * + * @remarks Fixups will only be applied if @a iDbgInfo is specified. + */ +DECLHIDDEN(int) rtLdrReadAt(RTLDRMOD hLdrMod, void *pvBuf, uint32_t iDbgInfo, RTFOFF off, size_t cb) +{ + AssertMsgReturn(rtldrIsValid(hLdrMod), ("hLdrMod=%p\n", hLdrMod), VERR_INVALID_HANDLE); + PRTLDRMODINTERNAL pMod = (PRTLDRMODINTERNAL)hLdrMod; + + if (iDbgInfo != UINT32_MAX) + { + AssertReturn(pMod->pOps->pfnReadDbgInfo, VERR_NOT_SUPPORTED); + return pMod->pOps->pfnReadDbgInfo(pMod, iDbgInfo, off, cb, pvBuf); + } + + AssertReturn(pMod->pReader, VERR_NOT_SUPPORTED); + return pMod->pReader->pfnRead(pMod->pReader, pvBuf, cb, off); +} + + +RTDECL(const char *) RTLdrArchName(RTLDRARCH enmArch) +{ + switch (enmArch) + { + case RTLDRARCH_INVALID: return "INVALID"; + case RTLDRARCH_WHATEVER: return "WHATEVER"; + case RTLDRARCH_HOST: return "HOST"; + case RTLDRARCH_AMD64: return "AMD64"; + case RTLDRARCH_X86_16: return "X86_16"; + case RTLDRARCH_X86_32: return "X86_32"; + case RTLDRARCH_ARM32: return "ARM32"; + case RTLDRARCH_ARM64: return "ARM64"; + + case RTLDRARCH_END: + case RTLDRARCH_32BIT_HACK: + break; + } + return "UNKNOWN"; +} +RT_EXPORT_SYMBOL(RTLdrArchName); + diff --git a/src/VBox/Runtime/common/ldr/ldrFile.cpp b/src/VBox/Runtime/common/ldr/ldrFile.cpp new file mode 100644 index 00000000..ea570524 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrFile.cpp @@ -0,0 +1,317 @@ +/* $Id: ldrFile.cpp $ */ +/** @file + * IPRT - Binary Image Loader, The File Oriented Parts. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include "internal/ldr.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * File Reader instance. + * This provides raw image bits from a file. + */ +typedef struct RTLDRREADERFILE +{ + /** The core. */ + RTLDRREADER Core; + /** The file. */ + RTFILE hFile; + /** The file size. */ + uint64_t cbFile; + /** The current offset. */ + RTFOFF off; + /** Number of users or the mapping. */ + RTUINT cMappings; + /** Pointer to the in memory mapping. */ + void *pvMapping; + /** The filename (variable size). */ + char szFilename[1]; +} RTLDRREADERFILE, *PRTLDRREADERFILE; + + +/** @copydoc RTLDRREADER::pfnRead */ +static DECLCALLBACK(int) rtldrFileRead(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off) +{ + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + + /* + * Seek. + */ + if (pFileReader->off != off) + { + int rc = RTFileSeek(pFileReader->hFile, off, RTFILE_SEEK_BEGIN, NULL); + if (RT_FAILURE(rc)) + { + pFileReader->off = -1; + return rc; + } + pFileReader->off = off; + } + + /* + * Read. + */ + int rc = RTFileRead(pFileReader->hFile, pvBuf, cb, NULL); + if (RT_SUCCESS(rc)) + pFileReader->off += cb; + else + pFileReader->off = -1; + return rc; +} + + +/** @copydoc RTLDRREADER::pfnTell */ +static DECLCALLBACK(RTFOFF) rtldrFileTell(PRTLDRREADER pReader) +{ + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + return pFileReader->off; +} + + +/** @copydoc RTLDRREADER::pfnSize */ +static DECLCALLBACK(uint64_t) rtldrFileSize(PRTLDRREADER pReader) +{ + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + return pFileReader->cbFile; +} + + +/** @copydoc RTLDRREADER::pfnLogName */ +static DECLCALLBACK(const char *) rtldrFileLogName(PRTLDRREADER pReader) +{ + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + return pFileReader->szFilename; +} + + +/** @copydoc RTLDRREADER::pfnMap */ +static DECLCALLBACK(int) rtldrFileMap(PRTLDRREADER pReader, const void **ppvBits) +{ + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + + /* + * Already mapped? + */ + if (pFileReader->pvMapping) + { + pFileReader->cMappings++; + *ppvBits = pFileReader->pvMapping; + return VINF_SUCCESS; + } + + /* + * Allocate memory. + */ + size_t cb = (size_t)pFileReader->cbFile; + if ((uint64_t)cb != pFileReader->cbFile) + return VERR_IMAGE_TOO_BIG; + pFileReader->pvMapping = RTMemAlloc(cb); + if (!pFileReader->pvMapping) + return VERR_NO_MEMORY; + int rc = rtldrFileRead(pReader, pFileReader->pvMapping, cb, 0); + if (RT_SUCCESS(rc)) + { + pFileReader->cMappings = 1; + *ppvBits = pFileReader->pvMapping; + } + else + { + RTMemFree(pFileReader->pvMapping); + pFileReader->pvMapping = NULL; + } + + return rc; +} + + +/** @copydoc RTLDRREADER::pfnUnmap */ +static DECLCALLBACK(int) rtldrFileUnmap(PRTLDRREADER pReader, const void *pvBits) +{ + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + AssertReturn(pFileReader->cMappings > 0, VERR_INVALID_PARAMETER); + + if (!--pFileReader->cMappings) + { + RTMemFree(pFileReader->pvMapping); + pFileReader->pvMapping = NULL; + } + + NOREF(pvBits); + return VINF_SUCCESS; +} + + +/** @copydoc RTLDRREADER::pfnDestroy */ +static DECLCALLBACK(int) rtldrFileDestroy(PRTLDRREADER pReader) +{ + int rc = VINF_SUCCESS; + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)pReader; + + Assert(!pFileReader->cMappings); + + if (pFileReader->hFile != NIL_RTFILE) + { + rc = RTFileClose(pFileReader->hFile); + AssertRC(rc); + pFileReader->hFile = NIL_RTFILE; + } + + if (pFileReader->pvMapping) + { + RTMemFree(pFileReader->pvMapping); + pFileReader->pvMapping = NULL; + } + + RTMemFree(pFileReader); + return rc; +} + + +/** + * Opens a loader file reader. + * + * @returns iprt status code. + * @param ppReader Where to store the reader instance on success. + * @param pszFilename The file to open. + */ +static int rtldrFileCreate(PRTLDRREADER *ppReader, const char *pszFilename) +{ + size_t cchFilename = strlen(pszFilename); + int rc = VERR_NO_MEMORY; + PRTLDRREADERFILE pFileReader = (PRTLDRREADERFILE)RTMemAlloc(sizeof(*pFileReader) + cchFilename); + if (pFileReader) + { + memcpy(pFileReader->szFilename, pszFilename, cchFilename + 1); + rc = RTFileOpen(&pFileReader->hFile, pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + rc = RTFileQuerySize(pFileReader->hFile, &pFileReader->cbFile); + if (RT_SUCCESS(rc)) + { + pFileReader->Core.uMagic = RTLDRREADER_MAGIC; + pFileReader->Core.pfnRead = rtldrFileRead; + pFileReader->Core.pfnTell = rtldrFileTell; + pFileReader->Core.pfnSize = rtldrFileSize; + pFileReader->Core.pfnLogName = rtldrFileLogName; + pFileReader->Core.pfnMap = rtldrFileMap; + pFileReader->Core.pfnUnmap = rtldrFileUnmap; + pFileReader->Core.pfnDestroy = rtldrFileDestroy; + pFileReader->off = 0; + pFileReader->cMappings = 0; + pFileReader->pvMapping = NULL; + *ppReader = &pFileReader->Core; + return VINF_SUCCESS; + } + + RTFileClose(pFileReader->hFile); + } + RTMemFree(pFileReader); + } + *ppReader = NULL; + return rc; +} + + +/** + * Open a binary image file. + * + * @returns iprt status code. + * @param pszFilename Image filename. + * @param fFlags Valid RTLDR_O_XXX combination. + * @param enmArch CPU architecture specifier for the image to be loaded. + * @param phLdrMod Where to store the handle to the loader module. + */ +RTDECL(int) RTLdrOpen(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod) +{ + return RTLdrOpenEx(pszFilename, fFlags, enmArch, phLdrMod, NULL /*pErrInfo*/); +} +RT_EXPORT_SYMBOL(RTLdrOpen); + + +/** + * Open a binary image file, extended version. + * + * @returns iprt status code. + * @param pszFilename Image filename. + * @param fFlags Valid RTLDR_O_XXX combination. + * @param enmArch CPU architecture specifier for the image to be loaded. + * @param phLdrMod Where to store the handle to the loader module. + * @param pErrInfo Where to return extended error information. Optional. + */ +RTDECL(int) RTLdrOpenEx(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch, PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + LogFlow(("RTLdrOpenEx: pszFilename=%p:{%s} fFlags=%#x enmArch=%d phLdrMod=%p\n", + pszFilename, pszFilename, fFlags, enmArch, phLdrMod)); + AssertMsgReturn(!(fFlags & ~RTLDR_O_VALID_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + AssertMsgReturn(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, ("%d\n", enmArch), VERR_INVALID_PARAMETER); + + /* + * Create file reader & invoke worker which identifies and calls the image interpreter. + */ + PRTLDRREADER pReader; + int rc = rtldrFileCreate(&pReader, pszFilename); + if (RT_SUCCESS(rc)) + { + rc = RTLdrOpenWithReader(pReader, fFlags, enmArch, phLdrMod, pErrInfo); + if (RT_SUCCESS(rc)) + { + LogFlow(("RTLdrOpenEx: return %Rrc *phLdrMod=%p\n", rc, *phLdrMod)); + return rc; + } + pReader->pfnDestroy(pReader); + } + *phLdrMod = NIL_RTLDRMOD; + LogFlow(("RTLdrOpenEx: return %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrOpenEx); + diff --git a/src/VBox/Runtime/common/ldr/ldrLX.cpp b/src/VBox/Runtime/common/ldr/ldrLX.cpp new file mode 100644 index 00000000..a61e8d50 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrLX.cpp @@ -0,0 +1,3121 @@ +/* $Id: ldrLX.cpp $ */ +/** @file + * kLdr - The Module Interpreter for the Linear eXecutable (LX) Format. + */ + +/* + * Copyright (C) 2007-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/kLdrModLX.c from kStuff r113. + * + * Copyright (c) 2006-2007 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 "internal/ldr.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** @def KLDRMODLX_STRICT + * Define KLDRMODLX_STRICT to enabled strict checks in KLDRMODLX. */ +#define KLDRMODLX_STRICT 1 + +/** @def KLDRMODLX_ASSERT + * Assert that an expression is true when KLDR_STRICT is defined. + */ +#ifdef KLDRMODLX_STRICT +# define KLDRMODLX_ASSERT(expr) Assert(expr) +#else +# define KLDRMODLX_ASSERT(expr) do {} while (0) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Instance data for the LX module interpreter. + */ +typedef struct KLDRMODLX +{ + /** Core module structure. */ + RTLDRMODINTERNAL Core; + + /** Pointer to the user mapping. */ + const void *pvMapping; + /** The size of the mapped LX image. */ + size_t cbMapped; + /** Reserved flags. */ + uint32_t f32Reserved; + + /** The offset of the LX header. */ + RTFOFF offHdr; + /** Copy of the LX header. */ + struct e32_exe Hdr; + + /** Pointer to the loader section. + * Allocated together with this strcture. */ + const uint8_t *pbLoaderSection; + /** Pointer to the last byte in the loader section. */ + const uint8_t *pbLoaderSectionLast; + /** Pointer to the object table in the loader section. */ + const struct o32_obj *paObjs; + /** Pointer to the object page map table in the loader section. */ + const struct o32_map *paPageMappings; + /** Pointer to the resource table in the loader section. */ + const struct rsrc32 *paRsrcs; + /** Pointer to the resident name table in the loader section. */ + const uint8_t *pbResNameTab; + /** Pointer to the entry table in the loader section. */ + const uint8_t *pbEntryTab; + + /** Pointer to the non-resident name table. */ + uint8_t *pbNonResNameTab; + /** Pointer to the last byte in the non-resident name table. */ + const uint8_t *pbNonResNameTabLast; + + /** Pointer to the fixup section. */ + uint8_t *pbFixupSection; + /** Pointer to the last byte in the fixup section. */ + const uint8_t *pbFixupSectionLast; + /** Pointer to the fixup page table within pvFixupSection. */ + const uint32_t *paoffPageFixups; + /** Pointer to the fixup record table within pvFixupSection. */ + const uint8_t *pbFixupRecs; + /** Pointer to the import module name table within pvFixupSection. */ + const uint8_t *pbImportMods; + /** Pointer to the import module name table within pvFixupSection. */ + const uint8_t *pbImportProcs; + + /** Pointer to the module name (in the resident name table). */ + const char *pszName; + /** The name length. */ + size_t cchName; + + /** The target CPU. */ + RTLDRCPU enmCpu; + /** Number of segments in aSegments. */ + uint32_t cSegments; + /** Segment info. */ + RT_FLEXIBLE_ARRAY_EXTENSION + RTLDRSEG aSegments[RT_FLEXIBLE_ARRAY]; +} KLDRMODLX, *PKLDRMODLX; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int kldrModLXHasDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits); +static DECLCALLBACK(int) rtldrLX_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, + RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser); +static const uint8_t *kldrModLXDoNameTableLookupByOrdinal(const uint8_t *pbNameTable, ssize_t cbNameTable, uint32_t iOrdinal); +static int kldrModLXDoNameLookup(PKLDRMODLX pModLX, const char *pchSymbol, size_t cchSymbol, uint32_t *piSymbol); +static const uint8_t *kldrModLXDoNameTableLookupByName(const uint8_t *pbNameTable, ssize_t cbNameTable, + const char *pchSymbol, size_t cchSymbol); +static int kldrModLXGetImport(PKLDRMODLX pThis, const void *pvBits, uint32_t iImport, + char *pszName, size_t cchName, size_t *pcbNeeded); +static int kldrModLXDoLoadBits(PKLDRMODLX pModLX, void *pvBits); +static int kldrModLXDoIterDataUnpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc); +static int kldrModLXDoIterData2Unpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc); +static void kLdrModLXMemCopyW(uint8_t *pbDst, const uint8_t *pbSrc, int cb); +static int kldrModLXDoForwarderQuery(PKLDRMODLX pModLX, const struct e32_entry *pEntry, + PFNRTLDRIMPORT pfnGetForwarder, void *pvUser, PRTLDRADDR puValue, uint32_t *pfKind); +#if 0 +static int kldrModLXDoProtect(PKLDRMODLX pModLX, void *pvBits, unsigned fUnprotectOrProtect); +static int kldrModLXDoCallDLL(PKLDRMODLX pModLX, void *pvMapping, unsigned uOp, uintptr_t uHandle); +static int32_t kldrModLXDoCall(uintptr_t uEntrypoint, uintptr_t uHandle, uint32_t uOp, void *pvReserved); +#endif +static int kldrModLXDoLoadFixupSection(PKLDRMODLX pModLX); +static int kldrModLXDoReloc(uint8_t *pbPage, int off, RTLDRADDR PageAddress, const struct r32_rlc *prlc, + int iSelector, RTLDRADDR uValue, uint32_t fKind); + + +/** + * Separate function for reading creating the LX module instance to + * simplify cleanup on failure. + */ +static int kldrModLXDoCreate(PRTLDRREADER pRdr, RTFOFF offNewHdr, uint32_t fFlags, PKLDRMODLX *ppModLX, PRTERRINFO pErrInfo) +{ + struct e32_exe Hdr; + PKLDRMODLX pModLX; + uint32_t off, offEnd; + uint32_t i; + int fCanOptimizeMapping; + uint32_t NextRVA; + + RT_NOREF(fFlags); + *ppModLX = NULL; + + /* + * Read the signature and file header. + */ + int rc = pRdr->pfnRead(pRdr, &Hdr, sizeof(Hdr), offNewHdr > 0 ? offNewHdr : 0); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, rc, "Error reading LX header at %RTfoff: %Rrc", offNewHdr, rc); + if ( Hdr.e32_magic[0] != E32MAGIC1 + || Hdr.e32_magic[1] != E32MAGIC2) + return RTErrInfoSetF(pErrInfo, VERR_INVALID_EXE_SIGNATURE, "Not LX magic: %02x %02x", Hdr.e32_magic[0], Hdr.e32_magic[1]); + + /* We're not interested in anything but x86 images. */ + if ( Hdr.e32_level != E32LEVEL + || Hdr.e32_border != E32LEBO + || Hdr.e32_worder != E32LEWO + || Hdr.e32_cpu < E32CPU286 + || Hdr.e32_cpu > E32CPU486 + || Hdr.e32_pagesize != OBJPAGELEN + ) + return VERR_LDRLX_BAD_HEADER; + + /* Some rough sanity checks. */ + offEnd = pRdr->pfnSize(pRdr) >= (uint64_t)~(uint32_t)16 ? ~(uint32_t)16 : (uint32_t)pRdr->pfnSize(pRdr); + if ( Hdr.e32_itermap > offEnd + || Hdr.e32_datapage > offEnd + || Hdr.e32_nrestab > offEnd + || Hdr.e32_nrestab + Hdr.e32_cbnrestab > offEnd + || Hdr.e32_ldrsize > offEnd - offNewHdr - sizeof(Hdr) + || Hdr.e32_fixupsize > offEnd - offNewHdr - sizeof(Hdr) + || Hdr.e32_fixupsize + Hdr.e32_ldrsize > offEnd - offNewHdr - sizeof(Hdr)) + return VERR_LDRLX_BAD_HEADER; + + /* Verify the loader section. */ + offEnd = Hdr.e32_objtab + Hdr.e32_ldrsize; + if (Hdr.e32_objtab < sizeof(Hdr) && Hdr.e32_objcnt) + return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION, + "Object table is inside the header: %#x", Hdr.e32_objtab); + off = Hdr.e32_objtab + sizeof(struct o32_obj) * Hdr.e32_objcnt; + if (off > offEnd) + return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION, + "Object table spans beyond the executable: e32_objcnt=%u", Hdr.e32_objcnt); + if (Hdr.e32_objcnt >= _32K) + return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION, "Too many segments: %#x\n", Hdr.e32_objcnt); + if ( Hdr.e32_objmap + && (Hdr.e32_objmap < off || Hdr.e32_objmap > offEnd)) + return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION, + "Bad object page map table offset: %#x", Hdr.e32_objmap); + if ( Hdr.e32_rsrccnt + && ( Hdr.e32_rsrctab < off + || Hdr.e32_rsrctab > offEnd + || Hdr.e32_rsrctab + sizeof(struct rsrc32) * Hdr.e32_rsrccnt > offEnd)) + return RTErrInfoSetF(pErrInfo, VERR_LDRLX_BAD_LOADER_SECTION, + "Resource table is out of bounds: %#x entries at %#x", Hdr.e32_rsrccnt, Hdr.e32_rsrctab); + if ( Hdr.e32_restab + && (Hdr.e32_restab < off || Hdr.e32_restab > offEnd - 2)) + return VERR_LDRLX_BAD_LOADER_SECTION; + if ( Hdr.e32_enttab + && (Hdr.e32_enttab < off || Hdr.e32_enttab >= offEnd)) + return VERR_LDRLX_BAD_LOADER_SECTION; + if ( Hdr.e32_dircnt + && (Hdr.e32_dirtab < off || Hdr.e32_dirtab > offEnd - 2)) + return VERR_LDRLX_BAD_LOADER_SECTION; + + /* Verify the fixup section. */ + off = offEnd; + offEnd = off + Hdr.e32_fixupsize; + if ( Hdr.e32_fpagetab + && (Hdr.e32_fpagetab < off || Hdr.e32_fpagetab > offEnd)) + { + /* + * wlink mixes the fixup section and the loader section. + */ + off = Hdr.e32_fpagetab; + offEnd = off + Hdr.e32_fixupsize; + Hdr.e32_ldrsize = off - Hdr.e32_objtab; + } + if ( Hdr.e32_frectab + && (Hdr.e32_frectab < off || Hdr.e32_frectab > offEnd)) + return VERR_LDRLX_BAD_FIXUP_SECTION; + if ( Hdr.e32_impmod + && (Hdr.e32_impmod < off || Hdr.e32_impmod > offEnd || Hdr.e32_impmod + Hdr.e32_impmodcnt > offEnd)) + return VERR_LDRLX_BAD_FIXUP_SECTION; + if ( Hdr.e32_impproc + && (Hdr.e32_impproc < off || Hdr.e32_impproc > offEnd)) + return VERR_LDRLX_BAD_FIXUP_SECTION; + + /* + * Calc the instance size, allocate and initialize it. + */ + size_t cbModLXAndSegments = RT_ALIGN_Z(RT_UOFFSETOF_DYN(KLDRMODLX, aSegments[Hdr.e32_objcnt + 1]), 8); + cbModLXAndSegments += sizeof("segXXXXX") * (Hdr.e32_objcnt + 1); + + pModLX = (PKLDRMODLX)RTMemAlloc(cbModLXAndSegments + Hdr.e32_ldrsize + 2 /*for two extra zeros*/); + if (!pModLX) + return VERR_NO_MEMORY; + *ppModLX = pModLX; + + /* Core & CPU. */ + pModLX->Core.u32Magic = 0; /* set by caller. */ + pModLX->Core.eState = LDR_STATE_OPENED; + pModLX->Core.pOps = NULL; /* set by caller. */ + pModLX->Core.pReader = pRdr; + switch (Hdr.e32_cpu) + { + case E32CPU286: + pModLX->enmCpu = RTLDRCPU_I80286; + pModLX->Core.enmArch = RTLDRARCH_X86_16; + break; + case E32CPU386: + pModLX->enmCpu = RTLDRCPU_I386; + pModLX->Core.enmArch = RTLDRARCH_X86_32; + break; + case E32CPU486: + pModLX->enmCpu = RTLDRCPU_I486; + pModLX->Core.enmArch = RTLDRARCH_X86_32; + break; + } + pModLX->Core.enmEndian = RTLDRENDIAN_LITTLE; + pModLX->Core.enmFormat = RTLDRFMT_LX; + switch (Hdr.e32_mflags & E32MODMASK) + { + case E32MODEXE: + pModLX->Core.enmType = !(Hdr.e32_mflags & E32NOINTFIX) + ? RTLDRTYPE_EXECUTABLE_RELOCATABLE + : RTLDRTYPE_EXECUTABLE_FIXED; + break; + + case E32MODDLL: + case E32PROTDLL: + case E32MODPROTDLL: + pModLX->Core.enmType = !(Hdr.e32_mflags & E32SYSDLL) + ? RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE + : RTLDRTYPE_SHARED_LIBRARY_FIXED; + break; + + case E32MODPDEV: + case E32MODVDEV: + pModLX->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; + break; + } + + /* KLDRMODLX */ + pModLX->cSegments = Hdr.e32_objcnt; + pModLX->pszName = NULL; /* finalized further down */ + pModLX->cchName = 0; + pModLX->pvMapping = 0; + pModLX->cbMapped = 0; + pModLX->f32Reserved = 0; + + pModLX->offHdr = offNewHdr >= 0 ? offNewHdr : 0; + memcpy(&pModLX->Hdr, &Hdr, sizeof(Hdr)); + + pModLX->pbLoaderSection = (uint8_t *)pModLX + cbModLXAndSegments; + pModLX->pbLoaderSectionLast = pModLX->pbLoaderSection + pModLX->Hdr.e32_ldrsize - 1; + pModLX->paObjs = NULL; + pModLX->paPageMappings = NULL; + pModLX->paRsrcs = NULL; + pModLX->pbResNameTab = NULL; + pModLX->pbEntryTab = NULL; + + pModLX->pbNonResNameTab = NULL; + pModLX->pbNonResNameTabLast = NULL; + + pModLX->pbFixupSection = NULL; + pModLX->pbFixupSectionLast = NULL; + pModLX->paoffPageFixups = NULL; + pModLX->pbFixupRecs = NULL; + pModLX->pbImportMods = NULL; + pModLX->pbImportProcs = NULL; + + /* + * Read the loader data. + */ + rc = pRdr->pfnRead(pRdr, (void *)pModLX->pbLoaderSection, pModLX->Hdr.e32_ldrsize, pModLX->Hdr.e32_objtab + pModLX->offHdr); + if (RT_FAILURE(rc)) + return rc; + ((uint8_t *)pModLX->pbLoaderSectionLast)[1] = 0; + ((uint8_t *)pModLX->pbLoaderSectionLast)[2] = 0; + if (pModLX->Hdr.e32_objcnt) + pModLX->paObjs = (const struct o32_obj *)pModLX->pbLoaderSection; + if (pModLX->Hdr.e32_objmap) + pModLX->paPageMappings = (const struct o32_map *)(pModLX->pbLoaderSection + pModLX->Hdr.e32_objmap - pModLX->Hdr.e32_objtab); + if (pModLX->Hdr.e32_rsrccnt) + pModLX->paRsrcs = (const struct rsrc32 *)(pModLX->pbLoaderSection + pModLX->Hdr.e32_rsrctab - pModLX->Hdr.e32_objtab); + if (pModLX->Hdr.e32_restab) + pModLX->pbResNameTab = pModLX->pbLoaderSection + pModLX->Hdr.e32_restab - pModLX->Hdr.e32_objtab; + if (pModLX->Hdr.e32_enttab) + pModLX->pbEntryTab = pModLX->pbLoaderSection + pModLX->Hdr.e32_enttab - pModLX->Hdr.e32_objtab; + + /* + * Get the soname from the resident name table. + * Very convenient that it's the 0 ordinal, because then we get a + * free string terminator. + * (The table entry consists of a pascal string followed by a 16-bit ordinal.) + */ + if (pModLX->pbResNameTab) + pModLX->pszName = (const char *)kldrModLXDoNameTableLookupByOrdinal(pModLX->pbResNameTab, + pModLX->pbLoaderSectionLast - pModLX->pbResNameTab + 1, + 0); + if (!pModLX->pszName) + return VERR_LDRLX_NO_SONAME; + pModLX->cchName = *(const uint8_t *)pModLX->pszName++; + if ( pModLX->pszName[pModLX->cchName] != '\0' + || pModLX->cchName != strlen(pModLX->pszName)) + return VERR_LDRLX_BAD_SONAME; + + /* + * Quick validation of the object table. + */ + for (i = 0; i < pModLX->cSegments; i++) + { + if (pModLX->paObjs[i].o32_base & (OBJPAGELEN - 1)) + return VERR_LDRLX_BAD_OBJECT_TABLE; + if (pModLX->paObjs[i].o32_base + pModLX->paObjs[i].o32_size <= pModLX->paObjs[i].o32_base) + return VERR_LDRLX_BAD_OBJECT_TABLE; + if (pModLX->paObjs[i].o32_mapsize > (pModLX->paObjs[i].o32_size + (OBJPAGELEN - 1))) + return VERR_LDRLX_BAD_OBJECT_TABLE; + if ( pModLX->paObjs[i].o32_mapsize + && ( (uint8_t *)&pModLX->paPageMappings[pModLX->paObjs[i].o32_pagemap] > pModLX->pbLoaderSectionLast + || (uint8_t *)&pModLX->paPageMappings[pModLX->paObjs[i].o32_pagemap + pModLX->paObjs[i].o32_mapsize] + > pModLX->pbLoaderSectionLast)) + return VERR_LDRLX_BAD_OBJECT_TABLE; + if (i > 0 && !(pModLX->paObjs[i].o32_flags & OBJRSRC)) + { + if (pModLX->paObjs[i].o32_base <= pModLX->paObjs[i - 1].o32_base) + return VERR_LDRLX_BAD_OBJECT_TABLE; + if (pModLX->paObjs[i].o32_base < pModLX->paObjs[i - 1].o32_base + pModLX->paObjs[i - 1].o32_mapsize) + return VERR_LDRLX_BAD_OBJECT_TABLE; + } + } + + /* + * Check if we can optimize the mapping by using a different + * object alignment. The linker typically uses 64KB alignment, + * we can easily get away with page alignment in most cases. + * + * However, this screws up DwARF debug info, let's not do this + * when the purpose is reading debug info. + */ + /** @todo Add flag for enabling this optimization. */ + fCanOptimizeMapping = !(Hdr.e32_mflags & (E32NOINTFIX | E32SYSDLL)) + && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)); + NextRVA = 0; + + /* + * Setup the KLDRMOD segment array. + */ + char *pszSegNm = (char *)&pModLX->aSegments[pModLX->cSegments]; + for (i = 0; i < pModLX->cSegments; i++) + { + /* dummy segment name */ + pModLX->aSegments[i].pszName = pszSegNm; + size_t cchName = RTStrPrintf(pszSegNm, sizeof("segXXXXX"), "seg%u", i); + pszSegNm += cchName + 1; + pModLX->aSegments[i].cchName = (uint32_t)cchName; + + /* unused */ + pModLX->aSegments[i].offFile = -1; + pModLX->aSegments[i].cbFile = -1; + pModLX->aSegments[i].SelFlat = 0; + pModLX->aSegments[i].Sel16bit = 0; + + /* flags */ + pModLX->aSegments[i].fFlags = 0; + if (pModLX->paObjs[i].o32_flags & OBJBIGDEF) + pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_16BIT; + if (pModLX->paObjs[i].o32_flags & OBJALIAS16) + pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_OS2_ALIAS16; + if (pModLX->paObjs[i].o32_flags & OBJCONFORM) + pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_OS2_CONFORM; + if (pModLX->paObjs[i].o32_flags & OBJIOPL) + pModLX->aSegments[i].fFlags = RTLDRSEG_FLAG_OS2_IOPL; + + /* size and addresses */ + pModLX->aSegments[i].Alignment = OBJPAGELEN; + pModLX->aSegments[i].cb = pModLX->paObjs[i].o32_size; + pModLX->aSegments[i].LinkAddress = pModLX->paObjs[i].o32_base; + pModLX->aSegments[i].RVA = NextRVA; + if ( fCanOptimizeMapping + || i + 1 >= pModLX->cSegments + || (pModLX->paObjs[i].o32_flags & OBJRSRC) + || (pModLX->paObjs[i + 1].o32_flags & OBJRSRC)) + pModLX->aSegments[i].cbMapped = RT_ALIGN_Z(pModLX->paObjs[i].o32_size, OBJPAGELEN); + else + pModLX->aSegments[i].cbMapped = pModLX->paObjs[i + 1].o32_base - pModLX->paObjs[i].o32_base; + /** @todo Above probably doesn't work for os2krnl and other images + * non-sequential virtual address assignments. */ + NextRVA += (uint32_t)pModLX->aSegments[i].cbMapped; + + /* protection */ + switch ( pModLX->paObjs[i].o32_flags + & (OBJSHARED | OBJREAD | OBJWRITE | OBJEXEC)) + { + case 0: + case OBJSHARED: + pModLX->aSegments[i].fProt = 0; + break; + case OBJREAD: + case OBJREAD | OBJSHARED: + pModLX->aSegments[i].fProt = RTMEM_PROT_READ; + break; + case OBJWRITE: + case OBJWRITE | OBJREAD: + pModLX->aSegments[i].fProt = RTMEM_PROT_READ | RTMEM_PROT_WRITECOPY; + break; + case OBJWRITE | OBJSHARED: + case OBJWRITE | OBJSHARED | OBJREAD: + pModLX->aSegments[i].fProt = RTMEM_PROT_READ | RTMEM_PROT_WRITE; + break; + case OBJEXEC: + case OBJEXEC | OBJSHARED: + pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC; + break; + case OBJEXEC | OBJREAD: + case OBJEXEC | OBJREAD | OBJSHARED: + pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC | RTMEM_PROT_READ; + break; + case OBJEXEC | OBJWRITE: + case OBJEXEC | OBJWRITE | OBJREAD: + pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC | RTMEM_PROT_READ | RTMEM_PROT_WRITECOPY; + break; + case OBJEXEC | OBJWRITE | OBJSHARED: + case OBJEXEC | OBJWRITE | OBJSHARED | OBJREAD: + pModLX->aSegments[i].fProt = RTMEM_PROT_EXEC | RTMEM_PROT_READ | RTMEM_PROT_WRITE; + break; + } + if ((pModLX->paObjs[i].o32_flags & (OBJREAD | OBJWRITE | OBJEXEC | OBJRSRC)) == OBJRSRC) + pModLX->aSegments[i].fProt = RTMEM_PROT_READ; + /*pModLX->aSegments[i].f16bit = !(pModLX->paObjs[i].o32_flags & OBJBIGDEF) + pModLX->aSegments[i].fIOPL = !(pModLX->paObjs[i].o32_flags & OBJIOPL) + pModLX->aSegments[i].fConforming = !(pModLX->paObjs[i].o32_flags & OBJCONFORM) */ + } + + /* set the mapping size */ + pModLX->cbMapped = NextRVA; + + /* + * We're done. + */ + *ppModLX = pModLX; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnClose} + */ +static DECLCALLBACK(int) rtldrLX_Close(PRTLDRMODINTERNAL pMod) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + KLDRMODLX_ASSERT(!pModLX->pvMapping); + + if (pModLX->pbNonResNameTab) + { + RTMemFree(pModLX->pbNonResNameTab); + pModLX->pbNonResNameTab = NULL; + } + if (pModLX->pbFixupSection) + { + RTMemFree(pModLX->pbFixupSection); + pModLX->pbFixupSection = NULL; + } + return VINF_SUCCESS; +} + + +/** + * Resolved base address aliases. + * + * @param pModLX The interpreter module instance + * @param pBaseAddress The base address, IN & OUT. + */ +static void kldrModLXResolveBaseAddress(PKLDRMODLX pModLX, PRTLDRADDR pBaseAddress) +{ + if (*pBaseAddress == RTLDR_BASEADDRESS_LINK) + *pBaseAddress = pModLX->aSegments[0].LinkAddress; +} + + +static int kldrModLXQuerySymbol(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, uint32_t iSymbol, + const char *pchSymbol, size_t cchSymbol, const char *pszVersion, + PFNRTLDRIMPORT pfnGetForwarder, void *pvUser, PRTLDRADDR puValue, uint32_t *pfKind) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t iOrdinal; + int rc; + const struct b32_bundle *pBundle; + RT_NOREF(pvBits); + RT_NOREF(pszVersion); + + /* + * Give up at once if there is no entry table. + */ + if (!pModLX->Hdr.e32_enttab) + return VERR_SYMBOL_NOT_FOUND; + + /* + * Translate the symbol name into an ordinal. + */ + if (pchSymbol) + { + rc = kldrModLXDoNameLookup(pModLX, pchSymbol, cchSymbol, &iSymbol); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Iterate the entry table. + * (The entry table is made up of bundles of similar exports.) + */ + iOrdinal = 1; + pBundle = (const struct b32_bundle *)pModLX->pbEntryTab; + while (pBundle->b32_cnt && iOrdinal <= iSymbol) + { + static const size_t s_cbEntry[] = { 0, 3, 5, 5, 7 }; + + /* + * Check for a hit first. + */ + iOrdinal += pBundle->b32_cnt; + if (iSymbol < iOrdinal) + { + uint32_t offObject; + const struct e32_entry *pEntry = (const struct e32_entry *)((uintptr_t)(pBundle + 1) + + (iSymbol - (iOrdinal - pBundle->b32_cnt)) + * s_cbEntry[pBundle->b32_type]); + + /* + * Calculate the return address. + */ + kldrModLXResolveBaseAddress(pModLX, &BaseAddress); + switch (pBundle->b32_type) + { + /* empty bundles are place holders unused ordinal ranges. */ + case EMPTY: + return VERR_SYMBOL_NOT_FOUND; + + /* e32_flags + a 16-bit offset. */ + case ENTRY16: + offObject = pEntry->e32_variant.e32_offset.offset16; + if (pfKind) + *pfKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_NO_TYPE; + break; + + /* e32_flags + a 16-bit offset + a 16-bit callgate selector. */ + case GATE16: + offObject = pEntry->e32_variant.e32_callgate.offset; + if (pfKind) + *pfKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_CODE; + break; + + /* e32_flags + a 32-bit offset. */ + case ENTRY32: + offObject = pEntry->e32_variant.e32_offset.offset32; + if (pfKind) + *pfKind = RTLDRSYMKIND_32BIT; + break; + + /* e32_flags + 16-bit import module ordinal + a 32-bit procname or ordinal. */ + case ENTRYFWD: + return kldrModLXDoForwarderQuery(pModLX, pEntry, pfnGetForwarder, pvUser, puValue, pfKind); + + default: + /* anyone actually using TYPEINFO will end up here. */ + KLDRMODLX_ASSERT(!"Bad bundle type"); + return VERR_LDRLX_BAD_BUNDLE; + } + + /* + * Validate the object number and calc the return address. + */ + if ( pBundle->b32_obj <= 0 + || pBundle->b32_obj > pModLX->cSegments) + return VERR_LDRLX_BAD_BUNDLE; + if (puValue) + *puValue = BaseAddress + + offObject + + pModLX->aSegments[pBundle->b32_obj - 1].RVA; + return VINF_SUCCESS; + } + + /* + * Skip the bundle. + */ + if (pBundle->b32_type > ENTRYFWD) + { + KLDRMODLX_ASSERT(!"Bad type"); /** @todo figure out TYPEINFO. */ + return VERR_LDRLX_BAD_BUNDLE; + } + if (pBundle->b32_type == 0) + pBundle = (const struct b32_bundle *)((const uint8_t *)pBundle + 2); + else + pBundle = (const struct b32_bundle *)((const uint8_t *)(pBundle + 1) + s_cbEntry[pBundle->b32_type] * pBundle->b32_cnt); + } + + return VERR_SYMBOL_NOT_FOUND; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnGetSymbolEx} + */ +static DECLCALLBACK(int) rtldrLX_GetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress, + uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue) +{ + uint32_t fKind = RTLDRSYMKIND_REQ_FLAT; + return kldrModLXQuerySymbol(pMod, pvBits, BaseAddress, iOrdinal, pszSymbol, pszSymbol ? strlen(pszSymbol) : 0, + NULL, NULL, NULL, pValue, &fKind); +} + + +/** + * Do name lookup. + * + * @returns IPRT status code. + * @param pModLX The module to lookup the symbol in. + * @param pchSymbol The symbol to lookup. + * @param cchSymbol The symbol name length. + * @param piSymbol Where to store the symbol ordinal. + */ +static int kldrModLXDoNameLookup(PKLDRMODLX pModLX, const char *pchSymbol, size_t cchSymbol, uint32_t *piSymbol) +{ + + /* + * First do a hash table lookup. + */ + /** @todo hash name table for speed. */ + + /* + * Search the name tables. + */ + const uint8_t *pbName = kldrModLXDoNameTableLookupByName(pModLX->pbResNameTab, + pModLX->pbLoaderSectionLast - pModLX->pbResNameTab + 1, + pchSymbol, cchSymbol); + if (!pbName) + { + if (!pModLX->pbNonResNameTab) + { + /* lazy load it */ + /** @todo non-resident name table. */ + } + if (pModLX->pbNonResNameTab) + pbName = kldrModLXDoNameTableLookupByName(pModLX->pbResNameTab, + pModLX->pbNonResNameTabLast - pModLX->pbResNameTab + 1, + pchSymbol, cchSymbol); + } + if (!pbName) + return VERR_SYMBOL_NOT_FOUND; + + *piSymbol = *(const uint16_t *)(pbName + 1 + *pbName); + return VINF_SUCCESS; +} + + +/** + * Lookup a name table entry by name. + * + * @returns Pointer to the name table entry if found. + * @returns NULL if not found. + * @param pbNameTable Pointer to the name table that should be searched. + * @param cbNameTable The size of the name table. + * @param pchSymbol The name of the symbol we're looking for. + * @param cchSymbol The length of the symbol name. + */ +static const uint8_t *kldrModLXDoNameTableLookupByName(const uint8_t *pbNameTable, ssize_t cbNameTable, + const char *pchSymbol, size_t cchSymbol) +{ + /* + * Determin the namelength up front so we can skip anything which doesn't matches the length. + */ + uint8_t cbSymbol8Bit = (uint8_t)cchSymbol; + if (cbSymbol8Bit != cchSymbol) + return NULL; /* too long. */ + + /* + * Walk the name table. + */ + while (*pbNameTable != 0 && cbNameTable > 0) + { + const uint8_t cbName = *pbNameTable; + + cbNameTable -= cbName + 1 + 2; + if (cbNameTable < 0) + break; + + if ( cbName == cbSymbol8Bit + && !memcmp(pbNameTable + 1, pchSymbol, cbName)) + return pbNameTable; + + /* next entry */ + pbNameTable += cbName + 1 + 2; + } + + return NULL; +} + + +/** + * Deal with a forwarder entry. + * + * @returns IPRT status code. + * @param pModLX The PE module interpreter instance. + * @param pEntry The forwarder entry. + * @param pfnGetForwarder The callback for resolving forwarder symbols. (optional) + * @param pvUser The user argument for the callback. + * @param puValue Where to put the value. (optional) + * @param pfKind Where to put the symbol kind. (optional) + */ +static int kldrModLXDoForwarderQuery(PKLDRMODLX pModLX, const struct e32_entry *pEntry, + PFNRTLDRIMPORT pfnGetForwarder, void *pvUser, PRTLDRADDR puValue, uint32_t *pfKind) +{ + if (!pfnGetForwarder) + return VERR_LDR_FORWARDER; + + /* + * Validate the entry import module ordinal. + */ + if ( !pEntry->e32_variant.e32_fwd.modord + || pEntry->e32_variant.e32_fwd.modord > pModLX->Hdr.e32_impmodcnt) + return VERR_LDRLX_BAD_FORWARDER; + + char szImpModule[256]; + int rc = kldrModLXGetImport(pModLX, NULL, pEntry->e32_variant.e32_fwd.modord - 1, szImpModule, sizeof(szImpModule), NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Figure out the parameters. + */ + uint32_t iSymbol; + const char *pszSymbol; + char szSymbol[256]; + if (pEntry->e32_flags & FWD_ORDINAL) + { + iSymbol = pEntry->e32_variant.e32_fwd.value; + pszSymbol = NULL; /* no symbol name. */ + } + else + { + const uint8_t *pbName; + + /* load the fixup section if necessary. */ + if (!pModLX->pbImportProcs) + { + rc = kldrModLXDoLoadFixupSection(pModLX); + if (RT_FAILURE(rc)) + return rc; + } + + /* Make name pointer. */ + pbName = pModLX->pbImportProcs + pEntry->e32_variant.e32_fwd.value; + if ( pbName >= pModLX->pbFixupSectionLast + || pbName < pModLX->pbFixupSection + || !*pbName) + return VERR_LDRLX_BAD_FORWARDER; + + + /* check for '#' name. */ + if (pbName[1] == '#') + { + uint8_t cbLeft = *pbName; + const uint8_t *pb = pbName + 1; + unsigned uBase; + + /* base detection */ + uBase = 10; + if ( cbLeft > 1 + && pb[1] == '0' + && (pb[2] == 'x' || pb[2] == 'X')) + { + uBase = 16; + pb += 2; + cbLeft -= 2; + } + + /* ascii to integer */ + iSymbol = 0; + while (cbLeft-- > 0) + { + /* convert char to digit. */ + unsigned uDigit = *pb++; + if (uDigit >= '0' && uDigit <= '9') + uDigit -= '0'; + else if (uDigit >= 'a' && uDigit <= 'z') + uDigit -= 'a' + 10; + else if (uDigit >= 'A' && uDigit <= 'Z') + uDigit -= 'A' + 10; + else if (!uDigit) + break; + else + return VERR_LDRLX_BAD_FORWARDER; + if (uDigit >= uBase) + return VERR_LDRLX_BAD_FORWARDER; + + /* insert the digit */ + iSymbol *= uBase; + iSymbol += uDigit; + } + if (!iSymbol) + return VERR_LDRLX_BAD_FORWARDER; + + pszSymbol = NULL; /* no symbol name. */ + } + else + { + memcpy(szSymbol, pbName + 1, *pbName); + szSymbol[*pbName] = '\0'; + pszSymbol = szSymbol; + iSymbol = UINT32_MAX; + } + } + + /* + * Resolve the forwarder. + */ + rc = pfnGetForwarder(&pModLX->Core, szImpModule, pszSymbol, iSymbol, puValue, /*pfKind, */pvUser); + if (RT_SUCCESS(rc) && pfKind) + *pfKind |= RTLDRSYMKIND_FORWARDER; + return rc; +} + + +/** + * Loads the fixup section from the executable image. + * + * The fixup section isn't loaded until it's accessed. It's also freed by kLdrModDone(). + * + * @returns IPRT status code. + * @param pModLX The PE module interpreter instance. + */ +static int kldrModLXDoLoadFixupSection(PKLDRMODLX pModLX) +{ + void *pv = RTMemAlloc(pModLX->Hdr.e32_fixupsize); + if (!pv) + return VERR_NO_MEMORY; + + uint32_t off = pModLX->Hdr.e32_objtab + pModLX->Hdr.e32_ldrsize; + int rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, pv, pModLX->Hdr.e32_fixupsize, + off + pModLX->offHdr); + if (RT_SUCCESS(rc)) + { + pModLX->pbFixupSection = (uint8_t *)pv; + pModLX->pbFixupSectionLast = pModLX->pbFixupSection + pModLX->Hdr.e32_fixupsize; + KLDRMODLX_ASSERT(!pModLX->paoffPageFixups); + if (pModLX->Hdr.e32_fpagetab) + pModLX->paoffPageFixups = (const uint32_t *)(pModLX->pbFixupSection + pModLX->Hdr.e32_fpagetab - off); + KLDRMODLX_ASSERT(!pModLX->pbFixupRecs); + if (pModLX->Hdr.e32_frectab) + pModLX->pbFixupRecs = pModLX->pbFixupSection + pModLX->Hdr.e32_frectab - off; + KLDRMODLX_ASSERT(!pModLX->pbImportMods); + if (pModLX->Hdr.e32_impmod) + pModLX->pbImportMods = pModLX->pbFixupSection + pModLX->Hdr.e32_impmod - off; + KLDRMODLX_ASSERT(!pModLX->pbImportProcs); + if (pModLX->Hdr.e32_impproc) + pModLX->pbImportProcs = pModLX->pbFixupSection + pModLX->Hdr.e32_impproc - off; + } + else + RTMemFree(pv); + return rc; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnEnumSymbols} + */ +static DECLCALLBACK(int) rtldrLX_EnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, + RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + RT_NOREF(pvBits); + RT_NOREF(fFlags); + + kldrModLXResolveBaseAddress(pModLX, &BaseAddress); + + /* + * Enumerate the entry table. + * (The entry table is made up of bundles of similar exports.) + */ + int rc = VINF_SUCCESS; + uint32_t iOrdinal = 1; + const struct b32_bundle *pBundle = (const struct b32_bundle *)pModLX->pbEntryTab; + while (pBundle->b32_cnt && iOrdinal) + { + static const size_t s_cbEntry[] = { 0, 3, 5, 5, 7 }; + + /* + * Enum the entries in the bundle. + */ + if (pBundle->b32_type != EMPTY) + { + const struct e32_entry *pEntry; + size_t cbEntry; + RTLDRADDR BundleRVA; + unsigned cLeft; + + + /* Validate the bundle. */ + switch (pBundle->b32_type) + { + case ENTRY16: + case GATE16: + case ENTRY32: + if ( pBundle->b32_obj <= 0 + || pBundle->b32_obj > pModLX->cSegments) + return VERR_LDRLX_BAD_BUNDLE; + BundleRVA = pModLX->aSegments[pBundle->b32_obj - 1].RVA; + break; + + case ENTRYFWD: + BundleRVA = 0; + break; + + default: + /* anyone actually using TYPEINFO will end up here. */ + KLDRMODLX_ASSERT(!"Bad bundle type"); + return VERR_LDRLX_BAD_BUNDLE; + } + + /* iterate the bundle entries. */ + cbEntry = s_cbEntry[pBundle->b32_type]; + pEntry = (const struct e32_entry *)(pBundle + 1); + cLeft = pBundle->b32_cnt; + while (cLeft-- > 0) + { + RTLDRADDR uValue; + uint32_t fKind; + int fFoundName; + const uint8_t *pbName; + + /* + * Calc the symbol value and kind. + */ + switch (pBundle->b32_type) + { + /* e32_flags + a 16-bit offset. */ + case ENTRY16: + uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_offset.offset16; + fKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_NO_TYPE; + break; + + /* e32_flags + a 16-bit offset + a 16-bit callgate selector. */ + case GATE16: + uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_callgate.offset; + fKind = RTLDRSYMKIND_16BIT | RTLDRSYMKIND_CODE; + break; + + /* e32_flags + a 32-bit offset. */ + case ENTRY32: + uValue = BaseAddress + BundleRVA + pEntry->e32_variant.e32_offset.offset32; + fKind = RTLDRSYMKIND_32BIT; + break; + + /* e32_flags + 16-bit import module ordinal + a 32-bit procname or ordinal. */ + case ENTRYFWD: + uValue = 0; /** @todo implement enumeration of forwarders properly. */ + fKind = RTLDRSYMKIND_FORWARDER; + break; + + default: /* shut up gcc. */ + uValue = 0; + fKind = RTLDRSYMKIND_NO_BIT | RTLDRSYMKIND_NO_TYPE; + break; + } + + /* + * Any symbol names? + */ + fFoundName = 0; + char szName[256]; + + /* resident name table. */ + pbName = pModLX->pbResNameTab; + if (pbName) + { + do + { + pbName = kldrModLXDoNameTableLookupByOrdinal(pbName, pModLX->pbLoaderSectionLast - pbName + 1, iOrdinal); + if (!pbName) + break; + fFoundName = 1; + memcpy(szName, (const char *)pbName + 1, *pbName); + szName[*pbName] = '\0'; + rc = pfnCallback(pMod, szName, iOrdinal, uValue, /*fKind,*/ pvUser); + if (rc != VINF_SUCCESS) + return rc; + + /* skip to the next entry */ + pbName += 1 + *pbName + 2; + } while (pbName < pModLX->pbLoaderSectionLast); + } + + /* resident name table. */ + pbName = pModLX->pbNonResNameTab; + /** @todo lazy load the non-resident name table. */ + if (pbName) + { + do + { + pbName = kldrModLXDoNameTableLookupByOrdinal(pbName, pModLX->pbNonResNameTabLast - pbName + 1, iOrdinal); + if (!pbName) + break; + fFoundName = 1; + memcpy(szName, (const char *)pbName + 1, *pbName); + szName[*pbName] = '\0'; + rc = pfnCallback(pMod, szName, iOrdinal, uValue, /*fKind,*/ pvUser); + if (rc != VINF_SUCCESS) + return rc; + + /* skip to the next entry */ + pbName += 1 + *pbName + 2; + } while (pbName < pModLX->pbLoaderSectionLast); + } + + /* + * If no names, call once with the ordinal only. + */ + if (!fFoundName) + { + RT_NOREF(fKind); + rc = pfnCallback(pMod, NULL /*pszName*/, iOrdinal, uValue, /*fKind,*/ pvUser); + if (rc != VINF_SUCCESS) + return rc; + } + + /* next */ + iOrdinal++; + pEntry = (const struct e32_entry *)((uintptr_t)pEntry + cbEntry); + } + } + + /* + * The next bundle. + */ + if (pBundle->b32_type > ENTRYFWD) + { + KLDRMODLX_ASSERT(!"Bad type"); /** @todo figure out TYPEINFO. */ + return VERR_LDRLX_BAD_BUNDLE; + } + if (pBundle->b32_type == 0) + pBundle = (const struct b32_bundle *)((const uint8_t *)pBundle + 2); + else + pBundle = (const struct b32_bundle *)((const uint8_t *)(pBundle + 1) + s_cbEntry[pBundle->b32_type] * pBundle->b32_cnt); + } + + return VINF_SUCCESS; +} + + +/** + * Lookup a name table entry by ordinal. + * + * @returns Pointer to the name table entry if found. + * @returns NULL if not found. + * @param pbNameTable Pointer to the name table that should be searched. + * @param cbNameTable The size of the name table. + * @param iOrdinal The ordinal to search for. + */ +static const uint8_t *kldrModLXDoNameTableLookupByOrdinal(const uint8_t *pbNameTable, ssize_t cbNameTable, uint32_t iOrdinal) +{ + while (*pbNameTable != 0 && cbNameTable > 0) + { + const uint8_t cbName = *pbNameTable; + uint32_t iName; + + cbNameTable -= cbName + 1 + 2; + if (cbNameTable < 0) + break; + + iName = *(pbNameTable + cbName + 1) + | ((unsigned)*(pbNameTable + cbName + 2) << 8); + if (iName == iOrdinal) + return pbNameTable; + + /* next entry */ + pbNameTable += cbName + 1 + 2; + } + + return NULL; +} + + +static int kldrModLXGetImport(PKLDRMODLX pModLX, const void *pvBits, uint32_t iImport, char *pszName, size_t cchName, + size_t *pcbNeeded) +{ + const uint8_t *pb; + int rc; + RT_NOREF(pvBits); + + /* + * Validate + */ + if (iImport >= pModLX->Hdr.e32_impmodcnt) + return VERR_LDRLX_IMPORT_ORDINAL_OUT_OF_BOUNDS; + + /* + * Lazy loading the fixup section. + */ + if (!pModLX->pbImportMods) + { + rc = kldrModLXDoLoadFixupSection(pModLX); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Iterate the module import table until we reach the requested import ordinal. + */ + pb = pModLX->pbImportMods; + while (iImport-- > 0) + pb += *pb + 1; + + /* + * Copy out the result. + */ + if (pcbNeeded) + *pcbNeeded = *pb + 1; + if (*pb < cchName) + { + memcpy(pszName, pb + 1, *pb); + pszName[*pb] = '\0'; + rc = VINF_SUCCESS; + } + else + { + memcpy(pszName, pb + 1, cchName); + if (cchName) + pszName[cchName - 1] = '\0'; + rc = VERR_BUFFER_OVERFLOW; + } + + return rc; +} + +#if 0 + +/** @copydoc kLdrModNumberOfImports */ +static int32_t kldrModLXNumberOfImports(PRTLDRMODINTERNAL pMod, const void *pvBits) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + RT_NOREF(pvBits); + return pModLX->Hdr.e32_impmodcnt; +} + + +/** @copydoc kLdrModGetStackInfo */ +static int kldrModLXGetStackInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PKLDRSTACKINFO pStackInfo) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + const uint32_t i = pModLX->Hdr.e32_stackobj; + RT_NOREF(pvBits); + + if ( i + && i <= pModLX->cSegments + && pModLX->Hdr.e32_esp <= pModLX->aSegments[i - 1].LinkAddress + pModLX->aSegments[i - 1].cb + && pModLX->Hdr.e32_stacksize + && pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize >= pModLX->aSegments[i - 1].LinkAddress) + { + + kldrModLXResolveBaseAddress(pModLX, &BaseAddress); + pStackInfo->LinkAddress = pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize; + pStackInfo->Address = BaseAddress + + pModLX->aSegments[i - 1].RVA + + pModLX->Hdr.e32_esp - pModLX->Hdr.e32_stacksize - pModLX->aSegments[i - 1].LinkAddress; + } + else + { + pSt0ackInfo->Address = NIL_RTLDRADDR; + pStackInfo->LinkAddress = NIL_RTLDRADDR; + } + pStackInfo->cbStack = pModLX->Hdr.e32_stacksize; + pStackInfo->cbStackThread = 0; + + return VINF_SUCCESS; +} + + +/** @copydoc kLdrModQueryMainEntrypoint */ +static int kldrModLXQueryMainEntrypoint(PRTLDRMODINTERNAL pMod, const void *pvBits, RTLDRADDR BaseAddress, PRTLDRADDR pMainEPAddress) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + RT_NOREF(pvBits); + + /* + * Convert the address from the header. + */ + kldrModLXResolveBaseAddress(pModLX, &BaseAddress); + *pMainEPAddress = pModLX->Hdr.e32_startobj + && pModLX->Hdr.e32_startobj <= pModLX->cSegments + && pModLX->Hdr.e32_eip < pModLX->aSegments[pModLX->Hdr.e32_startobj - 1].cb + ? BaseAddress + pModLX->aSegments[pModLX->Hdr.e32_startobj - 1].RVA + pModLX->Hdr.e32_eip + : NIL_RTLDRADDR; + return VINF_SUCCESS; +} + +#endif + +/** Helper for rtldrLX_EnumDbgInfo. */ +static int rtldrLx_EnumDbgInfoHelper(PKLDRMODLX pModLX, PFNRTLDRENUMDBG pfnCallback, void *pvUser, + uint8_t *pbBuf, uint32_t cbRead, uint32_t offDbgInfo, bool *pfReturn) +{ + RTLDRDBGINFO DbgInfo; + uint32_t iDbgInfo = 0; + uint32_t cbDbgInfo = pModLX->Hdr.e32_debuglen; + + /* + * Recent watcom linkers emit PE style IMAGE_DEBUG_MISC for specifying + * external file with CV info. + */ + if (cbRead >= sizeof(IMAGE_DEBUG_MISC)) + { + PCIMAGE_DEBUG_MISC pMisc = (PCIMAGE_DEBUG_MISC)pbBuf; + if ( pMisc->DataType == IMAGE_DEBUG_MISC_EXENAME + && pMisc->Length <= cbRead + && pMisc->Length >= RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data[4]) + && pMisc->Unicode == 0 + && pMisc->Reserved[0] == 0 + && pMisc->Reserved[1] == 0 + && pMisc->Reserved[2] == 0 + && pMisc->Data[0] >= 0x20 + && pMisc->Data[0] < 0x7f + && pMisc->Data[1] >= 0x20 + && pMisc->Data[1] < 0x7f + && pMisc->Data[2] >= 0x20 + && pMisc->Data[2] < 0x7f ) + { + uint32_t cchMaxName = pMisc->Length - RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data[0]); + for (uint32_t cchName = 3; cchName < cchMaxName; cchName++) + { + char const ch = pMisc->Data[cchName]; + if (ch == 0) + { + DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW; + DbgInfo.iDbgInfo = iDbgInfo; + DbgInfo.offFile = offDbgInfo; + DbgInfo.LinkAddress = NIL_RTLDRADDR; + DbgInfo.cb = pMisc->Length; + DbgInfo.pszExtFile = (char *)&pMisc->Data[0]; + DbgInfo.u.Cv.cbImage = pModLX->Hdr.e32_mpages * pModLX->Hdr.e32_pagesize; + DbgInfo.u.Cv.uTimestamp = 0; + DbgInfo.u.Cv.uMajorVer = 0; + DbgInfo.u.Cv.uMinorVer = 0; + + *pfReturn = true; + int rc = pfnCallback(&pModLX->Core, &DbgInfo, pvUser); + if (rc != VINF_SUCCESS) + return rc; + } + else if (ch >= 0x30 && ch < 0x7f) + continue; + break; + } + + /* Skip it. */ + pbBuf += pMisc->Length; + cbRead -= pMisc->Length; + offDbgInfo += pMisc->Length; + cbDbgInfo -= pMisc->Length; + iDbgInfo++; + } + } + + /* + * Look for codeview signature. + */ + RTCVHDR const *pCvHdr = (RTCVHDR const *)pbBuf; + if ( cbRead > sizeof(*pCvHdr) + && pCvHdr->off >= sizeof(*pCvHdr) + && pCvHdr->off < cbDbgInfo) + { + switch (pCvHdr->u32Magic) + { + case RTCVHDR_MAGIC_NB11: + case RTCVHDR_MAGIC_NB09: + case RTCVHDR_MAGIC_NB08: + case RTCVHDR_MAGIC_NB07: + case RTCVHDR_MAGIC_NB06: + case RTCVHDR_MAGIC_NB05: + case RTCVHDR_MAGIC_NB04: + case RTCVHDR_MAGIC_NB02: + case RTCVHDR_MAGIC_NB01: + case RTCVHDR_MAGIC_NB00: + DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW; + DbgInfo.iDbgInfo = iDbgInfo; + DbgInfo.offFile = offDbgInfo; + DbgInfo.LinkAddress = NIL_RTLDRADDR; + DbgInfo.cb = cbDbgInfo; + DbgInfo.pszExtFile = NULL; + DbgInfo.u.Cv.cbImage = pModLX->Hdr.e32_mpages * pModLX->Hdr.e32_pagesize; + DbgInfo.u.Cv.uTimestamp = 0; + DbgInfo.u.Cv.uMajorVer = 0; + DbgInfo.u.Cv.uMinorVer = 0; + + *pfReturn = true; + return pfnCallback(&pModLX->Core, &DbgInfo, pvUser); + } + } + + /* + * Watcom wraps its DWARF output in an ELF image, so look for and ELF magic. + */ + Elf32_Ehdr const *pElfHdr = (Elf32_Ehdr const *)pbBuf; + if ( cbRead >= sizeof(*pElfHdr) + && pElfHdr->e_ident[EI_MAG0] == ELFMAG0 + && pElfHdr->e_ident[EI_MAG1] == ELFMAG1 + && pElfHdr->e_ident[EI_MAG2] == ELFMAG2 + && pElfHdr->e_ident[EI_MAG3] == ELFMAG3 + && pElfHdr->e_ident[EI_CLASS] == ELFCLASS32 + && pElfHdr->e_ident[EI_DATA] == ELFDATA2LSB + && pElfHdr->e_ident[EI_VERSION] == EV_CURRENT + && pElfHdr->e_shentsize == sizeof(Elf32_Shdr) + && pElfHdr->e_shnum >= 2 + && pElfHdr->e_shnum < _32K + 10 + && pElfHdr->e_shstrndx <= pElfHdr->e_shnum + && pElfHdr->e_shstrndx > 0 + ) + { + /** @todo try use pBuf for reading into and try to read more at once. */ + uint32_t const offShdrs = pElfHdr->e_shoff + offDbgInfo; + uint32_t const cShdrs = pElfHdr->e_shnum; + uint32_t const cbShdr = pElfHdr->e_shentsize; + int rc = VINF_SUCCESS; + + /* Read the section string table. */ + Elf32_Shdr Shdr; + int rc2 = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &Shdr, sizeof(Shdr), + offShdrs + pElfHdr->e_shstrndx * cbShdr); + if ( RT_SUCCESS(rc2) + && Shdr.sh_offset > 0 + && Shdr.sh_size > 0 + && Shdr.sh_size < _256K + && Shdr.sh_type == SHT_STRTAB) + { + uint32_t const cbStrTab = Shdr.sh_size; + char * const pszStrTab = (char *)RTMemTmpAlloc(cbStrTab + 2); + if (pszStrTab) + { + rc2 = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, pszStrTab, Shdr.sh_size, offDbgInfo + Shdr.sh_offset); + if (RT_SUCCESS(rc2)) + { + pszStrTab[cbStrTab] = '\0'; + + /* Iterate the sections, one by one. */ + for (uint32_t i = 1; i < cShdrs; i++) + { + rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &Shdr, sizeof(Shdr), offShdrs + i * cbShdr); + if ( RT_SUCCESS(rc) + && Shdr.sh_name < cbStrTab + && strncmp(&pszStrTab[Shdr.sh_name], RT_STR_TUPLE(".debug_")) == 0) + { + DbgInfo.enmType = RTLDRDBGINFOTYPE_DWARF; + DbgInfo.iDbgInfo = iDbgInfo; + DbgInfo.offFile = offDbgInfo + Shdr.sh_offset; + DbgInfo.LinkAddress = NIL_RTLDRADDR; + DbgInfo.cb = Shdr.sh_size; + DbgInfo.pszExtFile = NULL; + DbgInfo.u.Dwarf.pszSection = &pszStrTab[Shdr.sh_name]; + + *pfReturn = true; + rc = pfnCallback(&pModLX->Core, &DbgInfo, pvUser); + if (rc != VINF_SUCCESS) + break; + iDbgInfo++; + } + } + } + RTMemTmpFree(pszStrTab); + } + } + return rc; + } + + /* + * Watcom debug info? Don't know how to detect it... + */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnEnumDbgInfo} + */ +static DECLCALLBACK(int) rtldrLX_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, + PFNRTLDRENUMDBG pfnCallback, void *pvUser) +{ + /*PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core);*/ + RT_NOREF(pfnCallback); + RT_NOREF(pvUser); + + /* + * Quit immediately if no debug info. + */ + if (kldrModLXHasDbgInfo(pMod, pvBits)) + return VINF_SUCCESS; + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + + /* + * Read the debug info and look for familiar magics and structures. + */ + union + { + uint8_t ab[1024]; + IMAGE_DEBUG_MISC Misc; + RTCVHDR CvHdr; + } uBuf; + + bool fReturn = false; + + /* Try the offset without header displacement first. */ + uint32_t cbToRead = RT_MIN(pModLX->Hdr.e32_debuglen, sizeof(uBuf)); + int rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &uBuf, cbToRead, pModLX->Hdr.e32_debuginfo); + if (RT_SUCCESS(rc)) + rc = rtldrLx_EnumDbgInfoHelper(pModLX, pfnCallback, pvUser, &uBuf.ab[0], cbToRead, pModLX->Hdr.e32_debuginfo, &fReturn); + + /* If that didn't yield anything, try displaying it by the header offset. */ + if (!fReturn && pModLX->offHdr > 0) + { + rc = pModLX->Core.pReader->pfnRead(pModLX->Core.pReader, &uBuf, cbToRead, pModLX->Hdr.e32_debuginfo + pModLX->offHdr); + if (RT_SUCCESS(rc)) + rc = rtldrLx_EnumDbgInfoHelper(pModLX, pfnCallback, pvUser, &uBuf.ab[0], cbToRead, + pModLX->Hdr.e32_debuginfo + pModLX->offHdr, &fReturn); + } + return rc; +} + + +static int kldrModLXHasDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + RT_NOREF(pvBits); + + /* + * Don't currently bother with linkers which doesn't advertise it in the header. + */ + if ( !pModLX->Hdr.e32_debuginfo + || !pModLX->Hdr.e32_debuglen) + return VERR_NOT_FOUND; + return VINF_SUCCESS; +} + +#if 0 + +/** @copydoc kLdrModMap */ +static int kldrModLXMap(PRTLDRMODINTERNAL pMod) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + unsigned fFixed; + void *pvBase; + int rc; + + /* + * Already mapped? + */ + if (pModLX->pvMapping) + return KLDR_ERR_ALREADY_MAPPED; + + /* + * Allocate memory for it. + */ + /* fixed image? */ + fFixed = pModLX->Core.enmType == RTLDRTYPE_EXECUTABLE_FIXED + || pModLX->Core.enmType == RTLDRTYPE_SHARED_LIBRARY_FIXED; + if (!fFixed) + pvBase = NULL; + else + { + pvBase = (void *)(uintptr_t)pModLX->aSegments[0].LinkAddress; + if ((uintptr_t)pvBase != pModLX->aSegments[0].LinkAddress) + return KLDR_ERR_ADDRESS_OVERFLOW; + } + rc = kHlpPageAlloc(&pvBase, pModLX->cbMapped, KPROT_EXECUTE_READWRITE, fFixed); + if (RT_FAILURE(rc)) + return rc; + + /* + * Load the bits, apply page protection, and update the segment table. + */ + rc = kldrModLXDoLoadBits(pModLX, pvBase); + if (RT_SUCCESS(rc)) + rc = kldrModLXDoProtect(pModLX, pvBase, 0 /* protect */); + if (RT_SUCCESS(rc)) + { + uint32_t i; + for (i = 0; i < pModLX->cSegments; i++) + { + if (pModLX->aSegments[i].RVA != NIL_RTLDRADDR) + pModLX->aSegments[i].MapAddress = (uintptr_t)pvBase + (uintptr_t)pModLX->aSegments[i].RVA; + } + pModLX->pvMapping = pvBase; + } + else + kHlpPageFree(pvBase, pModLX->cbMapped); + return rc; +} + +#endif + +/** + * Loads the LX pages into the specified memory mapping. + * + * @returns IPRT status code. + * + * @param pModLX The LX module interpreter instance. + * @param pvBits Where to load the bits. + */ +static int kldrModLXDoLoadBits(PKLDRMODLX pModLX, void *pvBits) +{ + const PRTLDRREADER pRdr = pModLX->Core.pReader; + uint8_t *pbTmpPage = NULL; + int rc = VINF_SUCCESS; + uint32_t i; + + /* + * Iterate the segments. + */ + for (i = 0; i < pModLX->Hdr.e32_objcnt; i++) + { + const struct o32_obj * const pObj = &pModLX->paObjs[i]; + const uint32_t cPages = (uint32_t)(pModLX->aSegments[i].cbMapped / OBJPAGELEN); + uint32_t iPage; + uint8_t *pbPage = (uint8_t *)pvBits + (uintptr_t)pModLX->aSegments[i].RVA; + + /* + * Iterate the page map pages. + */ + for (iPage = 0; RT_SUCCESS(rc) && iPage < pObj->o32_mapsize; iPage++, pbPage += OBJPAGELEN) + { + const struct o32_map *pMap = &pModLX->paPageMappings[iPage + pObj->o32_pagemap - 1]; + switch (pMap->o32_pageflags) + { + case VALID: + if (pMap->o32_pagesize == OBJPAGELEN) + rc = pRdr->pfnRead(pRdr, pbPage, OBJPAGELEN, + pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift)); + else if (pMap->o32_pagesize < OBJPAGELEN) + { + rc = pRdr->pfnRead(pRdr, pbPage, pMap->o32_pagesize, + pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift)); + memset(pbPage + pMap->o32_pagesize, 0, OBJPAGELEN - pMap->o32_pagesize); + } + else + rc = VERR_LDRLX_BAD_PAGE_MAP; + break; + + case ITERDATA: + case ITERDATA2: + /* make sure we've got a temp page .*/ + if (!pbTmpPage) + { + pbTmpPage = (uint8_t *)RTMemAlloc(OBJPAGELEN + 256); + if (!pbTmpPage) + break; + } + /* validate the size. */ + if (pMap->o32_pagesize > OBJPAGELEN + 252) + { + rc = VERR_LDRLX_BAD_PAGE_MAP; + break; + } + + /* read it and ensure 4 extra zero bytes. */ + rc = pRdr->pfnRead(pRdr, pbTmpPage, pMap->o32_pagesize, + pModLX->Hdr.e32_datapage + (pMap->o32_pagedataoffset << pModLX->Hdr.e32_pageshift)); + if (RT_FAILURE(rc)) + break; + memset(pbTmpPage + pMap->o32_pagesize, 0, 4); + + /* unpack it into the image page. */ + if (pMap->o32_pageflags == ITERDATA2) + rc = kldrModLXDoIterData2Unpacking(pbPage, pbTmpPage, pMap->o32_pagesize); + else + rc = kldrModLXDoIterDataUnpacking(pbPage, pbTmpPage, pMap->o32_pagesize); + break; + + case INVALID: /* we're probably not dealing correctly with INVALID pages... */ + case ZEROED: + memset(pbPage, 0, OBJPAGELEN); + break; + + case RANGE: + KLDRMODLX_ASSERT(!"RANGE"); + RT_FALL_THRU(); + default: + rc = VERR_LDRLX_BAD_PAGE_MAP; + break; + } + } + if (RT_FAILURE(rc)) + break; + + /* + * Zero the remaining pages. + */ + if (iPage < cPages) + memset(pbPage, 0, (cPages - iPage) * OBJPAGELEN); + } + + if (pbTmpPage) + RTMemFree(pbTmpPage); + return rc; +} + + +/** + * Unpacks iterdata (aka EXEPACK). + * + * @returns IPRT status code. + * @param pbDst Where to put the uncompressed data. (Assumes OBJPAGELEN size.) + * @param pbSrc The compressed source data. + * @param cbSrc The file size of the compressed data. The source buffer + * contains 4 additional zero bytes. + */ +static int kldrModLXDoIterDataUnpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc) +{ + const struct LX_Iter *pIter = (const struct LX_Iter *)pbSrc; + int cbDst = OBJPAGELEN; + + /* Validate size of data. */ + if (cbSrc >= (int)OBJPAGELEN - 2) + return VERR_LDRLX_BAD_ITERDATA; + + /* + * Expand the page. + */ + while (cbSrc > 0 && pIter->LX_nIter) + { + if (pIter->LX_nBytes == 1) + { + /* + * Special case - one databyte. + */ + cbDst -= pIter->LX_nIter; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA; + + cbSrc -= 4 + 1; + if (cbSrc < -4) + return VERR_LDRLX_BAD_ITERDATA; + + memset(pbDst, pIter->LX_Iterdata, pIter->LX_nIter); + pbDst += pIter->LX_nIter; + pIter++; + } + else + { + /* + * General. + */ + int i; + + cbDst -= pIter->LX_nIter * pIter->LX_nBytes; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA; + + cbSrc -= 4 + pIter->LX_nBytes; + if (cbSrc < -4) + return VERR_LDRLX_BAD_ITERDATA; + + for (i = pIter->LX_nIter; i > 0; i--, pbDst += pIter->LX_nBytes) + memcpy(pbDst, &pIter->LX_Iterdata, pIter->LX_nBytes); + pIter = (struct LX_Iter *)((char*)pIter + 4 + pIter->LX_nBytes); + } + } + + /* + * Zero remainder of the page. + */ + if (cbDst > 0) + memset(pbDst, 0, cbDst); + + return VINF_SUCCESS; +} + + +/** + * Unpacks iterdata (aka EXEPACK). + * + * @returns IPRT status code. + * @param pbDst Where to put the uncompressed data. (Assumes OBJPAGELEN size.) + * @param pbSrc The compressed source data. + * @param cbSrc The file size of the compressed data. The source buffer + * contains 4 additional zero bytes. + */ +static int kldrModLXDoIterData2Unpacking(uint8_t *pbDst, const uint8_t *pbSrc, int cbSrc) +{ + int cbDst = OBJPAGELEN; + + while (cbSrc > 0) + { + /* + * Bit 0 and 1 is the encoding type. + */ + switch (*pbSrc & 0x03) + { + /* + * + * 0 1 2 3 4 5 6 7 + * type | | + * ---------------- + * cb + * + * Bits 2-7 is, if not zero, the length of an uncompressed run + * starting at the following byte. + * + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + * type | | | | | | + * ---------------- ---------------------- ----------------------- + * zero cb char to multiply + * + * If the bits are zero, the following two bytes describes a 1 byte interation + * run. First byte is count, second is the byte to copy. A count of zero is + * means end of data, and we simply stops. In that case the rest of the data + * should be zero. + */ + case 0: + { + if (*pbSrc) + { + const int cb = *pbSrc >> 2; + cbDst -= cb; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + cbSrc -= cb + 1; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + memcpy(pbDst, ++pbSrc, cb); + pbDst += cb; + pbSrc += cb; + } + else if (cbSrc < 2) + return VERR_LDRLX_BAD_ITERDATA2; + else + { + const int cb = pbSrc[1]; + if (!cb) + goto l_endloop; + cbDst -= cb; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + cbSrc -= 3; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + memset(pbDst, pbSrc[2], cb); + pbDst += cb; + pbSrc += 3; + } + break; + } + + + /* + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * type | | | | | | + * ---- ------- ------------------------- + * cb1 cb2 - 3 offset + * + * Two bytes layed out as described above, followed by cb1 bytes of data to be copied. + * The cb2(+3) and offset describes an amount of data to be copied from the expanded + * data relative to the current position. The data copied as you would expect it to be. + */ + case 1: + { + cbSrc -= 2; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + else + { + const unsigned off = ((unsigned)pbSrc[1] << 1) | (*pbSrc >> 7); + const int cb1 = (*pbSrc >> 2) & 3; + const int cb2 = ((*pbSrc >> 4) & 7) + 3; + + pbSrc += 2; + cbSrc -= cb1; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + cbDst -= cb1; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + memcpy(pbDst, pbSrc, cb1); + pbDst += cb1; + pbSrc += cb1; + + if (off > OBJPAGELEN - (unsigned)cbDst) + return VERR_LDRLX_BAD_ITERDATA2; + cbDst -= cb2; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + memmove(pbDst, pbDst - off, cb2); + pbDst += cb2; + } + break; + } + + + /* + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 + * type | | | | + * ---- ---------------------------------- + * cb-3 offset + * + * Two bytes layed out as described above. + * The cb(+3) and offset describes an amount of data to be copied from the expanded + * data relative to the current position. + * + * If offset == 1 the data is not copied as expected, but in the memcpyw manner. + */ + case 2: + { + cbSrc -= 2; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + else + { + const unsigned off = ((unsigned)pbSrc[1] << 4) | (*pbSrc >> 4); + const int cb = ((*pbSrc >> 2) & 3) + 3; + + pbSrc += 2; + if (off > OBJPAGELEN - (unsigned)cbDst) + return VERR_LDRLX_BAD_ITERDATA2; + cbDst -= cb; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + kLdrModLXMemCopyW(pbDst, pbDst - off, cb); + pbDst += cb; + } + break; + } + + + /* + * 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 + * type | | | | | | + * ---------- ---------------- ---------------------------------- + * cb1 cb2 offset + * + * Three bytes layed out as described above, followed by cb1 bytes of data to be copied. + * The cb2 and offset describes an amount of data to be copied from the expanded + * data relative to the current position. + * + * If offset == 1 the data is not copied as expected, but in the memcpyw manner. + */ + case 3: + { + cbSrc -= 3; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + else + { + const int cb1 = (*pbSrc >> 2) & 0xf; + const int cb2 = ((pbSrc[1] & 0xf) << 2) | (*pbSrc >> 6); + const unsigned off = ((unsigned)pbSrc[2] << 4) | (pbSrc[1] >> 4); + + pbSrc += 3; + cbSrc -= cb1; + if (cbSrc < 0) + return VERR_LDRLX_BAD_ITERDATA2; + cbDst -= cb1; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + memcpy(pbDst, pbSrc, cb1); + pbDst += cb1; + pbSrc += cb1; + + if (off > OBJPAGELEN - (unsigned)cbDst) + return VERR_LDRLX_BAD_ITERDATA2; + cbDst -= cb2; + if (cbDst < 0) + return VERR_LDRLX_BAD_ITERDATA2; + kLdrModLXMemCopyW(pbDst, pbDst - off, cb2); + pbDst += cb2; + } + break; + } + } /* type switch. */ + } /* unpack loop */ + +l_endloop: + + + /* + * Zero remainder of the page. + */ + if (cbDst > 0) + memset(pbDst, 0, cbDst); + + return VINF_SUCCESS; +} + + +/** + * Special memcpy employed by the iterdata2 algorithm. + * + * Emulate a 16-bit memcpy (copying 16-bit at a time) and the effects this + * has if src is very close to the destination. + * + * @param pbDst Destination pointer. + * @param pbSrc Source pointer. Will always be <= pbDst. + * @param cb Amount of data to be copied. + * @remark This assumes that unaligned word and dword access is fine. + */ +static void kLdrModLXMemCopyW(uint8_t *pbDst, const uint8_t *pbSrc, int cb) +{ + switch (pbDst - pbSrc) + { + case 0: + case 1: + case 2: + case 3: + /* 16-bit copy (unaligned) */ + if (cb & 1) + *pbDst++ = *pbSrc++; + for (cb >>= 1; cb > 0; cb--, pbDst += 2, pbSrc += 2) + *(uint16_t *)pbDst = *(const uint16_t *)pbSrc; + break; + + default: + /* 32-bit copy (unaligned) */ + if (cb & 1) + *pbDst++ = *pbSrc++; + if (cb & 2) + { + *(uint16_t *)pbDst = *(const uint16_t *)pbSrc; + pbDst += 2; + pbSrc += 2; + } + for (cb >>= 2; cb > 0; cb--, pbDst += 4, pbSrc += 4) + *(uint32_t *)pbDst = *(const uint32_t *)pbSrc; + break; + } +} + +#if 0 + +/** + * Unprotects or protects the specified image mapping. + * + * @returns IPRT status code. + * + * @param pModLX The LX module interpreter instance. + * @param pvBits The mapping to protect. + * @param UnprotectOrProtect If 1 unprotect (i.e. make all writable), otherwise + * protect according to the object table. + */ +static int kldrModLXDoProtect(PKLDRMODLX pModLX, void *pvBits, unsigned fUnprotectOrProtect) +{ + uint32_t i; + + /* + * Change object protection. + */ + for (i = 0; i < pModLX->cSegments; i++) + { + int rc; + void *pv; + KPROT enmProt; + + /* calc new protection. */ + enmProt = pModLX->aSegments[i].enmProt; + if (fUnprotectOrProtect) + { + switch (enmProt) + { + case KPROT_NOACCESS: + case KPROT_READONLY: + case KPROT_READWRITE: + case KPROT_WRITECOPY: + enmProt = KPROT_READWRITE; + break; + case KPROT_EXECUTE: + case KPROT_EXECUTE_READ: + case KPROT_EXECUTE_READWRITE: + case KPROT_EXECUTE_WRITECOPY: + enmProt = KPROT_EXECUTE_READWRITE; + break; + default: + KLDRMODLX_ASSERT(!"bad enmProt"); + return -1; + } + } + else + { + /* copy on write -> normal write. */ + if (enmProt == KPROT_EXECUTE_WRITECOPY) + enmProt = KPROT_EXECUTE_READWRITE; + else if (enmProt == KPROT_WRITECOPY) + enmProt = KPROT_READWRITE; + } + + + /* calc the address and set page protection. */ + pv = (uint8_t *)pvBits + pModLX->aSegments[i].RVA; + + rc = kHlpPageProtect(pv, pModLX->aSegments[i].cbMapped, enmProt); + if (RT_FAILURE(rc)) + break; + + /** @todo the gap page should be marked NOACCESS! */ + } + + return VINF_SUCCESS; +} + + +/** @copydoc kLdrModUnmap */ +static int kldrModLXUnmap(PRTLDRMODINTERNAL pMod) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t i; + int rc; + + /* + * Mapped? + */ + if (!pModLX->pvMapping) + return KLDR_ERR_NOT_MAPPED; + + /* + * Free the mapping and update the segments. + */ + rc = kHlpPageFree((void *)pModLX->pvMapping, pModLX->cbMapped); + KLDRMODLX_ASSERT(!rc); + pModLX->pvMapping = NULL; + + for (i = 0; i < pModLX->cSegments; i++) + pModLX->aSegments[i].MapAddress = 0; + + return rc; +} + + +/** @copydoc kLdrModAllocTLS */ +static int kldrModLXAllocTLS(PRTLDRMODINTERNAL pMod, void *pvMapping) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + + /* no tls, just do the error checking. */ + if ( pvMapping == KLDRMOD_INT_MAP + && pModLX->pvMapping) + return KLDR_ERR_NOT_MAPPED; + return VINF_SUCCESS; +} + + +/** @copydoc kLdrModFreeTLS */ +static void kldrModLXFreeTLS(PRTLDRMODINTERNAL pMod, void *pvMapping) +{ + /* no tls. */ + RT_NOREF(pMod); + RT_NOREF(pvMapping); + +} + + +/** @copydoc kLdrModReload */ +static int kldrModLXReload(PRTLDRMODINTERNAL pMod) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + int rc, rc2; + + /* + * Mapped? + */ + if (!pModLX->pvMapping) + return KLDR_ERR_NOT_MAPPED; + + /* + * Before doing anything we'll have to make all pages writable. + */ + rc = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 1 /* unprotect */); + if (RT_FAILURE(rc)) + return rc; + + /* + * Load the bits again. + */ + rc = kldrModLXDoLoadBits(pModLX, (void *)pModLX->pvMapping); + + /* + * Restore protection. + */ + rc2 = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 0 /* protect */); + if (RT_SUCCESS(rc) && RT_FAILURE(rc2)) + rc = rc2; + return rc; +} + + +/** @copydoc kLdrModFixupMapping */ +static int kldrModLXFixupMapping(PRTLDRMODINTERNAL pMod, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + int rc, rc2; + + /* + * Mapped? + */ + if (!pModLX->pvMapping) + return KLDR_ERR_NOT_MAPPED; + + /* + * Before doing anything we'll have to make all pages writable. + */ + rc = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 1 /* unprotect */); + if (RT_FAILURE(rc)) + return rc; + + /* + * Apply fixups and resolve imports. + */ + rc = rtldrLX_RelocateBits(pMod, (void *)pModLX->pvMapping, (uintptr_t)pModLX->pvMapping, + pModLX->aSegments[0].LinkAddress, pfnGetImport, pvUser); + + /* + * Restore protection. + */ + rc2 = kldrModLXDoProtect(pModLX, (void *)pModLX->pvMapping, 0 /* protect */); + if (RT_SUCCESS(rc) && RT_FAILURE(rc2)) + rc = rc2; + return rc; +} + + +/** @copydoc kLdrModCallInit */ +static int kldrModLXCallInit(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + int rc; + + /* + * Mapped? + */ + if (pvMapping == KLDRMOD_INT_MAP) + { + pvMapping = (void *)pModLX->pvMapping; + if (!pvMapping) + return KLDR_ERR_NOT_MAPPED; + } + + /* + * Do TLS callbacks first and then call the init/term function if it's a DLL. + */ + if ((pModLX->Hdr.e32_mflags & E32MODMASK) == E32MODDLL) + rc = kldrModLXDoCallDLL(pModLX, pvMapping, 0 /* attach */, uHandle); + else + rc = VINF_SUCCESS; + return rc; +} + + +/** + * Call the DLL entrypoint. + * + * @returns 0 on success. + * @returns KLDR_ERR_MODULE_INIT_FAILED or KLDR_ERR_THREAD_ATTACH_FAILED on failure. + * @param pModLX The LX module interpreter instance. + * @param pvMapping The module mapping to use (resolved). + * @param uOp The operation (DLL_*). + * @param uHandle The module handle to present. + */ +static int kldrModLXDoCallDLL(PKLDRMODLX pModLX, void *pvMapping, unsigned uOp, uintptr_t uHandle) +{ + int rc; + + /* + * If no entrypoint there isn't anything to be done. + */ + if ( !pModLX->Hdr.e32_startobj + || pModLX->Hdr.e32_startobj > pModLX->Hdr.e32_objcnt) + return VINF_SUCCESS; + + /* + * Invoke the entrypoint and convert the boolean result to a kLdr status code. + */ + rc = kldrModLXDoCall((uintptr_t)pvMapping + + (uintptr_t)pModLX->aSegments[pModLX->Hdr.e32_startobj - 1].RVA + + pModLX->Hdr.e32_eip, + uHandle, uOp, NULL); + if (rc) + rc = VINF_SUCCESS; + else if (uOp == 0 /* attach */) + rc = KLDR_ERR_MODULE_INIT_FAILED; + else /* detach: ignore failures */ + rc = VINF_SUCCESS; + return rc; +} + + +/** + * Do a 3 parameter callback. + * + * @returns 32-bit callback return. + * @param uEntrypoint The address of the function to be called. + * @param uHandle The first argument, the module handle. + * @param uOp The second argumnet, the reason we're calling. + * @param pvReserved The third argument, reserved argument. (figure this one out) + */ +static int32_t kldrModLXDoCall(uintptr_t uEntrypoint, uintptr_t uHandle, uint32_t uOp, void *pvReserved) +{ +#if defined(__X86__) || defined(__i386__) || defined(_M_IX86) + int32_t rc; +/** @todo try/except */ + + /* + * Paranoia. + */ +# ifdef __GNUC__ + __asm__ __volatile__( + "pushl %2\n\t" + "pushl %1\n\t" + "pushl %0\n\t" + "lea 12(%%esp), %2\n\t" + "call *%3\n\t" + "movl %2, %%esp\n\t" + : "=a" (rc) + : "d" (uOp), + "S" (0), + "c" (uEntrypoint), + "0" (uHandle)); +# elif defined(_MSC_VER) + __asm { + mov eax, [uHandle] + mov edx, [uOp] + mov ecx, 0 + mov ebx, [uEntrypoint] + push edi + mov edi, esp + push ecx + push edx + push eax + call ebx + mov esp, edi + pop edi + mov [rc], eax + } +# else +# error "port me!" +# endif + RT_NOREF(pvReserved); + return rc; + +#else + RT_NOREF(uEntrypoint); + RT_NOREF(uHandle); + RT_NOREF(uOp); + RT_NOREF(pvReserved); + return KCPU_ERR_ARCH_CPU_NOT_COMPATIBLE; +#endif +} + + +/** @copydoc kLdrModCallTerm */ +static int kldrModLXCallTerm(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + + /* + * Mapped? + */ + if (pvMapping == KLDRMOD_INT_MAP) + { + pvMapping = (void *)pModLX->pvMapping; + if (!pvMapping) + return KLDR_ERR_NOT_MAPPED; + } + + /* + * Do the call. + */ + if ((pModLX->Hdr.e32_mflags & E32MODMASK) == E32MODDLL) + kldrModLXDoCallDLL(pModLX, pvMapping, 1 /* detach */, uHandle); + + return VINF_SUCCESS; +} + + +/** @copydoc kLdrModCallThread */ +static int kldrModLXCallThread(PRTLDRMODINTERNAL pMod, void *pvMapping, uintptr_t uHandle, unsigned fAttachingOrDetaching) +{ + /* no thread attach/detach callout. */ + 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) rtldrLX_GetImageSize(PRTLDRMODINTERNAL pMod) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + return pModLX->cbMapped; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnGetBits} + */ +static DECLCALLBACK(int) rtldrLX_GetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, + PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + + /* + * Load the image bits. + */ + int rc = kldrModLXDoLoadBits(pModLX, pvBits); + if (RT_SUCCESS(rc)) + { + /* + * Perform relocations. + */ + rc = rtldrLX_RelocateBits(pMod, pvBits, BaseAddress, pModLX->aSegments[0].LinkAddress, pfnGetImport, pvUser); + } + return rc; +} + + +/* GCC goes boinkers if we put this inside the function. */ +union RELOC_VISIBILITY_STUPIDITY +{ + const uint8_t *pb; + const struct r32_rlc *prlc; +}; + +/** + * @interface_method_impl{RTLDROPS,pfnRelocate} + */ +static DECLCALLBACK(int) rtldrLX_RelocateBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, + RTUINTPTR OldBaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PKLDRMODLX pModLX = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t iSeg; + int rc; + + /* + * Do we need to to *anything*? + */ + if ( NewBaseAddress == OldBaseAddress + && NewBaseAddress == pModLX->paObjs[0].o32_base + && !pModLX->Hdr.e32_impmodcnt) + return VINF_SUCCESS; + + /* + * Load the fixup section. + */ + if (!pModLX->pbFixupSection) + { + rc = kldrModLXDoLoadFixupSection(pModLX); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Iterate the segments. + */ + for (iSeg = 0; iSeg < pModLX->Hdr.e32_objcnt; iSeg++) + { + const struct o32_obj * const pObj = &pModLX->paObjs[iSeg]; + RTLDRADDR PageAddress = NewBaseAddress + pModLX->aSegments[iSeg].RVA; + uint32_t iPage; + uint8_t *pbPage = (uint8_t *)pvBits + (uintptr_t)pModLX->aSegments[iSeg].RVA; + + /* + * Iterate the page map pages. + */ + for (iPage = 0, rc = VINF_SUCCESS; + RT_SUCCESS(rc) && iPage < pObj->o32_mapsize; + iPage++, pbPage += OBJPAGELEN, PageAddress += OBJPAGELEN) + { + const uint8_t * const pbFixupRecEnd = pModLX->pbFixupRecs + pModLX->paoffPageFixups[iPage + pObj->o32_pagemap]; + const uint8_t *pb = pModLX->pbFixupRecs + pModLX->paoffPageFixups[iPage + pObj->o32_pagemap - 1]; + RTLDRADDR uValue = NIL_RTLDRADDR; + uint32_t fKind = 0; + int iSelector; + + /* sanity */ + if (pbFixupRecEnd < pb) + return VERR_LDR_BAD_FIXUP; + if (pbFixupRecEnd - 1 > pModLX->pbFixupSectionLast) + return VERR_LDR_BAD_FIXUP; + if (pb < pModLX->pbFixupSection) + return VERR_LDR_BAD_FIXUP; + + /* + * Iterate the fixup record. + */ + while (pb < pbFixupRecEnd) + { + union RELOC_VISIBILITY_STUPIDITY u; + char szImpModule[256]; + u.pb = pb; + pb += 3 + (u.prlc->nr_stype & NRCHAIN ? 0 : 1); /* place pch at the 4th member. */ + + /* + * Figure out the target. + */ + switch (u.prlc->nr_flags & NRRTYP) + { + /* + * Internal fixup. + */ + case NRRINT: + { + uint16_t iTrgObject; + uint32_t offTrgObject; + + /* the object */ + if (u.prlc->nr_flags & NR16OBJMOD) + { + iTrgObject = *(const uint16_t *)pb; + pb += 2; + } + else + iTrgObject = *pb++; + iTrgObject--; + if (iTrgObject >= pModLX->Hdr.e32_objcnt) + return VERR_LDR_BAD_FIXUP; + + /* the target */ + if ((u.prlc->nr_stype & NRSRCMASK) != NRSSEG) + { + if (u.prlc->nr_flags & NR32BITOFF) + { + offTrgObject = *(const uint32_t *)pb; + pb += 4; + } + else + { + offTrgObject = *(const uint16_t *)pb; + pb += 2; + } + + /* calculate the symbol info. */ + uValue = offTrgObject + NewBaseAddress + pModLX->aSegments[iTrgObject].RVA; + } + else + uValue = NewBaseAddress + pModLX->aSegments[iTrgObject].RVA; + if ( (u.prlc->nr_stype & NRALIAS) + || (pModLX->aSegments[iTrgObject].fFlags & RTLDRSEG_FLAG_16BIT)) + iSelector = pModLX->aSegments[iTrgObject].Sel16bit; + else + iSelector = pModLX->aSegments[iTrgObject].SelFlat; + fKind = 0; + break; + } + + /* + * Import by symbol ordinal. + */ + case NRRORD: + { + uint16_t iModule; + uint32_t iSymbol; + + /* the module ordinal */ + if (u.prlc->nr_flags & NR16OBJMOD) + { + iModule = *(const uint16_t *)pb; + pb += 2; + } + else + iModule = *pb++; + iModule--; + if (iModule >= pModLX->Hdr.e32_impmodcnt) + return VERR_LDR_BAD_FIXUP; + rc = kldrModLXGetImport(pModLX, NULL, iModule, szImpModule, sizeof(szImpModule), NULL); + if (RT_FAILURE(rc)) + return rc; + +#if 1 + if (u.prlc->nr_flags & NRICHAIN) + return VERR_LDR_BAD_FIXUP; +#endif + + /* . */ + if (u.prlc->nr_flags & NR32BITOFF) + { + iSymbol = *(const uint32_t *)pb; + pb += 4; + } + else if (!(u.prlc->nr_flags & NR8BITORD)) + { + iSymbol = *(const uint16_t *)pb; + pb += 2; + } + else + iSymbol = *pb++; + + /* resolve it. */ + rc = pfnGetImport(pMod, szImpModule, NULL, iSymbol, &uValue, /*&fKind,*/ pvUser); + if (RT_FAILURE(rc)) + return rc; + iSelector = -1; + break; + } + + /* + * Import by symbol name. + */ + case NRRNAM: + { + uint32_t iModule; + uint16_t offSymbol; + const uint8_t *pbSymbol; + + /* the module ordinal */ + if (u.prlc->nr_flags & NR16OBJMOD) + { + iModule = *(const uint16_t *)pb; + pb += 2; + } + else + iModule = *pb++; + iModule--; + if (iModule >= pModLX->Hdr.e32_impmodcnt) + return VERR_LDR_BAD_FIXUP; + rc = kldrModLXGetImport(pModLX, NULL, iModule, szImpModule, sizeof(szImpModule), NULL); + if (RT_FAILURE(rc)) + return rc; +#if 1 + if (u.prlc->nr_flags & NRICHAIN) + return VERR_LDR_BAD_FIXUP; +#endif + + /* . */ + if (u.prlc->nr_flags & NR32BITOFF) + { + offSymbol = *(const uint32_t *)pb; + pb += 4; + } + else if (!(u.prlc->nr_flags & NR8BITORD)) + { + offSymbol = *(const uint16_t *)pb; + pb += 2; + } + else + offSymbol = *pb++; + pbSymbol = pModLX->pbImportProcs + offSymbol; + if ( pbSymbol < pModLX->pbImportProcs + || pbSymbol > pModLX->pbFixupSectionLast) + return VERR_LDR_BAD_FIXUP; + char szSymbol[256]; + memcpy(szSymbol, pbSymbol + 1, *pbSymbol); + szSymbol[*pbSymbol] = '\0'; + + /* resolve it. */ + rc = pfnGetImport(pMod, szImpModule, szSymbol, UINT32_MAX, &uValue, /*&fKind,*/ pvUser); + if (RT_FAILURE(rc)) + return rc; + iSelector = -1; + break; + } + + case NRRENT: + KLDRMODLX_ASSERT(!"NRRENT"); + RT_FALL_THRU(); + default: + iSelector = -1; + break; + } + + /* addend */ + if (u.prlc->nr_flags & NRADD) + { + if (u.prlc->nr_flags & NR32BITADD) + { + uValue += *(const uint32_t *)pb; + pb += 4; + } + else + { + uValue += *(const uint16_t *)pb; + pb += 2; + } + } + + + /* + * Deal with the 'source' (i.e. the place that should be modified - very logical). + */ + if (!(u.prlc->nr_stype & NRCHAIN)) + { + int off = u.prlc->r32_soff; + + /* common / simple */ + if ( (u.prlc->nr_stype & NRSRCMASK) == NROFF32 + && off >= 0 + && off <= (int)OBJPAGELEN - 4) + *(uint32_t *)&pbPage[off] = (uint32_t)uValue; + else if ( (u.prlc->nr_stype & NRSRCMASK) == NRSOFF32 + && off >= 0 + && off <= (int)OBJPAGELEN - 4) + *(uint32_t *)&pbPage[off] = (uint32_t)(uValue - (PageAddress + off + 4)); + else + { + /* generic */ + rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind); + if (RT_FAILURE(rc)) + return rc; + } + } + else if (!(u.prlc->nr_flags & NRICHAIN)) + { + const int16_t *poffSrc = (const int16_t *)pb; + uint8_t c = u.pb[2]; + + /* common / simple */ + if ((u.prlc->nr_stype & NRSRCMASK) == NROFF32) + { + while (c-- > 0) + { + int off = *poffSrc++; + if (off >= 0 && off <= (int)OBJPAGELEN - 4) + *(uint32_t *)&pbPage[off] = (uint32_t)uValue; + else + { + rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind); + if (RT_FAILURE(rc)) + return rc; + } + } + } + else if ((u.prlc->nr_stype & NRSRCMASK) == NRSOFF32) + { + while (c-- > 0) + { + int off = *poffSrc++; + if (off >= 0 && off <= (int)OBJPAGELEN - 4) + *(uint32_t *)&pbPage[off] = (uint32_t)(uValue - (PageAddress + off + 4)); + else + { + rc = kldrModLXDoReloc(pbPage, off, PageAddress, u.prlc, iSelector, uValue, fKind); + if (RT_FAILURE(rc)) + return rc; + } + } + } + else + { + while (c-- > 0) + { + rc = kldrModLXDoReloc(pbPage, *poffSrc++, PageAddress, u.prlc, iSelector, uValue, fKind); + if (RT_FAILURE(rc)) + return rc; + } + } + pb = (const uint8_t *)poffSrc; + } + else + { + /* This is a pain because it will require virgin pages on a relocation. */ + KLDRMODLX_ASSERT(!"NRICHAIN"); + return VERR_LDRLX_NRICHAIN_NOT_SUPPORTED; + } + } + } + } + + return VINF_SUCCESS; +} + + +/** + * Applies the relocation to one 'source' in a page. + * + * This takes care of the more esotic case while the common cases + * are dealt with seperately. + * + * @returns IPRT status code. + * @param pbPage The page in which to apply the fixup. + * @param off Page relative offset of where to apply the offset. + * @param PageAddress The page address. + * @param prlc The relocation record. + * @param iSelector Selector value, -1 if flat. + * @param uValue The target value. + * @param fKind The target kind. + */ +static int kldrModLXDoReloc(uint8_t *pbPage, int off, RTLDRADDR PageAddress, const struct r32_rlc *prlc, + int iSelector, RTLDRADDR uValue, uint32_t fKind) +{ +#pragma pack(1) /* just to be sure */ + union + { + uint8_t ab[6]; + uint32_t off32; + uint16_t off16; + uint8_t off8; + struct + { + uint16_t off; + uint16_t Sel; + } Far16; + struct + { + uint32_t off; + uint16_t Sel; + } Far32; + } uData; +#pragma pack() + const uint8_t *pbSrc; + uint8_t *pbDst; + uint8_t cb; + + RT_NOREF(fKind); + + /* + * Compose the fixup data. + */ + switch (prlc->nr_stype & NRSRCMASK) + { + case NRSBYT: + uData.off8 = (uint8_t)uValue; + cb = 1; + break; + case NRSSEG: + if (iSelector == -1) + { + /* fixme */ + } + uData.off16 = iSelector; + cb = 2; + break; + case NRSPTR: + if (iSelector == -1) + { + /* fixme */ + } + uData.Far16.off = (uint16_t)uValue; + uData.Far16.Sel = iSelector; + cb = 4; + break; + case NRSOFF: + uData.off16 = (uint16_t)uValue; + cb = 2; + break; + case NRPTR48: + if (iSelector == -1) + { + /* fixme */ + } + uData.Far32.off = (uint32_t)uValue; + uData.Far32.Sel = iSelector; + cb = 6; + break; + case NROFF32: + uData.off32 = (uint32_t)uValue; + cb = 4; + break; + case NRSOFF32: + uData.off32 = (uint32_t)(uValue - (PageAddress + off + 4)); + cb = 4; + break; + default: + return VERR_LDRLX_BAD_FIXUP_SECTION; /** @todo fix error, add more checks! */ + } + + /* + * Apply it. This is sloooow... + */ + pbSrc = &uData.ab[0]; + pbDst = pbPage + off; + while (cb-- > 0) + { + if (off > (int)OBJPAGELEN) + break; + if (off >= 0) + *pbDst = *pbSrc; + pbSrc++; + pbDst++; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnEnumSegments} + */ +static DECLCALLBACK(int) rtldrLX_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t const cSegments = pThis->cSegments; + for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) + { + int rc = pfnCallback(pMod, &pThis->aSegments[iSeg], pvUser); + if (rc != VINF_SUCCESS) + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset} + */ +static DECLCALLBACK(int) rtldrLX_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, + uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t const cSegments = pThis->cSegments; + for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) + { + RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].LinkAddress; + if ( offSeg < pThis->aSegments[iSeg].cbMapped + || offSeg < pThis->aSegments[iSeg].cb) + { + *piSeg = iSeg; + *poffSeg = offSeg; + return VINF_SUCCESS; + } + } + + return VERR_LDR_INVALID_LINK_ADDRESS; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnLinkAddressToRva} + */ +static DECLCALLBACK(int) rtldrLX_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t const cSegments = pThis->cSegments; + for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) + { + RTLDRADDR offSeg = LinkAddress - pThis->aSegments[iSeg].LinkAddress; + if ( offSeg < pThis->aSegments[iSeg].cbMapped + || offSeg < pThis->aSegments[iSeg].cb) + { + *pRva = pThis->aSegments[iSeg].RVA + offSeg; + return VINF_SUCCESS; + } + } + + return VERR_LDR_INVALID_RVA; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnSegOffsetToRva} + */ +static DECLCALLBACK(int) rtldrLX_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg, PRTLDRADDR pRva) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + + if (iSeg >= pThis->cSegments) + return VERR_LDR_INVALID_SEG_OFFSET; + PCRTLDRSEG pSegment = &pThis->aSegments[iSeg]; + + if ( offSeg > pSegment->cbMapped + && offSeg > pSegment->cb + && ( pSegment->cbFile < 0 + || offSeg > (uint64_t)pSegment->cbFile)) + return VERR_LDR_INVALID_SEG_OFFSET; + + *pRva = pSegment->RVA + offSeg; + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnRvaToSegOffset} + */ +static DECLCALLBACK(int) rtldrLX_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva, uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + uint32_t const cSegments = pThis->cSegments; + for (uint32_t iSeg = 0; iSeg < cSegments; iSeg++) + { + RTLDRADDR offSeg = Rva - pThis->aSegments[iSeg].RVA; + if ( offSeg < pThis->aSegments[iSeg].cbMapped + || offSeg < pThis->aSegments[iSeg].cb) + { + *piSeg = iSeg; + *poffSeg = offSeg; + return VINF_SUCCESS; + } + } + + return VERR_LDR_INVALID_RVA; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnReadDbgInfo} + */ +static DECLCALLBACK(int) rtldrLX_ReadDbgInfo(PRTLDRMODINTERNAL pMod, uint32_t iDbgInfo, RTFOFF off, size_t cb, void *pvBuf) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + RT_NOREF(iDbgInfo); + return pThis->Core.pReader->pfnRead(pThis->Core.pReader, pvBuf, cb, off); +} + + +/** + * @interface_method_impl{RTLDROPS,pfnQueryProp} + */ +static DECLCALLBACK(int) rtldrLX_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits, + void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + PKLDRMODLX pThis = RT_FROM_MEMBER(pMod, KLDRMODLX, Core); + int rc; + switch (enmProp) + { + case RTLDRPROP_IMPORT_COUNT: + Assert(cbBuf == sizeof(uint32_t)); + Assert(*pcbRet == cbBuf); + *(uint32_t *)pvBuf = pThis->Hdr.e32_impmodcnt; + rc = VINF_SUCCESS; + break; + + case RTLDRPROP_IMPORT_MODULE: + rc = kldrModLXGetImport(pThis, pvBits, *(uint32_t const *)pvBuf, (char *)pvBuf, cbBuf, pcbRet); + break; + + case RTLDRPROP_INTERNAL_NAME: + *pcbRet = pThis->cchName + 1; + if (cbBuf >= pThis->cchName + 1) + { + memcpy(pvBuf, pThis->pszName, pThis->cchName + 1); + rc = VINF_SUCCESS; + } + else + rc = VERR_BUFFER_OVERFLOW; + break; + + + default: + rc = VERR_NOT_FOUND; + break; + } + RT_NOREF_PV(pvBits); + return rc; +} + + +/** + * Operations for a Mach-O module interpreter. + */ +static const RTLDROPS s_rtldrLXOps= +{ + "LX", + rtldrLX_Close, + NULL, + NULL /*pfnDone*/, + rtldrLX_EnumSymbols, + /* ext */ + rtldrLX_GetImageSize, + rtldrLX_GetBits, + rtldrLX_RelocateBits, + rtldrLX_GetSymbolEx, + NULL /*pfnQueryForwarderInfo*/, + rtldrLX_EnumDbgInfo, + rtldrLX_EnumSegments, + rtldrLX_LinkAddressToSegOffset, + rtldrLX_LinkAddressToRva, + rtldrLX_SegOffsetToRva, + rtldrLX_RvaToSegOffset, + rtldrLX_ReadDbgInfo, + rtldrLX_QueryProp, + NULL /*pfnVerifySignature*/, + NULL /*pfnHashImage*/, + NULL /*pfnUnwindFrame*/, + 42 +}; + + +/** + * Handles opening LX images. + */ +DECLHIDDEN(int) rtldrLXOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offLxHdr, + PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + + /* + * Create the instance data and do a minimal header validation. + */ + PKLDRMODLX pThis = NULL; + int rc = kldrModLXDoCreate(pReader, offLxHdr, 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_rtldrLXOps; + pThis->Core.u32Magic = RTLDRMOD_MAGIC; + *phLdrMod = &pThis->Core; + return VINF_SUCCESS; + } + rc = VERR_LDR_ARCH_MISMATCH; + } + if (pThis) + RTMemFree(pThis); + return rc; + +} + 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; + +} + diff --git a/src/VBox/Runtime/common/ldr/ldrMemory.cpp b/src/VBox/Runtime/common/ldr/ldrMemory.cpp new file mode 100644 index 00000000..80e8dce2 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrMemory.cpp @@ -0,0 +1,336 @@ + +/* $Id: ldrMemory.cpp $ */ +/** @file + * IPRT - Binary Image Loader, The Memory/Debugger Oriented Parts. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include "internal/ldr.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Memory reader (for debuggers) instance. + */ +typedef struct RTLDRRDRMEM +{ + /** The core. */ + RTLDRREADER Core; + /** The size of the image. */ + size_t cbImage; + /** The current offset. */ + size_t offCur; + + /** User parameter for the reader and destructor functions.*/ + void *pvUser; + /** Read function. */ + PFNRTLDRRDRMEMREAD pfnRead; + /** Destructor callback. */ + PFNRTLDRRDRMEMDTOR pfnDtor; + + /** Mapping of the file. */ + void *pvMapping; + /** Mapping usage counter. */ + uint32_t cMappings; + + /** The fake filename (variable size). */ + char szName[1]; +} RTLDRRDRMEM; +/** Memory based loader reader instance data. */ +typedef RTLDRRDRMEM *PRTLDRRDRMEM; + + +/** + * @callback_method_impl{FNRTLDRRDRMEMDTOR, + * Default destructor - pvUser points to the image memory block} + */ +static DECLCALLBACK(void) rtldrRdrMemDefaultDtor(void *pvUser, size_t cbImage) +{ + RT_NOREF(cbImage); + RTMemFree(pvUser); +} + + +/** + * @callback_method_impl{FNRTLDRRDRMEMREAD, + * Default memory reader - pvUser points to the image memory block} + */ +static DECLCALLBACK(int) rtldrRdrMemDefaultReader(void *pvBuf, size_t cb, size_t off, void *pvUser) +{ + memcpy(pvBuf, (uint8_t *)pvUser + off, cb); + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDRREADER,pfnRead} */ +static DECLCALLBACK(int) rtldrRdrMem_Read(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + + AssertReturn(off >= 0, VERR_INVALID_PARAMETER); + if ( cb > pThis->cbImage + || off > (RTFOFF)pThis->cbImage + || off + (RTFOFF)cb > (RTFOFF)pThis->cbImage) + { + pThis->offCur = pThis->cbImage; + return VERR_EOF; + } + + int rc = pThis->pfnRead(pvBuf, cb, (size_t)off, pThis->pvUser); + if (RT_SUCCESS(rc)) + pThis->offCur = (size_t)off + cb; + else + pThis->offCur = ~(size_t)0; + return rc; +} + + +/** @interface_method_impl{RTLDRREADER,pfnTell} */ +static DECLCALLBACK(RTFOFF) rtldrRdrMem_Tell(PRTLDRREADER pReader) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + return pThis->offCur; +} + + +/** @interface_method_impl{RTLDRREADER,pfnSize} */ +static DECLCALLBACK(uint64_t) rtldrRdrMem_Size(PRTLDRREADER pReader) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + return pThis->cbImage; +} + + +/** @interface_method_impl{RTLDRREADER,pfnLogName} */ +static DECLCALLBACK(const char *) rtldrRdrMem_LogName(PRTLDRREADER pReader) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + return pThis->szName; +} + + +/** @interface_method_impl{RTLDRREADER,pfnMap} */ +static DECLCALLBACK(int) rtldrRdrMem_Map(PRTLDRREADER pReader, const void **ppvBits) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + + /* + * Already mapped? + */ + if (pThis->pvMapping) + { + pThis->cMappings++; + *ppvBits = pThis->pvMapping; + return VINF_SUCCESS; + } + + /* + * Allocate memory. + */ + pThis->pvMapping = RTMemAlloc(pThis->cbImage); + if (!pThis->pvMapping) + return VERR_NO_MEMORY; + int rc = rtldrRdrMem_Read(pReader, pThis->pvMapping, pThis->cbImage, 0); + if (RT_SUCCESS(rc)) + { + pThis->cMappings = 1; + *ppvBits = pThis->pvMapping; + } + else + { + RTMemFree(pThis->pvMapping); + pThis->pvMapping = NULL; + } + + return rc; +} + + +/** @interface_method_impl{RTLDRREADER,pfnUnmap} */ +static DECLCALLBACK(int) rtldrRdrMem_Unmap(PRTLDRREADER pReader, const void *pvBits) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + AssertReturn(pThis->cMappings > 0, VERR_INVALID_PARAMETER); + + if (!--pThis->cMappings) + { + RTMemFree(pThis->pvMapping); + pThis->pvMapping = NULL; + } + + NOREF(pvBits); + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDRREADER,pfnDestroy} */ +static DECLCALLBACK(int) rtldrRdrMem_Destroy(PRTLDRREADER pReader) +{ + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)pReader; + pThis->pfnDtor(pThis->pvUser, pThis->cbImage); + pThis->pfnDtor = NULL; + pThis->pvUser = NULL; + RTMemFree(pThis); + return VINF_SUCCESS; +} + + +/** + * Opens a memory based loader reader. + * + * @returns iprt status code. + * @param ppReader Where to store the reader instance on success. + * @param pszName The name to give the image. + * @param cbImage The image size. + * @param pfnRead The reader function. If NULL, a default reader is + * used that assumes pvUser points to a memory buffer + * of at least @a cbImage size. + * @param pfnDtor The destructor. If NULL, a default destructore is + * used that will call RTMemFree on @a pvUser. + * @param pvUser User argument. If either @a pfnRead or @a pfnDtor + * is NULL, this must be a pointer to readable memory + * (see above). + */ +static int rtldrRdrMem_Create(PRTLDRREADER *ppReader, const char *pszName, size_t cbImage, + PFNRTLDRRDRMEMREAD pfnRead, PFNRTLDRRDRMEMDTOR pfnDtor, void *pvUser) +{ +#if ARCH_BITS > 32 /* 'ing gcc. */ + AssertReturn(cbImage < RTFOFF_MAX, VERR_INVALID_PARAMETER); +#endif + AssertReturn((RTFOFF)cbImage > 0, VERR_INVALID_PARAMETER); + + size_t cchName = strlen(pszName); + int rc = VERR_NO_MEMORY; + PRTLDRRDRMEM pThis = (PRTLDRRDRMEM)RTMemAlloc(sizeof(*pThis) + cchName); + if (pThis) + { + memcpy(pThis->szName, pszName, cchName + 1); + pThis->cbImage = cbImage; + pThis->pvUser = pvUser; + pThis->offCur = 0; + pThis->pvUser = pvUser; + pThis->pfnRead = pfnRead ? pfnRead : rtldrRdrMemDefaultReader; + pThis->pfnDtor = pfnDtor ? pfnDtor : rtldrRdrMemDefaultDtor; + pThis->pvMapping = NULL; + pThis->cMappings = 0; + pThis->Core.uMagic = RTLDRREADER_MAGIC; + pThis->Core.pfnRead = rtldrRdrMem_Read; + pThis->Core.pfnTell = rtldrRdrMem_Tell; + pThis->Core.pfnSize = rtldrRdrMem_Size; + pThis->Core.pfnLogName = rtldrRdrMem_LogName; + pThis->Core.pfnMap = rtldrRdrMem_Map; + pThis->Core.pfnUnmap = rtldrRdrMem_Unmap; + pThis->Core.pfnDestroy = rtldrRdrMem_Destroy; + *ppReader = &pThis->Core; + return VINF_SUCCESS; + } + + *ppReader = NULL; + return rc; +} + + +RTDECL(int) RTLdrOpenInMemory(const char *pszName, uint32_t fFlags, RTLDRARCH enmArch, size_t cbImage, + PFNRTLDRRDRMEMREAD pfnRead, PFNRTLDRRDRMEMDTOR pfnDtor, void *pvUser, + PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + LogFlow(("RTLdrOpenInMemory: pszName=%p:{%s} fFlags=%#x enmArch=%d cbImage=%#zx pfnRead=%p pfnDtor=%p pvUser=%p phLdrMod=%p pErrInfo=%p\n", + pszName, pszName, fFlags, enmArch, cbImage, pfnRead, pfnDtor, pvUser, phLdrMod, pErrInfo)); + + if (!pfnRead || !pfnDtor) + AssertPtrReturn(pvUser, VERR_INVALID_POINTER); + if (!pfnDtor) + pfnDtor = rtldrRdrMemDefaultDtor; + else + AssertPtrReturn(pfnDtor, VERR_INVALID_POINTER); + + /* The rest of the validations will call the destructor. */ + AssertMsgReturnStmt(!(fFlags & ~RTLDR_O_VALID_MASK), ("%#x\n", fFlags), + pfnDtor(pvUser, cbImage), VERR_INVALID_PARAMETER); + AssertMsgReturnStmt(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, ("%d\n", enmArch), + pfnDtor(pvUser, cbImage), VERR_INVALID_PARAMETER); + if (!pfnRead) + pfnRead = rtldrRdrMemDefaultReader; + else + AssertReturnStmt(RT_VALID_PTR(pfnRead), pfnDtor(pvUser, cbImage), VERR_INVALID_POINTER); + AssertReturnStmt(cbImage > 0, pfnDtor(pvUser, cbImage), VERR_INVALID_PARAMETER); + + /* + * Resolve RTLDRARCH_HOST. + */ + if (enmArch == RTLDRARCH_HOST) + enmArch = RTLdrGetHostArch(); + + /* + * Create file reader & invoke worker which identifies and calls the image interpreter. + */ + PRTLDRREADER pReader = NULL; /* gcc may be wrong */ + int rc = rtldrRdrMem_Create(&pReader, pszName, cbImage, pfnRead, pfnDtor, pvUser); + if (RT_SUCCESS(rc)) + { + rc = RTLdrOpenWithReader(pReader, fFlags, enmArch, phLdrMod, pErrInfo); + if (RT_SUCCESS(rc)) + { + LogFlow(("RTLdrOpen: return %Rrc *phLdrMod=%p\n", rc, *phLdrMod)); + return rc; + } + + pReader->pfnDestroy(pReader); + } + else + { + pfnDtor(pvUser, cbImage); + rc = RTErrInfoSetF(pErrInfo, rc, "rtldrRdrMem_Create failed: %Rrc", rc); + } + *phLdrMod = NIL_RTLDRMOD; + + LogFlow(("RTLdrOpen: return %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrOpenInMemory); + diff --git a/src/VBox/Runtime/common/ldr/ldrNative.cpp b/src/VBox/Runtime/common/ldr/ldrNative.cpp new file mode 100644 index 00000000..8675a5ae --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrNative.cpp @@ -0,0 +1,336 @@ +/* $Id: ldrNative.cpp $ */ +/** @file + * IPRT - Binary Image Loader, Native interface. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include "internal/ldr.h" + + +/** @copydoc RTLDROPS::pfnEnumSymbols */ +static DECLCALLBACK(int) rtldrNativeEnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, + RTUINTPTR BaseAddress, PFNRTLDRENUMSYMS pfnCallback, void *pvUser) +{ + NOREF(pMod); NOREF(fFlags); NOREF(pvBits); NOREF(BaseAddress); NOREF(pfnCallback); NOREF(pvUser); + return VERR_NOT_SUPPORTED; +} + + +/** @copydoc RTLDROPS::pfnDone */ +static DECLCALLBACK(int) rtldrNativeDone(PRTLDRMODINTERNAL pMod) +{ + NOREF(pMod); + return VINF_SUCCESS; +} + + +/** + * Operations for a native module. + */ +static const RTLDROPS g_rtldrNativeOps = +{ + "native", + rtldrNativeClose, + rtldrNativeGetSymbol, + rtldrNativeDone, + rtldrNativeEnumSymbols, + /* ext: */ + NULL, + NULL, + NULL, + NULL, + NULL /*pfnQueryForwarderInfo*/, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL /*pfnUnwindFrame*/, + 42 +}; + + + +RTDECL(int) RTLdrLoad(const char *pszFilename, PRTLDRMOD phLdrMod) +{ + return RTLdrLoadEx(pszFilename, phLdrMod, RTLDRLOAD_FLAGS_LOCAL, NULL); +} +RT_EXPORT_SYMBOL(RTLdrLoad); + + +RTDECL(int) RTLdrLoadEx(const char *pszFilename, PRTLDRMOD phLdrMod, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + LogFlow(("RTLdrLoadEx: pszFilename=%p:{%s} phLdrMod=%p fFlags=%#x pErrInfo=%p\n", pszFilename, pszFilename, phLdrMod, fFlags, pErrInfo)); + + /* + * Validate and massage the input. + */ + RTErrInfoClear(pErrInfo); + AssertPtrReturn(pszFilename, VERR_INVALID_POINTER); + AssertPtrReturn(phLdrMod, VERR_INVALID_POINTER); + AssertReturn(!(fFlags & ~RTLDRLOAD_FLAGS_VALID_MASK), VERR_INVALID_PARAMETER); + + /* + * Allocate and initialize module structure. + */ + int rc = VERR_NO_MEMORY; + PRTLDRMODNATIVE pMod = (PRTLDRMODNATIVE)RTMemAlloc(sizeof(*pMod)); + if (pMod) + { + pMod->Core.u32Magic = RTLDRMOD_MAGIC; + pMod->Core.eState = LDR_STATE_LOADED; + pMod->Core.pOps = &g_rtldrNativeOps; + pMod->Core.pReader = NULL; + pMod->Core.enmFormat = RTLDRFMT_NATIVE; + pMod->Core.enmType = RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; /* approx */ +#ifdef RT_BIG_ENDIAN + pMod->Core.enmEndian = RTLDRENDIAN_BIG; +#else + pMod->Core.enmEndian = RTLDRENDIAN_LITTLE; +#endif +#ifdef RT_ARCH_AMD64 + pMod->Core.enmArch = RTLDRARCH_AMD64; +#elif defined(RT_ARCH_X86) + pMod->Core.enmArch = RTLDRARCH_X86_32; +#else + pMod->Core.enmArch = RTLDRARCH_HOST; +#endif + pMod->hNative = ~(uintptr_t)0; + pMod->fFlags = fFlags; + + /* + * Attempt to open the module. + */ + rc = rtldrNativeLoad(pszFilename, &pMod->hNative, fFlags, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (fFlags & RTLDRLOAD_FLAGS_NO_UNLOAD) + RTMEM_MAY_LEAK(pMod); + + *phLdrMod = &pMod->Core; + LogFlow(("RTLdrLoad: returns %Rrc *phLdrMod=%RTldrm\n", rc,*phLdrMod)); + return rc; + } + + RTMemFree(pMod); + } + else + RTErrInfoSetF(pErrInfo, rc, "Failed to allocate %zu bytes for the module handle", sizeof(*pMod)); + *phLdrMod = NIL_RTLDRMOD; + LogFlow(("RTLdrLoad: returns %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrLoadEx); + + +RTDECL(int) RTLdrLoadSystem(const char *pszFilename, bool fNoUnload, PRTLDRMOD phLdrMod) +{ + return RTLdrLoadSystemEx(pszFilename, fNoUnload ? RTLDRLOAD_FLAGS_NO_UNLOAD : 0, phLdrMod); +} + + +RTDECL(int) RTLdrLoadSystemEx(const char *pszFilename, uint32_t fFlags, PRTLDRMOD phLdrMod) +{ + LogFlow(("RTLdrLoadSystemEx: pszFilename=%p:{%s} fFlags=%#RX32 phLdrMod=%p\n", pszFilename, pszFilename, fFlags, phLdrMod)); + + /* + * Validate input. + */ + AssertPtrReturn(phLdrMod, VERR_INVALID_PARAMETER); + *phLdrMod = NIL_RTLDRMOD; + AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER); + AssertMsgReturn(!RTPathHasPath(pszFilename), ("%s\n", pszFilename), VERR_INVALID_PARAMETER); + AssertMsgReturn(!(fFlags & ~(RTLDRLOAD_FLAGS_VALID_MASK | RTLDRLOAD_FLAGS_SO_VER_BEGIN_MASK | RTLDRLOAD_FLAGS_SO_VER_END_MASK)), + ("fFlags=%#RX32\n", fFlags), VERR_INVALID_FLAGS); + + /* + * Check the filename. + */ + size_t cchFilename = strlen(pszFilename); + AssertMsgReturn(cchFilename < (RTPATH_MAX / 4) * 3, ("%zu\n", cchFilename), VERR_INVALID_PARAMETER); + + const char *pszSuffix = NULL; + if (!RTPathHasSuffix(pszFilename)) + pszSuffix = RTLdrGetSuff(); + + /* + * Let the platform specific code do the rest. + */ + int rc = rtldrNativeLoadSystem(pszFilename, pszSuffix, fFlags, phLdrMod); + LogFlow(("RTLdrLoadSystem: returns %Rrc\n", rc)); + return rc; +} + + +RTDECL(void *) RTLdrGetSystemSymbol(const char *pszFilename, const char *pszSymbol) +{ + return RTLdrGetSystemSymbolEx(pszFilename, pszSymbol, RTLDRLOAD_FLAGS_NO_UNLOAD); +} + + +RTDECL(void *) RTLdrGetSystemSymbolEx(const char *pszFilename, const char *pszSymbol, uint32_t fFlags) +{ + void *pvRet = NULL; + RTLDRMOD hLdrMod; + int rc = RTLdrLoadSystemEx(pszFilename, fFlags | RTLDRLOAD_FLAGS_NO_UNLOAD, &hLdrMod); + if (RT_SUCCESS(rc)) + { + rc = RTLdrGetSymbol(hLdrMod, pszSymbol, &pvRet); + if (RT_FAILURE(rc)) + pvRet = NULL; /* paranoia */ + RTLdrClose(hLdrMod); + } + return pvRet; +} + + +RTDECL(int) RTLdrLoadAppPriv(const char *pszFilename, PRTLDRMOD phLdrMod) +{ + LogFlow(("RTLdrLoadAppPriv: pszFilename=%p:{%s} phLdrMod=%p\n", pszFilename, pszFilename, phLdrMod)); + + /* + * Validate input. + */ + AssertPtrReturn(phLdrMod, VERR_INVALID_PARAMETER); + *phLdrMod = NIL_RTLDRMOD; + AssertPtrReturn(pszFilename, VERR_INVALID_PARAMETER); + AssertMsgReturn(!RTPathHasPath(pszFilename), ("%s\n", pszFilename), VERR_INVALID_PARAMETER); + + /* + * Check the filename. + */ + size_t cchFilename = strlen(pszFilename); + AssertMsgReturn(cchFilename < (RTPATH_MAX / 4) * 3, ("%zu\n", cchFilename), VERR_INVALID_PARAMETER); + + const char *pszSuffix = ""; + size_t cchSuffix = 0; + if (!RTPathHasSuffix(pszFilename)) + { + pszSuffix = RTLdrGetSuff(); + cchSuffix = strlen(pszSuffix); + } + + /* + * Construct the private arch path and check if the file exists. + */ + char szPath[RTPATH_MAX]; + int rc = RTPathAppPrivateArch(szPath, sizeof(szPath) - 1 - cchSuffix - cchFilename); + AssertRCReturn(rc, rc); + + char *psz = strchr(szPath, '\0'); + *psz++ = RTPATH_SLASH; + memcpy(psz, pszFilename, cchFilename); + psz += cchFilename; + memcpy(psz, pszSuffix, cchSuffix + 1); + + if (!RTPathExists(szPath)) + { + LogRel(("RTLdrLoadAppPriv: \"%s\" not found\n", szPath)); + return VERR_FILE_NOT_FOUND; + } + + /* + * Pass it on to RTLdrLoad. + */ + rc = RTLdrLoad(szPath, phLdrMod); + + LogFlow(("RTLdrLoadAppPriv: returns %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrLoadAppPriv); + + +RTDECL(const char *) RTLdrGetSuff(void) +{ +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) + static const char s_szSuff[] = ".DLL"; +#elif defined(RT_OS_L4) + static const char s_szSuff[] = ".s.so"; +#elif defined(RT_OS_DARWIN) + static const char s_szSuff[] = ".dylib"; +#else + static const char s_szSuff[] = ".so"; +#endif + + return s_szSuff; +} +RT_EXPORT_SYMBOL(RTLdrGetSuff); + + +RTDECL(uintptr_t) RTLdrGetNativeHandle(RTLDRMOD hLdrMod) +{ + PRTLDRMODNATIVE pThis = (PRTLDRMODNATIVE)hLdrMod; + AssertPtrReturn(pThis, ~(uintptr_t)0); + AssertReturn(pThis->Core.u32Magic == RTLDRMOD_MAGIC, ~(uintptr_t)0); + AssertReturn(pThis->Core.pOps == &g_rtldrNativeOps, ~(uintptr_t)0); + return pThis->hNative; +} +RT_EXPORT_SYMBOL(RTLdrGetNativeHandle); + + +RTDECL(bool) RTLdrIsLoadable(const char *pszFilename) +{ + /* + * Try to load the library. + */ + RTLDRMOD hLib; + int rc = RTLdrLoad(pszFilename, &hLib); + if (RT_SUCCESS(rc)) + { + RTLdrClose(hLib); + return true; + } + return false; +} +RT_EXPORT_SYMBOL(RTLdrIsLoadable); + diff --git a/src/VBox/Runtime/common/ldr/ldrPE.cpp b/src/VBox/Runtime/common/ldr/ldrPE.cpp new file mode 100644 index 00000000..486be79e --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrPE.cpp @@ -0,0 +1,5194 @@ +/* $Id: ldrPE.cpp $ */ +/** @file + * IPRT - Binary Image Loader, Portable Executable (PE). + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* 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 +#if !defined(IPRT_WITHOUT_LDR_VERIFY) || !defined(IPRT_WITHOUT_LDR_PAGE_HASHING) +# include +#endif +#ifndef IPRT_WITHOUT_LDR_VERIFY +# include +# include +# include +#endif +#include +#include +#include "internal/ldr.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Converts rva to a type. + * @param pvBits Pointer to base of image bits. + * @param rva Relative virtual address. + * @param type Type. + */ +#define PE_RVA2TYPE(pvBits, rva, type) ((type) ((uintptr_t)pvBits + (uintptr_t)(rva)) ) + +/** The max size of the security directory. */ +#ifdef IN_RING3 +# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE _4M +#else +# define RTLDRMODPE_MAX_SECURITY_DIR_SIZE _1M +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The PE loader structure. + */ +typedef struct RTLDRMODPE +{ + /** Core module structure. */ + RTLDRMODINTERNAL Core; + /** Pointer to internal copy of image bits. + * @todo the reader should take care of this. */ + void *pvBits; + /** The offset of the NT headers. */ + RTFOFF offNtHdrs; + /** The offset of the first byte after the section table. */ + RTFOFF offEndOfHdrs; + + /** The machine type (IMAGE_FILE_HEADER::Machine). */ + uint16_t u16Machine; + /** The file flags (IMAGE_FILE_HEADER::Characteristics). */ + uint16_t fFile; + /** Number of sections (IMAGE_FILE_HEADER::NumberOfSections). */ + unsigned cSections; + /** Pointer to an array of the section headers related to the file. */ + PIMAGE_SECTION_HEADER paSections; + + /** The RVA of the entry point (IMAGE_OPTIONAL_HEADER32::AddressOfEntryPoint). */ + RTUINTPTR uEntryPointRVA; + /** The base address of the image at link time (IMAGE_OPTIONAL_HEADER32::ImageBase). */ + RTUINTPTR uImageBase; + /** The size of the loaded image (IMAGE_OPTIONAL_HEADER32::SizeOfImage). */ + uint32_t cbImage; + /** Size of the header (IMAGE_OPTIONAL_HEADER32::SizeOfHeaders). */ + uint32_t cbHeaders; + /** Section alignment (IMAGE_OPTIONAL_HEADER32::SectionAlignment). */ + uint32_t uSectionAlign; + /** The image timestamp. */ + uint32_t uTimestamp; + /** The number of imports. UINT32_MAX if not determined. */ + uint32_t cImports; + /** Set if the image is 64-bit, clear if 32-bit. */ + bool f64Bit; + /** The import data directory entry. */ + IMAGE_DATA_DIRECTORY ImportDir; + /** The base relocation data directory entry. */ + IMAGE_DATA_DIRECTORY RelocDir; + /** The export data directory entry. */ + IMAGE_DATA_DIRECTORY ExportDir; + /** The debug directory entry. */ + IMAGE_DATA_DIRECTORY DebugDir; + /** The security directory entry. */ + IMAGE_DATA_DIRECTORY SecurityDir; + /** The exception data directory entry. */ + IMAGE_DATA_DIRECTORY ExceptionDir; + + /** Offset of the first PKCS \#7 SignedData signature if present. */ + uint32_t offPkcs7SignedData; + /** Size of the first PKCS \#7 SignedData. */ + uint32_t cbPkcs7SignedData; + + /** Copy of the optional header field DllCharacteristics. */ + uint16_t fDllCharacteristics; +} RTLDRMODPE; +/** Pointer to the instance data for a PE loader module. */ +typedef RTLDRMODPE *PRTLDRMODPE; + + +/** + * PE Loader module operations. + * + * The PE loader has one operation which is a bit different between 32-bit and 64-bit PE images, + * and for historical and performance reasons have been split into separate functions. Thus the + * PE loader extends the RTLDROPS structure with this one entry. + */ +typedef struct RTLDROPSPE +{ + /** The usual ops. */ + RTLDROPS Core; + + /** + * Resolves all imports. + * + * @returns iprt status code. + * @param pModPe Pointer to the PE loader module structure. + * @param pvBitsR Where to read raw image bits. (optional) + * @param pvBitsW Where to store the imports. The size of this buffer is equal or + * larger to the value returned by pfnGetImageSize(). + * @param pfnGetImport The callback function to use to resolve imports (aka unresolved externals). + * @param pvUser User argument to pass to the callback. + */ + DECLCALLBACKMEMBER(int, pfnResolveImports,(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser)); + + /** Dummy entry to make sure we've initialized it all. */ + RTUINT uDummy; +} RTLDROPSPE, *PRTLDROPSPE; + + +/** + * PE hash context union. + */ +typedef union RTLDRPEHASHCTXUNION +{ + RTSHA512CONTEXT Sha512; + RTSHA384CONTEXT Sha384; + RTSHA256CONTEXT Sha256; + RTSHA1CONTEXT Sha1; + RTMD5CONTEXT Md5; +} RTLDRPEHASHCTXUNION; +/** Pointer to a PE hash context union. */ +typedef RTLDRPEHASHCTXUNION *PRTLDRPEHASHCTXUNION; + + +/** + * PE hash digests + */ +typedef union RTLDRPEHASHRESUNION +{ + uint8_t abSha512[RTSHA512_HASH_SIZE]; + uint8_t abSha384[RTSHA384_HASH_SIZE]; + uint8_t abSha256[RTSHA256_HASH_SIZE]; + uint8_t abSha1[RTSHA1_HASH_SIZE]; + uint8_t abMd5[RTMD5_HASH_SIZE]; +} RTLDRPEHASHRESUNION; +/** Pointer to a PE hash work set. */ +typedef RTLDRPEHASHRESUNION *PRTLDRPEHASHRESUNION; + +/** + * Special places to watch out for when hashing a PE image. + */ +typedef struct RTLDRPEHASHSPECIALS +{ + uint32_t cbToHash; + uint32_t offCksum; + uint32_t cbCksum; + uint32_t offSecDir; + uint32_t cbSecDir; + uint32_t offEndSpecial; +} RTLDRPEHASHSPECIALS; +/** Pointer to the structure with the special hash places. */ +typedef RTLDRPEHASHSPECIALS *PRTLDRPEHASHSPECIALS; + + +#ifndef IPRT_WITHOUT_LDR_VERIFY +/** + * Parsed data for one signature. + */ +typedef struct RTLDRPESIGNATUREONE +{ + /** The outer content info wrapper. */ + PRTCRPKCS7CONTENTINFO pContentInfo; + /** Pointer to the decoded SignedData inside the ContentInfo member. */ + PRTCRPKCS7SIGNEDDATA pSignedData; + /** Pointer to the indirect data content. */ + PRTCRSPCINDIRECTDATACONTENT pIndData; + /** The digest type employed by the signature. */ + RTDIGESTTYPE enmDigest; + /** Set if we've already validate the image hash. */ + bool fValidatedImageHash; + /** The signature number. */ + uint16_t iSignature; + /** Hash result. */ + RTLDRPEHASHRESUNION HashRes; +} RTLDRPESIGNATUREONE; +/** Pointer to the parsed data of one signature. */ +typedef RTLDRPESIGNATUREONE *PRTLDRPESIGNATUREONE; + +/** + * Parsed signature data. + */ +typedef struct RTLDRPESIGNATURE +{ + /** Pointer to the raw signatures. This is allocated in the continuation of + * this structure to keep things simple. The size is given by the security + * export directory. */ + WIN_CERTIFICATE const *pRawData; + /** The outer content info wrapper (primary signature). */ + RTCRPKCS7CONTENTINFO PrimaryContentInfo; + /** The info for the primary signature. */ + RTLDRPESIGNATUREONE Primary; + /** Number of nested signatures (zero if none). */ + uint16_t cNested; + /** Pointer to an array of nested signatures (NULL if none). */ + PRTLDRPESIGNATUREONE paNested; + /** Hash scratch data. */ + RTLDRPEHASHCTXUNION HashCtx; +} RTLDRPESIGNATURE; +/** Pointed to SigneData parsing stat and output. */ +typedef RTLDRPESIGNATURE *PRTLDRPESIGNATURE; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void rtldrPEConvert32BitOptionalHeaderTo64Bit(PIMAGE_OPTIONAL_HEADER64 pOptHdr); +static void rtldrPEConvert32BitLoadConfigTo64Bit(PIMAGE_LOAD_CONFIG_DIRECTORY64 pLoadCfg); +static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress, RTUINTPTR OldBaseAddress); +#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING +static int rtLdrPE_QueryPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE enmDigest, void *pvBuf, size_t cbBuf, size_t *pcbRet); +static uint32_t rtLdrPE_GetHashablePages(PRTLDRMODPE pModPe); +#endif + + + +/** + * Reads a section of a PE image given by RVA + size, using mapped bits if + * available or allocating heap memory and reading from the file. + * + * @returns IPRT status code. + * @param pThis Pointer to the PE loader module structure. + * @param pvBits Read only bits if available. NULL if not. + * @param uRva The RVA to read at. + * @param cbMem The number of bytes to read. + * @param ppvMem Where to return the memory on success (heap or + * inside pvBits). + */ +static int rtldrPEReadPartByRva(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void const **ppvMem) +{ + *ppvMem = NULL; + if (!cbMem) + return VINF_SUCCESS; + + /* + * Use bits if we've got some. + */ + if (pvBits) + { + *ppvMem = (uint8_t const *)pvBits + uRva; + return VINF_SUCCESS; + } + if (pThis->pvBits) + { + *ppvMem = (uint8_t const *)pThis->pvBits + uRva; + return VINF_SUCCESS; + } + + /* + * Allocate a buffer and read the bits from the file (or whatever). + */ + if (!pThis->Core.pReader) + return VERR_ACCESS_DENIED; + + uint8_t *pbMem = (uint8_t *)RTMemAllocZ(cbMem); + if (!pbMem) + return VERR_NO_MEMORY; + *ppvMem = pbMem; + + /* Do the reading on a per section base. */ + uint64_t const cbFile = pThis->Core.pReader->pfnSize(pThis->Core.pReader); + for (;;) + { + /* Translate the RVA into a file offset. */ + uint32_t offFile = uRva; + uint32_t cbToRead = cbMem; + uint32_t cbToAdv = cbMem; + + if (uRva < pThis->paSections[0].VirtualAddress) + { + /* Special header section. */ + cbToRead = pThis->paSections[0].VirtualAddress - uRva; + if (cbToRead > cbMem) + cbToRead = cbMem; + cbToAdv = cbToRead; + + /* The following capping is an approximation. */ + uint32_t offFirstRawData = RT_ALIGN(pThis->cbHeaders, _4K); + if ( pThis->paSections[0].PointerToRawData > 0 + && pThis->paSections[0].SizeOfRawData > 0) + offFirstRawData = pThis->paSections[0].PointerToRawData; + if (offFile >= offFirstRawData) + cbToRead = 0; + else if (offFile + cbToRead > offFirstRawData) + cbToRead = offFile - offFirstRawData; + } + else + { + /* Find the matching section and its mapping size. */ + uint32_t j = 0; + uint32_t cbMapping = 0; + uint32_t offSection = 0; + while (j < pThis->cSections) + { + cbMapping = (j + 1 < pThis->cSections ? pThis->paSections[j + 1].VirtualAddress : pThis->cbImage) + - pThis->paSections[j].VirtualAddress; + offSection = uRva - pThis->paSections[j].VirtualAddress; + if (offSection < cbMapping) + break; + j++; + } + if (j >= cbMapping) + break; /* This shouldn't happen, just return zeros if it does. */ + + /* Adjust the sizes and calc the file offset. */ + if (offSection + cbToAdv > cbMapping) + cbToAdv = cbToRead = cbMapping - offSection; + + if ( pThis->paSections[j].PointerToRawData > 0 + && pThis->paSections[j].SizeOfRawData > 0) + { + offFile = offSection; + if (offFile + cbToRead > pThis->paSections[j].SizeOfRawData) + cbToRead = pThis->paSections[j].SizeOfRawData - offFile; + offFile += pThis->paSections[j].PointerToRawData; + } + else + { + offFile = UINT32_MAX; + cbToRead = 0; + } + } + + /* Perform the read after adjusting a little (paranoia). */ + if (offFile > cbFile) + cbToRead = 0; + if (cbToRead) + { + if ((uint64_t)offFile + cbToRead > cbFile) + cbToRead = (uint32_t)(cbFile - (uint64_t)offFile); + int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbMem, cbToRead, offFile); + if (RT_FAILURE(rc)) + { + RTMemFree((void *)*ppvMem); + *ppvMem = NULL; + return rc; + } + } + + /* Advance */ + if (cbMem <= cbToAdv) + break; + cbMem -= cbToAdv; + pbMem += cbToAdv; + uRva += cbToAdv; + } + + return VINF_SUCCESS; +} + + +/** + * Reads a part of a PE file from the file and into a heap block. + * + * @returns IRPT status code. + * @param pThis Pointer to the PE loader module structure.. + * @param offFile The file offset. + * @param cbMem The number of bytes to read. + * @param ppvMem Where to return the heap block with the bytes on + * success. + */ +static int rtldrPEReadPartFromFile(PRTLDRMODPE pThis, uint32_t offFile, uint32_t cbMem, void const **ppvMem) +{ + *ppvMem = NULL; + if (!cbMem) + return VINF_SUCCESS; + + /* + * Allocate a buffer and read the bits from the file (or whatever). + */ + if (!pThis->Core.pReader) + return VERR_ACCESS_DENIED; + + uint8_t *pbMem = (uint8_t *)RTMemAlloc(cbMem); + if (!pbMem) + return VERR_NO_MEMORY; + + int rc = pThis->Core.pReader->pfnRead(pThis->Core.pReader, pbMem, cbMem, offFile); + if (RT_FAILURE(rc)) + { + RTMemFree((void *)*ppvMem); + return rc; + } + + *ppvMem = pbMem; + return VINF_SUCCESS; +} + + +/** + * Reads a part of a PE image into memory one way or another. + * + * Either the RVA or the offFile must be valid. We'll prefer the RVA if + * possible. + * + * @returns IPRT status code. + * @param pThis Pointer to the PE loader module structure. + * @param pvBits Read only bits if available. NULL if not. + * @param uRva The RVA to read at. + * @param offFile The file offset. + * @param cbMem The number of bytes to read. + * @param ppvMem Where to return the memory on success (heap or + * inside pvBits). + */ +static int rtldrPEReadPart(PRTLDRMODPE pThis, const void *pvBits, RTFOFF offFile, RTLDRADDR uRva, + uint32_t cbMem, void const **ppvMem) +{ + if ( uRva == NIL_RTLDRADDR + || uRva > pThis->cbImage + || cbMem > pThis->cbImage + || uRva + cbMem > pThis->cbImage) + { + if (offFile < 0 || offFile >= UINT32_MAX) + return VERR_INVALID_PARAMETER; + return rtldrPEReadPartFromFile(pThis, (uint32_t)offFile, cbMem, ppvMem); + } + return rtldrPEReadPartByRva(pThis, pvBits, (uint32_t)uRva, cbMem, ppvMem); +} + + +/** + * Frees up memory returned by rtldrPEReadPart*. + * + * @param pThis Pointer to the PE loader module structure.. + * @param pvBits Read only bits if available. NULL if not.. + * @param pvMem The memory we were given by the reader method. + */ +static void rtldrPEFreePart(PRTLDRMODPE pThis, const void *pvBits, void const *pvMem) +{ + if (!pvMem) + return; + + if (pvBits && (uintptr_t)pvMem - (uintptr_t)pvBits < pThis->cbImage) + return; + if (pThis->pvBits && (uintptr_t)pvMem - (uintptr_t)pThis->pvBits < pThis->cbImage) + return; + + RTMemFree((void *)pvMem); +} + + +/** + * Reads a section of a PE image given by RVA + size. + * + * @returns IPRT status code. + * @param pThis Pointer to the PE loader module structure. + * @param pvBits Read only bits if available. NULL if not. + * @param uRva The RVA to read at. + * @param cbMem The number of bytes to read. + * @param pvDst The destination buffer. + */ +static int rtldrPEReadPartByRvaInfoBuf(PRTLDRMODPE pThis, const void *pvBits, uint32_t uRva, uint32_t cbMem, void *pvDst) +{ + /** @todo consider optimizing this. */ + const void *pvSrc = NULL; + int rc = rtldrPEReadPartByRva(pThis, pvBits, uRva, cbMem, &pvSrc); + if (RT_SUCCESS(rc)) + { + memcpy(pvDst, pvSrc, cbMem); + rtldrPEFreePart(pThis, NULL, pvSrc); + } + return rc; +} + + + + + +/** @interface_method_impl{RTLDROPS,pfnGetImageSize} */ +static DECLCALLBACK(size_t) rtldrPEGetImageSize(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + return pModPe->cbImage; +} + + +/** + * Reads the image into memory. + * + * @returns iprt status code. + * @param pModPe The PE module. + * @param pvBits Where to store the bits, this buffer is at least pItem->Core.cbImage in size. + */ +static int rtldrPEGetBitsNoImportsNorFixups(PRTLDRMODPE pModPe, void *pvBits) +{ + /* + * Both these checks are related to pfnDone(). + */ + PRTLDRREADER pReader = pModPe->Core.pReader; + if (!pReader) + { + AssertMsgFailed(("You've called done!\n")); + return VERR_WRONG_ORDER; + } + if (!pvBits) + return VERR_NO_MEMORY; + + /* + * Zero everything (could be done per section). + */ + memset(pvBits, 0, pModPe->cbImage); + +#ifdef PE_FILE_OFFSET_EQUALS_RVA + /* + * Read the entire image / file. + */ + const uint64_t cbRawImage = pReader->pfnSize(pReader) + rc = pReader->pfnRead(pReader, pvBits, RT_MIN(pModPe->cbImage, cbRawImage), 0); + if (RT_FAILURE(rc)) + Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!! (the entire image)\n", + pReader->pfnLogName(pReader), RT_MIN(pModPe->cbImage, cbRawImage), 0, rc)); +#else + + /* + * Read the headers. + */ + int rc = pReader->pfnRead(pReader, pvBits, pModPe->cbHeaders, 0); + if (RT_SUCCESS(rc)) + { + /* + * Read the sections. + */ + PIMAGE_SECTION_HEADER pSH = pModPe->paSections; + for (unsigned cLeft = pModPe->cSections; cLeft > 0; cLeft--, pSH++) + if ( pSH->SizeOfRawData + && pSH->Misc.VirtualSize + && !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + { + uint32_t const cbToRead = RT_MIN(pSH->SizeOfRawData, pModPe->cbImage - pSH->VirtualAddress); + Assert(pSH->VirtualAddress <= pModPe->cbImage); + + rc = pReader->pfnRead(pReader, (uint8_t *)pvBits + pSH->VirtualAddress, cbToRead, pSH->PointerToRawData); + if (RT_FAILURE(rc)) + { + Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc - section #%d '%.*s'!!!\n", + pReader->pfnLogName(pReader), pSH->SizeOfRawData, pSH->PointerToRawData, rc, + pSH - pModPe->paSections, sizeof(pSH->Name), pSH->Name)); + break; + } + } + } + else + Log(("rtldrPE: %s: Reading %#x bytes at offset %#x failed, %Rrc!!!\n", + pReader->pfnLogName(pReader), pModPe->cbHeaders, 0, rc)); +#endif + return rc; +} + + +/** + * Reads the bits into the internal buffer pointed to by PRTLDRMODPE::pvBits. + * + * @returns iprt status code. + * @param pModPe The PE module. + */ +static int rtldrPEReadBits(PRTLDRMODPE pModPe) +{ + Assert(!pModPe->pvBits); + void *pvBitsW = RTMemAllocZ(pModPe->cbImage); + if (!pvBitsW) + return VERR_NO_MEMORY; + int rc = rtldrPEGetBitsNoImportsNorFixups(pModPe, pvBitsW); + if (RT_SUCCESS(rc)) + pModPe->pvBits = pvBitsW; + else + RTMemFree(pvBitsW); + return rc; +} + + +/** @interface_method_impl{RTLDROPS,pfnGetBits} */ +static DECLCALLBACK(int) rtldrPEGetBits(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR BaseAddress, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + /* + * Read the image. + */ + int rc = rtldrPEGetBitsNoImportsNorFixups(pModPe, pvBits); + if (RT_SUCCESS(rc)) + { + /* + * Resolve imports. + */ + if (pfnGetImport) + rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pvBits, pvBits, pfnGetImport, pvUser); + if (RT_SUCCESS(rc)) + { + /* + * Apply relocations. + */ + rc = rtldrPEApplyFixups(pModPe, pvBits, pvBits, BaseAddress, pModPe->uImageBase); + if (RT_SUCCESS(rc)) + return rc; + AssertMsgFailed(("Failed to apply fixups. rc=%Rrc\n", rc)); + } +#ifndef IN_SUP_HARDENED_R3 + else + AssertMsgFailed(("Failed to resolve imports. rc=%Rrc\n", rc)); +#endif + } + return rc; +} + + +/* The image_thunk_data32/64 structures are not very helpful except for getting RSI. keep them around till all the code has been converted. */ +typedef struct _IMAGE_THUNK_DATA32 +{ + union + { + uint32_t ForwarderString; + uint32_t Function; + uint32_t Ordinal; + uint32_t AddressOfData; + } u1; +} IMAGE_THUNK_DATA32; +typedef IMAGE_THUNK_DATA32 *PIMAGE_THUNK_DATA32; + + +/** @copydoc RTLDROPSPE::pfnResolveImports */ +static DECLCALLBACK(int) rtldrPEResolveImports32(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + /* + * Check if there is actually anything to work on. + */ + if ( !pModPe->ImportDir.VirtualAddress + || !pModPe->ImportDir.Size) + return 0; + + /* + * Walk the IMAGE_IMPORT_DESCRIPTOR table. + */ + int rc = VINF_SUCCESS; + PIMAGE_IMPORT_DESCRIPTOR pImps; + for (pImps = PE_RVA2TYPE(pvBitsR, pModPe->ImportDir.VirtualAddress, PIMAGE_IMPORT_DESCRIPTOR); + !rc && pImps->Name != 0 && pImps->FirstThunk != 0; + pImps++) + { + AssertReturn(pImps->Name < pModPe->cbImage, VERR_BAD_EXE_FORMAT); + const char *pszModName = PE_RVA2TYPE(pvBitsR, pImps->Name, const char *); + AssertReturn(pImps->FirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT); + AssertReturn(pImps->u.OriginalFirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT); + + Log3(("RTLdrPE: Import descriptor: %s\n", pszModName)); + Log4(("RTLdrPE: OriginalFirstThunk = %#RX32\n" + "RTLdrPE: TimeDateStamp = %#RX32\n" + "RTLdrPE: ForwarderChain = %#RX32\n" + "RTLdrPE: Name = %#RX32\n" + "RTLdrPE: FirstThunk = %#RX32\n", + pImps->u.OriginalFirstThunk, pImps->TimeDateStamp, + pImps->ForwarderChain, pImps->Name, pImps->FirstThunk)); + + /* + * Walk the thunks table(s). + */ + PIMAGE_THUNK_DATA32 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA32); /* update this. */ + PIMAGE_THUNK_DATA32 pThunk = pImps->u.OriginalFirstThunk == 0 /* read from this. */ + ? PE_RVA2TYPE(pvBitsR, pImps->FirstThunk, PIMAGE_THUNK_DATA32) + : PE_RVA2TYPE(pvBitsR, pImps->u.OriginalFirstThunk, PIMAGE_THUNK_DATA32); + while (!rc && pThunk->u1.Ordinal != 0) + { + RTUINTPTR Value = 0; + if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32) + { + rc = pfnGetImport(&pModPe->Core, pszModName, NULL, IMAGE_ORDINAL32(pThunk->u1.Ordinal), &Value, pvUser); + Log4((RT_SUCCESS(rc) ? "RTLdrPE: %RTptr #%u\n" : "RTLdrPE: %08RX32 #%u rc=%Rrc\n", + (uint32_t)Value, IMAGE_ORDINAL32(pThunk->u1.Ordinal), rc)); + } + else if ( pThunk->u1.Ordinal > 0 + && pThunk->u1.Ordinal < pModPe->cbImage) + { + rc = pfnGetImport(&pModPe->Core, pszModName, + PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *), + ~0U, &Value, pvUser); + Log4((RT_SUCCESS(rc) ? "RTLdrPE: %RTptr %s\n" : "RTLdrPE: %08RX32 %s rc=%Rrc\n", + (uint32_t)Value, PE_RVA2TYPE(pvBitsR, (char*)(uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc)); + } + else + { + AssertMsgFailed(("bad import data thunk!\n")); + rc = VERR_BAD_EXE_FORMAT; + } + pFirstThunk->u1.Function = (uint32_t)Value; + if (pFirstThunk->u1.Function != Value) + { + AssertMsgFailed(("external symbol address to big!\n")); + rc = VERR_ADDRESS_CONFLICT; /** @todo get me a better error status code. */ + } + pThunk++; + pFirstThunk++; + } + } + + return rc; +} + + +/* The image_thunk_data32/64 structures are not very helpful except for getting RSI. keep them around till all the code has been converted. */ +typedef struct _IMAGE_THUNK_DATA64 +{ + union + { + uint64_t ForwarderString; + uint64_t Function; + uint64_t Ordinal; + uint64_t AddressOfData; + } u1; +} IMAGE_THUNK_DATA64; +typedef IMAGE_THUNK_DATA64 *PIMAGE_THUNK_DATA64; + + +/** @copydoc RTLDROPSPE::pfnResolveImports */ +static DECLCALLBACK(int) rtldrPEResolveImports64(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, + PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + /* + * Check if there is actually anything to work on. + */ + if ( !pModPe->ImportDir.VirtualAddress + || !pModPe->ImportDir.Size) + return 0; + + /* + * Walk the IMAGE_IMPORT_DESCRIPTOR table. + */ + int rc = VINF_SUCCESS; + PIMAGE_IMPORT_DESCRIPTOR pImps; + for (pImps = PE_RVA2TYPE(pvBitsR, pModPe->ImportDir.VirtualAddress, PIMAGE_IMPORT_DESCRIPTOR); + !rc && pImps->Name != 0 && pImps->FirstThunk != 0; + pImps++) + { + AssertReturn(pImps->Name < pModPe->cbImage, VERR_BAD_EXE_FORMAT); + const char *pszModName = PE_RVA2TYPE(pvBitsR, pImps->Name, const char *); + AssertReturn(pImps->FirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT); + AssertReturn(pImps->u.OriginalFirstThunk < pModPe->cbImage, VERR_BAD_EXE_FORMAT); + + Log3(("RTLdrPE: Import descriptor: %s\n", pszModName)); + Log4(("RTLdrPE: OriginalFirstThunk = %#RX32\n" + "RTLdrPE: TimeDateStamp = %#RX32\n" + "RTLdrPE: ForwarderChain = %#RX32\n" + "RTLdrPE: Name = %#RX32\n" + "RTLdrPE: FirstThunk = %#RX32\n", + pImps->u.OriginalFirstThunk, pImps->TimeDateStamp, + pImps->ForwarderChain, pImps->Name, pImps->FirstThunk)); + + /* + * Walk the thunks table(s). + */ + PIMAGE_THUNK_DATA64 pFirstThunk = PE_RVA2TYPE(pvBitsW, pImps->FirstThunk, PIMAGE_THUNK_DATA64); /* update this. */ + PIMAGE_THUNK_DATA64 pThunk = pImps->u.OriginalFirstThunk == 0 /* read from this. */ + ? PE_RVA2TYPE(pvBitsR, pImps->FirstThunk, PIMAGE_THUNK_DATA64) + : PE_RVA2TYPE(pvBitsR, pImps->u.OriginalFirstThunk, PIMAGE_THUNK_DATA64); + while (!rc && pThunk->u1.Ordinal != 0) + { + RTUINTPTR Value = 0; + if (pThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) + { + rc = pfnGetImport(&pModPe->Core, pszModName, NULL, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), &Value, pvUser); + Log4((RT_SUCCESS(rc) ? "RTLdrPE: %016RX64 #%u\n" : "RTLdrPE: %016RX64 #%u rc=%Rrc\n", + (uint64_t)Value, (unsigned)IMAGE_ORDINAL64(pThunk->u1.Ordinal), rc)); + } + else if ( pThunk->u1.Ordinal > 0 + && pThunk->u1.Ordinal < pModPe->cbImage) + { + /** @todo add validation of the string pointer! */ + rc = pfnGetImport(&pModPe->Core, pszModName, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *), + ~0U, &Value, pvUser); + Log4((RT_SUCCESS(rc) ? "RTLdrPE: %016RX64 %s\n" : "RTLdrPE: %016RX64 %s rc=%Rrc\n", + (uint64_t)Value, PE_RVA2TYPE(pvBitsR, (uintptr_t)pThunk->u1.AddressOfData + 2, const char *), rc)); + } + else + { + AssertMsgFailed(("bad import data thunk!\n")); + rc = VERR_BAD_EXE_FORMAT; + } + pFirstThunk->u1.Function = Value; + pThunk++; + pFirstThunk++; + } + } + + return rc; +} + + +/** + * Applies fixups. + */ +static int rtldrPEApplyFixups(PRTLDRMODPE pModPe, const void *pvBitsR, void *pvBitsW, RTUINTPTR BaseAddress, + RTUINTPTR OldBaseAddress) +{ + if ( !pModPe->RelocDir.VirtualAddress + || !pModPe->RelocDir.Size) + return 0; + + /* + * Apply delta fixups iterating fixup chunks. + */ + PIMAGE_BASE_RELOCATION pbr = PE_RVA2TYPE(pvBitsR, pModPe->RelocDir.VirtualAddress, PIMAGE_BASE_RELOCATION); + PIMAGE_BASE_RELOCATION pBaseRelocs = pbr; + unsigned cbBaseRelocs = pModPe->RelocDir.Size; + RTUINTPTR uDelta = BaseAddress - OldBaseAddress; + Log2(("RTLdrPE: Fixups: uDelta=%#RTptr BaseAddress=%#RTptr OldBaseAddress=%#RTptr\n", uDelta, BaseAddress, OldBaseAddress)); + Log4(("RTLdrPE: BASERELOC: VirtualAddres=%RX32 Size=%RX32\n", pModPe->RelocDir.VirtualAddress, pModPe->RelocDir.Size)); + Assert(sizeof(*pbr) == sizeof(uint32_t) * 2); + + while ( (uintptr_t)pbr - (uintptr_t)pBaseRelocs + 8 < cbBaseRelocs /* 8= VirtualAddress and SizeOfBlock members */ + && pbr->SizeOfBlock >= 8) + { + uint16_t *pwoffFixup = (uint16_t *)(pbr + 1); + uint32_t cRelocations = (pbr->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(uint16_t); + Log3(("RTLdrPE: base relocs for %#010x, size %#06x (%d relocs)\n", pbr->VirtualAddress, pbr->SizeOfBlock, cRelocations)); + + /* Some bound checking just to be sure it works... */ + if ((uintptr_t)pbr - (uintptr_t)pBaseRelocs + pbr->SizeOfBlock > cbBaseRelocs) + cRelocations = (uint32_t)( (((uintptr_t)pBaseRelocs + cbBaseRelocs) - (uintptr_t)pbr - sizeof(IMAGE_BASE_RELOCATION)) + / sizeof(uint16_t) ); + + /* + * Loop thru the fixups in this chunk. + */ + while (cRelocations != 0) + { + /* + * Common fixup + */ + static const char * const s_apszReloc[16] = + { + "ABS", "HIGH", "LOW", "HIGHLOW", "HIGHADJ", "MIPS_JMPADDR", "RES6", "RES7", + "RES8", "IA64_IMM64", "DIR64", "HIGH3ADJ", "RES12", "RES13", "RES14", "RES15" + }; NOREF(s_apszReloc); + union + { + uint16_t *pu16; + uint32_t *pu32; + uint64_t *pu64; + } u; + const int offFixup = *pwoffFixup & 0xfff; + u.pu32 = PE_RVA2TYPE(pvBitsW, offFixup + pbr->VirtualAddress, uint32_t *); + const int fType = *pwoffFixup >> 12; + Log4(("RTLdrPE: %08x %s\n", offFixup + pbr->VirtualAddress, s_apszReloc[fType])); + switch (fType) + { + case IMAGE_REL_BASED_HIGHLOW: /* 32-bit, add delta. */ + *u.pu32 += (uint32_t)uDelta; + break; + case IMAGE_REL_BASED_DIR64: /* 64-bit, add delta. */ + *u.pu64 += (RTINTPTR)uDelta; + break; + case IMAGE_REL_BASED_ABSOLUTE: /* Alignment placeholder. */ + break; + /* odd ones */ + case IMAGE_REL_BASED_LOW: /* 16-bit, add 1st 16-bit part of the delta. */ + *u.pu16 += (uint16_t)uDelta; + break; + case IMAGE_REL_BASED_HIGH: /* 16-bit, add 2nd 16-bit part of the delta. */ + *u.pu16 += (uint16_t)(uDelta >> 16); + break; + /* never ever seen these next two, and I'm not 100% sure they are correctly implemented here. */ + case IMAGE_REL_BASED_HIGHADJ: + { + if (cRelocations <= 1) + { + AssertMsgFailed(("HIGHADJ missing 2nd record!\n")); + return VERR_BAD_EXE_FORMAT; + } + cRelocations--; + pwoffFixup++; + int32_t i32 = (uint32_t)(*u.pu16 << 16) | *pwoffFixup; + i32 += (uint32_t)uDelta; + i32 += 0x8000; //?? + *u.pu16 = (uint16_t)(i32 >> 16); + break; + } + case IMAGE_REL_BASED_HIGH3ADJ: + { + if (cRelocations <= 2) + { + AssertMsgFailed(("HIGHADJ3 missing 2nd record!\n")); + return VERR_BAD_EXE_FORMAT; + } + cRelocations -= 2; + pwoffFixup++; + int64_t i64 = ((uint64_t)*u.pu16 << 32) | *(uint32_t *)pwoffFixup++; + i64 += (int64_t)uDelta << 16; //?? + i64 += 0x80000000;//?? + *u.pu16 = (uint16_t)(i64 >> 32); + break; + } + default: + AssertMsgFailed(("Unknown fixup type %d offset=%#x\n", fType, offFixup)); + break; + } + + /* + * Next offset/type + */ + pwoffFixup++; + cRelocations--; + } /* while loop */ + + /* + * Next Fixup chunk. (i.e. next page) + */ + pbr = (PIMAGE_BASE_RELOCATION)((uintptr_t)pbr + pbr->SizeOfBlock); + } /* while loop */ + + return 0; +} + + +/** @interface_method_impl{RTLDROPS,pfnRelocate} */ +static DECLCALLBACK(int) rtldrPERelocate(PRTLDRMODINTERNAL pMod, void *pvBits, RTUINTPTR NewBaseAddress, RTUINTPTR OldBaseAddress, + PFNRTLDRIMPORT pfnGetImport, void *pvUser) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + /* + * Do we have to read the image bits? + */ + if (!pModPe->pvBits) + { + int rc = rtldrPEReadBits(pModPe); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Process imports. + */ + int rc = ((PRTLDROPSPE)pMod->pOps)->pfnResolveImports(pModPe, pModPe->pvBits, pvBits, pfnGetImport, pvUser); + if (RT_SUCCESS(rc)) + { + /* + * Apply relocations. + */ + rc = rtldrPEApplyFixups(pModPe, pModPe->pvBits, pvBits, NewBaseAddress, OldBaseAddress); + AssertRC(rc); + } + return rc; +} + + +/** + * Internal worker for pfnGetSymbolEx and pfnQueryForwarderInfo. + * + * @returns IPRT status code. + * @param pModPe The PE module instance. + * @param iOrdinal The symbol ordinal, UINT32_MAX if named symbol. + * @param pszSymbol The symbol name. + * @param ppvBits The image bits pointer (input/output). + * @param puRvaExport Where to return the symbol RVA. + * @param puOrdinal Where to return the ordinal number. Optional. + */ +static int rtLdrPE_ExportToRva(PRTLDRMODPE pModPe, uint32_t iOrdinal, const char *pszSymbol, + const void **ppvBits, uint32_t *puRvaExport, uint32_t *puOrdinal) +{ + /* + * Check if there is actually anything to work on. + */ + if ( !pModPe->ExportDir.VirtualAddress + || !pModPe->ExportDir.Size) + return VERR_SYMBOL_NOT_FOUND; + + /* + * No bits supplied? Do we need to read the bits? + */ + void const *pvBits = *ppvBits; + if (!pvBits) + { + if (!pModPe->pvBits) + { + int rc = rtldrPEReadBits(pModPe); + if (RT_FAILURE(rc)) + return rc; + } + *ppvBits = pvBits = pModPe->pvBits; + } + + PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY); + int iExpOrdinal = 0; /* index into address table. */ + if (iOrdinal != UINT32_MAX) + { + /* + * Find ordinal export: Simple table lookup. + */ + if ( iOrdinal >= pExpDir->Base + RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions) + || iOrdinal < pExpDir->Base) + return VERR_SYMBOL_NOT_FOUND; + iExpOrdinal = iOrdinal - pExpDir->Base; + } + else + { + /* + * Find Named Export: Do binary search on the name table. + */ + uint32_t *paRVANames = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNames, uint32_t *); + uint16_t *paOrdinals = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNameOrdinals, uint16_t *); + int iStart = 1; + int iEnd = pExpDir->NumberOfNames; + + for (;;) + { + /* end of search? */ + if (iStart > iEnd) + { +#ifdef RT_STRICT + /* do a linear search just to verify the correctness of the above algorithm */ + for (unsigned i = 0; i < pExpDir->NumberOfNames; i++) + { + AssertMsg(i == 0 || strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *)) > 0, + ("bug in binary export search!!!\n")); + AssertMsg(strcmp(PE_RVA2TYPE(pvBits, paRVANames[i], const char *), pszSymbol) != 0, + ("bug in binary export search!!!\n")); + } +#endif + return VERR_SYMBOL_NOT_FOUND; + } + + int i = (iEnd - iStart) / 2 + iStart; + const char *pszExpName = PE_RVA2TYPE(pvBits, paRVANames[i - 1], const char *); + int diff = strcmp(pszExpName, pszSymbol); + if (diff > 0) /* pszExpName > pszSymbol: search chunck before i */ + iEnd = i - 1; + else if (diff) /* pszExpName < pszSymbol: search chunk after i */ + iStart = i + 1; + else /* pszExpName == pszSymbol */ + { + iExpOrdinal = paOrdinals[i - 1]; + break; + } + } /* binary search thru name table */ + } + + /* + * Found export (iExpOrdinal). + */ + uint32_t *paAddress = PE_RVA2TYPE(pvBits, pExpDir->AddressOfFunctions, uint32_t *); + *puRvaExport = paAddress[iExpOrdinal]; + if (puOrdinal) + *puOrdinal = iExpOrdinal; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDROPS,pfnGetSymbolEx} */ +static DECLCALLBACK(int) rtldrPEGetSymbolEx(PRTLDRMODINTERNAL pMod, const void *pvBits, RTUINTPTR BaseAddress, + uint32_t iOrdinal, const char *pszSymbol, RTUINTPTR *pValue) +{ + PRTLDRMODPE pThis = (PRTLDRMODPE)pMod; + uint32_t uRvaExport; + int rc = rtLdrPE_ExportToRva(pThis, iOrdinal, pszSymbol, &pvBits, &uRvaExport, NULL); + if (RT_SUCCESS(rc)) + { + + uint32_t offForwarder = uRvaExport - pThis->ExportDir.VirtualAddress; + if (offForwarder >= pThis->ExportDir.Size) + /* Get plain export address */ + *pValue = PE_RVA2TYPE(BaseAddress, uRvaExport, RTUINTPTR); + else + { + /* Return the approximate length of the forwarder buffer. */ + const char *pszForwarder = PE_RVA2TYPE(pvBits, uRvaExport, const char *); + *pValue = sizeof(RTLDRIMPORTINFO) + RTStrNLen(pszForwarder, offForwarder - pThis->ExportDir.Size); + rc = VERR_LDR_FORWARDER; + } + } + return rc; +} + + +/** @interface_method_impl{RTLDROPS,pfnQueryForwarderInfo} */ +static DECLCALLBACK(int) rtldrPE_QueryForwarderInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, uint32_t iOrdinal, + const char *pszSymbol, PRTLDRIMPORTINFO pInfo, size_t cbInfo) +{ + AssertReturn(cbInfo >= sizeof(*pInfo), VERR_INVALID_PARAMETER); + + PRTLDRMODPE pThis = (PRTLDRMODPE)pMod; + uint32_t uRvaExport; + int rc = rtLdrPE_ExportToRva(pThis, iOrdinal, pszSymbol, &pvBits, &uRvaExport, &iOrdinal); + if (RT_SUCCESS(rc)) + { + uint32_t offForwarder = uRvaExport - pThis->ExportDir.VirtualAddress; + if (offForwarder < pThis->ExportDir.Size) + { + const char *pszForwarder = PE_RVA2TYPE(pvBits, uRvaExport, const char *); + + /* + * Parse and validate the string. We must make sure it's valid + * UTF-8, so we restrict it to ASCII. + */ + const char *pszEnd = RTStrEnd(pszForwarder, offForwarder - pThis->ExportDir.Size); + if (pszEnd) + { + /* The module name. */ + char ch; + uint32_t off = 0; + while ((ch = pszForwarder[off]) != '.' && ch != '\0') + { + if (RT_UNLIKELY((uint8_t)ch >= 0x80)) + return VERR_LDR_BAD_FORWARDER; + off++; + } + if (RT_UNLIKELY(ch != '.')) + return VERR_LDR_BAD_FORWARDER; + uint32_t const offDot = off; + off++; + + /* The function name or ordinal number. Ordinals starts with a hash. */ + uint32_t iImpOrdinal; + if (pszForwarder[off] != '#') + { + iImpOrdinal = UINT32_MAX; + while ((ch = pszForwarder[off]) != '\0') + { + if (RT_UNLIKELY((uint8_t)ch >= 0x80)) + return VERR_LDR_BAD_FORWARDER; + off++; + } + if (RT_UNLIKELY(off == offDot + 1)) + return VERR_LDR_BAD_FORWARDER; + } + else + { + rc = RTStrToUInt32Full(&pszForwarder[off + 1], 10, &iImpOrdinal); + if (RT_UNLIKELY(rc != VINF_SUCCESS || iImpOrdinal > UINT16_MAX)) + return VERR_LDR_BAD_FORWARDER; + } + + /* + * Enough buffer? + */ + uint32_t cbNeeded = RT_UOFFSETOF_DYN(RTLDRIMPORTINFO, szModule[iImpOrdinal != UINT32_MAX ? offDot + 1 : off + 1]); + if (cbNeeded > cbInfo) + return VERR_BUFFER_OVERFLOW; + + /* + * Fill in the return buffer. + */ + pInfo->iSelfOrdinal = iOrdinal; + pInfo->iOrdinal = iImpOrdinal; + if (iImpOrdinal == UINT32_MAX) + { + pInfo->pszSymbol = &pInfo->szModule[offDot + 1]; + memcpy(&pInfo->szModule[0], pszForwarder, off + 1); + } + else + { + pInfo->pszSymbol = NULL; + memcpy(&pInfo->szModule[0], pszForwarder, offDot); + } + pInfo->szModule[offDot] = '\0'; + rc = VINF_SUCCESS; + } + else + rc = VERR_LDR_BAD_FORWARDER; + } + else + rc = VERR_LDR_NOT_FORWARDER; + } + return rc; +} + + +/** + * Slow version of rtldrPEEnumSymbols that'll work without all of the image + * being accessible. + * + * This is mainly for use in debuggers and similar. + */ +static int rtldrPEEnumSymbolsSlow(PRTLDRMODPE pThis, unsigned fFlags, RTUINTPTR BaseAddress, + PFNRTLDRENUMSYMS pfnCallback, void *pvUser) +{ + /* + * We enumerates by ordinal, which means using a slow linear search for + * getting any name + */ + PCIMAGE_EXPORT_DIRECTORY pExpDir = NULL; + int rc = rtldrPEReadPartByRva(pThis, NULL, pThis->ExportDir.VirtualAddress, pThis->ExportDir.Size, + (void const **)&pExpDir); + if (RT_FAILURE(rc)) + return rc; + uint32_t const cOrdinals = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions); + + uint32_t const *paAddress = NULL; + rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfFunctions, cOrdinals * sizeof(uint32_t), + (void const **)&paAddress); + uint32_t const *paRVANames = NULL; + if (RT_SUCCESS(rc) && pExpDir->NumberOfNames) + rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNames, pExpDir->NumberOfNames * sizeof(uint32_t), + (void const **)&paRVANames); + uint16_t const *paOrdinals = NULL; + if (RT_SUCCESS(rc) && pExpDir->NumberOfNames) + rc = rtldrPEReadPartByRva(pThis, NULL, pExpDir->AddressOfNameOrdinals, pExpDir->NumberOfNames * sizeof(uint16_t), + (void const **)&paOrdinals); + if (RT_SUCCESS(rc)) + { + uint32_t uNamePrev = 0; + for (uint32_t uOrdinal = 0; uOrdinal < cOrdinals; uOrdinal++) + { + if (paAddress[uOrdinal] /* needed? */) + { + /* + * Look for name. + */ + uint32_t uRvaName = UINT32_MAX; + /* Search from previous + 1 to the end. */ + unsigned uName = uNamePrev + 1; + while (uName < pExpDir->NumberOfNames) + { + if (paOrdinals[uName] == uOrdinal) + { + uRvaName = paRVANames[uName]; + uNamePrev = uName; + break; + } + uName++; + } + if (uRvaName == UINT32_MAX) + { + /* Search from start to the previous. */ + uName = 0; + for (uName = 0 ; uName <= uNamePrev; uName++) + { + if (paOrdinals[uName] == uOrdinal) + { + uRvaName = paRVANames[uName]; + uNamePrev = uName; + break; + } + } + } + + /* + * Get address. + */ + uint32_t uRVAExport = paAddress[uOrdinal]; + RTUINTPTR Value; + if (uRVAExport - pThis->ExportDir.VirtualAddress >= pThis->ExportDir.Size) + Value = PE_RVA2TYPE(BaseAddress, uRVAExport, RTUINTPTR); + else if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD)) + Value = RTLDR_ENUM_SYMBOL_FWD_ADDRESS; + else + continue; + + /* Read in the name if found one. */ + char szAltName[32]; + const char *pszName = NULL; + if (uRvaName != UINT32_MAX) + { + uint32_t cbName = 0x1000 - (uRvaName & 0xfff); + if (cbName < 10 || cbName > 512) + cbName = 128; + rc = rtldrPEReadPartByRva(pThis, NULL, uRvaName, cbName, (void const **)&pszName); + while (RT_SUCCESS(rc) && RTStrNLen(pszName, cbName) == cbName) + { + rtldrPEFreePart(pThis, NULL, pszName); + pszName = NULL; + if (cbName >= _4K) + break; + cbName += 128; + rc = rtldrPEReadPartByRva(pThis, NULL, uRvaName, cbName, (void const **)&pszName); + } + } + if (!pszName) + { + RTStrPrintf(szAltName, sizeof(szAltName), "Ordinal%#x", uOrdinal); + pszName = szAltName; + } + + /* + * Call back. + */ + rc = pfnCallback(&pThis->Core, pszName, uOrdinal + pExpDir->Base, Value, pvUser); + if (pszName != szAltName && pszName) + rtldrPEFreePart(pThis, NULL, pszName); + if (rc) + break; + } + } + } + + rtldrPEFreePart(pThis, NULL, paOrdinals); + rtldrPEFreePart(pThis, NULL, paRVANames); + rtldrPEFreePart(pThis, NULL, paAddress); + rtldrPEFreePart(pThis, NULL, pExpDir); + return rc; + +} + + +/** @interface_method_impl{RTLDROPS,pfnEnumSymbols} */ +static DECLCALLBACK(int) rtldrPEEnumSymbols(PRTLDRMODINTERNAL pMod, unsigned fFlags, const void *pvBits, RTUINTPTR BaseAddress, + PFNRTLDRENUMSYMS pfnCallback, void *pvUser) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + NOREF(fFlags); /* ignored ... */ + + /* + * Check if there is actually anything to work on. + */ + if ( !pModPe->ExportDir.VirtualAddress + || !pModPe->ExportDir.Size) + return VERR_SYMBOL_NOT_FOUND; + + /* + * No bits supplied? Do we need to read the bits? + */ + if (!pvBits) + { + if (!pModPe->pvBits) + { + int rc = rtldrPEReadBits(pModPe); + if (RT_FAILURE(rc)) + return rtldrPEEnumSymbolsSlow(pModPe, fFlags, BaseAddress, pfnCallback, pvUser); + } + pvBits = pModPe->pvBits; + } + + /* + * We enumerates by ordinal, which means using a slow linear search for + * getting any name + */ + PIMAGE_EXPORT_DIRECTORY pExpDir = PE_RVA2TYPE(pvBits, pModPe->ExportDir.VirtualAddress, PIMAGE_EXPORT_DIRECTORY); + uint32_t *paAddress = PE_RVA2TYPE(pvBits, pExpDir->AddressOfFunctions, uint32_t *); + uint32_t *paRVANames = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNames, uint32_t *); + uint16_t *paOrdinals = PE_RVA2TYPE(pvBits, pExpDir->AddressOfNameOrdinals, uint16_t *); + uint32_t uNamePrev = 0; + unsigned cOrdinals = RT_MAX(pExpDir->NumberOfNames, pExpDir->NumberOfFunctions); + for (unsigned uOrdinal = 0; uOrdinal < cOrdinals; uOrdinal++) + { + if (paAddress[uOrdinal] /* needed? */) + { + /* + * Look for name. + */ + const char *pszName = NULL; + /* Search from previous + 1 to the end. */ + uint32_t uName = uNamePrev + 1; + while (uName < pExpDir->NumberOfNames) + { + if (paOrdinals[uName] == uOrdinal) + { + pszName = PE_RVA2TYPE(pvBits, paRVANames[uName], const char *); + uNamePrev = uName; + break; + } + uName++; + } + if (!pszName) + { + /* Search from start to the previous. */ + uName = 0; + for (uName = 0 ; uName <= uNamePrev; uName++) + { + if (paOrdinals[uName] == uOrdinal) + { + pszName = PE_RVA2TYPE(pvBits, paRVANames[uName], const char *); + uNamePrev = uName; + break; + } + } + } + + /* + * Get address. + */ + uint32_t uRVAExport = paAddress[uOrdinal]; + RTUINTPTR Value; + if (uRVAExport - pModPe->ExportDir.VirtualAddress >= pModPe->ExportDir.Size) + Value = PE_RVA2TYPE(BaseAddress, uRVAExport, RTUINTPTR); + else if (!(fFlags & RTLDR_ENUM_SYMBOL_FLAGS_NO_FWD)) + Value = RTLDR_ENUM_SYMBOL_FWD_ADDRESS; + else + continue; + + /* + * Call back. + */ + int rc = pfnCallback(pMod, pszName, uOrdinal + pExpDir->Base, Value, pvUser); + if (rc) + return rc; + } + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDROPS,pfnEnumDbgInfo} */ +static DECLCALLBACK(int) rtldrPE_EnumDbgInfo(PRTLDRMODINTERNAL pMod, const void *pvBits, + PFNRTLDRENUMDBG pfnCallback, void *pvUser) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + int rc; + + /* + * Debug info directory empty? + */ + if ( !pModPe->DebugDir.VirtualAddress + || !pModPe->DebugDir.Size) + return VINF_SUCCESS; + + /* + * Allocate temporary memory for a path buffer (this code is also compiled + * and maybe even used in stack starved environments). + */ + char *pszPath = (char *)RTMemTmpAlloc(RTPATH_MAX); + if (!pszPath) + return VERR_NO_TMP_MEMORY; + + /* + * Get the debug directory. + */ + if (!pvBits) + pvBits = pModPe->pvBits; + + PCIMAGE_DEBUG_DIRECTORY paDbgDir; + int rcRet = rtldrPEReadPartByRva(pModPe, pvBits, pModPe->DebugDir.VirtualAddress, pModPe->DebugDir.Size, + (void const **)&paDbgDir); + if (RT_FAILURE(rcRet)) + { + RTMemTmpFree(pszPath); + return rcRet; + } + + /* + * Enumerate the debug directory. + */ + uint32_t const cEntries = pModPe->DebugDir.Size / sizeof(paDbgDir[0]); + for (uint32_t i = 0; i < cEntries; i++) + { + if (paDbgDir[i].PointerToRawData < pModPe->offEndOfHdrs) + continue; + if (paDbgDir[i].SizeOfData < 4) + continue; + + void const *pvPart = NULL; + RTLDRDBGINFO DbgInfo; + RT_ZERO(DbgInfo.u); + DbgInfo.iDbgInfo = i; + DbgInfo.offFile = paDbgDir[i].PointerToRawData; + DbgInfo.LinkAddress = paDbgDir[i].AddressOfRawData < pModPe->cbImage + && paDbgDir[i].AddressOfRawData >= pModPe->offEndOfHdrs + ? paDbgDir[i].AddressOfRawData : NIL_RTLDRADDR; + DbgInfo.cb = paDbgDir[i].SizeOfData; + DbgInfo.pszExtFile = NULL; + + rc = VINF_SUCCESS; + switch (paDbgDir[i].Type) + { + case IMAGE_DEBUG_TYPE_CODEVIEW: + DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW; + DbgInfo.u.Cv.cbImage = pModPe->cbImage; + DbgInfo.u.Cv.uMajorVer = paDbgDir[i].MajorVersion; + DbgInfo.u.Cv.uMinorVer = paDbgDir[i].MinorVersion; + DbgInfo.u.Cv.uTimestamp = paDbgDir[i].TimeDateStamp; + if ( paDbgDir[i].SizeOfData < RTPATH_MAX + && paDbgDir[i].SizeOfData > 16 + && ( DbgInfo.LinkAddress != NIL_RTLDRADDR + || DbgInfo.offFile > 0) + ) + { + rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart); + if (RT_SUCCESS(rc)) + { + PCCVPDB20INFO pCv20 = (PCCVPDB20INFO)pvPart; + if ( pCv20->u32Magic == CVPDB20INFO_MAGIC + && pCv20->offDbgInfo == 0 + && paDbgDir[i].SizeOfData > RT_UOFFSETOF(CVPDB20INFO, szPdbFilename) ) + { + DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB20; + DbgInfo.u.Pdb20.cbImage = pModPe->cbImage; + DbgInfo.u.Pdb20.uTimestamp = pCv20->uTimestamp; + DbgInfo.u.Pdb20.uAge = pCv20->uAge; + DbgInfo.pszExtFile = (const char *)&pCv20->szPdbFilename[0]; + } + else if ( pCv20->u32Magic == CVPDB70INFO_MAGIC + && paDbgDir[i].SizeOfData > RT_UOFFSETOF(CVPDB70INFO, szPdbFilename) ) + { + PCCVPDB70INFO pCv70 = (PCCVPDB70INFO)pCv20; + DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_PDB70; + DbgInfo.u.Pdb70.cbImage = pModPe->cbImage; + DbgInfo.u.Pdb70.Uuid = pCv70->PdbUuid; + DbgInfo.u.Pdb70.uAge = pCv70->uAge; + DbgInfo.pszExtFile = (const char *)&pCv70->szPdbFilename[0]; + } + } + else + rcRet = rc; + } + break; + + case IMAGE_DEBUG_TYPE_MISC: + DbgInfo.enmType = RTLDRDBGINFOTYPE_UNKNOWN; + if ( paDbgDir[i].SizeOfData < RTPATH_MAX + && paDbgDir[i].SizeOfData > RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data)) + { + DbgInfo.enmType = RTLDRDBGINFOTYPE_CODEVIEW_DBG; + DbgInfo.u.Dbg.cbImage = pModPe->cbImage; + if (DbgInfo.LinkAddress != NIL_RTLDRADDR) + DbgInfo.u.Dbg.uTimestamp = paDbgDir[i].TimeDateStamp; + else + DbgInfo.u.Dbg.uTimestamp = pModPe->uTimestamp; /* NT4 SP1 ntfs.sys hack. Generic? */ + + rc = rtldrPEReadPart(pModPe, pvBits, DbgInfo.offFile, DbgInfo.LinkAddress, paDbgDir[i].SizeOfData, &pvPart); + if (RT_SUCCESS(rc)) + { + PCIMAGE_DEBUG_MISC pMisc = (PCIMAGE_DEBUG_MISC)pvPart; + if ( pMisc->DataType == IMAGE_DEBUG_MISC_EXENAME + && pMisc->Length == paDbgDir[i].SizeOfData) + { + if (!pMisc->Unicode) + DbgInfo.pszExtFile = (const char *)&pMisc->Data[0]; + else + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)&pMisc->Data[0], + (pMisc->Length - RT_UOFFSETOF(IMAGE_DEBUG_MISC, Data)) / sizeof(RTUTF16), + &pszPath, RTPATH_MAX, NULL); + if (RT_SUCCESS(rc)) + DbgInfo.pszExtFile = pszPath; + else + rcRet = rc; /* continue without a filename. */ + } + } + } + else + rcRet = rc; /* continue without a filename. */ + } + break; + + case IMAGE_DEBUG_TYPE_COFF: + DbgInfo.enmType = RTLDRDBGINFOTYPE_COFF; + DbgInfo.u.Coff.cbImage = pModPe->cbImage; + DbgInfo.u.Coff.uMajorVer = paDbgDir[i].MajorVersion; + DbgInfo.u.Coff.uMinorVer = paDbgDir[i].MinorVersion; + DbgInfo.u.Coff.uTimestamp = paDbgDir[i].TimeDateStamp; + break; + + default: + DbgInfo.enmType = RTLDRDBGINFOTYPE_UNKNOWN; + break; + } + + /* Fix (hack) the file name encoding. We don't have Windows-1252 handy, + so we'll be using Latin-1 as a reasonable approximation. + (I don't think we know exactly which encoding this is anyway, as + it's probably the current ANSI/Windows code page for the process + generating the image anyways.) */ + if (DbgInfo.pszExtFile && DbgInfo.pszExtFile != pszPath) + { + rc = RTLatin1ToUtf8Ex(DbgInfo.pszExtFile, + paDbgDir[i].SizeOfData - ((uintptr_t)DbgInfo.pszExtFile - (uintptr_t)pvBits), + &pszPath, RTPATH_MAX, NULL); + if (RT_FAILURE(rc)) + { + rcRet = rc; + DbgInfo.pszExtFile = NULL; + } + } + if (DbgInfo.pszExtFile) + RTPathChangeToUnixSlashes(pszPath, true /*fForce*/); + + rc = pfnCallback(pMod, &DbgInfo, pvUser); + rtldrPEFreePart(pModPe, pvBits, pvPart); + if (rc != VINF_SUCCESS) + { + rcRet = rc; + break; + } + } + + rtldrPEFreePart(pModPe, pvBits, paDbgDir); + RTMemTmpFree(pszPath); + return rcRet; +} + + +/** @interface_method_impl{RTLDROPS,pfnEnumSegments} */ +static DECLCALLBACK(int) rtldrPE_EnumSegments(PRTLDRMODINTERNAL pMod, PFNRTLDRENUMSEGS pfnCallback, void *pvUser) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + RTLDRSEG SegInfo; + + /* + * The first section is a fake one covering the headers. + */ + SegInfo.pszName = "NtHdrs"; + SegInfo.cchName = 6; + SegInfo.SelFlat = 0; + SegInfo.Sel16bit = 0; + SegInfo.fFlags = 0; + SegInfo.fProt = RTMEM_PROT_READ; + SegInfo.Alignment = 1; + SegInfo.LinkAddress = pModPe->uImageBase; + SegInfo.RVA = 0; + SegInfo.offFile = 0; + SegInfo.cb = pModPe->cbHeaders; + SegInfo.cbFile = pModPe->cbHeaders; + SegInfo.cbMapped = pModPe->cbHeaders; + if (!(pModPe->paSections[0].Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + SegInfo.cbMapped = pModPe->paSections[0].VirtualAddress; + int rc = pfnCallback(pMod, &SegInfo, pvUser); + + /* + * Then all the normal sections. + */ + PCIMAGE_SECTION_HEADER pSh = pModPe->paSections; + for (uint32_t i = 0; i < pModPe->cSections && rc == VINF_SUCCESS; i++, pSh++) + { + char szName[32]; + SegInfo.pszName = (const char *)&pSh->Name[0]; + SegInfo.cchName = (uint32_t)RTStrNLen(SegInfo.pszName, sizeof(pSh->Name)); + if (SegInfo.cchName >= sizeof(pSh->Name)) + { + memcpy(szName, &pSh->Name[0], sizeof(pSh->Name)); + szName[sizeof(pSh->Name)] = '\0'; + SegInfo.pszName = szName; + } + else if (SegInfo.cchName == 0) + { + SegInfo.pszName = szName; + SegInfo.cchName = (uint32_t)RTStrPrintf(szName, sizeof(szName), "UnamedSect%02u", i); + } + SegInfo.SelFlat = 0; + SegInfo.Sel16bit = 0; + SegInfo.fFlags = 0; + SegInfo.fProt = RTMEM_PROT_NONE; + if (pSh->Characteristics & IMAGE_SCN_MEM_READ) + SegInfo.fProt |= RTMEM_PROT_READ; + if (pSh->Characteristics & IMAGE_SCN_MEM_WRITE) + SegInfo.fProt |= RTMEM_PROT_WRITE; + if (pSh->Characteristics & IMAGE_SCN_MEM_EXECUTE) + SegInfo.fProt |= RTMEM_PROT_EXEC; + SegInfo.Alignment = (pSh->Characteristics & IMAGE_SCN_ALIGN_MASK) >> IMAGE_SCN_ALIGN_SHIFT; + if (SegInfo.Alignment > 0) + SegInfo.Alignment = RT_BIT_64(SegInfo.Alignment - 1); + else + SegInfo.Alignment = pModPe->uSectionAlign; + if (pSh->Characteristics & IMAGE_SCN_TYPE_NOLOAD) + { + SegInfo.LinkAddress = NIL_RTLDRADDR; + SegInfo.RVA = NIL_RTLDRADDR; + SegInfo.cbMapped = 0; + } + else + { + SegInfo.LinkAddress = pSh->VirtualAddress + pModPe->uImageBase; + SegInfo.RVA = pSh->VirtualAddress; + SegInfo.cbMapped = RT_ALIGN(pSh->Misc.VirtualSize, SegInfo.Alignment); + if (i + 1 < pModPe->cSections && !(pSh[1].Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + SegInfo.cbMapped = pSh[1].VirtualAddress - pSh->VirtualAddress; + } + SegInfo.cb = pSh->Misc.VirtualSize; + if (pSh->PointerToRawData == 0 || pSh->SizeOfRawData == 0) + { + SegInfo.offFile = -1; + SegInfo.cbFile = 0; + } + else + { + SegInfo.offFile = pSh->PointerToRawData; + SegInfo.cbFile = pSh->SizeOfRawData; + } + + rc = pfnCallback(pMod, &SegInfo, pvUser); + } + + return rc; +} + + +/** @interface_method_impl{RTLDROPS,pfnLinkAddressToSegOffset} */ +static DECLCALLBACK(int) rtldrPE_LinkAddressToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, + uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + LinkAddress -= pModPe->uImageBase; + + /* Special header segment. */ + if (LinkAddress < pModPe->paSections[0].VirtualAddress) + { + *piSeg = 0; + *poffSeg = LinkAddress; + return VINF_SUCCESS; + } + + /* + * Search the normal sections. (Could do this in binary fashion, they're + * sorted, but too much bother right now.) + */ + if (LinkAddress > pModPe->cbImage) + return VERR_LDR_INVALID_LINK_ADDRESS; + uint32_t i = pModPe->cSections; + PCIMAGE_SECTION_HEADER paShs = pModPe->paSections; + while (i-- > 0) + if (!(paShs[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + { + uint32_t uAddr = paShs[i].VirtualAddress; + if (LinkAddress >= uAddr) + { + *poffSeg = LinkAddress - uAddr; + *piSeg = i + 1; + return VINF_SUCCESS; + } + } + + return VERR_LDR_INVALID_LINK_ADDRESS; +} + + +/** @interface_method_impl{RTLDROPS,pfnLinkAddressToRva} */ +static DECLCALLBACK(int) rtldrPE_LinkAddressToRva(PRTLDRMODINTERNAL pMod, RTLDRADDR LinkAddress, PRTLDRADDR pRva) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + LinkAddress -= pModPe->uImageBase; + if (LinkAddress > pModPe->cbImage) + return VERR_LDR_INVALID_LINK_ADDRESS; + *pRva = LinkAddress; + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDROPS,pfnSegOffsetToRva} */ +static DECLCALLBACK(int) rtldrPE_SegOffsetToRva(PRTLDRMODINTERNAL pMod, uint32_t iSeg, RTLDRADDR offSeg, + PRTLDRADDR pRva) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + if (iSeg > pModPe->cSections) + return VERR_LDR_INVALID_SEG_OFFSET; + + /** @todo should validate offSeg here... too lazy right now. */ + if (iSeg == 0) + *pRva = offSeg; + else if (!(pModPe->paSections[iSeg - 1].Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + *pRva = offSeg + pModPe->paSections[iSeg - 1].VirtualAddress; + else + return VERR_LDR_INVALID_SEG_OFFSET; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDROPS,pfnRvaToSegOffset} */ +static DECLCALLBACK(int) rtldrPE_RvaToSegOffset(PRTLDRMODINTERNAL pMod, RTLDRADDR Rva, + uint32_t *piSeg, PRTLDRADDR poffSeg) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + int rc = rtldrPE_LinkAddressToSegOffset(pMod, Rva + pModPe->uImageBase, piSeg, poffSeg); + if (RT_FAILURE(rc)) + rc = VERR_LDR_INVALID_RVA; + return rc; +} + + +/** + * Worker for rtLdrPE_QueryProp and rtLdrPE_QueryImportModule that counts the + * number of imports, storing the result in RTLDRMODPE::cImports. + * + * @returns IPRT status code. + * @param pThis The PE module instance. + * @param pvBits Image bits if the caller had them available, NULL if + * not. Saves a couple of file accesses. + */ +static int rtLdrPE_CountImports(PRTLDRMODPE pThis, void const *pvBits) +{ + PCIMAGE_IMPORT_DESCRIPTOR paImpDescs; + int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ImportDir.VirtualAddress, pThis->ImportDir.Size, + (void const **)&paImpDescs); + if (RT_SUCCESS(rc)) + { + uint32_t const cMax = pThis->ImportDir.Size / sizeof(IMAGE_IMPORT_DESCRIPTOR); + uint32_t i = 0; + while ( i < cMax + && paImpDescs[i].Name > pThis->offNtHdrs + && paImpDescs[i].Name < pThis->cbImage + && paImpDescs[i].FirstThunk > pThis->offNtHdrs + && paImpDescs[i].FirstThunk < pThis->cbImage) + i++; + pThis->cImports = i; + + rtldrPEFreePart(pThis, pvBits, paImpDescs); + } + return rc; +} + +/** + * Worker for rtLdrPE_QueryImportModule and rtLdrPE_QueryInternalName that + * copies a zero termianted string at the given RVA into the RTLdrQueryPropEx + * output buffer. + * + * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size. + * @param pThis The PE module instance. + * @param pvBits Image bits if the caller had them available, NULL if + * not. Saves a couple of file accesses. + * @param uRvaString The RVA of the string to copy. + * @param cbMaxString The max string length. + * @param pvBuf The output buffer. + * @param cbBuf The buffer size. + * @param pcbRet Where to return the number of bytes we've returned + * (or in case of VERR_BUFFER_OVERFLOW would have). + */ +static int rtLdrPE_QueryNameAtRva(PRTLDRMODPE pThis, void const *pvBits, uint32_t uRvaString, uint32_t cbMaxString, + void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + int rc; + if ( uRvaString >= pThis->cbHeaders + && uRvaString < pThis->cbImage) + { + /* + * Limit the string. + */ + uint32_t cbMax = pThis->cbImage - uRvaString; + if (cbMax > cbMaxString) + cbMax = cbMaxString; + char *pszString; + rc = rtldrPEReadPartByRva(pThis, pvBits, uRvaString, cbMax, (void const **)&pszString); + if (RT_SUCCESS(rc)) + { + /* + * Make sure it's null terminated and valid UTF-8 encoding. + * + * Which encoding this really is isn't defined, I think, + * but we need to make sure we don't get bogus UTF-8 into + * the process, so making sure it's valid UTF-8 is a good + * as anything else since it covers ASCII. + */ + size_t cchString = RTStrNLen(pszString, cbMaxString); + if (cchString < cbMaxString) + { + rc = RTStrValidateEncodingEx(pszString, cchString, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + /* + * Copy out the result and we're done. + * (We have to do all the cleanup code though, so no return success here.) + */ + *pcbRet = cchString + 1; + if (cbBuf >= cchString + 1) + memcpy(pvBuf, pszString, cchString + 1); + else + rc = VERR_BUFFER_OVERFLOW; + } + } + else + rc = VERR_BAD_EXE_FORMAT; + rtldrPEFreePart(pThis, pvBits, pszString); + } + } + else + rc = VERR_BAD_EXE_FORMAT; + return rc; +} + + +/** + * Worker for rtLdrPE_QueryProp that retrievs the name of an import DLL. + * + * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size. + * @param pThis The PE module instance. + * @param pvBits Image bits if the caller had them available, NULL if + * not. Saves a couple of file accesses. + * @param iImport The index of the import table descriptor to fetch + * the name from. + * @param pvBuf The output buffer. + * @param cbBuf The buffer size. + * @param pcbRet Where to return the number of bytes we've returned + * (or in case of VERR_BUFFER_OVERFLOW would have). + */ +static int rtLdrPE_QueryImportModule(PRTLDRMODPE pThis, void const *pvBits, uint32_t iImport, + void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + /* + * Make sure we got the import count. + */ + int rc; + if (pThis->cImports == UINT32_MAX) + { + rc = rtLdrPE_CountImports(pThis, pvBits); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Check the index first, converting it to an RVA. + */ + if (iImport < pThis->cImports) + { + uint32_t offEntry = iImport * sizeof(IMAGE_IMPORT_DESCRIPTOR) + pThis->ImportDir.VirtualAddress; + + /* + * Retrieve the import table descriptor. + * Using 1024 as the max name length (should be more than enough). + */ + PCIMAGE_IMPORT_DESCRIPTOR pImpDesc; + rc = rtldrPEReadPartByRva(pThis, pvBits, offEntry, sizeof(*pImpDesc), (void const **)&pImpDesc); + if (RT_SUCCESS(rc)) + { + rc = rtLdrPE_QueryNameAtRva(pThis, pvBits, pImpDesc->Name, 1024 /*cchMaxString*/, pvBuf, cbBuf, pcbRet); + rtldrPEFreePart(pThis, pvBits, pImpDesc); + } + } + else + rc = VERR_NOT_FOUND; + + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + *pcbRet = 0; + return rc; +} + + +/** + * Worker for rtLdrPE_QueryProp that retrieves the internal module name. + * + * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size. + * @param pThis The PE module instance. + * @param pvBits Image bits if the caller had them available, NULL if + * not. Saves a couple of file accesses. + * @param pvBuf The output buffer. + * @param cbBuf The buffer size. + * @param pcbRet Where to return the number of bytes we've returned + * (or in case of VERR_BUFFER_OVERFLOW would have). + */ +static int rtLdrPE_QueryInternalName(PRTLDRMODPE pThis, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + *pcbRet = 0; + + if ( pThis->ExportDir.Size < sizeof(IMAGE_EXPORT_DIRECTORY) + || pThis->ExportDir.VirtualAddress == 0) + return VERR_NOT_FOUND; + + PCIMAGE_EXPORT_DIRECTORY pExpDir; + int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ExportDir.VirtualAddress, sizeof(*pExpDir), (void const **)&pExpDir); + if (RT_SUCCESS(rc)) + { + rc = rtLdrPE_QueryNameAtRva(pThis, pvBits, pExpDir->Name, 1024 /*cchMaxString*/, pvBuf, cbBuf, pcbRet); + rtldrPEFreePart(pThis, pvBits, pExpDir); + } + + return rc; +} + + +/** + * Worker for rtLdrPE_QueryProp that retrieves unwind information. + * + * @returns IPRT status code. If VERR_BUFFER_OVERFLOW, pcbBuf is required size. + * @param pThis The PE module instance. + * @param pvBits Image bits if the caller had them available, NULL if + * not. Saves a couple of file accesses. + * @param pvBuf The output buffer. + * @param cbBuf The buffer size. + * @param pcbRet Where to return the number of bytes we've returned + * (or in case of VERR_BUFFER_OVERFLOW would have). + */ +static int rtLdrPE_QueryUnwindTable(PRTLDRMODPE pThis, void const *pvBits, void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + int rc; + uint32_t const cbSrc = pThis->ExceptionDir.Size; + if ( cbSrc > 0 + && pThis->ExceptionDir.VirtualAddress > 0) + { + *pcbRet = cbSrc; + if (cbBuf >= cbSrc) + rc = rtldrPEReadPartByRvaInfoBuf(pThis, pvBits, pThis->ExceptionDir.VirtualAddress, cbSrc, pvBuf); + else + rc = VERR_BUFFER_OVERFLOW; + } + else + { + *pcbRet = 0; + rc = VERR_NOT_FOUND; + } + return rc; +} + + +/** @interface_method_impl{RTLDROPS,pfnQueryProp} */ +static DECLCALLBACK(int) rtldrPE_QueryProp(PRTLDRMODINTERNAL pMod, RTLDRPROP enmProp, void const *pvBits, + void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + switch (enmProp) + { + case RTLDRPROP_TIMESTAMP_SECONDS: + Assert(*pcbRet == cbBuf); + if (cbBuf == sizeof(int32_t)) + *(int32_t *)pvBuf = pModPe->uTimestamp; + else if (cbBuf == sizeof(int64_t)) + *(int64_t *)pvBuf = pModPe->uTimestamp; + else + AssertFailedReturn(VERR_INTERNAL_ERROR_3); + break; + + case RTLDRPROP_IS_SIGNED: + Assert(cbBuf == sizeof(bool)); + Assert(*pcbRet == cbBuf); + *(bool *)pvBuf = pModPe->offPkcs7SignedData != 0; + break; + + case RTLDRPROP_PKCS7_SIGNED_DATA: + { + if (pModPe->cbPkcs7SignedData == 0) + return VERR_NOT_FOUND; + Assert(pModPe->offPkcs7SignedData > pModPe->SecurityDir.VirtualAddress); + + *pcbRet = pModPe->cbPkcs7SignedData; + if (cbBuf < pModPe->cbPkcs7SignedData) + return VERR_BUFFER_OVERFLOW; + return pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pvBuf, pModPe->cbPkcs7SignedData, + pModPe->offPkcs7SignedData); + } + +#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING + case RTLDRPROP_HASHABLE_PAGES: + *pcbRet = sizeof(uint32_t); + *(uint32_t *)pvBuf = rtLdrPE_GetHashablePages(pModPe); + return VINF_SUCCESS; + + case RTLDRPROP_SHA1_PAGE_HASHES: + return rtLdrPE_QueryPageHashes(pModPe, RTDIGESTTYPE_SHA1, pvBuf, cbBuf, pcbRet); + + case RTLDRPROP_SHA256_PAGE_HASHES: + return rtLdrPE_QueryPageHashes(pModPe, RTDIGESTTYPE_SHA256, pvBuf, cbBuf, pcbRet); +#endif + + case RTLDRPROP_SIGNATURE_CHECKS_ENFORCED: + Assert(cbBuf == sizeof(bool)); + Assert(*pcbRet == cbBuf); + *(bool *)pvBuf = pModPe->offPkcs7SignedData > 0 + && (pModPe->fDllCharacteristics & IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY); + break; + + case RTLDRPROP_IMPORT_COUNT: + Assert(cbBuf == sizeof(uint32_t)); + Assert(*pcbRet == cbBuf); + if (pModPe->cImports == UINT32_MAX) + { + int rc = rtLdrPE_CountImports(pModPe, pvBits); + if (RT_FAILURE(rc)) + return rc; + } + *(uint32_t *)pvBuf = pModPe->cImports; + break; + + case RTLDRPROP_IMPORT_MODULE: + Assert(cbBuf >= sizeof(uint32_t)); + return rtLdrPE_QueryImportModule(pModPe, pvBits, *(uint32_t *)pvBuf, pvBuf, cbBuf, pcbRet); + + case RTLDRPROP_FILE_OFF_HEADER: + Assert(cbBuf == sizeof(uint32_t) || cbBuf == sizeof(uint64_t)); + if (cbBuf == sizeof(uint32_t)) + *(uint32_t *)pvBuf = pModPe->offNtHdrs; + else + *(uint64_t *)pvBuf = pModPe->offNtHdrs; + return VINF_SUCCESS; + + case RTLDRPROP_INTERNAL_NAME: + return rtLdrPE_QueryInternalName(pModPe, pvBits, pvBuf, cbBuf, pcbRet); + + case RTLDRPROP_UNWIND_TABLE: + return rtLdrPE_QueryUnwindTable(pModPe, pvBits, pvBuf, cbBuf, pcbRet); + + case RTLDRPROP_UNWIND_INFO: + { + uint32_t uRva = *(uint32_t const *)pvBuf; + if (uRva < pModPe->cbImage) + { + uint32_t cbLeft = pModPe->cbImage - uRva; + uint32_t cbToRead = (uint32_t)RT_MIN(cbLeft, cbBuf); + *pcbRet = cbToRead; + return rtldrPEReadPartByRvaInfoBuf(pModPe, pvBits, uRva, cbToRead, pvBuf); + } + *pcbRet = 0; + return VINF_SUCCESS; + } + + default: + return VERR_NOT_FOUND; + } + return VINF_SUCCESS; +} + + + +/* + * Lots of Authenticode fun ahead. + */ + + +/** + * Initializes the hash context. + * + * @returns VINF_SUCCESS or VERR_NOT_SUPPORTED. + * @param pHashCtx The hash context union. + * @param enmDigest The hash type we're calculating.. + */ +static int rtLdrPE_HashInit(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest) +{ + switch (enmDigest) + { + case RTDIGESTTYPE_SHA512: RTSha512Init(&pHashCtx->Sha512); break; + case RTDIGESTTYPE_SHA384: RTSha384Init(&pHashCtx->Sha384); break; + case RTDIGESTTYPE_SHA256: RTSha256Init(&pHashCtx->Sha256); break; + case RTDIGESTTYPE_SHA1: RTSha1Init(&pHashCtx->Sha1); break; + case RTDIGESTTYPE_MD5: RTMd5Init(&pHashCtx->Md5); break; + default: AssertFailedReturn(VERR_NOT_SUPPORTED); + } + return VINF_SUCCESS; +} + + +/** + * Updates the hash with more data. + * + * @param pHashCtx The hash context union. + * @param enmDigest The hash type we're calculating.. + * @param pvBuf Pointer to a buffer with bytes to add to thash. + * @param cbBuf How many bytes to add from @a pvBuf. + */ +static void rtLdrPE_HashUpdate(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, void const *pvBuf, size_t cbBuf) +{ + switch (enmDigest) + { + case RTDIGESTTYPE_SHA512: RTSha512Update(&pHashCtx->Sha512, pvBuf, cbBuf); break; + case RTDIGESTTYPE_SHA384: RTSha384Update(&pHashCtx->Sha384, pvBuf, cbBuf); break; + case RTDIGESTTYPE_SHA256: RTSha256Update(&pHashCtx->Sha256, pvBuf, cbBuf); break; + case RTDIGESTTYPE_SHA1: RTSha1Update(&pHashCtx->Sha1, pvBuf, cbBuf); break; + case RTDIGESTTYPE_MD5: RTMd5Update(&pHashCtx->Md5, pvBuf, cbBuf); break; + default: AssertReleaseFailed(); + } +} + + +/** + * Finalizes the hash calculations. + * + * @param pHashCtx The hash context union. + * @param enmDigest The hash type we're calculating.. + * @param pHashRes The hash result union. + */ +static void rtLdrPE_HashFinalize(PRTLDRPEHASHCTXUNION pHashCtx, RTDIGESTTYPE enmDigest, PRTLDRPEHASHRESUNION pHashRes) +{ + switch (enmDigest) + { + case RTDIGESTTYPE_SHA512: RTSha512Final(&pHashCtx->Sha512, pHashRes->abSha512); break; + case RTDIGESTTYPE_SHA384: RTSha384Final(&pHashCtx->Sha384, pHashRes->abSha384); break; + case RTDIGESTTYPE_SHA256: RTSha256Final(&pHashCtx->Sha256, pHashRes->abSha256); break; + case RTDIGESTTYPE_SHA1: RTSha1Final(&pHashCtx->Sha1, pHashRes->abSha1); break; + case RTDIGESTTYPE_MD5: RTMd5Final(pHashRes->abMd5, &pHashCtx->Md5); break; + default: AssertReleaseFailed(); + } +} + + +/** + * Returns the digest size for the given digest type. + * + * @returns Size in bytes. + * @param enmDigest The hash type in question. + */ +static uint32_t rtLdrPE_HashGetHashSize(RTDIGESTTYPE enmDigest) +{ + switch (enmDigest) + { + case RTDIGESTTYPE_SHA512: return RTSHA512_HASH_SIZE; + case RTDIGESTTYPE_SHA384: return RTSHA384_HASH_SIZE; + case RTDIGESTTYPE_SHA256: return RTSHA256_HASH_SIZE; + case RTDIGESTTYPE_SHA1: return RTSHA1_HASH_SIZE; + case RTDIGESTTYPE_MD5: return RTMD5_HASH_SIZE; + default: AssertReleaseFailedReturn(0); + } +} + + +#ifndef IPRT_WITHOUT_LDR_VERIFY +/** + * Checks if the hash type is supported. + * + * @returns true/false. + * @param enmDigest The hash type in question. + */ +static bool rtLdrPE_HashIsSupported(RTDIGESTTYPE enmDigest) +{ + switch (enmDigest) + { + case RTDIGESTTYPE_SHA512: + case RTDIGESTTYPE_SHA384: + case RTDIGESTTYPE_SHA256: + case RTDIGESTTYPE_SHA1: + case RTDIGESTTYPE_MD5: + return true; + default: + return false; + } +} +#endif + + +/** + * Calculate the special too watch out for when hashing the image. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param pPlaces The structure where to store the special places. + * @param pErrInfo Optional error info. + */ +static int rtldrPe_CalcSpecialHashPlaces(PRTLDRMODPE pModPe, PRTLDRPEHASHSPECIALS pPlaces, PRTERRINFO pErrInfo) +{ + /* + * If we're here despite a missing signature, we need to get the file size. + */ + pPlaces->cbToHash = pModPe->SecurityDir.VirtualAddress; + if (pPlaces->cbToHash == 0) + { + uint64_t cbFile = pModPe->Core.pReader->pfnSize(pModPe->Core.pReader); + pPlaces->cbToHash = (uint32_t)cbFile; + if (pPlaces->cbToHash != (uint64_t)cbFile) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_FILE_LENGTH_ERROR, "File is too large: %RTfoff", cbFile); + } + + /* + * Calculate the special places. + */ + pPlaces->offCksum = (uint32_t)pModPe->offNtHdrs + + (pModPe->f64Bit + ? RT_UOFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.CheckSum) + : RT_UOFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.CheckSum)); + pPlaces->cbCksum = RT_SIZEOFMEMB(IMAGE_NT_HEADERS32, OptionalHeader.CheckSum); + pPlaces->offSecDir = (uint32_t)pModPe->offNtHdrs + + (pModPe->f64Bit + ? RT_UOFFSETOF(IMAGE_NT_HEADERS64, OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]) + : RT_UOFFSETOF(IMAGE_NT_HEADERS32, OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY])); + pPlaces->cbSecDir = sizeof(IMAGE_DATA_DIRECTORY); + pPlaces->offEndSpecial = pPlaces->offSecDir + pPlaces->cbSecDir; + return VINF_SUCCESS; +} + + +/** + * Calculates the whole image hash. + * + * The Authenticode_PE.docx version 1.0 explains how the hash is calculated, + * points 8 thru 14 are bogus. If you study them a little carefully, it is + * clear that the algorithm will only work if the raw data for the section have + * no gaps between them or in front of them. So, this elaborate section sorting + * by PointerToRawData and working them section by section could simply be + * replaced by one point: + * + * 8. Add all the file content between SizeOfHeaders and the + * attribute certificate table to the hash. Then finalize + * the hash. + * + * Not sure if Microsoft is screwing with us on purpose here or whether they + * assigned some of this work to less talented engineers and tech writers. I + * love fact that they say it's "simplified" and should yield the correct hash + * for "almost all" files. Stupid, Stupid, Microsofties!! + * + * My simplified implementation that just hashes the entire file up to the + * signature or end of the file produces the same SHA1 values as "signtool + * verify /v" does both for edited executables with gaps between/before/after + * sections raw data and normal executables without any gaps. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param pvScratch Scratch buffer. + * @param cbScratch Size of the scratch buffer. + * @param enmDigest The hash digest type we're calculating. + * @param pHashCtx Hash context scratch area. + * @param pHashRes Hash result buffer. + * @param pErrInfo Optional error info buffer. + */ +static int rtldrPE_HashImageCommon(PRTLDRMODPE pModPe, void *pvScratch, uint32_t cbScratch, RTDIGESTTYPE enmDigest, + PRTLDRPEHASHCTXUNION pHashCtx, PRTLDRPEHASHRESUNION pHashRes, PRTERRINFO pErrInfo) +{ + int rc = rtLdrPE_HashInit(pHashCtx, enmDigest); + if (RT_FAILURE(rc)) + return rc; + + /* + * Calculate the special places. + */ + RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */ + rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + + /* + * Work our way thru the image data. + */ + uint32_t off = 0; + while (off < SpecialPlaces.cbToHash) + { + uint32_t cbRead = RT_MIN(SpecialPlaces.cbToHash - off, cbScratch); + uint8_t *pbCur = (uint8_t *)pvScratch; + rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbRead, off); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH, "Hash read error at %#x: %Rrc (cbRead=%#zx)", + off, rc, cbRead); + + if (off < SpecialPlaces.offEndSpecial) + { + if (off < SpecialPlaces.offCksum) + { + /* Hash everything up to the checksum. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbRead); + rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbChunk); + pbCur += cbChunk; + cbRead -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum) + { + /* Skip the checksum */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbRead); + pbCur += cbChunk; + cbRead -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum) + { + /* Hash everything between the checksum and the data dir entry. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbRead); + rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbChunk); + pbCur += cbChunk; + cbRead -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir) + { + /* Skip the security data directory entry. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbRead); + pbCur += cbChunk; + cbRead -= cbChunk; + off += cbChunk; + } + } + + rtLdrPE_HashUpdate(pHashCtx, enmDigest, pbCur, cbRead); + + /* Advance */ + off += cbRead; + } + + /* + * If there isn't a signature, experiments with signtool indicates that we + * have to zero padd the file size until it's a multiple of 8. (This is + * most likely to give 64-bit values in the certificate a natural alignment + * when memory mapped.) + */ + if ( pModPe->SecurityDir.VirtualAddress != SpecialPlaces.cbToHash + && SpecialPlaces.cbToHash != RT_ALIGN_32(SpecialPlaces.cbToHash, WIN_CERTIFICATE_ALIGNMENT)) + { + static const uint8_t s_abZeros[WIN_CERTIFICATE_ALIGNMENT] = { 0,0,0,0, 0,0,0,0 }; + rtLdrPE_HashUpdate(pHashCtx, enmDigest, s_abZeros, + RT_ALIGN_32(SpecialPlaces.cbToHash, WIN_CERTIFICATE_ALIGNMENT) - SpecialPlaces.cbToHash); + } + + /* + * Done. Finalize the hashes. + */ + rtLdrPE_HashFinalize(pHashCtx, enmDigest, pHashRes); + return VINF_SUCCESS; +} + +#ifndef IPRT_WITHOUT_LDR_PAGE_HASHING + +/** + * Returns the size of the page hashes, including the terminator entry. + * + * Used for handling RTLDRPROP_HASHABLE_PAGES. + * + * @returns Number of page hashes. + * @param pModPe The PE module. + */ +static uint32_t rtLdrPE_GetHashablePages(PRTLDRMODPE pModPe) +{ + uint32_t const cbPage = _4K; + uint32_t cPages = 1; /* termination entry */ + + /* Add implicit header section: */ + cPages += (pModPe->cbHeaders + cbPage - 1) / cbPage; + + /* Add on disk pages for each section. Each starts with a fresh page and + we ASSUMES that it is page aligned (in memory). */ + for (uint32_t i = 0; i < pModPe->cSections; i++) + { + uint32_t const cbRawData = pModPe->paSections[i].SizeOfRawData; + if (cbRawData > 0) + cPages += (cbRawData + cbPage - 1) / cbPage; + } + + return cPages; +} + + +/** + * Worker for rtLdrPE_QueryPageHashes. + * + * Keep in mind that rtldrPE_VerifyAllPageHashes does similar work, so some + * fixes may apply both places. + */ +static int rtLdrPE_CalcPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE const enmDigest, uint32_t const cbHash, + uint8_t *pbDst, uint8_t *pbScratch, uint32_t cbScratch, uint32_t const cbPage) +{ + /* + * Calculate the special places. + */ + RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */ + int rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Walk section table and hash the pages in each. Because the headers are + * in an implicit section, the loop advancing is a little funky. + */ + int32_t const cSections = pModPe->cSections; + int32_t iSection = -1; + uint32_t offRawData = 0; + uint32_t cbRawData = pModPe->cbHeaders; + uint32_t offLastPage = 0; + + uint32_t const cbScratchReadMax = cbScratch / cbPage * cbPage; + uint32_t cbScratchRead = 0; + uint32_t offScratchRead = 0; + + for (;;) + { + /* + * Process the pages in this section. + */ + uint32_t cPagesInSection = (cbRawData + cbPage - 1) / cbPage; + for (uint32_t iPage = 0; iPage < cPagesInSection; iPage++) + { + uint32_t const offPageInSect = iPage * cbPage; + uint32_t const offPageInFile = offRawData + offPageInSect; + uint32_t const cbPageInFile = RT_MIN(cbPage, cbRawData - offPageInSect); + offLastPage = offPageInFile; + + /* Calculate and output the page offset. */ + *(uint32_t *)pbDst = offPageInFile; + pbDst += sizeof(uint32_t); + + /* + * Read/find in the raw page. + */ + /* Did we get a cache hit? */ + uint8_t *pbCur = pbScratch; + if ( offPageInFile + cbPageInFile <= offScratchRead + cbScratchRead + && offPageInFile >= offScratchRead) + pbCur += offPageInFile - offScratchRead; + /* Missed, read more. */ + else + { + offScratchRead = offPageInFile; + cbScratchRead = SpecialPlaces.cbToHash - offPageInFile; + if (cbScratchRead > cbScratchReadMax) + cbScratchRead = cbScratchReadMax; + rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbScratchRead, offScratchRead); + if (RT_FAILURE(rc)) + return VERR_LDRVI_READ_ERROR_HASH; + } + + /* + * Hash it. + */ + RTLDRPEHASHCTXUNION HashCtx; + rc = rtLdrPE_HashInit(&HashCtx, enmDigest); + AssertRCReturn(rc, rc); + + /* Deal with special places. */ + uint32_t cbLeft = cbPageInFile; + if (offPageInFile < SpecialPlaces.offEndSpecial) + { + uint32_t off = offPageInFile; + if (off < SpecialPlaces.offCksum) + { + /* Hash everything up to the checksum. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbLeft); + rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum) + { + /* Skip the checksum */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbLeft); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum) + { + /* Hash everything between the checksum and the data dir entry. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbLeft); + rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir) + { + /* Skip the security data directory entry. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbLeft); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + } + + rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbLeft); + if (cbPageInFile < cbPage) + rtLdrPE_HashUpdate(&HashCtx, enmDigest, g_abRTZero4K, cbPage - cbPageInFile); + + /* + * Finish the hash calculation storing it in the table. + */ + rtLdrPE_HashFinalize(&HashCtx, enmDigest, (PRTLDRPEHASHRESUNION)pbDst); + pbDst += cbHash; + } + + /* + * Advance to the next section. + */ + iSection++; + if (iSection >= cSections) + break; + offRawData = pModPe->paSections[iSection].PointerToRawData; + cbRawData = pModPe->paSections[iSection].SizeOfRawData; + } + + /* + * Add the terminator entry. + */ + *(uint32_t *)pbDst = offLastPage + cbPage; + RT_BZERO(&pbDst[sizeof(uint32_t)], cbHash); + + return VINF_SUCCESS; +} + + +/** + * Creates the page hash table for the image. + * + * Used for handling RTLDRPROP_SHA1_PAGE_HASHES and + * RTLDRPROP_SHA256_PAGE_HASHES. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param enmDigest The digest to use when hashing the pages. + * @param pvBuf Where to return the page hash table. + * @param cbBuf The size of the buffer @a pvBuf points to. + * @param pcbRet Where to return the output/needed size. + */ +static int rtLdrPE_QueryPageHashes(PRTLDRMODPE pModPe, RTDIGESTTYPE enmDigest, void *pvBuf, size_t cbBuf, size_t *pcbRet) +{ + /* + * Check that we've got enough buffer space. + */ + uint32_t const cbPage = _4K; + uint32_t const cEntries = rtLdrPE_GetHashablePages(pModPe); + uint32_t const cbHash = rtLdrPE_HashGetHashSize(enmDigest); + AssertReturn(cbHash > 0, VERR_INTERNAL_ERROR_3); + + size_t const cbNeeded = (size_t)(cbHash + 4) * cEntries; + *pcbRet = cbNeeded; + if (cbNeeded > cbBuf) + return VERR_BUFFER_OVERFLOW; + + /* + * Allocate a scratch buffer and call worker to do the real job. + */ +# ifdef IN_RING0 + uint32_t cbScratch = _256K - _4K; +# else + uint32_t cbScratch = _1M; +# endif + void *pvScratch = RTMemTmpAlloc(cbScratch); + if (!pvScratch) + { + cbScratch = _4K; + pvScratch = RTMemTmpAlloc(cbScratch); + if (!pvScratch) + return VERR_NO_TMP_MEMORY; + } + + int rc = rtLdrPE_CalcPageHashes(pModPe, enmDigest, cbHash, (uint8_t *)pvBuf, (uint8_t *)pvScratch, cbScratch, cbPage); + + RTMemTmpFree(pvScratch); + return rc; +} + +#endif /* !IPRT_WITHOUT_LDR_PAGE_HASHING */ +#ifndef IPRT_WITHOUT_LDR_VERIFY + +/** + * Verifies image preconditions not checked by the open validation code. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param pErrInfo Optional error info buffer. + */ +static int rtldrPE_VerifySignatureImagePrecoditions(PRTLDRMODPE pModPe, PRTERRINFO pErrInfo) +{ + /* + * Validate the sections. While doing so, track the amount of section raw + * section data in the file so we can use this to validate the signature + * table location later. + */ + uint32_t offNext = pModPe->cbHeaders; /* same */ + for (uint32_t i = 0; i < pModPe->cSections; i++) + if (pModPe->paSections[i].SizeOfRawData > 0) + { + uint64_t offEnd = (uint64_t)pModPe->paSections[i].PointerToRawData + pModPe->paSections[i].SizeOfRawData; + if (offEnd > offNext) + { + if (offEnd >= _2G) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_SECTION_RAW_DATA_VALUES, + "Section %#u specifies file data after 2GB: PointerToRawData=%#x SizeOfRawData=%#x", + i, pModPe->paSections[i].PointerToRawData, pModPe->paSections[i].SizeOfRawData); + offNext = (uint32_t)offEnd; + } + } + uint32_t offEndOfSectionData = offNext; + + /* + * Validate the signature. + */ + if (!pModPe->SecurityDir.Size) + return RTErrInfoSet(pErrInfo, VERR_LDRVI_NOT_SIGNED, "Not signed."); + + uint32_t const offSignature = pModPe->SecurityDir.VirtualAddress; + uint32_t const cbSignature = pModPe->SecurityDir.Size; + if ( cbSignature <= sizeof(WIN_CERTIFICATE) + || cbSignature >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE + || offSignature >= _2G) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY, + "Invalid security data dir entry: cb=%#x off=%#x", cbSignature, offSignature); + + if (offSignature < offEndOfSectionData) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY, + "Invalid security data dir entry offset: %#x offEndOfSectionData=%#x", + offSignature, offEndOfSectionData); + + if (RT_ALIGN_32(offSignature, WIN_CERTIFICATE_ALIGNMENT) != offSignature) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY, + "Misaligned security dir entry offset: %#x (alignment=%#x)", + offSignature, WIN_CERTIFICATE_ALIGNMENT); + + + return VINF_SUCCESS; +} + + +/** + * Reads and checks the raw signature data. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param ppSignature Where to return the pointer to the parsed + * signature data. Pass to + * rtldrPE_VerifySignatureDestroy when done. + * @param pErrInfo Optional error info buffer. + */ +static int rtldrPE_VerifySignatureRead(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE *ppSignature, PRTERRINFO pErrInfo) +{ + *ppSignature = NULL; + AssertReturn(pModPe->SecurityDir.Size > 0, VERR_INTERNAL_ERROR_2); + + /* + * Allocate memory for reading and parsing it. + */ + if (pModPe->SecurityDir.Size >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_INVALID_SECURITY_DIR_ENTRY, + "Signature directory is to large: %#x", pModPe->SecurityDir.Size); + + PRTLDRPESIGNATURE pSignature = (PRTLDRPESIGNATURE)RTMemTmpAllocZ(sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size); + if (!pSignature) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_NO_MEMORY_SIGNATURE, "Failed to allocate %zu bytes", + sizeof(*pSignature) + 64 + pModPe->SecurityDir.Size); + pSignature->pRawData = RT_ALIGN_PT(pSignature + 1, 64, WIN_CERTIFICATE const *); + + + /* + * Read it. + */ + int rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, (void *)pSignature->pRawData, + pModPe->SecurityDir.Size, pModPe->SecurityDir.VirtualAddress); + if (RT_SUCCESS(rc)) + { + /* + * Check the table we've read in. + */ + uint32_t cbLeft = pModPe->SecurityDir.Size; + WIN_CERTIFICATE const *pEntry = pSignature->pRawData; + for (;;) + { + if ( cbLeft < sizeof(*pEntry) + || pEntry->dwLength > cbLeft + || pEntry->dwLength < sizeof(*pEntry)) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_LENGTH, + "Bad WIN_CERTIFICATE length: %#x (max %#x, signature=%u)", + pEntry->dwLength, cbLeft, 0); + else if (pEntry->wRevision != WIN_CERT_REVISION_2_0) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_REVISION, + "Unsupported WIN_CERTIFICATE revision value: %#x (signature=%u)", + pEntry->wRevision, 0); + else if (pEntry->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_BAD_CERT_HDR_TYPE, + "Unsupported WIN_CERTIFICATE certificate type: %#x (signature=%u)", + pEntry->wCertificateType, 0); + else + { + /* advance */ + uint32_t cbEntry = RT_ALIGN(pEntry->dwLength, WIN_CERTIFICATE_ALIGNMENT); + if (cbEntry >= cbLeft) + break; + cbLeft -= cbEntry; + pEntry = (WIN_CERTIFICATE *)((uintptr_t)pEntry + cbEntry); + + /* For now, only one entry is supported. */ + rc = RTErrInfoSet(pErrInfo, VERR_LDRVI_BAD_CERT_MULTIPLE, "Multiple WIN_CERTIFICATE entries are not supported."); + } + break; + } + if (RT_SUCCESS(rc)) + { + *ppSignature = pSignature; + return VINF_SUCCESS; + } + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_SIGNATURE, "Signature read error: %Rrc", rc); + RTMemTmpFree(pSignature); + return rc; +} + + +/** + * Destroys the parsed signature. + * + * @param pModPe The PE module. + * @param pSignature The signature data to destroy. + */ +static void rtldrPE_VerifySignatureDestroy(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature) +{ + RT_NOREF_PV(pModPe); + RTCrPkcs7ContentInfo_Delete(&pSignature->PrimaryContentInfo); + if (pSignature->paNested) + { + RTMemTmpFree(pSignature->paNested); + pSignature->paNested = NULL; + } + RTMemTmpFree(pSignature); +} + + +/** + * Handles nested signatures. + * + * @returns IPRT status code. + * @param pSignature The signature status structure. Returns with + * cNested = 0 and paNested = NULL if no nested + * signatures. + * @param pErrInfo Where to return extended error info (optional). + */ +static int rtldrPE_VerifySignatureDecodeNested(PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo) +{ + Assert(pSignature->cNested == 0); + Assert(pSignature->paNested == NULL); + + /* + * Count nested signatures. + */ + uint32_t cNested = 0; + for (uint32_t iSignerInfo = 0; iSignerInfo < pSignature->Primary.pSignedData->SignerInfos.cItems; iSignerInfo++) + { + PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignature->Primary.pSignedData->SignerInfos.papItems[iSignerInfo]; + for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]; + if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE) + { + Assert(pAttrib->uValues.pContentInfos); + cNested += pAttrib->uValues.pContentInfos->cItems; + } + } + } + if (!cNested) + return VINF_SUCCESS; + + /* + * Allocate and populate the info structures. + */ + pSignature->paNested = (PRTLDRPESIGNATUREONE)RTMemTmpAllocZ(sizeof(pSignature->paNested[0]) * cNested); + if (!pSignature->paNested) + return RTErrInfoSetF(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate space for %u nested signatures", cNested); + pSignature->cNested = cNested; + + cNested = 0; + for (uint32_t iSignerInfo = 0; iSignerInfo < pSignature->Primary.pSignedData->SignerInfos.cItems; iSignerInfo++) + { + PCRTCRPKCS7SIGNERINFO pSignerInfo = pSignature->Primary.pSignedData->SignerInfos.papItems[iSignerInfo]; + for (uint32_t iAttrib = 0; iAttrib < pSignerInfo->UnauthenticatedAttributes.cItems; iAttrib++) + { + PCRTCRPKCS7ATTRIBUTE pAttrib = pSignerInfo->UnauthenticatedAttributes.papItems[iAttrib]; + if (pAttrib->enmType == RTCRPKCS7ATTRIBUTETYPE_MS_NESTED_SIGNATURE) + { + for (uint32_t iItem = 0; iItem < pAttrib->uValues.pContentInfos->cItems; iItem++, cNested++) + { + PRTLDRPESIGNATUREONE pInfo = &pSignature->paNested[cNested]; + PRTCRPKCS7CONTENTINFO pContentInfo = pAttrib->uValues.pContentInfos->papItems[iItem]; + pInfo->pContentInfo = pContentInfo; + pInfo->iSignature = cNested; + + if (RTCrPkcs7ContentInfo_IsSignedData(pInfo->pContentInfo)) + { /* likely */ } + else + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/ + "Nested#%u: PKCS#7 is not 'signedData': %s", cNested, pInfo->pContentInfo->ContentType.szObjId); + PRTCRPKCS7SIGNEDDATA pSignedData = pContentInfo->u.pSignedData; + pInfo->pSignedData = pSignedData; + + /* + * Check the authenticode bits. + */ + if (!strcmp(pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID)) + { /* likely */ } + else + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, + "Nested#%u: Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)", + cNested, pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID); + pInfo->pIndData = pSignedData->ContentInfo.u.pIndirectDataContent; + Assert(pInfo->pIndData); + + /* + * Check that things add up. + */ + int rc = RTCrPkcs7SignedData_CheckSanity(pSignedData, + RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE + | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH + | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT, + pErrInfo, "SD"); + if (RT_SUCCESS(rc)) + rc = RTCrSpcIndirectDataContent_CheckSanityEx(pInfo->pIndData, + pSignedData, + RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH, + pErrInfo); + if (RT_SUCCESS(rc)) + { + PCRTCRX509ALGORITHMIDENTIFIER pDigestAlgorithm = &pInfo->pIndData->DigestInfo.DigestAlgorithm; + pInfo->enmDigest = RTCrX509AlgorithmIdentifier_GetDigestType(pDigestAlgorithm, + true /*fPureDigestsOnly*/); + AssertReturn(pInfo->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */ + } + else + return rc; + } + } + } + } + + return VINF_SUCCESS; +} + + +/** + * Decodes the raw signature. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param pSignature The signature data. + * @param pErrInfo Optional error info buffer. + */ +static int rtldrPE_VerifySignatureDecode(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo) +{ + WIN_CERTIFICATE const *pEntry = pSignature->pRawData; + AssertReturn(pEntry->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA, VERR_INTERNAL_ERROR_2); + AssertReturn(pEntry->wRevision == WIN_CERT_REVISION_2_0, VERR_INTERNAL_ERROR_2); + RT_NOREF_PV(pModPe); + + RTASN1CURSORPRIMARY PrimaryCursor; + RTAsn1CursorInitPrimary(&PrimaryCursor, + &pEntry->bCertificate[0], + pEntry->dwLength - RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate), + pErrInfo, + &g_RTAsn1DefaultAllocator, + 0, + "WinCert"); + + PRTLDRPESIGNATUREONE pInfo = &pSignature->Primary; + pInfo->pContentInfo = &pSignature->PrimaryContentInfo; + int rc = RTCrPkcs7ContentInfo_DecodeAsn1(&PrimaryCursor.Cursor, 0, pInfo->pContentInfo, "CI"); + if (RT_SUCCESS(rc)) + { + if (RTCrPkcs7ContentInfo_IsSignedData(pInfo->pContentInfo)) + { + pInfo->pSignedData = pInfo->pContentInfo->u.pSignedData; + + /* + * Decode the authenticode bits. + */ + if (!strcmp(pInfo->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID)) + { + pInfo->pIndData = pInfo->pSignedData->ContentInfo.u.pIndirectDataContent; + Assert(pInfo->pIndData); + + /* + * Check that things add up. + */ + rc = RTCrPkcs7SignedData_CheckSanity(pInfo->pSignedData, + RTCRPKCS7SIGNEDDATA_SANITY_F_AUTHENTICODE + | RTCRPKCS7SIGNEDDATA_SANITY_F_ONLY_KNOWN_HASH + | RTCRPKCS7SIGNEDDATA_SANITY_F_SIGNING_CERT_PRESENT, + pErrInfo, "SD"); + if (RT_SUCCESS(rc)) + rc = RTCrSpcIndirectDataContent_CheckSanityEx(pInfo->pIndData, + pInfo->pSignedData, + RTCRSPCINDIRECTDATACONTENT_SANITY_F_ONLY_KNOWN_HASH, + pErrInfo); + if (RT_SUCCESS(rc)) + { + PCRTCRX509ALGORITHMIDENTIFIER pDigestAlgorithm = &pInfo->pIndData->DigestInfo.DigestAlgorithm; + pInfo->enmDigest = RTCrX509AlgorithmIdentifier_GetDigestType(pDigestAlgorithm, true /*fPureDigestsOnly*/); + AssertReturn(pInfo->enmDigest != RTDIGESTTYPE_INVALID, VERR_INTERNAL_ERROR_4); /* Checked above! */ + + /* + * Deal with nested signatures. + */ + rc = rtldrPE_VerifySignatureDecodeNested(pSignature, pErrInfo); + } + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, + "Unknown pSignedData.ContentInfo.ContentType.szObjId value: %s (expected %s)", + pInfo->pSignedData->ContentInfo.ContentType.szObjId, RTCRSPCINDIRECTDATACONTENT_OID); + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_EXPECTED_INDIRECT_DATA_CONTENT_OID, /** @todo error code*/ + "PKCS#7 is not 'signedData': %s", pInfo->pContentInfo->ContentType.szObjId); + } + return rc; +} + + + +static int rtldrPE_VerifyAllPageHashes(PRTLDRMODPE pModPe, PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib, RTDIGESTTYPE enmDigest, + void *pvScratch, size_t cbScratch, uint32_t iSignature, PRTERRINFO pErrInfo) +{ + AssertReturn(cbScratch >= _4K, VERR_INTERNAL_ERROR_3); + + /* + * Calculate the special places. + */ + RTLDRPEHASHSPECIALS SpecialPlaces = { 0, 0, 0, 0, 0, 0 }; /* shut up gcc */ + int rc = rtldrPe_CalcSpecialHashPlaces(pModPe, &SpecialPlaces, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + + uint32_t const cbHash = rtLdrPE_HashGetHashSize(enmDigest); + uint32_t const cPages = pAttrib->u.pPageHashes->RawData.Asn1Core.cb / (cbHash + 4); + if (cPages * (cbHash + 4) != pAttrib->u.pPageHashes->RawData.Asn1Core.cb) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_SIZE_OVERFLOW, + "Signature #%u - Page hashes size issue in: cb=%#x cbHash=%#x", + iSignature, pAttrib->u.pPageHashes->RawData.Asn1Core.cb, cbHash); + + /* + * Walk the table. + */ + uint32_t const cbScratchReadMax = cbScratch & ~(uint32_t)(_4K - 1); + uint32_t cbScratchRead = 0; + uint32_t offScratchRead = 0; + + uint32_t offPrev = 0; +#ifdef COMPLICATED_AND_WRONG + uint32_t offSectEnd = pModPe->cbHeaders; + uint32_t iSh = UINT32_MAX; +#endif + uint8_t const *pbHashTab = pAttrib->u.pPageHashes->RawData.Asn1Core.uData.pu8; + for (uint32_t iPage = 0; iPage < cPages - 1; iPage++) + { + /* Decode the page offset. */ + uint32_t const offPageInFile = RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]); + if (RT_UNLIKELY(offPageInFile >= SpecialPlaces.cbToHash)) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_TOO_LONG, + "Signature #%u - Page hash entry #%u is beyond the signature table start: %#x, %#x", + iSignature, iPage, offPageInFile, SpecialPlaces.cbToHash); + if (RT_UNLIKELY(offPageInFile < offPrev)) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_NOT_STRICTLY_SORTED, + "Signature #%u - Page hash table is not strictly sorted: entry #%u @%#x, previous @%#x\n", + iSignature, iPage, offPageInFile, offPrev); + +#ifdef COMPLICATED_AND_WRONG + /* Figure out how much to read and how much to zero. Need keep track + of the on-disk section boundraries. */ + if (offPageInFile >= offSectEnd) + { + iSh++; + if ( iSh < pModPe->cSections + && offPageInFile - pModPe->paSections[iSh].PointerToRawData < pModPe->paSections[iSh].SizeOfRawData) + offSectEnd = pModPe->paSections[iSh].PointerToRawData + pModPe->paSections[iSh].SizeOfRawData; + else + { + iSh = 0; + while ( iSh < pModPe->cSections + && offPageInFile - pModPe->paSections[iSh].PointerToRawData >= pModPe->paSections[iSh].SizeOfRawData) + iSh++; + if (iSh < pModPe->cSections) + offSectEnd = pModPe->paSections[iSh].PointerToRawData + pModPe->paSections[iSh].SizeOfRawData; + else + return RTErrInfoSetF(pErrInfo, VERR_PAGE_HASH_TAB_HASHES_NON_SECTION_DATA, + "Signature #%u - Page hash entry #%u isn't in any section: %#x", + iSignature, iPage, offPageInFile); + } + } + +#else + /* Figure out how much to read and how much take as zero. Use the next + page offset and the signature as upper boundraries. */ +#endif + uint32_t cbPageInFile = _4K; +#ifdef COMPLICATED_AND_WRONG + if (offPageInFile + cbPageInFile > offSectEnd) + cbPageInFile = offSectEnd - offPageInFile; +#else + if (iPage + 1 < cPages) + { + uint32_t offNextPage = RT_MAKE_U32_FROM_U8(pbHashTab[0 + 4 + cbHash], pbHashTab[1 + 4 + cbHash], + pbHashTab[2 + 4 + cbHash], pbHashTab[3 + 4 + cbHash]); + if (offNextPage - offPageInFile < cbPageInFile) + cbPageInFile = offNextPage - offPageInFile; + } +#endif + + if (offPageInFile + cbPageInFile > SpecialPlaces.cbToHash) + cbPageInFile = SpecialPlaces.cbToHash - offPageInFile; + + /* Did we get a cache hit? */ + uint8_t *pbCur = (uint8_t *)pvScratch; + if ( offPageInFile + cbPageInFile <= offScratchRead + cbScratchRead + && offPageInFile >= offScratchRead) + pbCur += offPageInFile - offScratchRead; + /* Missed, read more. */ + else + { + offScratchRead = offPageInFile; +#ifdef COMPLICATED_AND_WRONG + cbScratchRead = offSectEnd - offPageInFile; +#else + cbScratchRead = SpecialPlaces.cbToHash - offPageInFile; +#endif + if (cbScratchRead > cbScratchReadMax) + cbScratchRead = cbScratchReadMax; + rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pbCur, cbScratchRead, offScratchRead); + if (RT_FAILURE(rc)) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_READ_ERROR_HASH, + "Signature #%u - Page hash read error at %#x: %Rrc (cbScratchRead=%#zx)", + iSignature, offScratchRead, rc, cbScratchRead); + } + + /* + * Hash it. + */ + RTLDRPEHASHCTXUNION HashCtx; + rc = rtLdrPE_HashInit(&HashCtx, enmDigest); + AssertRCReturn(rc, rc); + + /* Deal with special places. */ + uint32_t cbLeft = cbPageInFile; + if (offPageInFile < SpecialPlaces.offEndSpecial) + { + uint32_t off = offPageInFile; + if (off < SpecialPlaces.offCksum) + { + /* Hash everything up to the checksum. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum - off, cbLeft); + rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offCksum + SpecialPlaces.cbCksum && off >= SpecialPlaces.offCksum) + { + /* Skip the checksum */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offCksum + SpecialPlaces.cbCksum - off, cbLeft); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offSecDir && off >= SpecialPlaces.offCksum + SpecialPlaces.cbCksum) + { + /* Hash everything between the checksum and the data dir entry. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir - off, cbLeft); + rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbChunk); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + + if (off < SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir && off >= SpecialPlaces.offSecDir) + { + /* Skip the security data directory entry. */ + uint32_t cbChunk = RT_MIN(SpecialPlaces.offSecDir + SpecialPlaces.cbSecDir - off, cbLeft); + pbCur += cbChunk; + cbLeft -= cbChunk; + off += cbChunk; + } + } + + rtLdrPE_HashUpdate(&HashCtx, enmDigest, pbCur, cbLeft); + if (cbPageInFile < _4K) + rtLdrPE_HashUpdate(&HashCtx, enmDigest, g_abRTZero4K, _4K - cbPageInFile); + + /* + * Finish the hash calculation and compare the result. + */ + RTLDRPEHASHRESUNION HashRes; + rtLdrPE_HashFinalize(&HashCtx, enmDigest, &HashRes); + + pbHashTab += 4; + if (memcmp(pbHashTab, &HashRes, cbHash) != 0) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_MISMATCH, + "Signature #%u - Page hash failed for page #%u, @%#x, %#x bytes: %.*Rhxs != %.*Rhxs", + iSignature, iPage, offPageInFile, cbPageInFile, (size_t)cbHash, pbHashTab, + (size_t)cbHash, &HashRes); + pbHashTab += cbHash; + offPrev = offPageInFile; + } + + /* + * Check that the last table entry has a hash value of zero. + */ + if (!ASMMemIsZero(pbHashTab + 4, cbHash)) + return RTErrInfoSetF(pErrInfo, VERR_LDRVI_PAGE_HASH_TAB_TOO_LONG, + "Signature #%u - Malformed final page hash table entry: #%u %#010x %.*Rhxs", + iSignature, cPages - 1, RT_MAKE_U32_FROM_U8(pbHashTab[0], pbHashTab[1], pbHashTab[2], pbHashTab[3]), + (size_t)cbHash, pbHashTab + 4); + return VINF_SUCCESS; +} + + +static int rtldrPE_VerifySignatureValidateOnePageHashes(PRTLDRMODPE pModPe, PRTLDRPESIGNATUREONE pInfo, + void *pvScratch, uint32_t cbScratch, PRTERRINFO pErrInfo) +{ + /* + * Compare the page hashes if present. + * + * Seems the difference between V1 and V2 page hash attributes is + * that v1 uses SHA-1 while v2 uses SHA-256. The data structures + * seems to be identical otherwise. Initially we assumed the digest + * algorithm was supposed to be RTCRSPCINDIRECTDATACONTENT::DigestInfo, + * i.e. the same as for the whole image hash. The initial approach + * worked just fine, but this makes more sense. + * + * (See also comments in osslsigncode.c (google it).) + */ + PCRTCRSPCSERIALIZEDOBJECTATTRIBUTE pAttrib; + /* V2 - SHA-256: */ + pAttrib = RTCrSpcIndirectDataContent_GetPeImageObjAttrib(pInfo->pIndData, + RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V2); + if (pAttrib) + return rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA256, pvScratch, cbScratch, + pInfo->iSignature + 1, pErrInfo); + + /* V1 - SHA-1: */ + pAttrib = RTCrSpcIndirectDataContent_GetPeImageObjAttrib(pInfo->pIndData, + RTCRSPCSERIALIZEDOBJECTATTRIBUTETYPE_PAGE_HASHES_V1); + if (pAttrib) + return rtldrPE_VerifyAllPageHashes(pModPe, pAttrib, RTDIGESTTYPE_SHA1, pvScratch, cbScratch, + pInfo->iSignature + 1, pErrInfo); + + /* No page hashes: */ + return VINF_SUCCESS; +} + + +static int rtldrPE_VerifySignatureValidateOneImageHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, + PRTLDRPESIGNATUREONE pInfo, void *pvScratch, uint32_t cbScratch, + PRTERRINFO pErrInfo) +{ + /* + * Assert sanity. + */ + AssertReturn(pInfo->enmDigest > RTDIGESTTYPE_INVALID && pInfo->enmDigest < RTDIGESTTYPE_END, VERR_INTERNAL_ERROR_4); + AssertPtrReturn(pInfo->pIndData, VERR_INTERNAL_ERROR_5); + AssertReturn(RTASN1CORE_IS_PRESENT(&pInfo->pIndData->DigestInfo.Digest.Asn1Core), VERR_INTERNAL_ERROR_5); + AssertPtrReturn(pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, VERR_INTERNAL_ERROR_5); + + /* Check that the hash is supported by the code here before continuing. */ + AssertReturn(rtLdrPE_HashIsSupported(pInfo->enmDigest), + RTErrInfoSetF(pErrInfo, VERR_CR_DIGEST_NOT_SUPPORTED, "Unsupported digest type: %d", pInfo->enmDigest)); + + /* + * Skip it if we've already verified it. + */ + if (pInfo->fValidatedImageHash) + return VINF_SUCCESS; + + /* + * Calculate it. + */ + uint32_t const cbHash = rtLdrPE_HashGetHashSize(pInfo->enmDigest); + AssertReturn(pInfo->pIndData->DigestInfo.Digest.Asn1Core.cb == cbHash, VERR_INTERNAL_ERROR_5); + + int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, pInfo->enmDigest, + &pSignature->HashCtx, &pInfo->HashRes, pErrInfo); + if (RT_SUCCESS(rc)) + { + pInfo->fValidatedImageHash = true; + if (memcmp(&pInfo->HashRes, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash) == 0) + { + /* + * Verify other signatures with the same digest type. + */ + RTLDRPEHASHRESUNION const * const pHashRes = &pInfo->HashRes; + RTDIGESTTYPE const enmDigestType = pInfo->enmDigest; + for (uint32_t i = 0; i < pSignature->cNested; i++) + { + pInfo = &pSignature->paNested[i]; /* Note! pInfo changes! */ + if ( !pInfo->fValidatedImageHash + && pInfo->enmDigest == enmDigestType + /* paranoia from the top of this function: */ + && pInfo->pIndData + && RTASN1CORE_IS_PRESENT(&pInfo->pIndData->DigestInfo.Digest.Asn1Core) + && pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv + && pInfo->pIndData->DigestInfo.Digest.Asn1Core.cb == cbHash) + { + pInfo->fValidatedImageHash = true; + if (memcmp(pHashRes, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv, cbHash) != 0) + { + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH, + "Full image signature #%u mismatch: %.*Rhxs, expected %.*Rhxs", pInfo->iSignature + 1, + cbHash, pHashRes, + cbHash, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv); + break; + } + } + } + } + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRVI_IMAGE_HASH_MISMATCH, + "Full image signature #%u mismatch: %.*Rhxs, expected %.*Rhxs", pInfo->iSignature + 1, + cbHash, &pInfo->HashRes, + cbHash, pInfo->pIndData->DigestInfo.Digest.Asn1Core.uData.pv); + } + return rc; +} + + +/** + * Validates the image hash, including page hashes if present. + * + * @returns IPRT status code. + * @param pModPe The PE module. + * @param pSignature The decoded signature data. + * @param pErrInfo Optional error info buffer. + */ +static int rtldrPE_VerifySignatureValidateHash(PRTLDRMODPE pModPe, PRTLDRPESIGNATURE pSignature, PRTERRINFO pErrInfo) +{ + /* + * Allocate a temporary memory buffer. + * Note! The _4K that gets subtracted is to avoid that the 16-byte heap + * block header in ring-0 (iprt) caused any unnecessary internal + * heap fragmentation. + */ +# ifdef IN_RING0 + uint32_t cbScratch = _256K - _4K; +# else + uint32_t cbScratch = _1M; +# endif + void *pvScratch = RTMemTmpAlloc(cbScratch); + if (!pvScratch) + { + cbScratch = _4K; + pvScratch = RTMemTmpAlloc(cbScratch); + if (!pvScratch) + return RTErrInfoSet(pErrInfo, VERR_NO_TMP_MEMORY, "Failed to allocate 4KB of scratch space for hashing image."); + } + + /* + * Verify signatures. + */ + /* Image hashes: */ + int rc = rtldrPE_VerifySignatureValidateOneImageHash(pModPe, pSignature, &pSignature->Primary, + pvScratch, cbScratch, pErrInfo); + for (unsigned i = 0; i < pSignature->cNested && RT_SUCCESS(rc); i++) + rc = rtldrPE_VerifySignatureValidateOneImageHash(pModPe, pSignature, &pSignature->paNested[i], + pvScratch, cbScratch, pErrInfo); + + /* Page hashes: */ + if (RT_SUCCESS(rc)) + { + rc = rtldrPE_VerifySignatureValidateOnePageHashes(pModPe, &pSignature->Primary, pvScratch, cbScratch, pErrInfo); + for (unsigned i = 0; i < pSignature->cNested && RT_SUCCESS(rc); i++) + rc = rtldrPE_VerifySignatureValidateOnePageHashes(pModPe, &pSignature->paNested[i], pvScratch, cbScratch, pErrInfo); + } + + /* + * Ditch the scratch buffer. + */ + RTMemTmpFree(pvScratch); + return rc; +} + +#endif /* !IPRT_WITHOUT_LDR_VERIFY */ + + +/** @interface_method_impl{RTLDROPS,pfnVerifySignature} */ +static DECLCALLBACK(int) rtldrPE_VerifySignature(PRTLDRMODINTERNAL pMod, PFNRTLDRVALIDATESIGNEDDATA pfnCallback, void *pvUser, + PRTERRINFO pErrInfo) +{ +#ifndef IPRT_WITHOUT_LDR_VERIFY + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + int rc = rtldrPE_VerifySignatureImagePrecoditions(pModPe, pErrInfo); + if (RT_SUCCESS(rc)) + { + PRTLDRPESIGNATURE pSignature = NULL; + rc = rtldrPE_VerifySignatureRead(pModPe, &pSignature, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = rtldrPE_VerifySignatureDecode(pModPe, pSignature, pErrInfo); + if (RT_SUCCESS(rc)) + rc = rtldrPE_VerifySignatureValidateHash(pModPe, pSignature, pErrInfo); + if (RT_SUCCESS(rc)) + { + /* + * Work the callback. + */ + /* The primary signature: */ + RTLDRSIGNATUREINFO Info; + Info.iSignature = 0; + Info.cSignatures = (uint16_t)(1 + pSignature->cNested); + Info.enmType = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA; + Info.pvSignature = pSignature->Primary.pContentInfo; + Info.cbSignature = sizeof(*pSignature->Primary.pContentInfo); + Info.pvExternalData = NULL; + Info.cbExternalData = 0; + rc = pfnCallback(&pModPe->Core, &Info, pErrInfo, pvUser); + + /* The nested signatures: */ + for (uint32_t iNested = 0; iNested < pSignature->cNested && rc == VINF_SUCCESS; iNested++) + { + Info.iSignature = (uint16_t)(1 + iNested); + Info.cSignatures = (uint16_t)(1 + pSignature->cNested); + Info.enmType = RTLDRSIGNATURETYPE_PKCS7_SIGNED_DATA; + Info.pvSignature = pSignature->paNested[iNested].pContentInfo; + Info.cbSignature = sizeof(*pSignature->paNested[iNested].pContentInfo); + Info.pvExternalData = NULL; + Info.cbExternalData = 0; + rc = pfnCallback(&pModPe->Core, &Info, pErrInfo, pvUser); + } + } + rtldrPE_VerifySignatureDestroy(pModPe, 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 +} + + + +/** + * @interface_method_impl{RTLDROPS,pfnHashImage} + */ +static DECLCALLBACK(int) rtldrPE_HashImage(PRTLDRMODINTERNAL pMod, RTDIGESTTYPE enmDigest, uint8_t *pabHash, size_t cbHash) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + + /* + * Allocate a temporary memory buffer. + */ + uint32_t cbScratch = _16K; + void *pvScratch = RTMemTmpAlloc(cbScratch); + if (!pvScratch) + { + cbScratch = _4K; + pvScratch = RTMemTmpAlloc(cbScratch); + if (!pvScratch) + return VERR_NO_TMP_MEMORY; + } + + /* + * Do the hashing. + */ + RTLDRPEHASHCTXUNION HashCtx; + RTLDRPEHASHRESUNION HashRes; + int rc = rtldrPE_HashImageCommon(pModPe, pvScratch, cbScratch, enmDigest, &HashCtx, &HashRes, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Copy out the result. + */ + RT_NOREF(cbHash); /* verified by caller */ + switch (enmDigest) + { + case RTDIGESTTYPE_SHA512: memcpy(pabHash, HashRes.abSha512, sizeof(HashRes.abSha512)); break; + case RTDIGESTTYPE_SHA256: memcpy(pabHash, HashRes.abSha256, sizeof(HashRes.abSha256)); break; + case RTDIGESTTYPE_SHA1: memcpy(pabHash, HashRes.abSha1, sizeof(HashRes.abSha1)); break; + case RTDIGESTTYPE_MD5: memcpy(pabHash, HashRes.abMd5, sizeof(HashRes.abMd5)); break; + default: AssertFailedReturn(VERR_INTERNAL_ERROR_3); + } + } + return rc; +} + + +/** + * Binary searches the lookup table. + * + * @returns RVA of unwind info on success, UINT32_MAX on failure. + * @param paFunctions The table to lookup @a uRva in. + * @param iEnd Size of the table. + * @param uRva The RVA of the function we want. + */ +DECLINLINE(PCIMAGE_RUNTIME_FUNCTION_ENTRY) +rtldrPE_LookupRuntimeFunctionEntry(PCIMAGE_RUNTIME_FUNCTION_ENTRY paFunctions, size_t iEnd, uint32_t uRva) +{ + size_t iBegin = 0; + while (iBegin < iEnd) + { + size_t const i = iBegin + (iEnd - iBegin) / 2; + PCIMAGE_RUNTIME_FUNCTION_ENTRY pEntry = &paFunctions[i]; + if (uRva < pEntry->BeginAddress) + iEnd = i; + else if (uRva > pEntry->EndAddress) + iBegin = i + 1; + else + return pEntry; + } + return NULL; +} + + +/** + * Processes an IRET frame. + * + * @returns IPRT status code. + * @param pState The unwind state being worked. + * @param fErrCd Non-zero if there is an error code on the stack. + */ +static int rtldrPE_UnwindFrame_Amd64_IRet(PRTDBGUNWINDSTATE pState, uint8_t fErrCd) +{ + /* POP ErrCd (optional): */ + Assert(fErrCd <= 1); + int rcRet; + if (fErrCd) + { + pState->u.x86.uErrCd = 0; + pState->u.x86.Loaded.s.fErrCd = 1; + rcRet = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.uErrCd); + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + } + else + { + pState->u.x86.Loaded.s.fErrCd = 0; + rcRet = VINF_SUCCESS; + } + + /* Set return type and frame pointer. */ + pState->enmRetType = RTDBGRETURNTYPE_IRET64; + pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - /* pretend rbp is pushed on the stack */ 8; + pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS]; + + /* POP RIP: */ + int rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->uPc); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + + /* POP CS: */ + rc = RTDbgUnwindLoadStackU16(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auSegs[X86_SREG_CS]); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + + /* POP RFLAGS: */ + rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.uRFlags); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + + /* POP RSP, part 1: */ + uint64_t uNewRsp = (pState->u.x86.auRegs[X86_GREG_xSP] - 8) & ~(uint64_t)15; + rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &uNewRsp); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + + /* POP SS: */ + rc = RTDbgUnwindLoadStackU16(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auSegs[X86_SREG_SS]); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + + /* POP RSP, part 2: */ + pState->u.x86.auRegs[X86_GREG_xSP] = uNewRsp; + + /* Set loaded indicators: */ + pState->u.x86.Loaded.s.fRegs |= RT_BIT(X86_GREG_xSP); + pState->u.x86.Loaded.s.fSegs |= RT_BIT(X86_SREG_CS) | RT_BIT(X86_SREG_SS); + pState->u.x86.Loaded.s.fPc = 1; + pState->u.x86.Loaded.s.fFrameAddr = 1; + pState->u.x86.Loaded.s.fRFlags = 1; + return VINF_SUCCESS; +} + + +static int rtldrPE_UnwindFrame_Amd64(PRTLDRMODPE pThis, void const *pvBits, PRTDBGUNWINDSTATE pState, uint32_t uRvaPc, + PCIMAGE_RUNTIME_FUNCTION_ENTRY pEntry) +{ + /* Did we find any unwind information? */ + if (!pEntry) + return VERR_DBG_UNWIND_INFO_NOT_FOUND; + + /* + * Do the unwinding. + */ + IMAGE_RUNTIME_FUNCTION_ENTRY ChainedEntry; + unsigned iFrameReg = ~0U; + unsigned offFrameReg = 0; + + int fInEpilog = -1; /* -1: not-determined-assume-false; 0: false; 1: true. */ + uint8_t cbEpilog = 0; + uint8_t offEpilog = UINT8_MAX; + int rcRet = VINF_SUCCESS; + int rc; + for (unsigned cChainLoops = 0; ; cChainLoops++) + { + /* + * Get the info. + */ + union + { + uint32_t uRva; + uint8_t ab[ RT_OFFSETOF(IMAGE_UNWIND_INFO, aOpcodes) + + sizeof(IMAGE_UNWIND_CODE) * 256 + + sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY)]; + } uBuf; + rc = rtldrPEReadPartByRvaInfoBuf(pThis, pvBits, pEntry->UnwindInfoAddress, sizeof(uBuf), &uBuf); + if (RT_FAILURE(rc)) + return rc; + + /* + * Check the info. + */ + ASMCompilerBarrier(); /* we're aliasing */ + PCIMAGE_UNWIND_INFO pInfo = (PCIMAGE_UNWIND_INFO)&uBuf; + + if (pInfo->Version != 1 && pInfo->Version != 2) + return VERR_DBG_MALFORMED_UNWIND_INFO; + + /* + * Execute the opcodes. + */ + unsigned const cOpcodes = pInfo->CountOfCodes; + unsigned iOpcode = 0; + + /* + * Check for epilog opcodes at the start and see if we're in an epilog. + */ + if ( pInfo->Version >= 2 + && iOpcode < cOpcodes + && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG) + { + if (fInEpilog == -1) + { + cbEpilog = pInfo->aOpcodes[iOpcode].u.CodeOffset; + Assert(cbEpilog > 0); + + uint32_t uRvaEpilog = pEntry->EndAddress - cbEpilog; + iOpcode++; + if ( (pInfo->aOpcodes[iOpcode - 1].u.OpInfo & 1) + && uRvaPc >= uRvaEpilog) + { + offEpilog = uRvaPc - uRvaEpilog; + fInEpilog = 1; + } + else + { + fInEpilog = 0; + while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG) + { + uRvaEpilog = pEntry->EndAddress + - (pInfo->aOpcodes[iOpcode].u.CodeOffset + (pInfo->aOpcodes[iOpcode].u.OpInfo << 8)); + iOpcode++; + if (uRvaPc - uRvaEpilog < cbEpilog) + { + offEpilog = uRvaPc - uRvaEpilog; + fInEpilog = 1; + break; + } + } + } + } + while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.UnwindOp == IMAGE_AMD64_UWOP_EPILOG) + iOpcode++; + } + if (fInEpilog != 1) + { + /* + * Skip opcodes that doesn't apply to us if we're in the prolog. + */ + uint32_t offPc = uRvaPc - pEntry->BeginAddress; + if (offPc < pInfo->SizeOfProlog) + while (iOpcode < cOpcodes && pInfo->aOpcodes[iOpcode].u.CodeOffset > offPc) + iOpcode++; + + /* + * Execute the opcodes. + */ + if (pInfo->FrameRegister != 0) + { + iFrameReg = pInfo->FrameRegister; + offFrameReg = pInfo->FrameOffset * 16; + } + while (iOpcode < cOpcodes) + { + Assert(pInfo->aOpcodes[iOpcode].u.CodeOffset <= offPc); + uint8_t const uOpInfo = pInfo->aOpcodes[iOpcode].u.OpInfo; + uint8_t const uUnwindOp = pInfo->aOpcodes[iOpcode].u.UnwindOp; + switch (uUnwindOp) + { + case IMAGE_AMD64_UWOP_PUSH_NONVOL: + rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->u.x86.auRegs[uOpInfo]); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo); + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + iOpcode++; + break; + + case IMAGE_AMD64_UWOP_ALLOC_LARGE: + if (uOpInfo == 0) + { + iOpcode += 2; + AssertBreak(iOpcode <= cOpcodes); + pState->u.x86.auRegs[X86_GREG_xSP] += pInfo->aOpcodes[iOpcode - 1].FrameOffset * 8; + } + else + { + iOpcode += 3; + AssertBreak(iOpcode <= cOpcodes); + pState->u.x86.auRegs[X86_GREG_xSP] += RT_MAKE_U32(pInfo->aOpcodes[iOpcode - 2].FrameOffset, + pInfo->aOpcodes[iOpcode - 1].FrameOffset); + } + break; + + case IMAGE_AMD64_UWOP_ALLOC_SMALL: + AssertBreak(iOpcode <= cOpcodes); + pState->u.x86.auRegs[X86_GREG_xSP] += uOpInfo * 8 + 8; + iOpcode++; + break; + + case IMAGE_AMD64_UWOP_SET_FPREG: + iFrameReg = uOpInfo; + offFrameReg = pInfo->FrameOffset * 16; + pState->u.x86.auRegs[X86_GREG_xSP] = pState->u.x86.auRegs[iFrameReg] - offFrameReg; + iOpcode++; + break; + + case IMAGE_AMD64_UWOP_SAVE_NONVOL: + case IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR: + { + uint32_t off = 0; + iOpcode++; + if (iOpcode < cOpcodes) + { + off = pInfo->aOpcodes[iOpcode].FrameOffset; + iOpcode++; + if (uUnwindOp == IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR && iOpcode < cOpcodes) + { + off |= (uint32_t)pInfo->aOpcodes[iOpcode].FrameOffset << 16; + iOpcode++; + } + } + off *= 8; + rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP] + off, + &pState->u.x86.auRegs[uOpInfo]); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo); + break; + } + + case IMAGE_AMD64_UWOP_SAVE_XMM128: + iOpcode += 2; + break; + + case IMAGE_AMD64_UWOP_SAVE_XMM128_FAR: + iOpcode += 3; + break; + + case IMAGE_AMD64_UWOP_PUSH_MACHFRAME: + return rtldrPE_UnwindFrame_Amd64_IRet(pState, uOpInfo); + + case IMAGE_AMD64_UWOP_EPILOG: + iOpcode += 1; + break; + + case IMAGE_AMD64_UWOP_RESERVED_7: + AssertFailedReturn(VERR_DBG_MALFORMED_UNWIND_INFO); + + default: + AssertMsgFailedReturn(("%u\n", uUnwindOp), VERR_DBG_MALFORMED_UNWIND_INFO); + } + } + } + else + { + /* + * We're in the POP sequence of an epilog. The POP sequence should + * mirror the PUSH sequence exactly. + * + * Note! We should only end up here for the initial frame (just consider + * RSP, stack allocations, non-volatile register restores, ++). + */ + while (iOpcode < cOpcodes) + { + uint8_t const uOpInfo = pInfo->aOpcodes[iOpcode].u.OpInfo; + uint8_t const uUnwindOp = pInfo->aOpcodes[iOpcode].u.UnwindOp; + switch (uUnwindOp) + { + case IMAGE_AMD64_UWOP_PUSH_NONVOL: + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + if (offEpilog == 0) + { + rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], + &pState->u.x86.auRegs[uOpInfo]); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.Loaded.s.fRegs |= RT_BIT(uOpInfo); + } + else + { + /* Decrement offEpilog by estimated POP instruction length. */ + offEpilog -= 1; + if (offEpilog > 0 && uOpInfo >= 8) + offEpilog -= 1; + } + iOpcode++; + break; + + case IMAGE_AMD64_UWOP_PUSH_MACHFRAME: /* Must terminate an epilog, so always execute this. */ + return rtldrPE_UnwindFrame_Amd64_IRet(pState, uOpInfo); + + case IMAGE_AMD64_UWOP_ALLOC_SMALL: + case IMAGE_AMD64_UWOP_SET_FPREG: + case IMAGE_AMD64_UWOP_EPILOG: + iOpcode++; + break; + case IMAGE_AMD64_UWOP_SAVE_NONVOL: + case IMAGE_AMD64_UWOP_SAVE_XMM128: + iOpcode += 2; + break; + case IMAGE_AMD64_UWOP_ALLOC_LARGE: + case IMAGE_AMD64_UWOP_SAVE_NONVOL_FAR: + case IMAGE_AMD64_UWOP_SAVE_XMM128_FAR: + iOpcode += 3; + break; + + default: + AssertMsgFailedReturn(("%u\n", uUnwindOp), VERR_DBG_MALFORMED_UNWIND_INFO); + } + } + } + + /* + * Chained stuff? + */ + if (!(pInfo->Flags & IMAGE_UNW_FLAGS_CHAININFO)) + break; + ChainedEntry = *(PCIMAGE_RUNTIME_FUNCTION_ENTRY)&pInfo->aOpcodes[(cOpcodes + 1) & ~1]; + pEntry = &ChainedEntry; + AssertReturn(cChainLoops < 32, VERR_DBG_MALFORMED_UNWIND_INFO); + } + + /* + * RSP should now give us the return address, so perform a RET. + */ + pState->enmRetType = RTDBGRETURNTYPE_NEAR64; + + pState->u.x86.FrameAddr.off = pState->u.x86.auRegs[X86_GREG_xSP] - /* pretend rbp is pushed on the stack */ 8; + pState->u.x86.FrameAddr.sel = pState->u.x86.auSegs[X86_SREG_SS]; + pState->u.x86.Loaded.s.fFrameAddr = 1; + + rc = RTDbgUnwindLoadStackU64(pState, pState->u.x86.auRegs[X86_GREG_xSP], &pState->uPc); + if (RT_FAILURE(rc)) + rcRet = rc; + pState->u.x86.auRegs[X86_GREG_xSP] += 8; + pState->u.x86.Loaded.s.fPc = 1; + return rcRet; +} + + +/** + * @interface_method_impl{RTLDROPS,pfnUnwindFrame} + */ +static DECLCALLBACK(int) rtldrPE_UnwindFrame(PRTLDRMODINTERNAL pMod, void const *pvBits, + uint32_t iSeg, RTUINTPTR off, PRTDBGUNWINDSTATE pState) +{ + PRTLDRMODPE pThis = (PRTLDRMODPE)pMod; + + /* + * Translate the segment + offset into an RVA. + */ + RTLDRADDR uRvaPc = off; + if (iSeg != UINT32_MAX) + { + int rc = rtldrPE_SegOffsetToRva(pMod, iSeg, off, &uRvaPc); + if (RT_FAILURE(rc)) + return rc; + } + + /* + * Check for unwind info and match the architecture. + */ + if ( pThis->ExceptionDir.Size == 0 + || pThis->ExceptionDir.VirtualAddress < pThis->cbHeaders) + return VERR_DBG_NO_UNWIND_INFO; + if (pThis->Core.enmArch != pState->enmArch) + return VERR_DBG_UNWIND_INFO_NOT_FOUND; + + /* Currently only AMD64 unwinding is implemented, so head it off right away. */ + if (pThis->Core.enmArch != RTLDRARCH_AMD64) + return VERR_DBG_UNWIND_INFO_NOT_FOUND; + + /* + * Make the lookup table available to us. + */ + void const *pvTable = NULL; + uint32_t const cbTable = pThis->ExceptionDir.Size; + AssertReturn( cbTable < pThis->cbImage + && pThis->ExceptionDir.VirtualAddress < pThis->cbImage + && pThis->ExceptionDir.VirtualAddress + cbTable <= pThis->cbImage, VERR_INTERNAL_ERROR_3); + int rc = rtldrPEReadPartByRva(pThis, pvBits, pThis->ExceptionDir.VirtualAddress, pThis->ExceptionDir.Size, &pvTable); + if (RT_FAILURE(rc)) + return rc; + + /* + * The rest is architecture dependent. + * + * Note! On windows we try catch access violations so we can safely use + * this code on mapped images during assertions. + */ +#if defined(_MSC_VER) && defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3) + __try + { +#endif + switch (pThis->Core.enmArch) + { + case RTLDRARCH_AMD64: + rc = rtldrPE_UnwindFrame_Amd64(pThis, pvBits, pState, uRvaPc, + rtldrPE_LookupRuntimeFunctionEntry((PCIMAGE_RUNTIME_FUNCTION_ENTRY)pvTable, + cbTable / sizeof(IMAGE_RUNTIME_FUNCTION_ENTRY), + (uint32_t)uRvaPc)); + break; + + default: + rc = VERR_DBG_UNWIND_INFO_NOT_FOUND; + break; + } +#if defined(_MSC_VER) && defined(IN_RING3) && !defined(IN_SUP_HARDENED_R3) + } + __except (1 /*EXCEPTION_EXECUTE_HANDLER*/) + { + rc = VERR_DBG_UNWIND_INFO_NOT_FOUND; + } +#endif + rtldrPEFreePart(pThis, pvBits, pvTable); + return rc; +} + + +/** @interface_method_impl{RTLDROPS,pfnDone} */ +static DECLCALLBACK(int) rtldrPEDone(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + if (pModPe->pvBits) + { + RTMemFree(pModPe->pvBits); + pModPe->pvBits = NULL; + } + return VINF_SUCCESS; +} + + +/** @interface_method_impl{RTLDROPS,pfnClose} */ +static DECLCALLBACK(int) rtldrPEClose(PRTLDRMODINTERNAL pMod) +{ + PRTLDRMODPE pModPe = (PRTLDRMODPE)pMod; + if (pModPe->paSections) + { + RTMemFree(pModPe->paSections); + pModPe->paSections = NULL; + } + if (pModPe->pvBits) + { + RTMemFree(pModPe->pvBits); + pModPe->pvBits = NULL; + } + return VINF_SUCCESS; +} + + +/** + * Operations for a 32-bit PE module. + */ +static const RTLDROPSPE s_rtldrPE32Ops = +{ + { + "pe32", + rtldrPEClose, + NULL, + rtldrPEDone, + rtldrPEEnumSymbols, + /* ext */ + rtldrPEGetImageSize, + rtldrPEGetBits, + rtldrPERelocate, + rtldrPEGetSymbolEx, + rtldrPE_QueryForwarderInfo, + rtldrPE_EnumDbgInfo, + rtldrPE_EnumSegments, + rtldrPE_LinkAddressToSegOffset, + rtldrPE_LinkAddressToRva, + rtldrPE_SegOffsetToRva, + rtldrPE_RvaToSegOffset, + NULL, + rtldrPE_QueryProp, + rtldrPE_VerifySignature, + rtldrPE_HashImage, + NULL /*pfnUnwindFrame*/, + 42 + }, + rtldrPEResolveImports32, + 42 +}; + + +/** + * Operations for a 64-bit PE module. + */ +static const RTLDROPSPE s_rtldrPE64Ops = +{ + { + "pe64", + rtldrPEClose, + NULL, + rtldrPEDone, + rtldrPEEnumSymbols, + /* ext */ + rtldrPEGetImageSize, + rtldrPEGetBits, + rtldrPERelocate, + rtldrPEGetSymbolEx, + rtldrPE_QueryForwarderInfo, + rtldrPE_EnumDbgInfo, + rtldrPE_EnumSegments, + rtldrPE_LinkAddressToSegOffset, + rtldrPE_LinkAddressToRva, + rtldrPE_SegOffsetToRva, + rtldrPE_RvaToSegOffset, + NULL, + rtldrPE_QueryProp, + rtldrPE_VerifySignature, + rtldrPE_HashImage, + rtldrPE_UnwindFrame, + 42 + }, + rtldrPEResolveImports64, + 42 +}; + + +/** + * Converts the optional header from 32 bit to 64 bit. + * This is a rather simple task, if you start from the right end. + * + * @param pOptHdr On input this is a PIMAGE_OPTIONAL_HEADER32. + * On output this will be a PIMAGE_OPTIONAL_HEADER64. + */ +static void rtldrPEConvert32BitOptionalHeaderTo64Bit(PIMAGE_OPTIONAL_HEADER64 pOptHdr) +{ + /* + * volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff. + */ + IMAGE_OPTIONAL_HEADER32 volatile *pOptHdr32 = (IMAGE_OPTIONAL_HEADER32 volatile *)pOptHdr; + IMAGE_OPTIONAL_HEADER64 volatile *pOptHdr64 = pOptHdr; + + /* from LoaderFlags and out the difference is 4 * 32-bits. */ + Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, LoaderFlags) + 16 == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, LoaderFlags)); + Assert( RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]) + 16 + == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES])); + uint32_t volatile *pu32Dst = (uint32_t *)&pOptHdr64->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1; + const uint32_t volatile *pu32Src = (uint32_t *)&pOptHdr32->DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES] - 1; + const uint32_t volatile *pu32SrcLast = (uint32_t *)&pOptHdr32->LoaderFlags; + while (pu32Src >= pu32SrcLast) + *pu32Dst-- = *pu32Src--; + + /* the previous 4 fields are 32/64 and needs special attention. */ + pOptHdr64->SizeOfHeapCommit = pOptHdr32->SizeOfHeapCommit; + pOptHdr64->SizeOfHeapReserve = pOptHdr32->SizeOfHeapReserve; + pOptHdr64->SizeOfStackCommit = pOptHdr32->SizeOfStackCommit; + uint32_t u32SizeOfStackReserve = pOptHdr32->SizeOfStackReserve; + pOptHdr64->SizeOfStackReserve = u32SizeOfStackReserve; + + /* The rest matches except for BaseOfData which has been merged into ImageBase in the 64-bit version.. + * Thus, ImageBase needs some special treatment. It will probably work fine assigning one to the + * other since this is all declared volatile, but taking now chances, we'll use a temp variable. + */ + Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, SizeOfStackReserve) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, SizeOfStackReserve)); + Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, BaseOfData) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, ImageBase)); + Assert(RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER32, SectionAlignment) == RT_UOFFSETOF(IMAGE_OPTIONAL_HEADER64, SectionAlignment)); + uint32_t u32ImageBase = pOptHdr32->ImageBase; + pOptHdr64->ImageBase = u32ImageBase; +} + + +/** + * Converts the load config directory from 32 bit to 64 bit. + * This is a rather simple task, if you start from the right end. + * + * @param pLoadCfg On input this is a PIMAGE_LOAD_CONFIG_DIRECTORY32. + * On output this will be a PIMAGE_LOAD_CONFIG_DIRECTORY64. + */ +static void rtldrPEConvert32BitLoadConfigTo64Bit(PIMAGE_LOAD_CONFIG_DIRECTORY64 pLoadCfg) +{ + /* + * volatile everywhere! Trying to prevent the compiler being a smarta$$ and reorder stuff. + */ + IMAGE_LOAD_CONFIG_DIRECTORY32_V13 volatile *pLoadCfg32 = (IMAGE_LOAD_CONFIG_DIRECTORY32_V13 volatile *)pLoadCfg; + IMAGE_LOAD_CONFIG_DIRECTORY64_V13 volatile *pLoadCfg64 = pLoadCfg; + + pLoadCfg64->CastGuardOsDeterminedFailureMode = pLoadCfg32->CastGuardOsDeterminedFailureMode; + pLoadCfg64->GuardXFGTableDispatchFunctionPointer = pLoadCfg32->GuardXFGTableDispatchFunctionPointer; + pLoadCfg64->GuardXFGDispatchFunctionPointer = pLoadCfg32->GuardXFGDispatchFunctionPointer; + pLoadCfg64->GuardXFGCheckFunctionPointer = pLoadCfg32->GuardXFGCheckFunctionPointer; + pLoadCfg64->GuardEHContinuationCount = pLoadCfg32->GuardEHContinuationCount; + pLoadCfg64->GuardEHContinuationTable = pLoadCfg32->GuardEHContinuationTable; + pLoadCfg64->VolatileMetadataPointer = pLoadCfg32->VolatileMetadataPointer; + pLoadCfg64->EnclaveConfigurationPointer = pLoadCfg32->EnclaveConfigurationPointer; + pLoadCfg64->Reserved3 = pLoadCfg32->Reserved3; + pLoadCfg64->HotPatchTableOffset = pLoadCfg32->HotPatchTableOffset; + pLoadCfg64->GuardRFVerifyStackPointerFunctionPointer = pLoadCfg32->GuardRFVerifyStackPointerFunctionPointer; + pLoadCfg64->Reserved2 = pLoadCfg32->Reserved2; + pLoadCfg64->DynamicValueRelocTableSection = pLoadCfg32->DynamicValueRelocTableSection; + pLoadCfg64->DynamicValueRelocTableOffset = pLoadCfg32->DynamicValueRelocTableOffset; + pLoadCfg64->GuardRFFailureRoutineFunctionPointer = pLoadCfg32->GuardRFFailureRoutineFunctionPointer; + pLoadCfg64->GuardRFFailureRoutine = pLoadCfg32->GuardRFFailureRoutine; + pLoadCfg64->CHPEMetadataPointer = pLoadCfg32->CHPEMetadataPointer; + pLoadCfg64->DynamicValueRelocTable = pLoadCfg32->DynamicValueRelocTable; + pLoadCfg64->GuardLongJumpTargetCount = pLoadCfg32->GuardLongJumpTargetCount; + pLoadCfg64->GuardLongJumpTargetTable = pLoadCfg32->GuardLongJumpTargetTable; + pLoadCfg64->GuardAddressTakenIatEntryCount = pLoadCfg32->GuardAddressTakenIatEntryCount; + pLoadCfg64->GuardAddressTakenIatEntryTable = pLoadCfg32->GuardAddressTakenIatEntryTable; + pLoadCfg64->CodeIntegrity.Reserved = pLoadCfg32->CodeIntegrity.Reserved; + pLoadCfg64->CodeIntegrity.CatalogOffset = pLoadCfg32->CodeIntegrity.CatalogOffset; + pLoadCfg64->CodeIntegrity.Catalog = pLoadCfg32->CodeIntegrity.Catalog; + pLoadCfg64->CodeIntegrity.Flags = pLoadCfg32->CodeIntegrity.Flags; + pLoadCfg64->GuardFlags = pLoadCfg32->GuardFlags; + pLoadCfg64->GuardCFFunctionCount = pLoadCfg32->GuardCFFunctionCount; + pLoadCfg64->GuardCFFunctionTable = pLoadCfg32->GuardCFFunctionTable; + pLoadCfg64->GuardCFDispatchFunctionPointer = pLoadCfg32->GuardCFDispatchFunctionPointer; + pLoadCfg64->GuardCFCCheckFunctionPointer = pLoadCfg32->GuardCFCCheckFunctionPointer; + pLoadCfg64->SEHandlerCount = pLoadCfg32->SEHandlerCount; + pLoadCfg64->SEHandlerTable = pLoadCfg32->SEHandlerTable; + pLoadCfg64->SecurityCookie = pLoadCfg32->SecurityCookie; + pLoadCfg64->EditList = pLoadCfg32->EditList; + pLoadCfg64->DependentLoadFlags = pLoadCfg32->DependentLoadFlags; + pLoadCfg64->CSDVersion = pLoadCfg32->CSDVersion; + pLoadCfg64->ProcessHeapFlags = pLoadCfg32->ProcessHeapFlags; /* switched place with ProcessAffinityMask, but we're more than 16 byte off by now so it doesn't matter. */ + pLoadCfg64->ProcessAffinityMask = pLoadCfg32->ProcessAffinityMask; + pLoadCfg64->VirtualMemoryThreshold = pLoadCfg32->VirtualMemoryThreshold; + pLoadCfg64->MaximumAllocationSize = pLoadCfg32->MaximumAllocationSize; + pLoadCfg64->LockPrefixTable = pLoadCfg32->LockPrefixTable; + pLoadCfg64->DeCommitTotalFreeThreshold = pLoadCfg32->DeCommitTotalFreeThreshold; + uint32_t u32DeCommitFreeBlockThreshold = pLoadCfg32->DeCommitFreeBlockThreshold; + pLoadCfg64->DeCommitFreeBlockThreshold = u32DeCommitFreeBlockThreshold; + /* the rest is equal. */ + Assert( RT_UOFFSETOF(IMAGE_LOAD_CONFIG_DIRECTORY32, DeCommitFreeBlockThreshold) + == RT_UOFFSETOF(IMAGE_LOAD_CONFIG_DIRECTORY64, DeCommitFreeBlockThreshold)); +} + + +/** + * Translate the PE/COFF machine name to a string. + * + * @returns Name string (read-only). + * @param uMachine The PE/COFF machine. + */ +static const char *rtldrPEGetArchName(uint16_t uMachine) +{ + switch (uMachine) + { + case IMAGE_FILE_MACHINE_I386: return "X86_32"; + case IMAGE_FILE_MACHINE_AMD64: return "AMD64"; + + case IMAGE_FILE_MACHINE_UNKNOWN: return "UNKNOWN"; + case IMAGE_FILE_MACHINE_AM33: return "AM33"; + case IMAGE_FILE_MACHINE_ARM: return "ARM"; + case IMAGE_FILE_MACHINE_THUMB: return "THUMB"; + case IMAGE_FILE_MACHINE_ARMNT: return "ARMNT"; + case IMAGE_FILE_MACHINE_ARM64: return "ARM64"; + case IMAGE_FILE_MACHINE_EBC: return "EBC"; + case IMAGE_FILE_MACHINE_IA64: return "IA64"; + case IMAGE_FILE_MACHINE_M32R: return "M32R"; + case IMAGE_FILE_MACHINE_MIPS16: return "MIPS16"; + case IMAGE_FILE_MACHINE_MIPSFPU: return "MIPSFPU"; + case IMAGE_FILE_MACHINE_MIPSFPU16: return "MIPSFPU16"; + case IMAGE_FILE_MACHINE_WCEMIPSV2: return "WCEMIPSV2"; + case IMAGE_FILE_MACHINE_POWERPC: return "POWERPC"; + case IMAGE_FILE_MACHINE_POWERPCFP: return "POWERPCFP"; + case IMAGE_FILE_MACHINE_R4000: return "R4000"; + case IMAGE_FILE_MACHINE_SH3: return "SH3"; + case IMAGE_FILE_MACHINE_SH3DSP: return "SH3DSP"; + case IMAGE_FILE_MACHINE_SH4: return "SH4"; + case IMAGE_FILE_MACHINE_SH5: return "SH5"; + default: return "UnknownMachine"; + } +} + + +/** + * Validates the file header. + * + * @returns iprt status code. + * @param pFileHdr Pointer to the file header that needs validating. + * @param fFlags Valid RTLDR_O_XXX combination. + * @param pszLogName The log name to prefix the errors with. + * @param penmArch Where to store the CPU architecture. + * @param pErrInfo Where to return additional error information. + */ +static int rtldrPEValidateFileHeader(PIMAGE_FILE_HEADER pFileHdr, uint32_t fFlags, const char *pszLogName, + PRTLDRARCH penmArch, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(pszLogName); + + size_t cbOptionalHeader; + switch (pFileHdr->Machine) + { + case IMAGE_FILE_MACHINE_I386: + cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32); + *penmArch = RTLDRARCH_X86_32; + break; + case IMAGE_FILE_MACHINE_AMD64: + cbOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER64); + *penmArch = RTLDRARCH_AMD64; + break; + + default: + Log(("rtldrPEOpen: %s: Unsupported Machine=%#x\n", pszLogName, pFileHdr->Machine)); + *penmArch = RTLDRARCH_INVALID; + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Unsupported Machine=%#x", pFileHdr->Machine); + } + if (pFileHdr->SizeOfOptionalHeader != cbOptionalHeader) + { + Log(("rtldrPEOpen: %s: SizeOfOptionalHeader=%#x expected %#x\n", pszLogName, pFileHdr->SizeOfOptionalHeader, cbOptionalHeader)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfOptionalHeader=%#x expected %#x", + pFileHdr->SizeOfOptionalHeader, cbOptionalHeader); + } + /* This restriction needs to be implemented elsewhere. */ + if ( (pFileHdr->Characteristics & IMAGE_FILE_RELOCS_STRIPPED) + && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))) + { + Log(("rtldrPEOpen: %s: IMAGE_FILE_RELOCS_STRIPPED\n", pszLogName)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "IMAGE_FILE_RELOCS_STRIPPED"); + } + if (pFileHdr->NumberOfSections > 42) + { + Log(("rtldrPEOpen: %s: NumberOfSections=%d - our limit is 42, please raise it if the binary makes sense.(!!!)\n", + pszLogName, pFileHdr->NumberOfSections)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "NumberOfSections=%d, implementation max is 42", pFileHdr->NumberOfSections); + } + if (pFileHdr->NumberOfSections < 1) + { + Log(("rtldrPEOpen: %s: NumberOfSections=%d - we can't have an image without sections (!!!)\n", + pszLogName, pFileHdr->NumberOfSections)); + return RTERRINFO_LOG_SET(pErrInfo, VERR_BAD_EXE_FORMAT, "Image has no sections"); + } + return VINF_SUCCESS; +} + + +/** + * Validates the optional header (64/32-bit) + * + * @returns iprt status code. + * @param pOptHdr Pointer to the optional header which needs validation. + * @param pszLogName The log name to prefix the errors with. + * @param offNtHdrs The offset of the NT headers from the start of the file. + * @param pFileHdr Pointer to the file header (valid). + * @param cbRawImage The raw image size. + * @param fFlags Loader flags, RTLDR_O_XXX. + * @param pErrInfo Where to return additional error information. + */ +static int rtldrPEValidateOptionalHeader(const IMAGE_OPTIONAL_HEADER64 *pOptHdr, const char *pszLogName, RTFOFF offNtHdrs, + const IMAGE_FILE_HEADER *pFileHdr, uint64_t cbRawImage, uint32_t fFlags, PRTERRINFO pErrInfo) +{ + RT_NOREF_PV(pszLogName); + + const uint16_t CorrectMagic = pFileHdr->SizeOfOptionalHeader == sizeof(IMAGE_OPTIONAL_HEADER32) + ? IMAGE_NT_OPTIONAL_HDR32_MAGIC : IMAGE_NT_OPTIONAL_HDR64_MAGIC; + if (pOptHdr->Magic != CorrectMagic) + { + Log(("rtldrPEOpen: %s: Magic=%#x - expected %#x!!!\n", pszLogName, pOptHdr->Magic, CorrectMagic)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Magic=%#x, expected %#x", pOptHdr->Magic, CorrectMagic); + } + const uint32_t cbImage = pOptHdr->SizeOfImage; + if (cbImage > _1G) + { + Log(("rtldrPEOpen: %s: SizeOfImage=%#x - Our limit is 1GB (%#x)!!!\n", pszLogName, cbImage, _1G)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfImage=%#x - Our limit is 1GB (%#x)", cbImage, _1G); + } + const uint32_t cbMinImageSize = pFileHdr->SizeOfOptionalHeader + sizeof(*pFileHdr) + 4 + (uint32_t)offNtHdrs; + if (cbImage < cbMinImageSize) + { + Log(("rtldrPEOpen: %s: SizeOfImage=%#x to small, minimum %#x!!!\n", pszLogName, cbImage, cbMinImageSize)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfImage=%#x to small, minimum %#x", cbImage, cbMinImageSize); + } + if (pOptHdr->AddressOfEntryPoint >= cbImage) + { + Log(("rtldrPEOpen: %s: AddressOfEntryPoint=%#x - beyond image size (%#x)!!!\n", + pszLogName, pOptHdr->AddressOfEntryPoint, cbImage)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "AddressOfEntryPoint=%#x - beyond image size (%#x)", pOptHdr->AddressOfEntryPoint, cbImage); + } + if (pOptHdr->BaseOfCode >= cbImage) + { + Log(("rtldrPEOpen: %s: BaseOfCode=%#x - beyond image size (%#x)!!!\n", + pszLogName, pOptHdr->BaseOfCode, cbImage)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "BaseOfCode=%#x - beyond image size (%#x)", pOptHdr->BaseOfCode, cbImage); + } +#if 0/* only in 32-bit header */ + if (pOptHdr->BaseOfData >= cbImage) + { + Log(("rtldrPEOpen: %s: BaseOfData=%#x - beyond image size (%#x)!!!\n", + pszLogName, pOptHdr->BaseOfData, cbImage)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "BaseOfData=%#x - beyond image size (%#x)", pOptHdr->BaseOfData, cbImage); + } +#endif + if (!RT_IS_POWER_OF_TWO(pOptHdr->SectionAlignment)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "SectionAlignment=%#x - not a power of two", pOptHdr->SectionAlignment); + if (pOptHdr->SectionAlignment < 16 || pOptHdr->SectionAlignment > _128K) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "SectionAlignment=%#x - unsupported value, not between 16 and 128KB", pOptHdr->SectionAlignment); + if (pOptHdr->SizeOfHeaders >= cbImage) + { + Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - beyond image size (%#x)!!!\n", + pszLogName, pOptHdr->SizeOfHeaders, cbImage)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, + "SizeOfHeaders=%#x - beyond image size (%#x)", pOptHdr->SizeOfHeaders, cbImage); + } + /* don't know how to do the checksum, so ignore it. */ + if (pOptHdr->Subsystem == IMAGE_SUBSYSTEM_UNKNOWN) + { + Log(("rtldrPEOpen: %s: Subsystem=%#x (unknown)!!!\n", pszLogName, pOptHdr->Subsystem)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "Subsystem=%#x (unknown)", pOptHdr->Subsystem); + } + if (pOptHdr->SizeOfHeaders < cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER)) + { + Log(("rtldrPEOpen: %s: SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx!!!\n", + pszLogName, pOptHdr->SizeOfHeaders, + cbMinImageSize, pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER), + cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER))); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfHeaders=%#x - cbMinImageSize %#x + sections %#x = %#llx", + pOptHdr->SizeOfHeaders, cbMinImageSize, + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER), + cbMinImageSize + pFileHdr->NumberOfSections * sizeof(IMAGE_SECTION_HEADER) ); + } + if (pOptHdr->SizeOfStackReserve < pOptHdr->SizeOfStackCommit) + { + Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n", + pszLogName, pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfStackReserve %#x < SizeOfStackCommit %#x", + pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit); + } + if (pOptHdr->SizeOfHeapReserve < pOptHdr->SizeOfHeapCommit) + { + Log(("rtldrPEOpen: %s: SizeOfStackReserve %#x < SizeOfStackCommit %#x!!!\n", + pszLogName, pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "SizeOfStackReserve %#x < SizeOfStackCommit %#x\n", + pOptHdr->SizeOfStackReserve, pOptHdr->SizeOfStackCommit); + } + + /* DataDirectory */ + if (pOptHdr->NumberOfRvaAndSizes != RT_ELEMENTS(pOptHdr->DataDirectory)) + { + Log(("rtldrPEOpen: %s: NumberOfRvaAndSizes=%d!!!\n", pszLogName, pOptHdr->NumberOfRvaAndSizes)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "NumberOfRvaAndSizes=%d, expected %d", + pOptHdr->NumberOfRvaAndSizes, RT_ELEMENTS(pOptHdr->DataDirectory)); + } + for (unsigned i = 0; i < RT_ELEMENTS(pOptHdr->DataDirectory); i++) + { + IMAGE_DATA_DIRECTORY const *pDir = &pOptHdr->DataDirectory[i]; + if (!pDir->Size) + continue; + size_t cb = cbImage; + switch (i) + { + case IMAGE_DIRECTORY_ENTRY_EXPORT: // 0 + case IMAGE_DIRECTORY_ENTRY_IMPORT: // 1 + case IMAGE_DIRECTORY_ENTRY_RESOURCE: // 2 + case IMAGE_DIRECTORY_ENTRY_EXCEPTION: // 3 + case IMAGE_DIRECTORY_ENTRY_BASERELOC: // 5 + case IMAGE_DIRECTORY_ENTRY_DEBUG: // 6 + case IMAGE_DIRECTORY_ENTRY_COPYRIGHT: // 7 + case IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT: // 11 + case IMAGE_DIRECTORY_ENTRY_IAT: // 12 /* Import Address Table */ + break; + case IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG: // 10 - need to check for lock prefixes. + /* Delay inspection after section table is validated. */ + break; + + case IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT: // 13 + if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) + break; + Log(("rtldrPEOpen: %s: dir no. %d (DELAY_IMPORT) VirtualAddress=%#x Size=%#x is not supported!!!\n", + pszLogName, i, pDir->VirtualAddress, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_DELAY_IMPORT, + "DELAY_IMPORT VirtualAddress=%#x Size=%#x: not supported", pDir->VirtualAddress, pDir->Size); + + case IMAGE_DIRECTORY_ENTRY_SECURITY: // 4 + /* The VirtualAddress is a PointerToRawData. */ + cb = (size_t)cbRawImage; Assert((uint64_t)cb == cbRawImage); + Log(("rtldrPEOpen: %s: dir no. %d (SECURITY) VirtualAddress=%#x Size=%#x\n", pszLogName, i, pDir->VirtualAddress, pDir->Size)); + if (pDir->Size < sizeof(WIN_CERTIFICATE)) + { + Log(("rtldrPEOpen: %s: Security directory #%u is too small: %#x bytes\n", pszLogName, i, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED, + "Security directory is too small: %#x bytes", pDir->Size); + } + if (pDir->Size >= RTLDRMODPE_MAX_SECURITY_DIR_SIZE) + { + Log(("rtldrPEOpen: %s: Security directory #%u is too large: %#x bytes\n", pszLogName, i, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED, + "Security directory is too large: %#x bytes", pDir->Size); + } + if (pDir->VirtualAddress & 7) + { + Log(("rtldrPEOpen: %s: Security directory #%u is misaligned: %#x\n", pszLogName, i, pDir->VirtualAddress)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_CERT_MALFORMED, + "Security directory is misaligned: %#x", pDir->VirtualAddress); + } + /* When using the in-memory reader with a debugger, we may get + into trouble here since we might not have access to the whole + physical file. So skip the tests below. Makes VBoxGuest.sys + load and check out just fine, for instance. */ + if (fFlags & RTLDR_O_FOR_DEBUG) + continue; + break; + + case IMAGE_DIRECTORY_ENTRY_GLOBALPTR: // 8 /* (MIPS GP) */ + Log(("rtldrPEOpen: %s: dir no. %d (GLOBALPTR) VirtualAddress=%#x Size=%#x is not supported!!!\n", + pszLogName, i, pDir->VirtualAddress, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_GLOBALPTR, "GLOBALPTR VirtualAddress=%#x Size=%#x: not supported", + pDir->VirtualAddress, pDir->Size); + + case IMAGE_DIRECTORY_ENTRY_TLS: // 9 + if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) + break; + Log(("rtldrPEOpen: %s: dir no. %d (TLS) VirtualAddress=%#x Size=%#x is not supported!!!\n", + pszLogName, i, pDir->VirtualAddress, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_TLS, "TLS VirtualAddress=%#x Size=%#x: not supported", + pDir->VirtualAddress, pDir->Size); + + case IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR:// 14 + if (fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) + break; + Log(("rtldrPEOpen: %s: dir no. %d (COM_DESCRIPTOR) VirtualAddress=%#x Size=%#x is not supported!!!\n", + pszLogName, i, pDir->VirtualAddress, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDRPE_COM_DESCRIPTOR, + "COM_DESCRIPTOR VirtualAddress=%#x Size=%#x: not supported", + pDir->VirtualAddress, pDir->Size); + + default: + Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x Size=%#x is not supported!!!\n", + pszLogName, i, pDir->VirtualAddress, pDir->Size)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d VirtualAddress=%#x Size=%#x is not supported", + i, pDir->VirtualAddress, pDir->Size); + } + if (pDir->VirtualAddress >= cb) + { + Log(("rtldrPEOpen: %s: dir no. %d VirtualAddress=%#x is invalid (limit %#x)!!!\n", + pszLogName, i, pDir->VirtualAddress, cb)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d VirtualAddress=%#x is invalid (limit %#x)", + i, pDir->VirtualAddress, cb); + } + if (pDir->Size > cb - pDir->VirtualAddress) + { + Log(("rtldrPEOpen: %s: dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)!!!\n", + pszLogName, i, pDir->Size, pDir->VirtualAddress, cb)); + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_BAD_EXE_FORMAT, "dir no. %d Size=%#x is invalid (rva=%#x, limit=%#x)", + i, pDir->Size, pDir->VirtualAddress, cb); + } + } + return VINF_SUCCESS; +} + + +/** + * Validates and touch up the section headers. + * + * The touching up is restricted to setting the VirtualSize field for old-style + * linkers that sets it to zero. + * + * @returns iprt status code. + * @param paSections Pointer to the array of sections that is to be validated. + * @param cSections Number of sections in that array. + * @param pszLogName The log name to prefix the errors with. + * @param pOptHdr Pointer to the optional header (valid). + * @param cbRawImage The raw image size. + * @param fFlags Loader flags, RTLDR_O_XXX. + * @param fNoCode Verify that the image contains no code. + */ +static int rtldrPEValidateAndTouchUpSectionHeaders(IMAGE_SECTION_HEADER *paSections, unsigned cSections, const char *pszLogName, + const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint64_t cbRawImage, uint32_t fFlags, + bool fNoCode) +{ + RT_NOREF_PV(pszLogName); + + /* + * Do a quick pass to detect linker setting VirtualSize to zero. + */ + bool fFixupVirtualSize = true; + IMAGE_SECTION_HEADER *pSH = &paSections[0]; + for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++) + if ( pSH->Misc.VirtualSize != 0 + && !(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD)) + { + fFixupVirtualSize = false; + break; + } + + /* + * Actual pass. + */ + const uint32_t cbImage = pOptHdr->SizeOfImage; + uint32_t uRvaPrev = pOptHdr->SizeOfHeaders; + pSH = &paSections[0]; + Log3(("RTLdrPE: Section Headers:\n")); + for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++) + { + const unsigned iSH = (unsigned)(pSH - &paSections[0]); NOREF(iSH); + Log3(("RTLdrPE: #%d '%-8.8s' Characteristics: %08RX32\n" + "RTLdrPE: VirtAddr: %08RX32 VirtSize: %08RX32\n" + "RTLdrPE: FileOff: %08RX32 FileSize: %08RX32\n" + "RTLdrPE: RelocOff: %08RX32 #Relocs: %08RX32\n" + "RTLdrPE: LineOff: %08RX32 #Lines: %08RX32\n", + iSH, pSH->Name, pSH->Characteristics, + pSH->VirtualAddress, pSH->Misc.VirtualSize, + pSH->PointerToRawData, pSH->SizeOfRawData, + pSH->PointerToRelocations, pSH->NumberOfRelocations, + pSH->PointerToLinenumbers, pSH->NumberOfLinenumbers)); + + AssertCompile(IMAGE_SCN_MEM_16BIT == IMAGE_SCN_MEM_PURGEABLE); + if ( ( pSH->Characteristics & (IMAGE_SCN_MEM_PURGEABLE | IMAGE_SCN_MEM_PRELOAD | IMAGE_SCN_MEM_FARDATA) ) + && !(fFlags & RTLDR_O_FOR_DEBUG)) /* purgable/16-bit seen on w2ksp0 hal.dll, ignore the bunch. */ + { + Log(("rtldrPEOpen: %s: Unsupported section flag(s) %#x section #%d '%.*s'!!!\n", + pszLogName, pSH->Characteristics, iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } + + if ( pSH->PointerToRawData > cbRawImage /// @todo pSH->PointerToRawData >= cbRawImage ? + || pSH->SizeOfRawData > cbRawImage + || pSH->PointerToRawData + pSH->SizeOfRawData > cbRawImage) + { + Log(("rtldrPEOpen: %s: PointerToRawData=%#x SizeOfRawData=%#x - beyond end of file (%#llx) - section #%d '%.*s'!!!\n", + pszLogName, pSH->PointerToRawData, pSH->SizeOfRawData, cbRawImage, + iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } + + if (pSH->PointerToRawData & (pOptHdr->FileAlignment - 1)) //ASSUMES power of 2 alignment. + { + Log(("rtldrPEOpen: %s: PointerToRawData=%#x misaligned (%#x) - section #%d '%.*s'!!!\n", + pszLogName, pSH->PointerToRawData, pOptHdr->FileAlignment, iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } + + if (!(pSH->Characteristics & IMAGE_SCN_TYPE_NOLOAD)) /* binutils uses this for '.stab' even if it's reserved/obsoleted by MS. */ + { + /* Calc VirtualSize if necessary. This is for internal reasons. */ + if ( pSH->Misc.VirtualSize == 0 + && fFixupVirtualSize) + { + pSH->Misc.VirtualSize = cbImage - RT_MIN(pSH->VirtualAddress, cbImage); + for (uint32_t i = 1; i < cSHdrsLeft; i++) + if ( !(pSH[i].Characteristics & IMAGE_SCN_TYPE_NOLOAD) + && pSH[i].VirtualAddress >= pSH->VirtualAddress) + { + pSH->Misc.VirtualSize = RT_MIN(pSH[i].VirtualAddress - pSH->VirtualAddress, pSH->Misc.VirtualSize); + break; + } + } + + if (pSH->Misc.VirtualSize > 0) + { + if (pSH->VirtualAddress < uRvaPrev) + { + Log(("rtldrPEOpen: %s: Overlaps previous section or sections aren't in ascending order, VirtualAddress=%#x uRvaPrev=%#x - section #%d '%.*s'!!!\n", + pszLogName, pSH->VirtualAddress, uRvaPrev, iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } + if (pSH->VirtualAddress > cbImage) + { + Log(("rtldrPEOpen: %s: VirtualAddress=%#x - beyond image size (%#x) - section #%d '%.*s'!!!\n", + pszLogName, pSH->VirtualAddress, cbImage, iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } + + if (pSH->VirtualAddress & (pOptHdr->SectionAlignment - 1)) //ASSUMES power of 2 alignment. + { + Log(("rtldrPEOpen: %s: VirtualAddress=%#x misaligned (%#x) - section #%d '%.*s'!!!\n", + pszLogName, pSH->VirtualAddress, pOptHdr->SectionAlignment, iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } + +#ifdef PE_FILE_OFFSET_EQUALS_RVA + /* Our loader code assume rva matches the file offset. */ + if ( pSH->SizeOfRawData + && pSH->PointerToRawData != pSH->VirtualAddress) + { + Log(("rtldrPEOpen: %s: ASSUMPTION FAILED: file offset %#x != RVA %#x - section #%d '%.*s'!!!\n", + pszLogName, pSH->PointerToRawData, pSH->VirtualAddress, iSH, sizeof(pSH->Name), pSH->Name)); + return VERR_BAD_EXE_FORMAT; + } +#endif + + uRvaPrev = pSH->VirtualAddress + pSH->Misc.VirtualSize; + } + } + + /* ignore the relocations and linenumbers. */ + } + + /* + * Do a separate run if we need to validate the no-code claim from the + * optional header. + */ + if (fNoCode) + { + pSH = &paSections[0]; + for (unsigned cSHdrsLeft = cSections; cSHdrsLeft > 0; cSHdrsLeft--, pSH++) + if (pSH->Characteristics & (IMAGE_SCN_CNT_CODE | IMAGE_SCN_MEM_EXECUTE)) + return VERR_LDR_ARCH_MISMATCH; + } + + + /** @todo r=bird: more sanity checks! */ + return VINF_SUCCESS; +} + + +/** + * Reads image data by RVA using the section headers. + * + * @returns iprt status code. + * @param pModPe The PE module instance. + * @param pvBuf Where to store the bits. + * @param cb Number of bytes to tread. + * @param RVA Where to read from. + */ +static int rtldrPEReadRVA(PRTLDRMODPE pModPe, void *pvBuf, uint32_t cb, uint32_t RVA) +{ + const IMAGE_SECTION_HEADER *pSH = pModPe->paSections; + PRTLDRREADER pReader = pModPe->Core.pReader; + uint32_t cbRead; + int rc; + + /* + * Is it the headers, i.e. prior to the first section. + */ + if (RVA < pModPe->cbHeaders) + { + cbRead = RT_MIN(pModPe->cbHeaders - RVA, cb); + rc = pReader->pfnRead(pReader, pvBuf, cbRead, RVA); + if ( cbRead == cb + || RT_FAILURE(rc)) + return rc; + cb -= cbRead; + RVA += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + + /* In the zero space between headers and the first section? */ + if (RVA < pSH->VirtualAddress) + { + cbRead = RT_MIN(pSH->VirtualAddress - RVA, cb); + memset(pvBuf, 0, cbRead); + if (cbRead == cb) + return VINF_SUCCESS; + cb -= cbRead; + RVA += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + + /* + * Iterate the sections. + */ + for (unsigned cLeft = pModPe->cSections; + cLeft > 0; + cLeft--, pSH++) + { + uint32_t off = RVA - pSH->VirtualAddress; + if (off < pSH->Misc.VirtualSize) + { + cbRead = RT_MIN(pSH->Misc.VirtualSize - off, cb); + rc = pReader->pfnRead(pReader, pvBuf, cbRead, pSH->PointerToRawData + off); + if ( cbRead == cb + || RT_FAILURE(rc)) + return rc; + cb -= cbRead; + RVA += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + uint32_t RVANext = cLeft ? pSH[1].VirtualAddress : pModPe->cbImage; + if (RVA < RVANext) + { + cbRead = RT_MIN(RVANext - RVA, cb); + memset(pvBuf, 0, cbRead); + if (cbRead == cb) + return VINF_SUCCESS; + cb -= cbRead; + RVA += cbRead; + pvBuf = (uint8_t *)pvBuf + cbRead; + } + } + + AssertFailed(); + return VERR_INTERNAL_ERROR; +} + + +/** + * Validates the data of some selected data directories entries and remember + * important bits for later. + * + * This requires a valid section table and thus has to wait till after we've + * read and validated it. + * + * @returns iprt status code. + * @param pModPe The PE module instance. + * @param pOptHdr Pointer to the optional header (valid). + * @param fFlags Loader flags, RTLDR_O_XXX. + * @param pErrInfo Where to return extended error information. Optional. + */ +static int rtldrPEValidateDirectoriesAndRememberStuff(PRTLDRMODPE pModPe, const IMAGE_OPTIONAL_HEADER64 *pOptHdr, uint32_t fFlags, + PRTERRINFO pErrInfo) +{ + const char *pszLogName = pModPe->Core.pReader->pfnLogName(pModPe->Core.pReader); NOREF(pszLogName); + union /* combine stuff we're reading to help reduce stack usage. */ + { + IMAGE_LOAD_CONFIG_DIRECTORY64 Cfg64; + uint8_t abZeros[sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64) * 4]; + } u; + + /* + * The load config entry may include lock prefix tables and whatnot which we don't implement. + * It does also include a lot of stuff which we can ignore, so we'll have to inspect the + * actual data before we can make up our mind about it all. + */ + IMAGE_DATA_DIRECTORY Dir = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG]; + if (Dir.Size) + { + const size_t cbExpectV13 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V13) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V13); + const size_t cbExpectV12 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V12) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V12); + const size_t cbExpectV11 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V11) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V11); + const size_t cbExpectV10 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V10) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V10); + const size_t cbExpectV9 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V9) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V9); + const size_t cbExpectV8 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V8) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V8); + const size_t cbExpectV7 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V7) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V7); + const size_t cbExpectV6 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V6) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V6); + const size_t cbExpectV5 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V5) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V5); + const size_t cbExpectV4 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V4) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V4); + const size_t cbExpectV3 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V3) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V3); + const size_t cbExpectV2 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V2) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2); + const size_t cbExpectV1 = !pModPe->f64Bit + ? sizeof(IMAGE_LOAD_CONFIG_DIRECTORY32_V1) + : sizeof(IMAGE_LOAD_CONFIG_DIRECTORY64_V2) /*No V1*/; + const size_t cbNewHack = cbExpectV5; /* Playing safe here since there might've been revisions between V5 and V6 we don't know about . */ + const size_t cbMaxKnown = cbExpectV12; + + bool fNewerStructureHack = false; + if ( Dir.Size != cbExpectV13 + && Dir.Size != cbExpectV12 + && Dir.Size != cbExpectV11 + && Dir.Size != cbExpectV10 + && Dir.Size != cbExpectV9 + && Dir.Size != cbExpectV8 + && Dir.Size != cbExpectV7 + && Dir.Size != cbExpectV6 + && Dir.Size != cbExpectV5 + && Dir.Size != cbExpectV4 + && Dir.Size != cbExpectV3 + && Dir.Size != cbExpectV2 + && Dir.Size != cbExpectV1) + { + fNewerStructureHack = Dir.Size > cbNewHack /* These structure changes are slowly getting to us! More futher down. */ + && Dir.Size <= sizeof(u); + Log(("rtldrPEOpen: %s: load cfg dir: unexpected dir size of %u bytes, expected %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu.%s\n", + pszLogName, Dir.Size, cbExpectV13, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1, + fNewerStructureHack ? " Will try ignore extra bytes if all zero." : "")); + if (!fNewerStructureHack) + return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, + "Unexpected load config dir size of %u bytes; supported sized: %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu", + Dir.Size, cbExpectV13, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1); + } + + /* + * Read, check new stuff and convert to 64-bit. + * + * If we accepted a newer structures when loading for debug or validation, + * otherwise we require the new bits to be all zero and hope that they are + * insignificant where image loading is concerned (that's mostly been the + * case even for non-zero bits, only hard exception is LockPrefixTable). + */ + RT_ZERO(u.Cfg64); + int rc = rtldrPEReadRVA(pModPe, &u.Cfg64, Dir.Size, Dir.VirtualAddress); + if (RT_FAILURE(rc)) + return rc; + if ( fNewerStructureHack + && Dir.Size > cbMaxKnown + && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) + && !ASMMemIsZero(&u.abZeros[cbMaxKnown], Dir.Size - cbMaxKnown)) + { + Log(("rtldrPEOpen: %s: load cfg dir: Unexpected bytes are non-zero (%u bytes of which %u expected to be zero): %.*Rhxs\n", + pszLogName, Dir.Size, Dir.Size - cbMaxKnown, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown])); + return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, + "Grown load config (%u to %u bytes) includes non-zero bytes: %.*Rhxs", + cbMaxKnown, Dir.Size, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]); + } + rtldrPEConvert32BitLoadConfigTo64Bit(&u.Cfg64); + + if (u.Cfg64.Size != Dir.Size) + { + /* Kludge #1: ntdll.dll from XP seen with Dir.Size=0x40 and Cfg64.Size=0x00. */ + if (Dir.Size == 0x40 && u.Cfg64.Size == 0x00 && !pModPe->f64Bit) + { + Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the XP kludge.\n", + pszLogName, u.Cfg64.Size, Dir.Size)); + u.Cfg64.Size = Dir.Size; + } + /* Kludge #2: This happens a lot. Structure changes, but the linker doesn't get + updated and stores some old size in the directory. Use the header size. */ + else if ( u.Cfg64.Size == cbExpectV13 + || u.Cfg64.Size == cbExpectV12 + || u.Cfg64.Size == cbExpectV11 + || u.Cfg64.Size == cbExpectV10 + || u.Cfg64.Size == cbExpectV9 + || u.Cfg64.Size == cbExpectV8 + || u.Cfg64.Size == cbExpectV7 + || u.Cfg64.Size == cbExpectV6 + || u.Cfg64.Size == cbExpectV5 + || u.Cfg64.Size == cbExpectV4 + || u.Cfg64.Size == cbExpectV3 + || u.Cfg64.Size == cbExpectV2 + || u.Cfg64.Size == cbExpectV1 + || (fNewerStructureHack = (u.Cfg64.Size > cbNewHack && u.Cfg64.Size <= sizeof(u))) ) + { + Log(("rtldrPEOpen: %s: load cfg dir: Header (%d) and directory (%d) size mismatch, applying the old linker kludge.\n", + pszLogName, u.Cfg64.Size, Dir.Size)); + + uint32_t const uOrgDir = Dir.Size; + Dir.Size = u.Cfg64.Size; + RT_ZERO(u.Cfg64); + rc = rtldrPEReadRVA(pModPe, &u.Cfg64, Dir.Size, Dir.VirtualAddress); + if (RT_FAILURE(rc)) + return rc; + if ( fNewerStructureHack + && Dir.Size > cbMaxKnown + && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) + && !ASMMemIsZero(&u.abZeros[cbMaxKnown], Dir.Size - cbMaxKnown)) + { + Log(("rtldrPEOpen: %s: load cfg dir: Unknown bytes are non-zero (%u bytes of which %u expected to be zero): %.*Rhxs\n", + pszLogName, Dir.Size, Dir.Size - cbMaxKnown, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown])); + return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, + "Grown load config (%u to %u bytes, dir %u) includes non-zero bytes: %.*Rhxs", + cbMaxKnown, Dir.Size, uOrgDir, Dir.Size - cbMaxKnown, &u.abZeros[cbMaxKnown]); + } + rtldrPEConvert32BitLoadConfigTo64Bit(&u.Cfg64); + AssertReturn(u.Cfg64.Size == Dir.Size, + RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, "Data changed while reading! (%d vs %d)\n", + u.Cfg64.Size, Dir.Size)); + } + else + { + Log(("rtldrPEOpen: %s: load cfg hdr: unexpected hdr size of %u bytes (dir %u), expected %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu.\n", + pszLogName, u.Cfg64.Size, Dir.Size, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1)); + return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOAD_CONFIG_SIZE, + "Unexpected load config header size of %u bytes (dir %u); supported sized: %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, %zu, or %zu", + u.Cfg64.Size, Dir.Size, cbExpectV12, cbExpectV11, cbExpectV10, cbExpectV9, cbExpectV8, cbExpectV7, cbExpectV6, cbExpectV5, cbExpectV4, cbExpectV3, cbExpectV2, cbExpectV1); + } + } + if (u.Cfg64.LockPrefixTable && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))) + { + Log(("rtldrPEOpen: %s: load cfg dir: lock prefix table at %RX64. We don't support lock prefix tables!\n", + pszLogName, u.Cfg64.LockPrefixTable)); + return RTErrInfoSetF(pErrInfo, VERR_LDRPE_LOCK_PREFIX_TABLE, + "Lock prefix table not supported: %RX64", u.Cfg64.LockPrefixTable); + } +#if 0/* this seems to be safe to ignore. */ + if ( u.Cfg64.SEHandlerTable + || u.Cfg64.SEHandlerCount) + { + Log(("rtldrPEOpen: %s: load cfg dir: SEHandlerTable=%RX64 and SEHandlerCount=%RX64 are unsupported!\n", + pszLogName, u.Cfg64.SEHandlerTable, u.Cfg64.SEHandlerCount)); + return VERR_BAD_EXE_FORMAT; + } +#endif + if (u.Cfg64.EditList && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION))) + { + Log(("rtldrPEOpen: %s: load cfg dir: EditList=%RX64 is unsupported!\n", + pszLogName, u.Cfg64.EditList)); + return RTErrInfoSetF(pErrInfo, VERR_BAD_EXE_FORMAT, "Load config EditList=%RX64 is not supported", u.Cfg64.EditList); + } + /** @todo GuardCFC? Possibly related to: + * http://research.microsoft.com/pubs/69217/ccs05-cfi.pdf + * Not trusting something designed by bakas who don't know how to modify a + * structure without messing up its natural alignment. */ + if ( ( u.Cfg64.GuardCFCCheckFunctionPointer + || u.Cfg64.GuardCFDispatchFunctionPointer + || u.Cfg64.GuardCFFunctionTable + || u.Cfg64.GuardCFFunctionCount + || u.Cfg64.GuardFlags + || u.Cfg64.GuardAddressTakenIatEntryTable + || u.Cfg64.GuardAddressTakenIatEntryCount + || u.Cfg64.GuardLongJumpTargetTable + || u.Cfg64.GuardLongJumpTargetCount) + && !(fFlags & (RTLDR_O_FOR_DEBUG | RTLDR_O_FOR_VALIDATION)) ) + { + Log(("rtldrPEOpen: %s: load cfg dir: Guard stuff: %RX64,%RX64,%RX64,%RX64,%RX32,%RX64,%RX64,%RX64,%RX64!\n", + pszLogName, u.Cfg64.GuardCFCCheckFunctionPointer, u.Cfg64.GuardCFDispatchFunctionPointer, + u.Cfg64.GuardCFFunctionTable, u.Cfg64.GuardCFFunctionCount, u.Cfg64.GuardFlags, + u.Cfg64.GuardAddressTakenIatEntryTable, u.Cfg64.GuardAddressTakenIatEntryCount, + u.Cfg64.GuardLongJumpTargetTable, u.Cfg64.GuardLongJumpTargetCount )); +#if 0 /* ntdll 15002 uses this. */ + return RTErrInfoSetF(pErrInfo, VERR_LDRPE_GUARD_CF_STUFF, + "Guard bits in load config: %RX64,%RX64,%RX64,%RX64,%RX32,%RX64,%RX64,%RX64,%RX64!", + u.Cfg64.GuardCFCCheckFunctionPointer, u.Cfg64.GuardCFDispatchFunctionPointer, + u.Cfg64.GuardCFFunctionTable, u.Cfg64.GuardCFFunctionCount, u.Cfg64.GuardFlags, + u.Cfg64.GuardAddressTakenIatEntryTable, u.Cfg64.GuardAddressTakenIatEntryCount, + u.Cfg64.GuardLongJumpTargetTable, u.Cfg64.GuardLongJumpTargetCount); +#endif + } + } + + /* + * If the image is signed and we're not doing this for debug purposes, + * take a look at the signature. + */ + Dir = pOptHdr->DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]; + if (Dir.Size) + { + PWIN_CERTIFICATE pFirst = (PWIN_CERTIFICATE)RTMemTmpAlloc(Dir.Size); + if (!pFirst) + return VERR_NO_TMP_MEMORY; + int rc = pModPe->Core.pReader->pfnRead(pModPe->Core.pReader, pFirst, Dir.Size, Dir.VirtualAddress); + if (RT_SUCCESS(rc)) + { + uint32_t off = 0; + do + { + PWIN_CERTIFICATE pCur = (PWIN_CERTIFICATE)((uint8_t *)pFirst + off); + + /* validate the members. */ + if ( pCur->dwLength < sizeof(WIN_CERTIFICATE) + || pCur->dwLength + off > Dir.Size) + { + Log(("rtldrPEOpen: %s: cert at %#x/%#x: dwLength=%#x\n", pszLogName, off, Dir.Size, pCur->dwLength)); + rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED, + "Cert at %#x LB %#x: Bad header length value: %#x", off, Dir.Size, pCur->dwLength); + break; + } + if ( pCur->wRevision != WIN_CERT_REVISION_2_0 + && pCur->wRevision != WIN_CERT_REVISION_1_0) + { + Log(("rtldrPEOpen: %s: cert at %#x/%#x: wRevision=%#x\n", pszLogName, off, Dir.Size, pCur->wRevision)); + if (pCur->wRevision >= WIN_CERT_REVISION_1_0) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_UNSUPPORTED, + "Cert at %#x LB %#x: Unsupported revision: %#x", off, Dir.Size, pCur->wRevision); + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED, + "Cert at %#x LB %#x: Malformed revision: %#x", off, Dir.Size, pCur->wRevision); + break; + } + if ( pCur->wCertificateType != WIN_CERT_TYPE_PKCS_SIGNED_DATA + && pCur->wCertificateType != WIN_CERT_TYPE_X509 + /*&& pCur->wCertificateType != WIN_CERT_TYPE_RESERVED_1*/ + /*&& pCur->wCertificateType != WIN_CERT_TYPE_TS_STACK_SIGNED*/ + && pCur->wCertificateType != WIN_CERT_TYPE_EFI_PKCS115 + && pCur->wCertificateType != WIN_CERT_TYPE_EFI_GUID + ) + { + Log(("rtldrPEOpen: %s: cert at %#x/%#x: wCertificateType=%#x\n", pszLogName, off, Dir.Size, pCur->wCertificateType)); + if (pCur->wCertificateType) + rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_UNSUPPORTED, + "Cert at %#x LB %#x: Unsupported certificate type: %#x", + off, Dir.Size, pCur->wCertificateType); + else + rc = RTErrInfoSetF(pErrInfo, VERR_LDRPE_CERT_MALFORMED, + "Cert at %#x LB %#x: Malformed certificate type: %#x", + off, Dir.Size, pCur->wCertificateType); + break; + } + + /* Remember the first signed data certificate. */ + if ( pCur->wCertificateType == WIN_CERT_TYPE_PKCS_SIGNED_DATA + && pModPe->offPkcs7SignedData == 0) + { + pModPe->offPkcs7SignedData = Dir.VirtualAddress + + (uint32_t)((uintptr_t)&pCur->bCertificate[0] - (uintptr_t)pFirst); + pModPe->cbPkcs7SignedData = pCur->dwLength - RT_UOFFSETOF(WIN_CERTIFICATE, bCertificate); + } + + /* next */ + off += RT_ALIGN(pCur->dwLength, WIN_CERTIFICATE_ALIGNMENT); + } while (off < Dir.Size); + } + RTMemTmpFree(pFirst); + if (RT_FAILURE(rc) && !(fFlags & RTLDR_O_FOR_DEBUG)) + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * Open a PE image. + * + * @returns iprt status code. + * @param pReader The loader reader instance which will provide the raw image bits. + * @param fFlags Loader flags, RTLDR_O_XXX. + * @param enmArch Architecture specifier. + * @param offNtHdrs The offset of the NT headers (where you find "PE\0\0"). + * @param phLdrMod Where to store the handle. + * @param pErrInfo Where to return extended error information. Optional. + */ +DECLHIDDEN(int) rtldrPEOpen(PRTLDRREADER pReader, uint32_t fFlags, RTLDRARCH enmArch, RTFOFF offNtHdrs, + PRTLDRMOD phLdrMod, PRTERRINFO pErrInfo) +{ + /* + * Read and validate the file header. + */ + IMAGE_FILE_HEADER FileHdr; + int rc = pReader->pfnRead(pReader, &FileHdr, sizeof(FileHdr), offNtHdrs + 4); + if (RT_FAILURE(rc)) + return rc; + RTLDRARCH enmArchImage; + const char *pszLogName = pReader->pfnLogName(pReader); + rc = rtldrPEValidateFileHeader(&FileHdr, fFlags, pszLogName, &enmArchImage, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + + /* + * Match the CPU architecture. + */ + bool fArchNoCodeCheckPending = false; + if ( enmArch != enmArchImage + && ( enmArch != RTLDRARCH_WHATEVER + && !(fFlags & RTLDR_O_WHATEVER_ARCH)) ) + { + if (!(fFlags & RTLDR_O_IGNORE_ARCH_IF_NO_CODE)) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDR_ARCH_MISMATCH, "Image is for '%s', only accepting images for '%s'.", + rtldrPEGetArchName(FileHdr.Machine), RTLdrArchName(enmArch)); + fArchNoCodeCheckPending = true; + } + + /* + * Read and validate the "optional" header. Convert 32->64 if necessary. + */ + IMAGE_OPTIONAL_HEADER64 OptHdr; + rc = pReader->pfnRead(pReader, &OptHdr, FileHdr.SizeOfOptionalHeader, offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER)); + if (RT_FAILURE(rc)) + return rc; + if (FileHdr.SizeOfOptionalHeader != sizeof(OptHdr)) + rtldrPEConvert32BitOptionalHeaderTo64Bit(&OptHdr); + rc = rtldrPEValidateOptionalHeader(&OptHdr, pszLogName, offNtHdrs, &FileHdr, pReader->pfnSize(pReader), fFlags, pErrInfo); + if (RT_FAILURE(rc)) + return rc; + if (fArchNoCodeCheckPending && OptHdr.SizeOfCode != 0) + return RTERRINFO_LOG_SET_F(pErrInfo, VERR_LDR_ARCH_MISMATCH, + "Image is for '%s' and contains code (%#x), only accepting images for '%s' with code.", + rtldrPEGetArchName(FileHdr.Machine), OptHdr.SizeOfCode, RTLdrArchName(enmArch)); + + /* + * Read and validate section headers. + */ + const size_t cbSections = sizeof(IMAGE_SECTION_HEADER) * FileHdr.NumberOfSections; + PIMAGE_SECTION_HEADER paSections = (PIMAGE_SECTION_HEADER)RTMemAlloc(cbSections); + if (!paSections) + return VERR_NO_MEMORY; + rc = pReader->pfnRead(pReader, paSections, cbSections, + offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader); + if (RT_SUCCESS(rc)) + { + rc = rtldrPEValidateAndTouchUpSectionHeaders(paSections, FileHdr.NumberOfSections, pszLogName, + &OptHdr, pReader->pfnSize(pReader), fFlags, fArchNoCodeCheckPending); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize the PE module structure. + */ + PRTLDRMODPE pModPe = (PRTLDRMODPE)RTMemAllocZ(sizeof(*pModPe)); + if (pModPe) + { + pModPe->Core.u32Magic = RTLDRMOD_MAGIC; + pModPe->Core.eState = LDR_STATE_OPENED; + if (FileHdr.SizeOfOptionalHeader == sizeof(OptHdr)) + pModPe->Core.pOps = &s_rtldrPE64Ops.Core; + else + pModPe->Core.pOps = &s_rtldrPE32Ops.Core; + pModPe->Core.pReader = pReader; + pModPe->Core.enmFormat= RTLDRFMT_PE; + pModPe->Core.enmType = FileHdr.Characteristics & IMAGE_FILE_DLL + ? FileHdr.Characteristics & IMAGE_FILE_RELOCS_STRIPPED + ? RTLDRTYPE_EXECUTABLE_FIXED + : RTLDRTYPE_EXECUTABLE_RELOCATABLE + : FileHdr.Characteristics & IMAGE_FILE_RELOCS_STRIPPED + ? RTLDRTYPE_SHARED_LIBRARY_FIXED + : RTLDRTYPE_SHARED_LIBRARY_RELOCATABLE; + pModPe->Core.enmEndian= RTLDRENDIAN_LITTLE; + pModPe->Core.enmArch = FileHdr.Machine == IMAGE_FILE_MACHINE_I386 + ? RTLDRARCH_X86_32 + : FileHdr.Machine == IMAGE_FILE_MACHINE_AMD64 + ? RTLDRARCH_AMD64 + : RTLDRARCH_WHATEVER; + pModPe->pvBits = NULL; + pModPe->offNtHdrs = offNtHdrs; + pModPe->offEndOfHdrs = offNtHdrs + 4 + sizeof(IMAGE_FILE_HEADER) + FileHdr.SizeOfOptionalHeader + cbSections; + pModPe->u16Machine = FileHdr.Machine; + pModPe->fFile = FileHdr.Characteristics; + pModPe->cSections = FileHdr.NumberOfSections; + pModPe->paSections = paSections; + pModPe->uEntryPointRVA= OptHdr.AddressOfEntryPoint; + pModPe->uImageBase = (RTUINTPTR)OptHdr.ImageBase; + pModPe->cbImage = OptHdr.SizeOfImage; + pModPe->cbHeaders = OptHdr.SizeOfHeaders; + pModPe->uSectionAlign = OptHdr.SectionAlignment; + pModPe->uTimestamp = FileHdr.TimeDateStamp; + pModPe->cImports = UINT32_MAX; + pModPe->f64Bit = FileHdr.SizeOfOptionalHeader == sizeof(OptHdr); + pModPe->ImportDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + pModPe->RelocDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + pModPe->ExportDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT]; + pModPe->DebugDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_DEBUG]; + pModPe->SecurityDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_SECURITY]; + pModPe->ExceptionDir = OptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION]; + pModPe->fDllCharacteristics = OptHdr.DllCharacteristics; + + /* + * Perform validation of some selected data directories which requires + * inspection of the actual data. This also saves some certificate + * information. + */ + rc = rtldrPEValidateDirectoriesAndRememberStuff(pModPe, &OptHdr, fFlags, pErrInfo); + if (RT_SUCCESS(rc)) + { + *phLdrMod = &pModPe->Core; + return VINF_SUCCESS; + } + RTMemFree(pModPe); + } + else + rc = VERR_NO_MEMORY; + } + } + RTMemFree(paSections); + return rc; +} + diff --git a/src/VBox/Runtime/common/ldr/ldrVfsFile.cpp b/src/VBox/Runtime/common/ldr/ldrVfsFile.cpp new file mode 100644 index 00000000..3f717583 --- /dev/null +++ b/src/VBox/Runtime/common/ldr/ldrVfsFile.cpp @@ -0,0 +1,293 @@ +/* $Id: ldrVfsFile.cpp $ */ +/** @file + * IPRT - Binary Image Loader, The File Oriented Parts, VFS variant. + */ + +/* + * Copyright (C) 2006-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 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_LDR +#include +#include "internal/iprt.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal/ldr.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * VFS file Reader instance. + * This provides raw image bits from a file. + */ +typedef struct RTLDRREADERVFSFILE +{ + /** The core. */ + RTLDRREADER Core; + /** The VFS file. */ + RTVFSFILE hVfsFile; + /** Number of users or the mapping. */ + RTUINT cMappings; + /** Pointer to the in memory mapping. */ + void *pvMapping; + /** The filename (variable size). */ + char szFilename[1]; +} RTLDRREADERVFSFILE; +typedef RTLDRREADERVFSFILE *PRTLDRREADERVFSFILE; + + +/** @copydoc RTLDRREADER::pfnRead */ +static DECLCALLBACK(int) rtldrVfsFileRead(PRTLDRREADER pReader, void *pvBuf, size_t cb, RTFOFF off) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + return RTVfsFileReadAt(pFileReader->hVfsFile, off, pvBuf, cb, NULL); +} + + +/** @copydoc RTLDRREADER::pfnTell */ +static DECLCALLBACK(RTFOFF) rtldrVfsFileTell(PRTLDRREADER pReader) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + return RTVfsFileTell(pFileReader->hVfsFile); +} + + +/** @copydoc RTLDRREADER::pfnSize */ +static DECLCALLBACK(uint64_t) rtldrVfsFileSize(PRTLDRREADER pReader) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + uint64_t cbFile; + int rc = RTVfsFileQuerySize(pFileReader->hVfsFile, &cbFile); + if (RT_SUCCESS(rc)) + return cbFile; + return 0; +} + + +/** @copydoc RTLDRREADER::pfnLogName */ +static DECLCALLBACK(const char *) rtldrVfsFileLogName(PRTLDRREADER pReader) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + return pFileReader->szFilename; +} + + +/** @copydoc RTLDRREADER::pfnMap */ +static DECLCALLBACK(int) rtldrVfsFileMap(PRTLDRREADER pReader, const void **ppvBits) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + + /* + * Already mapped? + */ + if (pFileReader->pvMapping) + { + pFileReader->cMappings++; + *ppvBits = pFileReader->pvMapping; + return VINF_SUCCESS; + } + + /* + * Allocate memory. + */ + uint64_t cbFile = rtldrVfsFileSize(pReader); + size_t cb = (size_t)cbFile; + if ((uint64_t)cb != cbFile) + return VERR_IMAGE_TOO_BIG; + pFileReader->pvMapping = RTMemAlloc(cb); + if (!pFileReader->pvMapping) + return VERR_NO_MEMORY; + int rc = rtldrVfsFileRead(pReader, pFileReader->pvMapping, cb, 0); + if (RT_SUCCESS(rc)) + { + pFileReader->cMappings = 1; + *ppvBits = pFileReader->pvMapping; + } + else + { + RTMemFree(pFileReader->pvMapping); + pFileReader->pvMapping = NULL; + } + + return rc; +} + + +/** @copydoc RTLDRREADER::pfnUnmap */ +static DECLCALLBACK(int) rtldrVfsFileUnmap(PRTLDRREADER pReader, const void *pvBits) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + AssertReturn(pFileReader->cMappings > 0, VERR_INVALID_PARAMETER); + + if (!--pFileReader->cMappings) + { + RTMemFree(pFileReader->pvMapping); + pFileReader->pvMapping = NULL; + } + + NOREF(pvBits); + return VINF_SUCCESS; +} + + +/** @copydoc RTLDRREADER::pfnDestroy */ +static DECLCALLBACK(int) rtldrVfsFileDestroy(PRTLDRREADER pReader) +{ + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)pReader; + if (pFileReader->hVfsFile != NIL_RTVFSFILE) + { + RTVfsFileRelease(pFileReader->hVfsFile); + pFileReader->hVfsFile = NIL_RTVFSFILE; + } + RTMemFree(pFileReader); + return VINF_SUCCESS; +} + + +/** + * Opens a loader file reader. + * + * @returns iprt status code. + * @param pszFilename The file to open, can be VFS chain. + * @param ppReader Where to store the reader instance on success. + * @param poffError Where to return the offset into @a pszFilename of an VFS + * chain element causing trouble. Optional. + * @param pErrInfo Where to return extended error information. Optional. + */ +static int rtldrVfsFileCreate(const char *pszFilename, PRTLDRREADER *ppReader, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + size_t cbFilename = strlen(pszFilename) + 1; + int rc = VERR_NO_MEMORY; + PRTLDRREADERVFSFILE pFileReader = (PRTLDRREADERVFSFILE)RTMemAlloc(RT_UOFFSETOF_DYN(RTLDRREADERVFSFILE, szFilename[cbFilename])); + if (pFileReader) + { + memcpy(pFileReader->szFilename, pszFilename, cbFilename); + pFileReader->szFilename[0] = '\0'; + rc = RTVfsChainOpenFile(pszFilename, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE, &pFileReader->hVfsFile, + poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + pFileReader->Core.uMagic = RTLDRREADER_MAGIC; + pFileReader->Core.pfnRead = rtldrVfsFileRead; + pFileReader->Core.pfnTell = rtldrVfsFileTell; + pFileReader->Core.pfnSize = rtldrVfsFileSize; + pFileReader->Core.pfnLogName = rtldrVfsFileLogName; + pFileReader->Core.pfnMap = rtldrVfsFileMap; + pFileReader->Core.pfnUnmap = rtldrVfsFileUnmap; + pFileReader->Core.pfnDestroy = rtldrVfsFileDestroy; + pFileReader->cMappings = 0; + pFileReader->pvMapping = NULL; + *ppReader = &pFileReader->Core; + return VINF_SUCCESS; + } + RTMemFree(pFileReader); + } + *ppReader = NULL; + return rc; +} + + +/** + * Open a binary image file allowing VFS chains in the filename. + * + * @returns iprt status code. + * @param pszFilename Image filename, VFS chain specifiers allowed. + * @param fFlags Valid RTLDR_O_XXX combination. + * @param enmArch CPU architecture specifier for the image to be loaded. + * @param phLdrMod Where to store the handle to the loader module. + * @param poffError Where to return the offset into @a pszFilename of an VFS + * chain element causing trouble. Optional. + * @param pErrInfo Where to return extended error information. Optional. + */ +RTDECL(int) RTLdrOpenVfsChain(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch, + PRTLDRMOD phLdrMod, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + LogFlow(("RTLdrOpenVfsChain: pszFilename=%p:{%s} fFlags=%#x enmArch=%d phLdrMod=%p\n", + pszFilename, pszFilename, fFlags, enmArch, phLdrMod)); + AssertMsgReturn(!(fFlags & ~RTLDR_O_VALID_MASK), ("%#x\n", fFlags), VERR_INVALID_PARAMETER); + AssertMsgReturn(enmArch > RTLDRARCH_INVALID && enmArch < RTLDRARCH_END, ("%d\n", enmArch), VERR_INVALID_PARAMETER); + + /* + * Create file reader & invoke worker which identifies and calls the image interpreter. + */ + PRTLDRREADER pReader; + int rc = rtldrVfsFileCreate(pszFilename, &pReader, poffError, pErrInfo); + if (RT_SUCCESS(rc)) + { + if (poffError) + *poffError = 0; + rc = RTLdrOpenWithReader(pReader, fFlags, enmArch, phLdrMod, pErrInfo); + if (RT_SUCCESS(rc)) + { + LogFlow(("RTLdrOpenEx: return %Rrc *phLdrMod=%p\n", rc, *phLdrMod)); + return rc; + } + pReader->pfnDestroy(pReader); + } + *phLdrMod = NIL_RTLDRMOD; + LogFlow(("RTLdrOpenEx: return %Rrc\n", rc)); + return rc; +} +RT_EXPORT_SYMBOL(RTLdrOpenVfsChain); + + +/** + * Open a binary image file using kLdr allowing VFS chains in the filename. + * + * @returns iprt status code. + * @param pszFilename Image filename. + * @param fFlags Reserved, MBZ. + * @param enmArch CPU architecture specifier for the image to be loaded. + * @param phLdrMod Where to store the handle to the loaded module. + * @param poffError Where to return the offset into @a pszFilename of an VFS + * chain element causing trouble. Optional. + * @param pErrInfo Where to return extended error information. Optional. + * @remark Primarily for testing the loader. + */ +RTDECL(int) RTLdrOpenVfsChainkLdr(const char *pszFilename, uint32_t fFlags, RTLDRARCH enmArch, + PRTLDRMOD phLdrMod, uint32_t *poffError, PRTERRINFO pErrInfo) +{ + return RTLdrOpenVfsChain(pszFilename, fFlags, enmArch, phLdrMod, poffError, pErrInfo); +} +RT_EXPORT_SYMBOL(RTLdrOpenVfsChainkLdr); + -- cgit v1.2.3