From 36c34ed6724416f212aa81eb8596f0bfed59d3f1 Mon Sep 17 00:00:00 2001 From: David Wrighton Date: Mon, 25 Mar 2019 16:04:26 -0700 Subject: [PATCH] Replace Win32 resource reading logic with cross platform implementation (#23363) * FindResource direct implementation in PEDecoder * Fixup bugs identified in resource reading --- src/inc/pedecoder.h | 6 +- src/utilcode/pedecoder.cpp | 145 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 134 insertions(+), 17 deletions(-) diff --git a/src/inc/pedecoder.h b/src/inc/pedecoder.h index 4fdbee5..91990d2 100644 --- a/src/inc/pedecoder.h +++ b/src/inc/pedecoder.h @@ -258,10 +258,12 @@ class PEDecoder PTR_VOID GetTlsRange(COUNT_T *pSize = NULL) const; UINT32 GetTlsIndex() const; -#ifndef FEATURE_PAL // Win32 resources void *GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSize = NULL) const; -#endif // FEATURE_PAL + private: + DWORD ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pIsDictionary) const; + DWORD ReadResourceDataEntry(DWORD rva, COUNT_T *pSize) const; + public: // COR header fields diff --git a/src/utilcode/pedecoder.cpp b/src/utilcode/pedecoder.cpp index 794540a..7324f6e 100644 --- a/src/utilcode/pedecoder.cpp +++ b/src/utilcode/pedecoder.cpp @@ -1790,7 +1790,96 @@ void PEDecoder::LayoutILOnly(void *base, BOOL allowFullPE) const #endif // #ifndef DACCESS_COMPILE -#ifndef FEATURE_PAL +DWORD PEDecoder::ReadResourceDictionary(DWORD rvaOfResourceSection, DWORD rva, LPCWSTR name, BOOL *pIsDictionary) const +{ + *pIsDictionary = FALSE; + + if (!CheckRva(rva, sizeof(IMAGE_RESOURCE_DIRECTORY))) + { + return 0; + } + + IMAGE_RESOURCE_DIRECTORY *pResourceDirectory = (IMAGE_RESOURCE_DIRECTORY *)GetRvaData(rva); + + if (pResourceDirectory->MajorVersion != 4) + return 0; + + if (pResourceDirectory->MinorVersion != 0) + return 0; + + // Check to see if entire resource dictionary is accessible + if (!CheckRva(rva + sizeof(IMAGE_RESOURCE_DIRECTORY), + (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * pResourceDirectory->NumberOfNamedEntries) + + (sizeof(IMAGE_RESOURCE_DIRECTORY_ENTRY) * pResourceDirectory->NumberOfIdEntries))) + { + return 0; + } + + IMAGE_RESOURCE_DIRECTORY_ENTRY* pDirectoryEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY *)GetRvaData(rva + sizeof(IMAGE_RESOURCE_DIRECTORY)); + + // A fast implementation of resource lookup uses a binary search, but our needs are simple, and a linear search + // is easier to prove correct, so do that instead. + DWORD iEntryCount = (DWORD)pResourceDirectory->NumberOfNamedEntries + (DWORD)pResourceDirectory->NumberOfIdEntries; + + for (DWORD iEntry = 0; iEntry < iEntryCount; iEntry++) + { + BOOL foundEntry = FALSE; + + if (((UINT_PTR)name) <= 0xFFFF) + { + // name is id + if (pDirectoryEntries[iEntry].Name == (DWORD)name) + foundEntry = TRUE; + } + else + { + // name is string + DWORD entryName = pDirectoryEntries[iEntry].Name; + if (!(entryName & IMAGE_RESOURCE_NAME_IS_STRING)) + continue; + + DWORD entryNameRva = (entryName & ~IMAGE_RESOURCE_NAME_IS_STRING) + rvaOfResourceSection; + + if (!CheckRva(entryNameRva, sizeof(WORD))) + return 0; + + size_t entryNameLen = *(WORD*)GetRvaData(entryNameRva); + if (wcslen(name) != entryNameLen) + continue; + + if (!CheckRva(entryNameRva, (COUNT_T)(sizeof(WORD) * (1 + entryNameLen)))) + return 0; + + if (memcmp((WCHAR*)GetRvaData(entryNameRva + sizeof(WORD)), name, entryNameLen * sizeof(WCHAR)) == 0) + foundEntry = TRUE; + } + + if (!foundEntry) + continue; + + *pIsDictionary = !!(pDirectoryEntries[iEntry].OffsetToData & IMAGE_RESOURCE_DATA_IS_DIRECTORY); + DWORD offsetToData = pDirectoryEntries[iEntry].OffsetToData & ~IMAGE_RESOURCE_DATA_IS_DIRECTORY; + DWORD dataRva = rvaOfResourceSection + offsetToData; + return dataRva; + } + + return 0; +} + +DWORD PEDecoder::ReadResourceDataEntry(DWORD rva, COUNT_T *pSize) const +{ + *pSize = 0; + + if (!CheckRva(rva, sizeof(IMAGE_RESOURCE_DATA_ENTRY))) + { + return 0; + } + + IMAGE_RESOURCE_DATA_ENTRY *pDataEntry = (IMAGE_RESOURCE_DATA_ENTRY *)GetRvaData(rva); + *pSize = pDataEntry->Size; + return pDataEntry->OffsetToData; +} + void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSize /*=NULL*/) const { CONTRACTL { @@ -1800,31 +1889,57 @@ void * PEDecoder::GetWin32Resource(LPCWSTR lpName, LPCWSTR lpType, COUNT_T *pSiz GC_NOTRIGGER; } CONTRACTL_END; - if (pSize != NULL) - *pSize = 0; + COUNT_T sizeUnused = 0; // Use this variable if pSize is null + if (pSize == NULL) + pSize = &sizeUnused; + + *pSize = 0; + + if (!HasDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE)) + return NULL; + + COUNT_T resourceDataSize = 0; + IMAGE_DATA_DIRECTORY *pDir = GetDirectoryEntry(IMAGE_DIRECTORY_ENTRY_RESOURCE); - HMODULE hModule = (HMODULE) dac_cast(GetBase()); + if (pDir->VirtualAddress == 0) + return NULL; - // Use the Win32 functions to decode the resources + BOOL isDictionary = FALSE; + DWORD nameTableRva = ReadResourceDictionary(pDir->VirtualAddress, pDir->VirtualAddress, lpType, &isDictionary); - HRSRC hResource = WszFindResourceEx(hModule, lpType, lpName, MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL)); - if (!hResource) + if (!isDictionary) + return NULL; + + if (nameTableRva == 0) return NULL; - HGLOBAL hLoadedResource = ::LoadResource(hModule, hResource); - if (!hLoadedResource) + DWORD languageTableRva = ReadResourceDictionary(pDir->VirtualAddress, nameTableRva, lpName, &isDictionary); + if (!isDictionary) return NULL; - PVOID pResource = ::LockResource(hLoadedResource); - if (!pResource) + if (languageTableRva == 0) return NULL; - if (pSize != NULL) - *pSize = ::SizeofResource(hModule, hResource); + // This api is designed to find resources with LANGID = MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL) + // This translates to LANGID 0 as the initial lookup point, which is sufficient for the needs of this api at this time + // (FindResource in the Windows api implements a large number of fallback paths which this api does not implement) + + DWORD resourceDataEntryRva = ReadResourceDictionary(pDir->VirtualAddress, languageTableRva, 0, &isDictionary); + if (isDictionary) // This must not be a resource dictionary itself + return NULL; + + if (resourceDataEntryRva == 0) + return NULL; + + DWORD resourceDataRva = ReadResourceDataEntry(resourceDataEntryRva, pSize); + if (!CheckRva(resourceDataRva, *pSize)) + { + *pSize = 0; + return NULL; + } - return pResource; + return (void*)GetRvaData(resourceDataRva); } -#endif // FEATURE_PAL BOOL PEDecoder::HasNativeHeader() const { -- 2.7.4