add string checking support to check-structures-sanity

try to avoid crashes on bad vtables
develop
Ben Lubar 2020-02-05 18:24:10 -06:00
parent 3d037db789
commit d7d0923044
No known key found for this signature in database
GPG Key ID: 92939677AB59EDA4
1 changed files with 146 additions and 37 deletions

@ -40,7 +40,8 @@ class Checker
bool ok;
bool check_access(void *, type_identity *);
void *safe_dereference(void *, ptrdiff_t = 0);
bool check_access(void *, type_identity *, size_t);
bool check_vtable(void *, type_identity *);
void check_global(global_identity *);
void check_fields(void *, const struct_field_info *);
void check_field(void *, const struct_field_info *);
@ -52,6 +53,7 @@ class Checker
void check_deque(void *, container_identity *, type_identity *);
void check_bit_container(void *, container_identity *);
void check_virtual(void *, virtual_identity *);
void check_primitive(void *, type_identity *);
class Scope
{
@ -129,6 +131,11 @@ Checker::Scope::~Scope()
#define PTR_ADD(base, offset) (reinterpret_cast<void *>(reinterpret_cast<uintptr_t>((base)) + static_cast<ptrdiff_t>((offset))))
bool Checker::check_access(void *base, type_identity *identity)
{
return check_access(base, identity, identity ? identity->byte_size() : 0);
}
bool Checker::check_access(void *base, type_identity *identity, size_t size)
{
if (!base)
{
@ -149,7 +156,7 @@ bool Checker::check_access(void *base, type_identity *identity)
return false;
}
if (identity && !range.isInRange(PTR_ADD(base, identity->byte_size())))
if (!range.isInRange(PTR_ADD(base, size)))
{
FAIL("pointer exceeds mapped memory bounds");
return false;
@ -162,36 +169,60 @@ bool Checker::check_access(void *base, type_identity *identity)
return false;
}
void *Checker::safe_dereference(void *base, ptrdiff_t offset)
bool Checker::check_vtable(void *vtable, type_identity *identity)
{
if (!base)
{
return nullptr;
}
if (!check_access(PTR_ADD(vtable, -sizeof(void *)), identity, sizeof(void *)))
return false;
char **info = *(reinterpret_cast<char ***>(vtable) - 1);
#ifdef WIN32
if (!check_access(PTR_ADD(info, 12), identity, 4))
return false;
type_identity *identity = nullptr; // void
#ifdef DFHACK64
void *base;
if (!RtlPcToFileHeader(info, &base))
return false;
char *typeinfo = reinterpret_cast<char *>(base) + reinterpret_cast<int32_t *>(info)[3];
char *name = typeinfo + 16;
#else
char *name = reinterpret_cast<char *>(info)[3];
#endif
#else
if (!check_access(info + 1, identity, sizeof(void *)))
return false;
char *name = *(info + 1);
#endif
for (auto & range : mapped)
{
if (!range.isInRange(base))
if (!range.isInRange(name))
{
continue;
}
if (!range.valid || !range.read)
{
return nullptr;
FAIL("pointer to invalid memory range");
return false;
}
if (!range.isInRange(PTR_ADD(base, offset)))
for (char *p = name; ; p++)
{
if (!range.isInRange(p))
{
return nullptr;
return false;
}
return *reinterpret_cast<void **>(PTR_ADD(base, offset));
if (!*p)
{
return true;
}
}
}
return nullptr;
return false;
}
void Checker::check_global(global_identity *identity)
@ -236,7 +267,7 @@ void Checker::check_field(void *base, const struct_field_info *field)
// can't happen - already checked
break;
case struct_field_info::PRIMITIVE:
// don't need to check primitives
check_primitive(base, field->type);
break;
case struct_field_info::STATIC_STRING:
// TODO: check static strings?
@ -285,7 +316,7 @@ void Checker::check_item(void *base, type_identity *identity)
// ignore
break;
case IDTYPE_PRIMITIVE:
// don't need to check primitives
check_primitive(base, identity);
break;
case IDTYPE_POINTER:
check_pointer(base, static_cast<pointer_identity *>(identity)->getTarget());
@ -338,6 +369,11 @@ void Checker::check_pointer(void *base, type_identity *identity)
return;
}
if (!check_access(base, identity, sizeof(void *)))
{
return;
}
auto ptr = *reinterpret_cast<void **>(base);
if (!check_access(ptr, identity))
{
@ -349,11 +385,6 @@ void Checker::check_pointer(void *base, type_identity *identity)
void Checker::check_static_array(void *base, type_identity *identity, size_t count)
{
if (identity->isPrimitive())
{
return;
}
size_t item_size = identity->byte_size();
for (size_t i = 0; i < count; i++)
@ -464,7 +495,7 @@ void Checker::check_vector(void *base, container_identity *identity, type_identi
FAIL("vector capacity is non-integer (" << (ucapacity / item_size) << " items plus " << (ucapacity % item_size) << " bytes)");
}
if (local_ok)
if (local_ok && check_access(reinterpret_cast<void *>(vector.start), identity, length))
{
check_static_array(reinterpret_cast<void *>(vector.start), item_identity, ulength / item_size);
}
@ -512,26 +543,21 @@ void Checker::check_virtual(void *base, virtual_identity *identity)
return;
}
if (!base)
if (!check_access(base, identity))
{
// null pointer
return;
}
auto ptr = reinterpret_cast<virtual_ptr>(base);
if (!identity->is_instance(ptr))
{
FAIL("vtable is not a known subclass");
// write separately to avoid losing the previous line if this segfaults
void *vtable = *reinterpret_cast<void **>(base);
if (!vtable)
if (!check_vtable(vtable, identity))
{
FAIL("(vtable is null)");
UNEXPECTED;
FAIL("invalid vtable pointer");
return;
}
else if (!identity->is_instance(reinterpret_cast<virtual_ptr>(base)))
{
auto class_name = Core::getInstance().p->readClassName(vtable);
FAIL("(subclass is " << class_name << ")");
FAIL("vtable is not a known subclass (subclass is " << class_name << ")");
return;
}
@ -539,3 +565,86 @@ void Checker::check_virtual(void *base, virtual_identity *identity)
check_fields(base, identity->getFields());
}
void Checker::check_primitive(void *base, type_identity *identity)
{
if (identity->getFullName() != "string")
{
// we only care about string "primitives"
return;
}
if (!seen_addr.insert(base).second)
{
return;
}
if (!check_access(base, identity))
{
return;
}
#ifdef WIN32
struct string_data
{
union
{
uintptr_t start;
char local_data[16];
};
size_t length;
size_t capacity;
};
#else
struct string_data
{
struct string_data_inner
{
size_t length;
size_t capacity;
size_t refcount;
} *ptr;
};
#endif
if (identity->byte_size() != sizeof(string_data))
{
UNEXPECTED;
return;
}
auto string = reinterpret_cast<string_data *>(base);
#ifdef WIN32
bool is_local = string->capacity < 16;
char *start = is_local ? &string->local_data[0] : reinterpret_cast<char *>(string->start);
ptrdiff_t length = string->length;
ptrdiff_t capacity = string->capacity;
#else
if (!check_access(string->ptr, identity, 1))
{
return;
}
if (!check_access(string->ptr - 1, identity, sizeof(*string->ptr)))
{
return;
}
char *start = reinterpret_cast<char *>(string->ptr);
ptrdiff_t length = (string->ptr - 1)->length;
ptrdiff_t capacity = (string->ptr - 1)->capacity;
#endif
if (length < 0)
{
FAIL("string length is negative (" << length << ")");
}
if (capacity < 0)
{
FAIL("string capacity is negative (" << capacity << ")");
}
else if (capacity < length)
{
FAIL("string capacity (" << capacity << ") is less than length (" << length << ")");
}
check_access(start, identity, capacity);
}