Add quarantine unit tests.
Verify that freed regions are quarantined, and that redzone corruption is detected. Introduce a testing idiom for intercepting/replacing internal functions. In this case the replaced function is ordinarily a static function, but the idiom should work similarly for library-private functions.
This commit is contained in:
parent
eca367b779
commit
0d6c5d8bd0
@ -110,7 +110,8 @@ C_UTIL_INTEGRATION_SRCS := $(srcroot)src/util.c
|
||||
TESTS_UNIT := $(srcroot)test/unit/bitmap.c $(srcroot)test/unit/ckh.c \
|
||||
$(srcroot)test/unit/hash.c $(srcroot)test/unit/math.c \
|
||||
$(srcroot)test/unit/mq.c $(srcroot)test/unit/mtx.c \
|
||||
$(srcroot)test/unit/SFMT.c $(srcroot)test/unit/tsd.c
|
||||
$(srcroot)test/unit/quarantine.c $(srcroot)test/unit/SFMT.c \
|
||||
$(srcroot)test/unit/tsd.c
|
||||
TESTS_INTEGRATION := $(srcroot)test/integration/aligned_alloc.c \
|
||||
$(srcroot)test/integration/allocated.c \
|
||||
$(srcroot)test/integration/mallocx.c \
|
||||
|
@ -405,7 +405,13 @@ void arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin,
|
||||
size_t binind, uint64_t prof_accumbytes);
|
||||
void arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info,
|
||||
bool zero);
|
||||
#ifdef JEMALLOC_JET
|
||||
typedef void (arena_redzone_corruption_t)(void *, size_t, bool, size_t,
|
||||
uint8_t);
|
||||
extern arena_redzone_corruption_t *arena_redzone_corruption_fptr;
|
||||
#endif
|
||||
void arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info);
|
||||
void arena_quarantine_junk_small(void *ptr, size_t usize);
|
||||
void *arena_malloc_small(arena_t *arena, size_t size, bool zero);
|
||||
void *arena_malloc_large(arena_t *arena, size_t size, bool zero);
|
||||
void *arena_palloc(arena_t *arena, size_t size, size_t alignment, bool zero);
|
||||
|
@ -50,8 +50,10 @@ arena_prof_ctx_set
|
||||
arena_prof_promoted
|
||||
arena_ptr_small_binind_get
|
||||
arena_purge_all
|
||||
arena_quarantine_junk_small
|
||||
arena_ralloc
|
||||
arena_ralloc_no_move
|
||||
arena_redzone_corruption
|
||||
arena_run_regind
|
||||
arena_salloc
|
||||
arena_stats_merge
|
||||
|
66
src/arena.c
66
src/arena.c
@ -1432,8 +1432,28 @@ arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info, bool zero)
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info)
|
||||
#ifdef JEMALLOC_JET
|
||||
#undef arena_redzone_corruption
|
||||
#define arena_redzone_corruption JEMALLOC_N(arena_redzone_corruption_impl)
|
||||
#endif
|
||||
static void
|
||||
arena_redzone_corruption(void *ptr, size_t usize, bool after,
|
||||
size_t offset, uint8_t byte)
|
||||
{
|
||||
|
||||
malloc_printf("<jemalloc>: Corrupt redzone %zu byte%s %s %p "
|
||||
"(size %zu), byte=%#x\n", offset, (offset == 1) ? "" : "s",
|
||||
after ? "after" : "before", ptr, usize, byte);
|
||||
}
|
||||
#ifdef JEMALLOC_JET
|
||||
arena_redzone_corruption_t *arena_redzone_corruption_fptr =
|
||||
arena_redzone_corruption;
|
||||
#undef arena_redzone_corruption
|
||||
#define arena_redzone_corruption arena_redzone_corruption_fptr
|
||||
#endif
|
||||
|
||||
static void
|
||||
arena_redzones_validate(void *ptr, arena_bin_info_t *bin_info, bool reset)
|
||||
{
|
||||
size_t size = bin_info->reg_size;
|
||||
size_t redzone_size = bin_info->redzone_size;
|
||||
@ -1441,30 +1461,52 @@ arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info)
|
||||
bool error = false;
|
||||
|
||||
for (i = 1; i <= redzone_size; i++) {
|
||||
unsigned byte;
|
||||
if ((byte = *(uint8_t *)((uintptr_t)ptr - i)) != 0xa5) {
|
||||
uint8_t *byte = (uint8_t *)((uintptr_t)ptr - i);
|
||||
if (*byte != 0xa5) {
|
||||
error = true;
|
||||
malloc_printf("<jemalloc>: Corrupt redzone "
|
||||
"%zu byte%s before %p (size %zu), byte=%#x\n", i,
|
||||
(i == 1) ? "" : "s", ptr, size, byte);
|
||||
arena_redzone_corruption(ptr, size, false, i, *byte);
|
||||
if (reset)
|
||||
*byte = 0xa5;
|
||||
}
|
||||
}
|
||||
for (i = 0; i < redzone_size; i++) {
|
||||
unsigned byte;
|
||||
if ((byte = *(uint8_t *)((uintptr_t)ptr + size + i)) != 0xa5) {
|
||||
uint8_t *byte = (uint8_t *)((uintptr_t)ptr + size + i);
|
||||
if (*byte != 0xa5) {
|
||||
error = true;
|
||||
malloc_printf("<jemalloc>: Corrupt redzone "
|
||||
"%zu byte%s after end of %p (size %zu), byte=%#x\n",
|
||||
i, (i == 1) ? "" : "s", ptr, size, byte);
|
||||
arena_redzone_corruption(ptr, size, true, i, *byte);
|
||||
if (reset)
|
||||
*byte = 0xa5;
|
||||
}
|
||||
}
|
||||
if (opt_abort && error)
|
||||
abort();
|
||||
}
|
||||
|
||||
void
|
||||
arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info)
|
||||
{
|
||||
size_t redzone_size = bin_info->redzone_size;
|
||||
|
||||
arena_redzones_validate(ptr, bin_info, false);
|
||||
memset((void *)((uintptr_t)ptr - redzone_size), 0x5a,
|
||||
bin_info->reg_interval);
|
||||
}
|
||||
|
||||
void
|
||||
arena_quarantine_junk_small(void *ptr, size_t usize)
|
||||
{
|
||||
size_t binind;
|
||||
arena_bin_info_t *bin_info;
|
||||
cassert(config_fill);
|
||||
assert(opt_junk);
|
||||
assert(opt_quarantine);
|
||||
assert(usize <= SMALL_MAXCLASS);
|
||||
|
||||
binind = SMALL_SIZE2BIN(usize);
|
||||
bin_info = &arena_bin_info[binind];
|
||||
arena_redzones_validate(ptr, bin_info, true);
|
||||
}
|
||||
|
||||
void *
|
||||
arena_malloc_small(arena_t *arena, size_t size, bool zero)
|
||||
{
|
||||
|
@ -141,8 +141,17 @@ quarantine(void *ptr)
|
||||
obj->usize = usize;
|
||||
quarantine->curbytes += usize;
|
||||
quarantine->curobjs++;
|
||||
if (opt_junk)
|
||||
if (config_fill && opt_junk) {
|
||||
/*
|
||||
* Only do redzone validation if Valgrind isn't in
|
||||
* operation.
|
||||
*/
|
||||
if ((config_valgrind == false || opt_valgrind == false)
|
||||
&& usize <= SMALL_MAXCLASS)
|
||||
arena_quarantine_junk_small(ptr, usize);
|
||||
else
|
||||
memset(ptr, 0x5a, usize);
|
||||
}
|
||||
} else {
|
||||
assert(quarantine->curbytes == 0);
|
||||
idalloc(ptr);
|
||||
|
@ -187,12 +187,22 @@ f(void) \
|
||||
p_test_init(#f);
|
||||
|
||||
#define TEST_END \
|
||||
goto label_test_end; \
|
||||
label_test_end: \
|
||||
p_test_fini(); \
|
||||
}
|
||||
|
||||
#define test(tests...) \
|
||||
p_test(tests, NULL)
|
||||
|
||||
#define test_skip_if(e) do { \
|
||||
if (e) { \
|
||||
test_skip("%s:%s:%d: Test skipped: (%s)", \
|
||||
__func__, __FILE__, __LINE__, #e); \
|
||||
goto label_test_end; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void test_skip(const char *format, ...) JEMALLOC_ATTR(format(printf, 1, 2));
|
||||
void test_fail(const char *format, ...) JEMALLOC_ATTR(format(printf, 1, 2));
|
||||
|
||||
|
108
test/unit/quarantine.c
Normal file
108
test/unit/quarantine.c
Normal file
@ -0,0 +1,108 @@
|
||||
#include "test/jemalloc_test.h"
|
||||
|
||||
#define QUARANTINE_SIZE 8192
|
||||
#define STRINGIFY_HELPER(x) #x
|
||||
#define STRINGIFY(x) STRINGIFY_HELPER(x)
|
||||
|
||||
#ifdef JEMALLOC_FILL
|
||||
const char *malloc_conf = "abort:false,junk:true,redzone:true,quarantine:"
|
||||
STRINGIFY(QUARANTINE_SIZE);
|
||||
#endif
|
||||
|
||||
void
|
||||
quarantine_clear(void)
|
||||
{
|
||||
void *p;
|
||||
|
||||
p = mallocx(QUARANTINE_SIZE*2, 0);
|
||||
assert_ptr_not_null(p, "Unexpected mallocx() failure");
|
||||
dallocx(p, 0);
|
||||
}
|
||||
|
||||
TEST_BEGIN(test_quarantine)
|
||||
{
|
||||
#define SZ 256
|
||||
#define NQUARANTINED (QUARANTINE_SIZE/SZ)
|
||||
void *quarantined[NQUARANTINED+1];
|
||||
size_t i, j;
|
||||
|
||||
test_skip_if(!config_fill);
|
||||
|
||||
assert_zu_eq(nallocx(SZ, 0), SZ,
|
||||
"SZ=%zu does not precisely equal a size class", SZ);
|
||||
|
||||
quarantine_clear();
|
||||
|
||||
/*
|
||||
* Allocate enough regions to completely fill the quarantine, plus one
|
||||
* more. The last iteration occurs with a completely full quarantine,
|
||||
* but no regions should be drained from the quarantine until the last
|
||||
* deallocation occurs. Therefore no region recycling should occur
|
||||
* until after this loop completes.
|
||||
*/
|
||||
for (i = 0; i < NQUARANTINED+1; i++) {
|
||||
void *p = mallocx(SZ, 0);
|
||||
assert_ptr_not_null(p, "Unexpected mallocx() failure");
|
||||
quarantined[i] = p;
|
||||
dallocx(p, 0);
|
||||
for (j = 0; j < i; j++) {
|
||||
assert_ptr_ne(p, quarantined[j],
|
||||
"Quarantined region recycled too early; "
|
||||
"i=%zu, j=%zu", i, j);
|
||||
}
|
||||
}
|
||||
#undef NQUARANTINED
|
||||
#undef SZ
|
||||
}
|
||||
TEST_END
|
||||
|
||||
static bool detected_redzone_corruption;
|
||||
|
||||
static void
|
||||
arena_redzone_corruption_replacement(void *ptr, size_t usize, bool after,
|
||||
size_t offset, uint8_t byte)
|
||||
{
|
||||
|
||||
detected_redzone_corruption = true;
|
||||
}
|
||||
|
||||
TEST_BEGIN(test_quarantine_redzone)
|
||||
{
|
||||
char *s;
|
||||
arena_redzone_corruption_t *arena_redzone_corruption_orig;
|
||||
|
||||
test_skip_if(!config_fill);
|
||||
|
||||
arena_redzone_corruption_orig = arena_redzone_corruption_fptr;
|
||||
arena_redzone_corruption_fptr = arena_redzone_corruption_replacement;
|
||||
|
||||
/* Test underflow. */
|
||||
detected_redzone_corruption = false;
|
||||
s = (char *)mallocx(1, 0);
|
||||
assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
|
||||
s[-1] = 0xbb;
|
||||
dallocx(s, 0);
|
||||
assert_true(detected_redzone_corruption,
|
||||
"Did not detect redzone corruption");
|
||||
|
||||
/* Test overflow. */
|
||||
detected_redzone_corruption = false;
|
||||
s = (char *)mallocx(1, 0);
|
||||
assert_ptr_not_null((void *)s, "Unexpected mallocx() failure");
|
||||
s[sallocx(s, 0)] = 0xbb;
|
||||
dallocx(s, 0);
|
||||
assert_true(detected_redzone_corruption,
|
||||
"Did not detect redzone corruption");
|
||||
|
||||
arena_redzone_corruption_fptr = arena_redzone_corruption_orig;
|
||||
}
|
||||
TEST_END
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
|
||||
return (test(
|
||||
test_quarantine,
|
||||
test_quarantine_redzone));
|
||||
}
|
Loading…
Reference in New Issue
Block a user