Add double free detection in thread cache for debug build

Add new runtime option `debug_double_free_max_scan` that specifies the max
number of stack entries to scan in the cache bit when trying to detect the
double free bug (currently debug build only).
This commit is contained in:
Ivan Zaitsev
2022-07-20 15:25:56 -07:00
committed by Qi Wang
parent adc70c0511
commit 36366f3c4c
8 changed files with 97 additions and 9 deletions

View File

@@ -10,13 +10,13 @@ void fake_abort(const char *message) {
}
void
test_large_double_free_pre(void) {
test_double_free_pre(void) {
safety_check_set_abort(&fake_abort);
fake_abort_called = false;
}
void
test_large_double_free_post() {
test_double_free_post() {
expect_b_eq(fake_abort_called, true, "Double-free check didn't fire.");
safety_check_set_abort(NULL);
}
@@ -29,7 +29,7 @@ TEST_BEGIN(test_large_double_free_tcache) {
*/
test_skip_if(config_debug);
test_large_double_free_pre();
test_double_free_pre();
char *ptr = malloc(SC_LARGE_MINCLASS);
bool guarded = extent_is_guarded(tsdn_fetch(), ptr);
free(ptr);
@@ -44,7 +44,7 @@ TEST_BEGIN(test_large_double_free_tcache) {
fake_abort_called = true;
}
mallctl("thread.tcache.flush", NULL, NULL, NULL, 0);
test_large_double_free_post();
test_double_free_post();
}
TEST_END
@@ -52,7 +52,7 @@ TEST_BEGIN(test_large_double_free_no_tcache) {
test_skip_if(!config_opt_safety_checks);
test_skip_if(config_debug);
test_large_double_free_pre();
test_double_free_pre();
char *ptr = mallocx(SC_LARGE_MINCLASS, MALLOCX_TCACHE_NONE);
bool guarded = extent_is_guarded(tsdn_fetch(), ptr);
dallocx(ptr, MALLOCX_TCACHE_NONE);
@@ -66,12 +66,45 @@ TEST_BEGIN(test_large_double_free_no_tcache) {
*/
fake_abort_called = true;
}
test_large_double_free_post();
test_double_free_post();
}
TEST_END
TEST_BEGIN(test_small_double_free_tcache) {
test_skip_if(!config_debug);
test_skip_if(opt_debug_double_free_max_scan == 0);
bool tcache_enabled;
size_t sz = sizeof(tcache_enabled);
assert_d_eq(
mallctl("thread.tcache.enabled", &tcache_enabled, &sz, NULL, 0), 0,
"Unexpected mallctl failure");
test_skip_if(!tcache_enabled);
test_double_free_pre();
char *ptr = malloc(1);
bool guarded = extent_is_guarded(tsdn_fetch(), ptr);
free(ptr);
if (!guarded) {
free(ptr);
} else {
/*
* Skip because guarded extents may unguard immediately on
* deallocation, in which case the second free will crash before
* reaching the intended safety check.
*/
fake_abort_called = true;
}
mallctl("thread.tcache.flush", NULL, NULL, NULL, 0);
test_double_free_post();
}
TEST_END
int
main(void) {
return test(test_large_double_free_no_tcache,
test_large_double_free_tcache);
return test(
test_large_double_free_no_tcache,
test_large_double_free_tcache,
test_small_double_free_tcache);
}

View File

@@ -325,6 +325,7 @@ TEST_BEGIN(test_mallctl_opt) {
TEST_MALLCTL_OPT(bool, prof_stats, prof);
TEST_MALLCTL_OPT(bool, prof_sys_thread_name, prof);
TEST_MALLCTL_OPT(ssize_t, lg_san_uaf_align, uaf_detection);
TEST_MALLCTL_OPT(unsigned, debug_double_free_max_scan, always);
#undef TEST_MALLCTL_OPT
}