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:
Jason Evans 2013-12-17 15:14:36 -08:00
parent eca367b779
commit 0d6c5d8bd0
7 changed files with 193 additions and 15 deletions

View File

@ -110,7 +110,8 @@ C_UTIL_INTEGRATION_SRCS := $(srcroot)src/util.c
TESTS_UNIT := $(srcroot)test/unit/bitmap.c $(srcroot)test/unit/ckh.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/hash.c $(srcroot)test/unit/math.c \
$(srcroot)test/unit/mq.c $(srcroot)test/unit/mtx.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 \ TESTS_INTEGRATION := $(srcroot)test/integration/aligned_alloc.c \
$(srcroot)test/integration/allocated.c \ $(srcroot)test/integration/allocated.c \
$(srcroot)test/integration/mallocx.c \ $(srcroot)test/integration/mallocx.c \

View File

@ -405,7 +405,13 @@ void arena_tcache_fill_small(arena_t *arena, tcache_bin_t *tbin,
size_t binind, uint64_t prof_accumbytes); size_t binind, uint64_t prof_accumbytes);
void arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info, void arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info,
bool zero); 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_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_small(arena_t *arena, size_t size, bool zero);
void *arena_malloc_large(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); void *arena_palloc(arena_t *arena, size_t size, size_t alignment, bool zero);

View File

@ -50,8 +50,10 @@ arena_prof_ctx_set
arena_prof_promoted arena_prof_promoted
arena_ptr_small_binind_get arena_ptr_small_binind_get
arena_purge_all arena_purge_all
arena_quarantine_junk_small
arena_ralloc arena_ralloc
arena_ralloc_no_move arena_ralloc_no_move
arena_redzone_corruption
arena_run_regind arena_run_regind
arena_salloc arena_salloc
arena_stats_merge arena_stats_merge

View File

@ -1432,8 +1432,28 @@ arena_alloc_junk_small(void *ptr, arena_bin_info_t *bin_info, bool zero)
} }
} }
void #ifdef JEMALLOC_JET
arena_dalloc_junk_small(void *ptr, arena_bin_info_t *bin_info) #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 size = bin_info->reg_size;
size_t redzone_size = bin_info->redzone_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; bool error = false;
for (i = 1; i <= redzone_size; i++) { for (i = 1; i <= redzone_size; i++) {
unsigned byte; uint8_t *byte = (uint8_t *)((uintptr_t)ptr - i);
if ((byte = *(uint8_t *)((uintptr_t)ptr - i)) != 0xa5) { if (*byte != 0xa5) {
error = true; error = true;
malloc_printf("<jemalloc>: Corrupt redzone " arena_redzone_corruption(ptr, size, false, i, *byte);
"%zu byte%s before %p (size %zu), byte=%#x\n", i, if (reset)
(i == 1) ? "" : "s", ptr, size, byte); *byte = 0xa5;
} }
} }
for (i = 0; i < redzone_size; i++) { for (i = 0; i < redzone_size; i++) {
unsigned byte; uint8_t *byte = (uint8_t *)((uintptr_t)ptr + size + i);
if ((byte = *(uint8_t *)((uintptr_t)ptr + size + i)) != 0xa5) { if (*byte != 0xa5) {
error = true; error = true;
malloc_printf("<jemalloc>: Corrupt redzone " arena_redzone_corruption(ptr, size, true, i, *byte);
"%zu byte%s after end of %p (size %zu), byte=%#x\n", if (reset)
i, (i == 1) ? "" : "s", ptr, size, byte); *byte = 0xa5;
} }
} }
if (opt_abort && error) if (opt_abort && error)
abort(); 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, memset((void *)((uintptr_t)ptr - redzone_size), 0x5a,
bin_info->reg_interval); 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 * void *
arena_malloc_small(arena_t *arena, size_t size, bool zero) arena_malloc_small(arena_t *arena, size_t size, bool zero)
{ {

View File

@ -141,8 +141,17 @@ quarantine(void *ptr)
obj->usize = usize; obj->usize = usize;
quarantine->curbytes += usize; quarantine->curbytes += usize;
quarantine->curobjs++; quarantine->curobjs++;
if (opt_junk) if (config_fill && opt_junk) {
memset(ptr, 0x5a, usize); /*
* 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 { } else {
assert(quarantine->curbytes == 0); assert(quarantine->curbytes == 0);
idalloc(ptr); idalloc(ptr);

View File

@ -187,12 +187,22 @@ f(void) \
p_test_init(#f); p_test_init(#f);
#define TEST_END \ #define TEST_END \
goto label_test_end; \
label_test_end: \
p_test_fini(); \ p_test_fini(); \
} }
#define test(tests...) \ #define test(tests...) \
p_test(tests, NULL) 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_skip(const char *format, ...) JEMALLOC_ATTR(format(printf, 1, 2));
void test_fail(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
View 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));
}