check-structures-sanity: warn when an integer might be a pointer

develop
Ben Lubar 2020-03-11 11:20:10 -05:00
parent e2138a6cc2
commit eabff06eef
No known key found for this signature in database
GPG Key ID: 92939677AB59EDA4
5 changed files with 76 additions and 15 deletions

@ -34,6 +34,7 @@ struct CheckedStructure
type_identity *identity; type_identity *identity;
size_t count; size_t count;
enum_identity *eid; enum_identity *eid;
bool ptr_is_array;
CheckedStructure(); CheckedStructure();
explicit CheckedStructure(type_identity *, size_t = 0); explicit CheckedStructure(type_identity *, size_t = 0);
@ -76,6 +77,7 @@ public:
bool sizes; bool sizes;
bool unnamed; bool unnamed;
bool failfast; bool failfast;
bool noprogress;
Checker(color_ostream & out); Checker(color_ostream & out);
bool queue_item(const QueueItem & item, const CheckedStructure & cs); bool queue_item(const QueueItem & item, const CheckedStructure & cs);
@ -133,6 +135,7 @@ private:
void check_unknown_pointer(const QueueItem &); void check_unknown_pointer(const QueueItem &);
void check_stl_vector(const QueueItem &, type_identity *, type_identity *); void check_stl_vector(const QueueItem &, type_identity *, type_identity *);
void check_stl_string(const QueueItem &); void check_stl_string(const QueueItem &);
void check_possible_pointer(const QueueItem &, const CheckedStructure &);
friend struct CheckedStructure; friend struct CheckedStructure;
static type_identity *wrap_in_pointer(type_identity *); static type_identity *wrap_in_pointer(type_identity *);

@ -11,7 +11,8 @@ Checker::Checker(color_ostream & out) :
enums(false), enums(false),
sizes(false), sizes(false),
unnamed(false), unnamed(false),
failfast(false) failfast(false),
noprogress(!out.is_console())
{ {
Core::getInstance().p->getMemRanges(mapped); Core::getInstance().p->getMemRanges(mapped);
} }
@ -252,6 +253,8 @@ void Checker::dispatch_primitive(const QueueItem & item, const CheckedStructure
} }
else if (auto int_id = dynamic_cast<df::integer_identity_base *>(cs.identity)) else if (auto int_id = dynamic_cast<df::integer_identity_base *>(cs.identity))
{ {
check_possible_pointer(item, cs);
// TODO check ints? // TODO check ints?
} }
else if (auto float_id = dynamic_cast<df::float_identity_base *>(cs.identity)) else if (auto float_id = dynamic_cast<df::float_identity_base *>(cs.identity))
@ -288,22 +291,46 @@ void Checker::dispatch_pointer(const QueueItem & item, const CheckedStructure &
return; return;
} }
CheckedStructure target_cs(target);
if (auto allocated_size = get_allocated_size(target_item))
{
auto expected_size = target->byte_size();
if (target->type() == IDTYPE_CLASS && get_vtable_name(target_item, target_cs, true))
{
auto target_virtual = virtual_identity::get(static_cast<virtual_ptr>(const_cast<void *>(target_item.ptr)));
expected_size = target_virtual->byte_size();
}
if (cs.ptr_is_array && allocated_size % expected_size == 0)
{
target_cs.count = allocated_size / expected_size;
}
else if (allocated_size > expected_size)
{
FAIL("identified structure is too small (expected " << expected_size << " bytes, but there are " << allocated_size << " bytes allocated)");
}
else if (allocated_size < expected_size)
{
FAIL("identified structure is too big (expected " << expected_size << " bytes, but there are " << allocated_size << " bytes allocated)");
}
}
// 256 is an arbitrarily chosen size threshold // 256 is an arbitrarily chosen size threshold
if (cs.count || target->byte_size() <= 256) if (cs.count || target->byte_size() <= 256)
{ {
// target is small, or we are inside an array of pointers; handle now // target is small, or we are inside an array of pointers; handle now
if (queue_item(target_item, CheckedStructure(target))) if (queue_item(target_item, target_cs))
{ {
// we insert it into the queue to make sure we're not stuck in a loop // we insert it into the queue to make sure we're not stuck in a loop
// get it back out of the queue to prevent the queue growing too big // get it back out of the queue to prevent the queue growing too big
queue.pop_back(); queue.pop_back();
dispatch_item(target_item, CheckedStructure(target)); dispatch_item(target_item, target_cs);
} }
} }
else else
{ {
// target is large and not part of an array; handle later // target is large and not part of an array; handle later
queue_item(target_item, CheckedStructure(target)); queue_item(target_item, target_cs);
} }
} }
void Checker::dispatch_container(const QueueItem & item, const CheckedStructure & cs) void Checker::dispatch_container(const QueueItem & item, const CheckedStructure & cs)
@ -350,6 +377,8 @@ void Checker::dispatch_bit_container(const QueueItem & item, const CheckedStruct
} }
void Checker::dispatch_bitfield(const QueueItem & item, const CheckedStructure & cs) void Checker::dispatch_bitfield(const QueueItem & item, const CheckedStructure & cs)
{ {
check_possible_pointer(item, cs);
if (!enums) if (!enums)
{ {
return; return;
@ -418,6 +447,8 @@ void Checker::dispatch_bitfield(const QueueItem & item, const CheckedStructure &
} }
void Checker::dispatch_enum(const QueueItem & item, const CheckedStructure & cs) void Checker::dispatch_enum(const QueueItem & item, const CheckedStructure & cs)
{ {
check_possible_pointer(item, cs);
if (!enums) if (!enums)
{ {
return; return;
@ -669,7 +700,7 @@ void Checker::dispatch_untagged_union(const QueueItem & item, const CheckedStruc
void Checker::check_unknown_pointer(const QueueItem & item) void Checker::check_unknown_pointer(const QueueItem & item)
{ {
CheckedStructure cs(df::identity_traits<void *>::get()); const static CheckedStructure cs(nullptr, 0);
if (auto allocated_size = get_allocated_size(item)) if (auto allocated_size = get_allocated_size(item))
{ {
FAIL("pointer to a block of " << allocated_size << " bytes of allocated memory"); FAIL("pointer to a block of " << allocated_size << " bytes of allocated memory");
@ -680,13 +711,14 @@ void Checker::check_unknown_pointer(const QueueItem & item)
// check recursively if it's the right size for a pointer // check recursively if it's the right size for a pointer
// or if it starts with what might be a valid pointer // or if it starts with what might be a valid pointer
if (allocated_size == sizeof(void *) || (allocated_size > sizeof(void *) && is_valid_dereference(item, cs, true)))
{
QueueItem ptr_item(item, "?ptr?", item.ptr); QueueItem ptr_item(item, "?ptr?", item.ptr);
if (queue_item(ptr_item, cs)) if (allocated_size == sizeof(void *) || (allocated_size > sizeof(void *) && is_valid_dereference(ptr_item, 1, true)))
{
CheckedStructure ptr_cs(df::identity_traits<void *>::get());
if (queue_item(ptr_item, ptr_cs))
{ {
queue.pop_back(); queue.pop_back();
dispatch_pointer(ptr_item, cs); dispatch_pointer(ptr_item, ptr_cs);
} }
} }
} }
@ -805,3 +837,15 @@ void Checker::check_stl_string(const QueueItem & item)
} }
#endif #endif
} }
void Checker::check_possible_pointer(const QueueItem & item, const CheckedStructure & cs)
{
if (sizes && uintptr_t(item.ptr) % sizeof(void *) == 0)
{
auto ptr = validate_and_dereference<const void *>(item, true);
QueueItem ptr_item(item, "?maybe_pointer?", ptr);
if (ptr && is_valid_dereference(ptr_item, 1, true))
{
check_unknown_pointer(ptr_item);
}
}
}

@ -37,6 +37,7 @@ static command_result command(color_ostream & out, std::vector<std::string> & pa
CoreSuspender suspend; CoreSuspender suspend;
Checker checker(out); Checker checker(out);
// check parameters with values first // check parameters with values first
#define VAL_PARAM(name, expr_using_value) \ #define VAL_PARAM(name, expr_using_value) \
auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \ auto name ## _idx = std::find(parameters.begin(), parameters.end(), "-" #name); \
@ -73,6 +74,7 @@ static command_result command(color_ostream & out, std::vector<std::string> & pa
BOOL_PARAM(sizes); BOOL_PARAM(sizes);
BOOL_PARAM(unnamed); BOOL_PARAM(unnamed);
BOOL_PARAM(failfast); BOOL_PARAM(failfast);
BOOL_PARAM(noprogress);
#undef BOOL_PARAM #undef BOOL_PARAM
if (parameters.size() > 1) if (parameters.size() > 1)
@ -117,7 +119,7 @@ static command_result command(color_ostream & out, std::vector<std::string> & pa
while (checker.process_queue()) while (checker.process_queue())
{ {
if (out.is_console()) if (!checker.noprogress)
{ {
out << "checked " << checker.checked_count << " fields\r" << std::flush; out << "checked " << checker.checked_count << " fields\r" << std::flush;
} }

@ -19,15 +19,14 @@ CheckedStructure::CheckedStructure() :
{ {
} }
CheckedStructure::CheckedStructure(type_identity *identity, size_t count) : CheckedStructure::CheckedStructure(type_identity *identity, size_t count) :
identity(identity), CheckedStructure(identity, count, nullptr)
count(count),
eid(nullptr)
{ {
} }
CheckedStructure::CheckedStructure(type_identity *identity, size_t count, enum_identity *eid) : CheckedStructure::CheckedStructure(type_identity *identity, size_t count, enum_identity *eid) :
identity(identity), identity(identity),
count(count), count(count),
eid(eid) eid(eid),
ptr_is_array(false)
{ {
} }
CheckedStructure::CheckedStructure(const struct_field_info *field) : CheckedStructure::CheckedStructure(const struct_field_info *field) :
@ -60,7 +59,10 @@ CheckedStructure::CheckedStructure(const struct_field_info *field) :
count = field->count; count = field->count;
break; break;
case struct_field_info::POINTER: case struct_field_info::POINTER:
// TODO: check flags (stored in field->count) if (field->count & PTRFLAG_IS_ARRAY)
{
ptr_is_array = true;
}
identity = Checker::wrap_in_pointer(field->type); identity = Checker::wrap_in_pointer(field->type);
break; break;
case struct_field_info::STATIC_ARRAY: case struct_field_info::STATIC_ARRAY:

@ -283,6 +283,11 @@ size_t Checker::get_allocated_size(const QueueItem & item)
return 0; return 0;
} }
if (uintptr_t(item.ptr) % 32 != 16)
{
return 0;
}
uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(item.ptr, -8)); uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(item.ptr, -8));
if (tag == 0xdfdf4ac8) if (tag == 0xdfdf4ac8)
{ {
@ -308,6 +313,11 @@ const std::string *Checker::validate_stl_string_pointer(const void *const* base)
int32_t refcount; int32_t refcount;
} *str_data = static_cast<const string_data_inner *>(*base) - 1; } *str_data = static_cast<const string_data_inner *>(*base) - 1;
if (!is_valid_dereference(QueueItem("str", PTR_ADD(str_data, -16)), 16, true))
{
return nullptr;
}
uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(str_data, -8)); uint32_t tag = *reinterpret_cast<const uint32_t *>(PTR_ADD(str_data, -8));
if (tag == 0xdfdf4ac8) if (tag == 0xdfdf4ac8)
{ {