blob: 6e51291135c75d9ec18b34bddfa7dc708bd30f1d [file] [log] [blame]
#include "jemalloc/internal/jemalloc_preamble.h"
#include "jemalloc/internal/jemalloc_internal_includes.h"
#include "jemalloc/internal/assert.h"
#include "jemalloc/internal/ehooks.h"
#include "jemalloc/internal/san.h"
#include "jemalloc/internal/tsd.h"
/* The sanitizer options. */
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) {
assert(!edata_guarded_get(edata));
assert(size % PAGE == 0);
*addr = (uintptr_t)edata_base_get(edata);
if (left) {
*guard1 = *addr;
*addr += SAN_PAGE_GUARD;
} else {
*guard1 = 0;
}
if (right) {
*guard2 = *addr + size;
} else {
*guard2 = 0;
}
}
static inline void
san_find_unguarded_addr(edata_t *edata, uintptr_t *guard1, uintptr_t *guard2,
uintptr_t *addr, size_t size, bool left, bool right) {
assert(edata_guarded_get(edata));
assert(size % PAGE == 0);
*addr = (uintptr_t)edata_base_get(edata);
if (right) {
*guard2 = *addr + size;
} else {
*guard2 = 0;
}
if (left) {
*guard1 = *addr - SAN_PAGE_GUARD;
assert(*guard1 != 0);
*addr = *guard1;
} else {
*guard1 = 0;
}
}
void
san_guard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata, emap_t *emap,
bool left, bool right, bool remap) {
assert(left || right);
if (remap) {
emap_deregister_boundary(tsdn, emap, edata);
}
size_t size_with_guards = edata_size_get(edata);
size_t usize = (left && right)
? san_two_side_unguarded_sz(size_with_guards)
: san_one_side_unguarded_sz(size_with_guards);
uintptr_t guard1, guard2, addr;
san_find_guarded_addr(edata, &guard1, &guard2, &addr, usize, left,
right);
assert(edata_state_get(edata) == extent_state_active);
ehooks_guard(tsdn, ehooks, (void *)guard1, (void *)guard2);
/* Update the guarded addr and usable size of the edata. */
edata_size_set(edata, usize);
edata_addr_set(edata, (void *)addr);
edata_guarded_set(edata, true);
if (remap) {
emap_register_boundary(tsdn, emap, edata, SC_NSIZES,
/* slab */ false);
}
}
static void
san_unguard_pages_impl(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
emap_t *emap, bool left, bool right, bool remap) {
assert(left || right);
/* Remove the inner boundary which no longer exists. */
if (remap) {
assert(edata_state_get(edata) == extent_state_active);
emap_deregister_boundary(tsdn, emap, edata);
} else {
assert(edata_state_get(edata) == extent_state_retained);
}
size_t size = edata_size_get(edata);
size_t size_with_guards = (left && right)
? san_two_side_guarded_sz(size)
: san_one_side_guarded_sz(size);
uintptr_t guard1, guard2, addr;
san_find_unguarded_addr(edata, &guard1, &guard2, &addr, size, left,
right);
ehooks_unguard(tsdn, ehooks, (void *)guard1, (void *)guard2);
/* Update the true addr and usable size of the edata. */
edata_size_set(edata, size_with_guards);
edata_addr_set(edata, (void *)addr);
edata_guarded_set(edata, false);
/*
* Then re-register the outer boundary including the guards, if
* requested.
*/
if (remap) {
emap_register_boundary(tsdn, emap, edata, SC_NSIZES,
/* slab */ false);
}
}
void
san_unguard_pages(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
emap_t *emap, bool left, bool right) {
san_unguard_pages_impl(tsdn, ehooks, edata, emap, left, right,
/* remap */ true);
}
void
san_unguard_pages_pre_destroy(tsdn_t *tsdn, ehooks_t *ehooks, edata_t *edata,
emap_t *emap) {
emap_assert_not_mapped(tsdn, emap, edata);
/*
* We don't want to touch the emap of about to be destroyed extents, as
* they have been unmapped upon eviction from the retained ecache. Also,
* we unguard the extents to the right, because retained extents only
* own their right guard page per san_bump_alloc's logic.
*/
san_unguard_pages_impl(tsdn, ehooks, edata, emap, /* left */ false,
/* 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;
}