Eagerly detect double free and sized dealloc bugs for large sizes.

This commit is contained in:
Qi Wang 2020-10-14 16:45:19 -07:00 committed by Qi Wang
parent be9548f2be
commit 3de19ba401
7 changed files with 136 additions and 23 deletions

View File

@ -204,6 +204,7 @@ TESTS_UNIT := \
$(srcroot)test/unit/counter.c \ $(srcroot)test/unit/counter.c \
$(srcroot)test/unit/decay.c \ $(srcroot)test/unit/decay.c \
$(srcroot)test/unit/div.c \ $(srcroot)test/unit/div.c \
$(srcroot)test/unit/double_free.c \
$(srcroot)test/unit/edata_cache.c \ $(srcroot)test/unit/edata_cache.c \
$(srcroot)test/unit/emitter.c \ $(srcroot)test/unit/emitter.c \
$(srcroot)test/unit/extent_quantize.c \ $(srcroot)test/unit/extent_quantize.c \
@ -310,7 +311,6 @@ TESTS_STRESS := $(srcroot)test/stress/batch_alloc.c \
$(srcroot)test/stress/microbench.c $(srcroot)test/stress/microbench.c
TESTS := $(TESTS_UNIT) $(TESTS_INTEGRATION) $(TESTS_INTEGRATION_CPP) \ TESTS := $(TESTS_UNIT) $(TESTS_INTEGRATION) $(TESTS_INTEGRATION_CPP) \
$(TESTS_ANALYZE) $(TESTS_STRESS) $(TESTS_ANALYZE) $(TESTS_STRESS)

View File

@ -5,6 +5,7 @@
#include "jemalloc/internal/jemalloc_internal_types.h" #include "jemalloc/internal/jemalloc_internal_types.h"
#include "jemalloc/internal/mutex.h" #include "jemalloc/internal/mutex.h"
#include "jemalloc/internal/rtree.h" #include "jemalloc/internal/rtree.h"
#include "jemalloc/internal/safety_check.h"
#include "jemalloc/internal/sc.h" #include "jemalloc/internal/sc.h"
#include "jemalloc/internal/sz.h" #include "jemalloc/internal/sz.h"
#include "jemalloc/internal/ticker.h" #include "jemalloc/internal/ticker.h"
@ -203,6 +204,32 @@ arena_vsalloc(tsdn_t *tsdn, const void *ptr) {
return sz_index2size(full_alloc_ctx.szind); return sz_index2size(full_alloc_ctx.szind);
} }
JEMALLOC_ALWAYS_INLINE bool
large_dalloc_safety_checks(edata_t *edata, szind_t szind) {
if (!config_opt_safety_checks) {
return false;
}
/*
* Eagerly detect double free and sized dealloc bugs for large sizes.
* The cost is low enough (as edata will be accessed anyway) to be
* enabled all the time.
*/
if (unlikely(edata_state_get(edata) != extent_state_active)) {
safety_check_fail("Invalid deallocation detected: "
"pages being freed (%p) not currently active, "
"possibly caused by double free bugs.",
(uintptr_t)edata_addr_get(edata));
return true;
}
if (unlikely(sz_index2size(szind) != edata_usize_get(edata))) {
safety_check_fail_sized_dealloc(/* current_dealloc */ true);
return true;
}
return false;
}
static inline void static inline void
arena_dalloc_large_no_tcache(tsdn_t *tsdn, void *ptr, szind_t szind) { arena_dalloc_large_no_tcache(tsdn_t *tsdn, void *ptr, szind_t szind) {
if (config_prof && unlikely(szind < SC_NBINS)) { if (config_prof && unlikely(szind < SC_NBINS)) {
@ -210,6 +237,10 @@ arena_dalloc_large_no_tcache(tsdn_t *tsdn, void *ptr, szind_t szind) {
} else { } else {
edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global,
ptr); ptr);
if (large_dalloc_safety_checks(edata, szind)) {
/* See the comment in isfree. */
return;
}
large_dalloc(tsdn, edata); large_dalloc(tsdn, edata);
} }
} }
@ -250,6 +281,10 @@ arena_dalloc_large(tsdn_t *tsdn, void *ptr, tcache_t *tcache, szind_t szind,
} else { } else {
edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global, edata_t *edata = emap_edata_lookup(tsdn, &arena_emap_global,
ptr); ptr);
if (large_dalloc_safety_checks(edata, szind)) {
/* See the comment in isfree. */
return;
}
large_dalloc(tsdn, edata); large_dalloc(tsdn, edata);
} }
} }

View File

@ -2812,7 +2812,7 @@ maybe_check_alloc_ctx(tsd_t *tsd, void *ptr, emap_alloc_ctx_t *alloc_ctx) {
&dbg_ctx); &dbg_ctx);
if (alloc_ctx->szind != dbg_ctx.szind) { if (alloc_ctx->szind != dbg_ctx.szind) {
safety_check_fail_sized_dealloc( safety_check_fail_sized_dealloc(
/* curent_dealloc */ true); /* current_dealloc */ true);
return true; return true;
} }
if (alloc_ctx->slab != dbg_ctx.slab) { if (alloc_ctx->slab != dbg_ctx.slab) {

View File

@ -428,6 +428,10 @@ tcache_bin_flush_impl(tsd_t *tsd, tcache_t *tcache, cache_bin_t *cache_bin,
dalloc_count++; dalloc_count++;
} }
} else { } else {
if (large_dalloc_safety_checks(edata, binind)) {
/* See the comment in isfree. */
continue;
}
large_dalloc_finish(tsdn, edata); large_dalloc_finish(tsdn, edata);
} }
} }

56
test/unit/double_free.c Normal file
View File

@ -0,0 +1,56 @@
#include "test/jemalloc_test.h"
#include "jemalloc/internal/safety_check.h"
bool fake_abort_called;
void fake_abort(const char *message) {
(void)message;
fake_abort_called = true;
}
void
test_large_double_free_pre(void) {
safety_check_set_abort(&fake_abort);
fake_abort_called = false;
}
void
test_large_double_free_post() {
expect_b_eq(fake_abort_called, true, "Double-free check didn't fire.");
safety_check_set_abort(NULL);
}
TEST_BEGIN(test_large_double_free_tcache) {
test_skip_if(!config_opt_safety_checks);
/*
* Skip debug builds, since too many assertions will be triggered with
* double-free before hitting the one we are interested in.
*/
test_skip_if(config_debug);
test_large_double_free_pre();
char *ptr = malloc(SC_LARGE_MINCLASS);
free(ptr);
free(ptr);
mallctl("thread.tcache.flush", NULL, NULL, NULL, 0);
test_large_double_free_post();
}
TEST_END
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();
char *ptr = mallocx(SC_LARGE_MINCLASS, MALLOCX_TCACHE_NONE);
dallocx(ptr, MALLOCX_TCACHE_NONE);
dallocx(ptr, MALLOCX_TCACHE_NONE);
test_large_double_free_post();
}
TEST_END
int
main(void) {
return test(test_large_double_free_no_tcache,
test_large_double_free_tcache);
}

1
test/unit/double_free.h Normal file
View File

@ -0,0 +1 @@

View File

@ -8,48 +8,65 @@ void fake_abort(const char *message) {
fake_abort_called = true; fake_abort_called = true;
} }
#define SIZE1 SC_SMALL_MAXCLASS #define SMALL_SIZE1 SC_SMALL_MAXCLASS
#define SIZE2 (SC_SMALL_MAXCLASS / 2) #define SMALL_SIZE2 (SC_SMALL_MAXCLASS / 2)
TEST_BEGIN(test_invalid_size_sdallocx) { #define LARGE_SIZE1 SC_LARGE_MINCLASS
test_skip_if(!config_opt_size_checks); #define LARGE_SIZE2 (LARGE_SIZE1 * 2)
void *
test_invalid_size_pre(size_t sz) {
safety_check_set_abort(&fake_abort); safety_check_set_abort(&fake_abort);
fake_abort_called = false; fake_abort_called = false;
void *ptr = malloc(SIZE1); void *ptr = malloc(sz);
assert_ptr_not_null(ptr, "Unexpected failure"); assert_ptr_not_null(ptr, "Unexpected failure");
sdallocx(ptr, SIZE2, 0);
expect_true(fake_abort_called, "Safety check didn't fire");
return ptr;
}
void
test_invalid_size_post(void) {
expect_true(fake_abort_called, "Safety check didn't fire");
safety_check_set_abort(NULL); safety_check_set_abort(NULL);
} }
TEST_BEGIN(test_invalid_size_sdallocx) {
test_skip_if(!config_opt_size_checks);
void *ptr = test_invalid_size_pre(SMALL_SIZE1);
sdallocx(ptr, SMALL_SIZE2, 0);
test_invalid_size_post();
ptr = test_invalid_size_pre(LARGE_SIZE1);
sdallocx(ptr, LARGE_SIZE2, 0);
test_invalid_size_post();
}
TEST_END TEST_END
TEST_BEGIN(test_invalid_size_sdallocx_nonzero_flag) { TEST_BEGIN(test_invalid_size_sdallocx_nonzero_flag) {
test_skip_if(!config_opt_size_checks); test_skip_if(!config_opt_size_checks);
safety_check_set_abort(&fake_abort);
fake_abort_called = false; void *ptr = test_invalid_size_pre(SMALL_SIZE1);
void *ptr = malloc(SIZE1); sdallocx(ptr, SMALL_SIZE2, MALLOCX_TCACHE_NONE);
assert_ptr_not_null(ptr, "Unexpected failure"); test_invalid_size_post();
sdallocx(ptr, SIZE2, MALLOCX_TCACHE_NONE);
expect_true(fake_abort_called, "Safety check didn't fire");
safety_check_set_abort(NULL); ptr = test_invalid_size_pre(LARGE_SIZE1);
sdallocx(ptr, LARGE_SIZE2, MALLOCX_TCACHE_NONE);
test_invalid_size_post();
} }
TEST_END TEST_END
TEST_BEGIN(test_invalid_size_sdallocx_noflags) { TEST_BEGIN(test_invalid_size_sdallocx_noflags) {
test_skip_if(!config_opt_size_checks); test_skip_if(!config_opt_size_checks);
safety_check_set_abort(&fake_abort);
fake_abort_called = false; void *ptr = test_invalid_size_pre(SMALL_SIZE1);
void *ptr = malloc(SIZE1); je_sdallocx_noflags(ptr, SMALL_SIZE2);
assert_ptr_not_null(ptr, "Unexpected failure"); test_invalid_size_post();
je_sdallocx_noflags(ptr, SIZE2);
expect_true(fake_abort_called, "Safety check didn't fire");
safety_check_set_abort(NULL); ptr = test_invalid_size_pre(LARGE_SIZE1);
je_sdallocx_noflags(ptr, LARGE_SIZE2);
test_invalid_size_post();
} }
TEST_END TEST_END