259 lines
7.5 KiB
C
259 lines
7.5 KiB
C
#include "test/jemalloc_test.h"
|
|
#include "test/arena_util.h"
|
|
|
|
#include "jemalloc/internal/cache_bin.h"
|
|
#include "jemalloc/internal/safety_check.h"
|
|
|
|
static size_t san_uaf_align;
|
|
|
|
static bool fake_abort_called;
|
|
void fake_abort(const char *message) {
|
|
(void)message;
|
|
fake_abort_called = true;
|
|
}
|
|
|
|
static void
|
|
test_write_after_free_pre(void) {
|
|
safety_check_set_abort(&fake_abort);
|
|
fake_abort_called = false;
|
|
}
|
|
|
|
static void
|
|
test_write_after_free_post(void) {
|
|
assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
|
|
0, "Unexpected tcache flush failure");
|
|
expect_true(fake_abort_called, "Use-after-free check didn't fire.");
|
|
safety_check_set_abort(NULL);
|
|
}
|
|
|
|
static bool
|
|
uaf_detection_enabled(void) {
|
|
if (!config_uaf_detection) {
|
|
return false;
|
|
}
|
|
|
|
ssize_t lg_san_uaf_align;
|
|
size_t sz = sizeof(lg_san_uaf_align);
|
|
assert_d_eq(mallctl("opt.lg_san_uaf_align", &lg_san_uaf_align, &sz,
|
|
NULL, 0), 0, "Unexpected mallctl failure");
|
|
if (lg_san_uaf_align < 0) {
|
|
return false;
|
|
}
|
|
assert_zd_ge(lg_san_uaf_align, LG_PAGE, "san_uaf_align out of range");
|
|
san_uaf_align = (size_t)1 << lg_san_uaf_align;
|
|
|
|
bool tcache_enabled;
|
|
sz = sizeof(tcache_enabled);
|
|
assert_d_eq(mallctl("thread.tcache.enabled", &tcache_enabled, &sz, NULL,
|
|
0), 0, "Unexpected mallctl failure");
|
|
if (!tcache_enabled) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static size_t
|
|
read_tcache_stashed_bytes(unsigned arena_ind) {
|
|
if (!config_stats) {
|
|
return 0;
|
|
}
|
|
|
|
uint64_t epoch;
|
|
assert_d_eq(mallctl("epoch", NULL, NULL, (void *)&epoch, sizeof(epoch)),
|
|
0, "Unexpected mallctl() failure");
|
|
|
|
size_t tcache_stashed_bytes;
|
|
size_t sz = sizeof(tcache_stashed_bytes);
|
|
assert_d_eq(mallctl(
|
|
"stats.arenas." STRINGIFY(MALLCTL_ARENAS_ALL)
|
|
".tcache_stashed_bytes", &tcache_stashed_bytes, &sz, NULL, 0), 0,
|
|
"Unexpected mallctl failure");
|
|
|
|
return tcache_stashed_bytes;
|
|
}
|
|
|
|
static void
|
|
test_use_after_free(size_t alloc_size, bool write_after_free) {
|
|
void *ptr = (void *)(uintptr_t)san_uaf_align;
|
|
assert_true(cache_bin_nonfast_aligned(ptr), "Wrong alignment");
|
|
ptr = (void *)((uintptr_t)123 * (uintptr_t)san_uaf_align);
|
|
assert_true(cache_bin_nonfast_aligned(ptr), "Wrong alignment");
|
|
ptr = (void *)((uintptr_t)san_uaf_align + 1);
|
|
assert_false(cache_bin_nonfast_aligned(ptr), "Wrong alignment");
|
|
|
|
/*
|
|
* Disable purging (-1) so that all dirty pages remain committed, to
|
|
* make use-after-free tolerable.
|
|
*/
|
|
unsigned arena_ind = do_arena_create(-1, -1);
|
|
int flags = MALLOCX_ARENA(arena_ind) | MALLOCX_TCACHE_NONE;
|
|
|
|
size_t n_max = san_uaf_align * 2;
|
|
void **items = mallocx(n_max * sizeof(void *), flags);
|
|
assert_ptr_not_null(items, "Unexpected mallocx failure");
|
|
|
|
bool found = false;
|
|
size_t iter = 0;
|
|
char magic = 's';
|
|
assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
|
|
0, "Unexpected tcache flush failure");
|
|
while (!found) {
|
|
ptr = mallocx(alloc_size, flags);
|
|
assert_ptr_not_null(ptr, "Unexpected mallocx failure");
|
|
|
|
found = cache_bin_nonfast_aligned(ptr);
|
|
*(char *)ptr = magic;
|
|
items[iter] = ptr;
|
|
assert_zu_lt(iter++, n_max, "No aligned ptr found");
|
|
}
|
|
|
|
if (write_after_free) {
|
|
test_write_after_free_pre();
|
|
}
|
|
bool junked = false;
|
|
while (iter-- != 0) {
|
|
char *volatile mem = items[iter];
|
|
assert_c_eq(*mem, magic, "Unexpected memory content");
|
|
size_t stashed_before = read_tcache_stashed_bytes(arena_ind);
|
|
free(mem);
|
|
if (*mem != magic) {
|
|
junked = true;
|
|
assert_c_eq(*mem, (char)uaf_detect_junk,
|
|
"Unexpected junk-filling bytes");
|
|
if (write_after_free) {
|
|
*(char *)mem = magic + 1;
|
|
}
|
|
|
|
size_t stashed_after = read_tcache_stashed_bytes(
|
|
arena_ind);
|
|
/*
|
|
* An edge case is the deallocation above triggering the
|
|
* tcache GC event, in which case the stashed pointers
|
|
* may get flushed immediately, before returning from
|
|
* free(). Treat these cases as checked already.
|
|
*/
|
|
if (stashed_after <= stashed_before) {
|
|
fake_abort_called = true;
|
|
}
|
|
}
|
|
/* Flush tcache (including stashed). */
|
|
assert_d_eq(mallctl("thread.tcache.flush", NULL, NULL, NULL, 0),
|
|
0, "Unexpected tcache flush failure");
|
|
}
|
|
expect_true(junked, "Aligned ptr not junked");
|
|
if (write_after_free) {
|
|
test_write_after_free_post();
|
|
}
|
|
|
|
dallocx(items, flags);
|
|
do_arena_destroy(arena_ind);
|
|
}
|
|
|
|
TEST_BEGIN(test_read_after_free) {
|
|
test_skip_if(!uaf_detection_enabled());
|
|
|
|
test_use_after_free(sizeof(void *), /* write_after_free */ false);
|
|
test_use_after_free(sizeof(void *) + 1, /* write_after_free */ false);
|
|
test_use_after_free(16, /* write_after_free */ false);
|
|
test_use_after_free(20, /* write_after_free */ false);
|
|
test_use_after_free(32, /* write_after_free */ false);
|
|
test_use_after_free(33, /* write_after_free */ false);
|
|
test_use_after_free(48, /* write_after_free */ false);
|
|
test_use_after_free(64, /* write_after_free */ false);
|
|
test_use_after_free(65, /* write_after_free */ false);
|
|
test_use_after_free(129, /* write_after_free */ false);
|
|
test_use_after_free(255, /* write_after_free */ false);
|
|
test_use_after_free(256, /* write_after_free */ false);
|
|
}
|
|
TEST_END
|
|
|
|
TEST_BEGIN(test_write_after_free) {
|
|
test_skip_if(!uaf_detection_enabled());
|
|
|
|
test_use_after_free(sizeof(void *), /* write_after_free */ true);
|
|
test_use_after_free(sizeof(void *) + 1, /* write_after_free */ true);
|
|
test_use_after_free(16, /* write_after_free */ true);
|
|
test_use_after_free(20, /* write_after_free */ true);
|
|
test_use_after_free(32, /* write_after_free */ true);
|
|
test_use_after_free(33, /* write_after_free */ true);
|
|
test_use_after_free(48, /* write_after_free */ true);
|
|
test_use_after_free(64, /* write_after_free */ true);
|
|
test_use_after_free(65, /* write_after_free */ true);
|
|
test_use_after_free(129, /* write_after_free */ true);
|
|
test_use_after_free(255, /* write_after_free */ true);
|
|
test_use_after_free(256, /* write_after_free */ true);
|
|
}
|
|
TEST_END
|
|
|
|
static bool
|
|
check_allocated_intact(void **allocated, size_t n_alloc) {
|
|
for (unsigned i = 0; i < n_alloc; i++) {
|
|
void *ptr = *(void **)allocated[i];
|
|
bool found = false;
|
|
for (unsigned j = 0; j < n_alloc; j++) {
|
|
if (ptr == allocated[j]) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
TEST_BEGIN(test_use_after_free_integration) {
|
|
test_skip_if(!uaf_detection_enabled());
|
|
|
|
unsigned arena_ind = do_arena_create(-1, -1);
|
|
int flags = MALLOCX_ARENA(arena_ind);
|
|
|
|
size_t n_alloc = san_uaf_align * 2;
|
|
void **allocated = mallocx(n_alloc * sizeof(void *), flags);
|
|
assert_ptr_not_null(allocated, "Unexpected mallocx failure");
|
|
|
|
for (unsigned i = 0; i < n_alloc; i++) {
|
|
allocated[i] = mallocx(sizeof(void *) * 8, flags);
|
|
assert_ptr_not_null(allocated[i], "Unexpected mallocx failure");
|
|
if (i > 0) {
|
|
/* Emulate a circular list. */
|
|
*(void **)allocated[i] = allocated[i - 1];
|
|
}
|
|
}
|
|
*(void **)allocated[0] = allocated[n_alloc - 1];
|
|
expect_true(check_allocated_intact(allocated, n_alloc),
|
|
"Allocated data corrupted");
|
|
|
|
for (unsigned i = 0; i < n_alloc; i++) {
|
|
free(allocated[i]);
|
|
}
|
|
/* Read-after-free */
|
|
expect_false(check_allocated_intact(allocated, n_alloc),
|
|
"Junk-filling not detected");
|
|
|
|
test_write_after_free_pre();
|
|
for (unsigned i = 0; i < n_alloc; i++) {
|
|
allocated[i] = mallocx(sizeof(void *), flags);
|
|
assert_ptr_not_null(allocated[i], "Unexpected mallocx failure");
|
|
*(void **)allocated[i] = (void *)(uintptr_t)i;
|
|
}
|
|
/* Write-after-free */
|
|
for (unsigned i = 0; i < n_alloc; i++) {
|
|
free(allocated[i]);
|
|
*(void **)allocated[i] = NULL;
|
|
}
|
|
test_write_after_free_post();
|
|
}
|
|
TEST_END
|
|
|
|
int
|
|
main(void) {
|
|
return test(
|
|
test_read_after_free,
|
|
test_write_after_free,
|
|
test_use_after_free_integration);
|
|
}
|