Eagerly detect double free and sized dealloc bugs for large sizes.
This commit is contained in:
parent
be9548f2be
commit
3de19ba401
@ -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)
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
56
test/unit/double_free.c
Normal 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
1
test/unit/double_free.h
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user