Skip to content

Commit e2cc735

Browse files
committed
add vtable validation
doReadClassName will now first validate that the vtable pointer points to mapped memory before attempting to read it, and throws an exception if it does not
1 parent 28129e3 commit e2cc735

4 files changed

Lines changed: 23 additions & 2 deletions

File tree

docs/changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ Template for new versions:
6161
## Fixes
6262

6363
## Misc Improvements
64+
- Core: DFHack now validates vtable pointers in objects read from memory and will throw an exception instead of crashing when an invalid vtable pointer is encountered. This makes it easier to identify which DF data structure contains corrupted data when this manifests in the form of a bad vtable pointer, and shifts blame for such crashes from DFHack to DF.
6465

6566
## Documentation
6667

library/DataDefs.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ const virtual_identity *virtual_identity::find(void *vtable)
370370

371371
// If using a reader/writer lock, re-grab as write here, and recheck
372372
Core &core = Core::getInstance();
373-
std::string name = core.p->doReadClassName(vtable);
373+
std::string name = core.p->readClassName(vtable);
374374

375375
auto name_it = (*name_lookup).find(name);
376376
if (name_it != (*name_lookup).end()) {

library/Process.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ Process::~Process()
236236

237237
string Process::doReadClassName (void * vptr)
238238
{
239+
if (!checkValidAddress(vptr))
240+
throw std::runtime_error(std::format("invalid vtable ptr {}", vptr));
241+
239242
char* rtti = Process::readPtr(((char*)vptr - sizeof(void*)));
240243
#ifndef WIN32
241244
char* typestring = Process::readPtr(rtti + sizeof(void*));
@@ -591,6 +594,20 @@ void Process::getMemRanges(vector<t_memrange>& ranges)
591594
}
592595
#endif
593596

597+
bool Process::checkValidAddress(void* ptr)
598+
{
599+
uintptr_t addr = reinterpret_cast<uintptr_t>(ptr);
600+
auto validate = [&] (t_memrange& r) {
601+
uintptr_t lo = reinterpret_cast<uintptr_t>(r.start);
602+
uintptr_t hi = reinterpret_cast<uintptr_t>(r.end);
603+
return addr >= lo && addr < hi;
604+
};
605+
std::vector<t_memrange> mr;
606+
getMemRanges(mr);
607+
bool valid = std::any_of(mr.begin(), mr.end(), validate);
608+
return valid;
609+
}
610+
594611
uintptr_t Process::getBase()
595612
{
596613
#if WIN32

library/include/MemAccess.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ namespace DFHack
221221

222222
std::string readClassName(void* vptr)
223223
{
224-
std::map<void*, std::string>::iterator it = classNameCache.find(vptr);
224+
auto it = classNameCache.find(vptr);
225225
if (it != classNameCache.end())
226226
return it->second;
227227
return classNameCache[vptr] = doReadClassName(vptr);
@@ -247,6 +247,9 @@ namespace DFHack
247247
/// get virtual memory ranges of the process (what is mapped where)
248248
static void getMemRanges(std::vector<t_memrange>& ranges);
249249

250+
/// check if an address has a mapping
251+
bool checkValidAddress(void* ptr);
252+
250253
/// get the symbol table extension of this process
251254
std::shared_ptr<DFHack::VersionInfo> getDescriptor()
252255
{

0 commit comments

Comments
 (0)