#include "check-structures-sanity.h" bool Checker::is_valid_dereference(const QueueItem & item, const CheckedStructure & cs, size_t size, bool quiet) { auto base = const_cast(item.ptr); if (!base) { // cannot dereference null pointer, but not an error return false; } // assumes MALLOC_PERTURB_=45 #ifdef DFHACK64 #define UNINIT_PTR 0xd2d2d2d2d2d2d2d2 #define FAIL_PTR(message) FAIL(stl_sprintf("0x%016zx: ", reinterpret_cast(base)) << message) #else #define UNINIT_PTR 0xd2d2d2d2 #define FAIL_PTR(message) FAIL(stl_sprintf("0x%08zx: ", reinterpret_cast(base)) << message) #endif if (uintptr_t(base) == UNINIT_PTR) { if (!quiet) { FAIL_PTR("uninitialized pointer"); } return false; } bool found = true; auto expected_start = base; size_t remaining_size = size; while (found) { found = false; for (auto & range : mapped) { if (!range.isInRange(expected_start)) { continue; } found = true; if (!range.valid || !range.read) { if (!quiet) { FAIL_PTR("pointer to invalid memory range"); } return false; } auto expected_end = const_cast(PTR_ADD(expected_start, remaining_size - 1)); if (size && !range.isInRange(expected_end)) { auto next_start = PTR_ADD(range.end, 1); remaining_size -= ptrdiff_t(next_start) - ptrdiff_t(expected_start); expected_start = const_cast(next_start); break; } return true; } } if (quiet) { return false; } if (expected_start == base) { FAIL_PTR("pointer not in any mapped range"); } else { FAIL_PTR("pointer exceeds mapped memory bounds (size " << size << ")"); } return false; #undef FAIL_PTR } int64_t Checker::get_int_value(const QueueItem & item, type_identity *type, bool quiet) { if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else if (type == df::identity_traits::get()) { return int64_t(validate_and_dereference(item, quiet)); } else if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else if (type == df::identity_traits::get()) { return validate_and_dereference(item, quiet); } else { UNEXPECTED; return 0; } } const char *Checker::get_vtable_name(const QueueItem & item, const CheckedStructure & cs, bool quiet) { auto vtable = validate_and_dereference(QueueItem(item, "?vtable?", item.ptr), quiet); if (!vtable) return nullptr; auto info = validate_and_dereference(QueueItem(item, "?vtable?.info", vtable - 1), quiet); if (!info) return nullptr; #ifdef WIN32 #ifdef DFHACK64 void *base; if (!RtlPcToFileHeader(info, &base)) return nullptr; const char *typeinfo = reinterpret_cast(base) + reinterpret_cast(info)[3]; const char *name = typeinfo + 16; #else const char *name = reinterpret_cast(info) + 8; #endif #else auto name = validate_and_dereference(QueueItem(item, "?vtable?.info.name", info + 1), quiet); #endif for (auto & range : mapped) { if (!range.isInRange(const_cast(name))) { continue; } if (!range.valid || !range.read) { if (!quiet) { FAIL("pointer to invalid memory range"); } return nullptr; } const char *first_letter = nullptr; bool letter = false; for (const char *p = name; ; p++) { if (!range.isInRange(const_cast(p))) { return nullptr; } if ((*p >= 'a' && *p <= 'z') || *p == '_') { if (!letter) { first_letter = p; } letter = true; } else if (!*p) { return first_letter; } } } return nullptr; } std::pair Checker::validate_vector_size(const QueueItem & item, const CheckedStructure & cs, bool quiet) { using ret_type = std::pair; struct vector_data { uintptr_t start; uintptr_t finish; uintptr_t end_of_storage; }; vector_data vector = *reinterpret_cast(item.ptr); ptrdiff_t length = vector.finish - vector.start; ptrdiff_t capacity = vector.end_of_storage - vector.start; bool local_ok = true; auto item_size = cs.identity ? cs.identity->byte_size() : 0; if (!item_size) { item_size = 1; local_ok = false; } if (vector.start > vector.finish) { local_ok = false; if (!quiet) { FAIL("vector length is negative (" << (length / ptrdiff_t(item_size)) << ")"); } } if (vector.start > vector.end_of_storage) { local_ok = false; if (!quiet) { FAIL("vector capacity is negative (" << (capacity / ptrdiff_t(item_size)) << ")"); } } else if (vector.finish > vector.end_of_storage) { local_ok = false; if (!quiet) { FAIL("vector capacity (" << (capacity / ptrdiff_t(item_size)) << ") is less than its length (" << (length / ptrdiff_t(item_size)) << ")"); } } size_t ulength = size_t(length); size_t ucapacity = size_t(capacity); if (ulength % item_size != 0) { local_ok = false; if (!quiet) { FAIL("vector length is non-integer (" << (ulength / item_size) << " items plus " << (ulength % item_size) << " bytes)"); } } if (ucapacity % item_size != 0) { local_ok = false; if (!quiet) { FAIL("vector capacity is non-integer (" << (ucapacity / item_size) << " items plus " << (ucapacity % item_size) << " bytes)"); } } if (local_ok && capacity && !vector.start) { if (!quiet) { FAIL("vector has null pointer but capacity " << (capacity / item_size)); } return ret_type(); } auto start_ptr = reinterpret_cast(vector.start); if (capacity && !is_valid_dereference(QueueItem(item, "?items?", start_ptr), CheckedStructure(cs.identity, capacity / item_size), quiet)) { local_ok = false; } if (!local_ok) { return ret_type(); } CheckedStructure ret_cs(cs.identity, ulength / item_size); ret_cs.allocated_count = ucapacity / item_size; return std::make_pair(start_ptr, ret_cs); } size_t Checker::get_allocated_size(const QueueItem & item) { if (!sizes) { return 0; } if (uintptr_t(item.ptr) % 32 != 16) { return 0; } uint32_t tag = *reinterpret_cast(PTR_ADD(item.ptr, -8)); if (tag == 0xdfdf4ac8) { return *reinterpret_cast(PTR_ADD(item.ptr, -16)); } return 0; } #ifndef WIN32 const std::string *Checker::validate_stl_string_pointer(const void *const* base) { std::string empty_string; if (*base == *reinterpret_cast(&empty_string)) { return reinterpret_cast(base); } const struct string_data_inner { size_t length; size_t capacity; int32_t refcount; } *str_data = static_cast(*base) - 1; if (!is_valid_dereference(QueueItem("str", PTR_ADD(str_data, -16)), 16, true)) { return nullptr; } uint32_t tag = *reinterpret_cast(PTR_ADD(str_data, -8)); if (tag == 0xdfdf4ac8) { size_t allocated_size = *reinterpret_cast(PTR_ADD(str_data, -16)); size_t expected_size = sizeof(*str_data) + str_data->capacity + 1; if (allocated_size != expected_size) { return nullptr; } } else { return nullptr; } if (str_data->capacity < str_data->length) { return nullptr; } const char *ptr = reinterpret_cast(*base); for (size_t i = 0; i < str_data->length; i++) { if (!*ptr++) { return nullptr; } } if (*ptr++) { return nullptr; } return reinterpret_cast(base); } #endif const char *const *Checker::get_enum_item_key(enum_identity *identity, int64_t value) { size_t index; if (auto cplx = identity->getComplex()) { auto it = cplx->value_index_map.find(value); if (it == cplx->value_index_map.cend()) { return nullptr; } index = it->second; } else { if (value < identity->getFirstItem() || value > identity->getLastItem()) { return nullptr; } index = value - identity->getFirstItem(); } return &identity->getKeys()[index]; }