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/ldrPE.cpp | 5194 +++++++++++++++++++++++++++++++++ 1 file changed, 5194 insertions(+) create mode 100644 src/VBox/Runtime/common/ldr/ldrPE.cpp (limited to 'src/VBox/Runtime/common/ldr/ldrPE.cpp') 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; +} + -- cgit v1.2.3