Implement use-after-free detection using junk and stash.

On deallocation, sampled pointers (specially aligned) get junked and stashed
into tcache (to prevent immediate reuse).  The expected behavior is to have
read-after-free corrupted and stopped by the junk-filling, while
write-after-free is checked when flushing the stashed pointers.
This commit is contained in:
Qi Wang
2021-10-18 17:33:15 -07:00
committed by Qi Wang
parent 06aac61c4b
commit b75822bc6e
22 changed files with 793 additions and 42 deletions

View File

@@ -10,6 +10,15 @@
size_t opt_san_guard_large = SAN_GUARD_LARGE_EVERY_N_EXTENTS_DEFAULT;
size_t opt_san_guard_small = SAN_GUARD_SMALL_EVERY_N_EXTENTS_DEFAULT;
/* Aligned (-1 is off) ptrs will be junked & stashed on dealloc. */
ssize_t opt_lg_san_uaf_align = SAN_LG_UAF_ALIGN_DEFAULT;
/*
* Initialized in san_init(). When disabled, the mask is set to (uintptr_t)-1
* to always fail the nonfast_align check.
*/
uintptr_t san_cache_bin_nonfast_mask = SAN_CACHE_BIN_NONFAST_MASK_DEFAULT;
static inline void
san_find_guarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2,
uintptr_t *addr, size_t size, bool left, bool right) {
@@ -141,8 +150,59 @@ san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
/* right */ true, /* remap */ false);
}
static bool
san_stashed_corrupted(void *ptr, size_t size) {
if (san_junk_ptr_should_slow()) {
for (size_t i = 0; i < size; i++) {
if (((char *)ptr)[i] != (char)uaf_detect_junk) {
return true;
}
}
return false;
}
void *first, *mid, *last;
san_junk_ptr_locations(ptr, size, &first, &mid, &last);
if (*(uintptr_t *)first != uaf_detect_junk ||
*(uintptr_t *)mid != uaf_detect_junk ||
*(uintptr_t *)last != uaf_detect_junk) {
return true;
}
return false;
}
void
san_check_stashed_ptrs(void **ptrs, size_t nstashed, size_t usize) {
/*
* Verify that the junked-filled & stashed pointers remain unchanged, to
* detect write-after-free.
*/
for (size_t n = 0; n < nstashed; n++) {
void *stashed = ptrs[n];
assert(stashed != NULL);
assert(cache_bin_nonfast_aligned(stashed));
if (unlikely(san_stashed_corrupted(stashed, usize))) {
safety_check_fail("<jemalloc>: Write-after-free "
"detected on deallocated pointer %p (size %zu).\n",
stashed, usize);
}
}
}
void
tsd_san_init(tsd_t *tsd) {
*tsd_san_extents_until_guard_smallp_get(tsd) = opt_san_guard_small;
*tsd_san_extents_until_guard_largep_get(tsd) = opt_san_guard_large;
}
void
san_init(ssize_t lg_san_uaf_align) {
assert(lg_san_uaf_align == -1 || lg_san_uaf_align >= LG_PAGE);
if (lg_san_uaf_align == -1) {
san_cache_bin_nonfast_mask = (uintptr_t)-1;
return;
}
san_cache_bin_nonfast_mask = ((uintptr_t)1 << lg_san_uaf_align) - 1;
}