@@ -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