Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: Add a fuzzing harness for heap allocator #931

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -605,3 +605,26 @@ if (MI_OVERRIDE)
endif()
endif()
endif()


# -----------------------------------------------------------------------------
# Build fuzz tests
# -----------------------------------------------------------------------------
if(DEFINED ENV{LIB_FUZZING_ENGINE})
set(FUZZING_ENGINE $ENV{LIB_FUZZING_ENGINE})
set(FUZZING_COMPILE_FLAGS "")
set(FUZZING_LINK_FLAGS "${FUZZING_ENGINE}")
else()
set(FUZZING_COMPILE_FLAGS "-fsanitize=fuzzer,address")
set(FUZZING_LINK_FLAGS "-fsanitize=fuzzer,address")
endif()

if('${CMAKE_CXX_COMPILER_ID}' STREQUAL 'Clang')
## fuzz testing
add_executable(fuzz-random-alloc test/fuzz-random-alloc.c)
target_link_libraries(fuzz-random-alloc PUBLIC mimalloc)
set_target_properties(fuzz-random-alloc PROPERTIES
COMPILE_FLAGS "${FUZZING_COMPILE_FLAGS}"
LINK_FLAGS "${FUZZING_LINK_FLAGS}"
)
endif()
158 changes: 158 additions & 0 deletions test/fuzz-random-alloc.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
#include <mimalloc.h>
#include <stddef.h>
#include <stdint.h>
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define MAX_ALLOC (1024 * 512)
#define MAX_COUNT 32
#define ALLOCATION_POINTERS 1024

#define DEBUG 0
#define debug_print(fmt, ...) \
do { if (DEBUG) fprintf(stderr, fmt, __VA_ARGS__); } while (0)

typedef enum {
CALLOC = 0,
FREE,
MALLOC,
MALLOCN,
REALLOC,
REALLOCF,
REALLOCN,
ZALLOC,
LAST_NOP,
} allocation_op_t;

typedef struct {
uint32_t count;
uint32_t size;
} arg_t;

typedef struct {
// The index of the pointer to apply this operation too.
uint32_t index;
// The arguments to use in the operation.
arg_t any_arg;
// The type of operation to apply.
allocation_op_t type;
} fuzzing_operation_t;

void debug_print_operation(fuzzing_operation_t *operation) {
const char *names[] = {"CALLOC", "FREE", "MALLOC", "MALLOCN", "REALLOC", "REALLOCF", "REALLOCN", "ZALLOC", "LAST_NOP"};
debug_print("index: %d, arg.count: %d, arg.size: %d, type: %s\n", operation->index, operation->any_arg.count, operation->any_arg.size, names[operation->type]);
}

#define FUZZING_OPERATION_DATA_SIZE sizeof(fuzzing_operation_t)

int init_fuzzing_operation(fuzzing_operation_t* out, const uint8_t* fuzzed_data, size_t len) {
fuzzing_operation_t result = {0, {0,0},FREE};

// Return a free operation if we don't have enough data to construct
// a full operation.
if(sizeof(fuzzing_operation_t) > len) {
*out = result;
return 0;
}

// Randomly populate operation using fuzzed data.
memcpy(&result, fuzzed_data, sizeof(fuzzing_operation_t));

// Fix up bounds for args and indicies. Randomly copying fuzzed data may result
// in out of bounds indicies or the fuzzer trying to allocate way too much data.
result.index %= ALLOCATION_POINTERS;
result.any_arg.count %= MAX_COUNT;
result.any_arg.size %= MAX_ALLOC;
result.type = (uint8_t)result.type % (uint8_t)LAST_NOP;
*out = result;

return sizeof(fuzzing_operation_t);
}


int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
mi_heap_t * heap = mi_heap_new();
void* allocation_ptrs[ALLOCATION_POINTERS] = {NULL};

for(int i = 0; i < size; i = i + FUZZING_OPERATION_DATA_SIZE) {
fuzzing_operation_t operation = {0, {0,0}, FREE};
init_fuzzing_operation(&operation, data + i, size - i);

debug_print_operation(&operation);

switch(operation.type) {
case CALLOC:
if(allocation_ptrs[operation.index] == NULL) {
debug_print("%s\n","CALLOC");
allocation_ptrs[operation.index] = mi_heap_calloc(heap, operation.any_arg.count, operation.any_arg.size);
} else {
debug_print("%s\n","CALLOC conditions not met");
}
break;
case FREE:
// Can be ptr or be NULL so we don't need to check first.
mi_free(allocation_ptrs[operation.index]);
allocation_ptrs[operation.index] = NULL;
break;
case MALLOC:
if(allocation_ptrs[operation.index] == NULL){
debug_print("%s\n","MALLOC");
allocation_ptrs[operation.index] = mi_heap_malloc(heap, operation.any_arg.size);
} else {
debug_print("%s\n","MALLOC conditions not met");
}
break;
case MALLOCN:
if(allocation_ptrs[operation.index] == NULL){
debug_print("%s\n","MALLOCN");
allocation_ptrs[operation.index] = mi_heap_mallocn(heap, operation.any_arg.count, operation.any_arg.size);
} else {
debug_print("%s\n","MALLOCN conditions not met");
}
break;
case REALLOC:
if(allocation_ptrs[operation.index] != NULL){
debug_print("%s\n","REALLOC");
allocation_ptrs[operation.index] = mi_heap_realloc(heap, allocation_ptrs[operation.index], operation.any_arg.size);
} else {
debug_print("%s\n","REALLOC conditions not met");
}
break;
case REALLOCN:
if(allocation_ptrs[operation.index] != NULL){
debug_print("%s\n","REALLOCN");
allocation_ptrs[operation.index] = mi_heap_reallocn(heap, allocation_ptrs[operation.index], operation.any_arg.count, operation.any_arg.size);
} else {
debug_print("%s\n","REALLOCN conditions not met");
}
break;
case REALLOCF:
if(allocation_ptrs[operation.index] != NULL){
debug_print("%s\n","REALLOCF");
allocation_ptrs[operation.index] = mi_heap_reallocf(heap, allocation_ptrs[operation.index], operation.any_arg.size);
} else {
debug_print("%s\n","REALLOCF conditions not met");
}
break;
case ZALLOC:
if(allocation_ptrs[operation.index] == NULL){
debug_print("%s\n","ZALLOC");
allocation_ptrs[operation.index] = mi_heap_zalloc(heap, operation.any_arg.size);
} else {
debug_print("%s\n","ZALLOC conditions not met");
}
break;
case LAST_NOP:
// No-op
break;
default:
mi_heap_destroy(heap);
exit(1);
break;
}
}
mi_heap_destroy(heap);
return 0;
}
Loading