Skip to content

Commit 75c6f01

Browse files
committed
better validation for malformed PE RSRC records and cleanup (fixes an infinite loop)
1 parent 4eeabc6 commit 75c6f01

File tree

1 file changed

+101
-5
lines changed

1 file changed

+101
-5
lines changed

view/pe/peview.cpp

Lines changed: 101 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2707,12 +2707,40 @@ bool PEView::Init()
27072707
QualifiedName resourceDataEntryTypeName = DefineType(resourceDataEntryTypeId, resourceDataEntryName, resourceDataEntryType);
27082708

27092709
std::list<uint64_t> tableAddrsToParse = {dir.virtualAddress};
2710+
std::unordered_set<uint64_t> visitedTables;
2711+
2712+
uint64_t maxTableCount = 10000;
2713+
if (settings && settings->Contains("loader.pe.maxResourceDirectoryTableCount"))
2714+
maxTableCount = settings->Get<uint64_t>("loader.pe.maxResourceDirectoryTableCount", this);
27102715

27112716
uint32_t resourceDirectoryTableNum = 0;
27122717
while (!tableAddrsToParse.empty())
27132718
{
2719+
// Check for safety limit to prevent infinite parsing
2720+
if (resourceDirectoryTableNum >= maxTableCount)
2721+
{
2722+
m_logger->LogError("Resource directory parsing exceeded safety limit (%" PRIu64 " tables), possible malformed/malicious file", maxTableCount);
2723+
break;
2724+
}
2725+
27142726
uint64_t tableAddr = tableAddrsToParse.front();
27152727
tableAddrsToParse.pop_front();
2728+
2729+
// Cycle detection - skip if we've already processed this table
2730+
if (visitedTables.count(tableAddr))
2731+
{
2732+
m_logger->LogWarn("Resource directory cycle detected at RVA 0x%" PRIx64 ", skipping", tableAddr);
2733+
continue;
2734+
}
2735+
visitedTables.insert(tableAddr);
2736+
2737+
// Bounds check - ensure table address is within resource section
2738+
if (tableAddr < dir.virtualAddress || tableAddr >= dir.virtualAddress + dir.size)
2739+
{
2740+
m_logger->LogWarn("Resource directory table at RVA 0x%" PRIx64 " is outside resource section bounds", tableAddr);
2741+
continue;
2742+
}
2743+
27162744
// Read in next directory entry
27172745
reader.Seek(RVAToFileOffset(tableAddr));
27182746
PEResourceDirectoryTable importDirTable;
@@ -2753,23 +2781,58 @@ bool PEView::Init()
27532781

27542782
size_t nameAddr = dir.virtualAddress + (importDirEntry.id ^ 0x80000000);
27552783

2784+
// Bounds check - ensure name address is within resource section
2785+
if (nameAddr < dir.virtualAddress || nameAddr >= dir.virtualAddress + dir.size)
2786+
{
2787+
m_logger->LogWarn("Resource name at RVA 0x%zx is outside resource section bounds, skipping", nameAddr);
2788+
continue;
2789+
}
2790+
27562791
BinaryReader nameReader(GetParentView(), LittleEndian);
27572792
nameReader.Seek(RVAToFileOffset(nameAddr));
27582793

27592794
uint16_t nameLen = nameReader.Read16();
2795+
2796+
// Check that the full name fits within the resource section
2797+
// nameLen is in UTF-16 code-units, each 2 bytes, plus 2 bytes for the length prefix
2798+
if (nameAddr + 2 + (nameLen * 2) > dir.virtualAddress + dir.size)
2799+
{
2800+
m_logger->LogWarn("Resource name extends beyond resource section bounds, skipping");
2801+
continue;
2802+
}
2803+
27602804
// Plus 2 because it's length-prefixed
27612805
DefineDataVariable(m_imageBase + nameAddr + 2, Type::ArrayType(Type::WideCharType(2), nameLen));
27622806
}
27632807

27642808
if (importDirEntry.offset & 0x80000000)
27652809
{
27662810
// Lower 31 bits are address of another table
2767-
tableAddrsToParse.push_back(dir.virtualAddress + (importDirEntry.offset ^ 0x80000000));
2811+
uint64_t nextTableAddr = dir.virtualAddress + (importDirEntry.offset ^ 0x80000000);
2812+
2813+
// Bounds check before adding to queue to prevent invalid references
2814+
if (nextTableAddr >= dir.virtualAddress && nextTableAddr < dir.virtualAddress + dir.size)
2815+
{
2816+
tableAddrsToParse.push_back(nextTableAddr);
2817+
}
2818+
else
2819+
{
2820+
m_logger->LogWarn("Resource directory entry points to invalid address 0x%" PRIx64 ", skipping", nextTableAddr);
2821+
}
27682822
}
27692823
else
27702824
{
27712825
// Address of data entry
2772-
dataEntryOffsets.push_back(importDirEntry.offset);
2826+
// Validate offset is within resource section bounds
2827+
uint64_t dataEntryAddr = dir.virtualAddress + importDirEntry.offset;
2828+
if (dataEntryAddr >= dir.virtualAddress && dataEntryAddr + sizeof(PEResourceDataEntry) <= dir.virtualAddress + dir.size)
2829+
{
2830+
dataEntryOffsets.push_back(importDirEntry.offset);
2831+
}
2832+
else
2833+
{
2834+
m_logger->LogWarn("Resource data entry at offset 0x%" PRIx32 " is outside resource section bounds, skipping", importDirEntry.offset);
2835+
}
27732836
}
27742837
}
27752838

@@ -2778,10 +2841,11 @@ bool PEView::Init()
27782841
DefineAutoSymbol(new Symbol(DataSymbol, fmt::format("__resource_directory_table_{}_entries", resourceDirectoryTableNum), tableEntriesStart, NoBinding));
27792842
}
27802843

2844+
// Create BinaryReader once outside the loop for better performance
2845+
BinaryReader entryReader(GetParentView(), LittleEndian);
2846+
27812847
for(size_t dataEntryNum = 0; dataEntryNum < dataEntryOffsets.size(); dataEntryNum++)
27822848
{
2783-
BinaryReader entryReader(GetParentView(), LittleEndian);
2784-
27852849
size_t entryOffset = dataEntryOffsets[dataEntryNum];
27862850
entryReader.Seek(RVAToFileOffset(dir.virtualAddress + entryOffset));
27872851
PEResourceDataEntry dataEntry;
@@ -2796,13 +2860,35 @@ bool PEView::Init()
27962860
continue;
27972861
}
27982862

2863+
// Limit excessively large data sizes
2864+
const uint32_t MAX_REASONABLE_RESOURCE_SIZE = 100 * 1024 * 1024; // 100 MB
2865+
if (dataEntry.dataSize > MAX_REASONABLE_RESOURCE_SIZE)
2866+
{
2867+
m_logger->LogWarn("Resource data size 0x%" PRIx32 " exceeds reasonable limit, skipping", dataEntry.dataSize);
2868+
continue;
2869+
}
2870+
2871+
// Check for overflow when adding dataRva + dataSize
2872+
if (dataEntry.dataSize > 0)
2873+
{
2874+
uint64_t dataEnd = (uint64_t)dataEntry.dataRva + dataEntry.dataSize;
2875+
if (dataEnd < dataEntry.dataRva)
2876+
{
2877+
m_logger->LogWarn("Resource data RVA 0x%" PRIx32 " + size 0x%" PRIx32 " would overflow, skipping", dataEntry.dataRva, dataEntry.dataSize);
2878+
continue;
2879+
}
2880+
}
2881+
27992882
size_t entryAddr = m_imageBase + dir.virtualAddress + entryOffset;
28002883

28012884
DefineDataVariable(entryAddr, Type::NamedType(this, resourceDataEntryTypeName));
28022885
DefineAutoSymbol(new Symbol(DataSymbol, fmt::format("__resource_directory_table_{}_data_entry_{}", resourceDirectoryTableNum, dataEntryNum), entryAddr, NoBinding));
28032886

28042887
//TODO: properly name based on path taken to get here
2805-
DefineDataVariable(m_imageBase + dataEntry.dataRva, Type::ArrayType(Type::IntegerType(1, true), dataEntry.dataSize));
2888+
if (dataEntry.dataSize > 0)
2889+
{
2890+
DefineDataVariable(m_imageBase + dataEntry.dataRva, Type::ArrayType(Type::IntegerType(1, true), dataEntry.dataSize));
2891+
}
28062892
}
28072893

28082894
resourceDirectoryTableNum++;
@@ -3139,6 +3225,16 @@ Ref<Settings> PEViewType::GetLoadSettingsForData(BinaryView* data)
31393225
"description" : "Add function starts sourced from the Structured Exception Handling (SEH) table to the core for analysis."
31403226
})");
31413227

3228+
settings->RegisterSetting("loader.pe.maxResourceDirectoryTableCount",
3229+
R"({
3230+
"title" : "Maximum PE Resource Directory Table Count",
3231+
"type" : "number",
3232+
"default" : 10000,
3233+
"minValue" : 1,
3234+
"maxValue" : 1000000,
3235+
"description" : "Maximum number of resource directory tables to parse. This limit prevents infinite loops when processing malformed or malicious PE files with circular resource directory references."
3236+
})");
3237+
31423238
return settings;
31433239
}
31443240

0 commit comments

Comments
 (0)