Allocate the new vtable in its own page and make it read-only

This commit is contained in:
Jeremy Rifkin 2024-08-17 18:28:09 -05:00
parent 2692a1ee42
commit 25517d4789
No known key found for this signature in database
GPG Key ID: 19AA8270105E8EB4

View File

@ -42,43 +42,68 @@ namespace cpptrace {
constexpr size_t vtable_size = 0;
#endif
std::array<void*, vtable_size> new_vtable;
#if IS_WINDOWS
int get_page_size() {
SYSTEM_INFO info;
GetSystemInfo(&info);
return info.dwPageSize;
}
constexpr auto memory_read = PAGE_READONLY;
constexpr auto memory_readonly = PAGE_READONLY;
constexpr auto memory_readwrite = PAGE_READWRITE;
void mprotect_page(void* page, int page_size, int protections) {
DWORD old_protections;
if(!VirtualProtect(page, page_size, protections, &old_protections)) {
throw std::runtime_error(microfmt::format("VirtualProtect call failed: {}", std::system_error(GetLastError(), std::system_category()).what()));
throw std::runtime_error(
microfmt::format(
"VirtualProtect call failed: {}",
std::system_error(GetLastError(), std::system_category()).what()
)
);
}
}
void* allocate_page(int page_size) {
auto page = VirtualAlloc(nullptr, page_size, MEM_COMMIT | MEM_RESERVE, memory_readwrite);
if(!page) {
throw std::runtime_error(
microfmt::format(
"VirtualAlloc call failed: {}",
std::system_error(GetLastError(), std::system_category()).what()
)
);
}
return page;
}
#else
int get_page_size() {
return getpagesize();
}
constexpr auto memory_read = PROT_READ;
constexpr auto memory_readonly = PROT_READ;
constexpr auto memory_readwrite = PROT_READ | PROT_WRITE;
void mprotect_page(void* page, int page_size, int protections) {
if(mprotect(page, page_size, protections) != 0) {
throw std::runtime_error(microfmt::format("mprotect call failed: {}", strerror(errno)));
}
}
void* allocate_page(int page_size) {
auto page = mmap(nullptr, page_size, memory_readwrite, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if(page == MAP_FAILED) {
throw std::runtime_error(microfmt::format("mmap call failed: {}", strerror(errno)));
}
return page;
}
#endif
void clobber_type_info(const std::type_info& info) {
// allocated below, cleaned up by OS after exit
void* new_vtable_page = nullptr;
void perform_typeinfo_surgery(const std::type_info& info) {
if(vtable_size == 0) { // set to zero if we don't know what standard library we're working with
return;
}
void* type_info_pointer = const_cast<void*>(reinterpret_cast<const void*>(&info));
void* type_info_vtable_pointer = *reinterpret_cast<void**>(type_info_pointer);
void* type_info_pointer = const_cast<void*>(static_cast<const void*>(&info));
void* type_info_vtable_pointer = *static_cast<void**>(type_info_pointer);
// the type info vtable pointer points to two pointers inside the vtable, adjust it back
type_info_vtable_pointer = reinterpret_cast<void*>(reinterpret_cast<void**>(type_info_vtable_pointer) - 2);
type_info_vtable_pointer = static_cast<void*>(static_cast<void**>(type_info_vtable_pointer) - 2);
// for libstdc++ the class type info vtable looks like
// 0x7ffff7f89d18 <_ZTVN10__cxxabiv117__class_type_infoE>: 0x0000000000000000 0x00007ffff7f89d00
@ -108,17 +133,25 @@ namespace cpptrace {
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxx/include/typeinfo
// https://github.com/llvm/llvm-project/blob/648f4d0658ab00cf1e95330c8811aaea9481a274/libcxxabi/src/private_typeinfo.h
// make our own copy of the vtable
memcpy(new_vtable.data(), type_info_vtable_pointer, vtable_size * sizeof(void*));
// ninja in the custom __do_catch interceptor
new_vtable[6] = reinterpret_cast<void*>(intercept_unwind);
// make the vtable pointer for unwind_interceptor's type_info point to the new vtable
// probably 4096 but out of an abundance of caution
auto page_size = get_page_size();
if(page_size <= 0 && (page_size & (page_size - 1)) != 0) {
throw std::runtime_error(
microfmt::format("getpagesize() is not a power of 2 greater than zero (was {})", page_size)
);
}
// allocate a page for the new vtable so it can be made read-only later
new_vtable_page = allocate_page(page_size);
// make our own copy of the vtable
memcpy(new_vtable_page, type_info_vtable_pointer, vtable_size * sizeof(void*));
// ninja in the custom __do_catch interceptor
auto new_vtable = static_cast<void**>(new_vtable_page);
new_vtable[6] = reinterpret_cast<void*>(intercept_unwind);
// make the page read-only
mprotect_page(new_vtable_page, page_size, memory_readonly);
// make the vtable pointer for unwind_interceptor's type_info point to the new vtable
auto type_info_addr = reinterpret_cast<uintptr_t>(type_info_pointer);
auto page_addr = type_info_addr & ~(page_size - 1);
// make sure the memory we're going to set is within the page
@ -127,23 +160,23 @@ namespace cpptrace {
}
// TODO: Check perms of page
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, memory_readwrite);
*reinterpret_cast<void**>(type_info_pointer) = new_vtable.data() + 2;
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, memory_read);
*static_cast<void**>(type_info_pointer) = static_cast<void*>(new_vtable + 2);
mprotect_page(reinterpret_cast<void*>(page_addr), page_size, memory_readonly);
}
void do_prepare_unwind_interceptor() {
static bool did_prepare = false;
if(!did_prepare) {
try {
clobber_type_info(typeid(cpptrace::detail::unwind_interceptor));
perform_typeinfo_surgery(typeid(cpptrace::detail::unwind_interceptor));
} catch(std::exception& e) {
std::fprintf(
stderr,
"Cpptrace: Exception occurred while preparing from_current support: %s",
"Cpptrace: Exception occurred while preparing from_current support: %s\n",
e.what()
);
} catch(...) {
std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support");
std::fprintf(stderr, "Cpptrace: Unknown exception occurred while preparing from_current support\n");
}
did_prepare = true;
}
@ -155,6 +188,7 @@ namespace cpptrace {
}
#endif
}
const raw_trace& raw_trace_from_current_exception() {
return detail::current_exception_trace.get_raw_trace();
}