From 32827fa8d17e476faa4b11b7d6bafe0ef779a07a Mon Sep 17 00:00:00 2001 From: Byron Hawkins Date: Thu, 30 Jul 2015 19:24:19 -0400 Subject: [PATCH] i#1734 Create Dr. Fuzz: add exception API This CL adds the exception API, along with a general iterator for walking the set of fuzz targets currently live on the call stack. It also adds a single-threaded test of the new fault handling API, and the Dr. Fuzz entries in the doxygen tree. Review-URL: https://codereview.appspot.com/257120043 --- common/utils.h | 2 +- docs/CMakeLists.txt | 4 +- docs/CMake_doxyfile.cmake | 2 +- docs/Doxyfile.in | 2 +- drfuzz/CMakeLists.txt | 4 + drfuzz/drfuzz.c | 549 ++++++++++++++++++++--- drfuzz/drfuzz.dox | 85 ++++ drfuzz/drfuzz.h | 316 ++++++++++++- drmemory/docs/release.dox | 12 +- framework/drmf.dox | 2 + tests/framework/CMakeLists.txt | 34 +- tests/framework/drfuzz_app_segfault.c | 67 +++ tests/framework/drfuzz_client_repeat.c | 35 +- tests/framework/drfuzz_client_segfault.c | 266 +++++++++++ umbra/umbra.dox | 4 +- 15 files changed, 1293 insertions(+), 91 deletions(-) create mode 100644 drfuzz/drfuzz.dox create mode 100644 tests/framework/drfuzz_app_segfault.c create mode 100644 tests/framework/drfuzz_client_segfault.c diff --git a/common/utils.h b/common/utils.h index 3d9a6f179..d9ce08ab4 100644 --- a/common/utils.h +++ b/common/utils.h @@ -939,7 +939,7 @@ heap_dump_stats(file_t f); char * drmem_strdup(const char *src, heapstat_t type); -/* note: Guarantees that even if src overflows n, the allocated buffer will be +/* note: Guarantees that even if src overflows max, the allocated buffer will be * large enough for max characters plus the null-terminator. */ char * diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 2e68f5cbf..2f2a9deb9 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -61,6 +61,7 @@ set(headers ${PROJECT_SOURCE_DIR}/drsyscall/drsyscall.h ${PROJECT_SOURCE_DIR}/drsymcache/drsymcache.h ${PROJECT_SOURCE_DIR}/umbra/umbra.h + ${PROJECT_SOURCE_DIR}/drfuzz/drfuzz.h ${PROJECT_SOURCE_DIR}/framework/public.h) # some defines are set by DR config files so we must add here @@ -104,7 +105,8 @@ add_custom_command( # When adding new DRMF extension docs, also add to Doxyfile.in and to # DR's api/docs/ext.gendox. ARGS -D srcdir=${PROJECT_SOURCE_DIR}/${tooldir}/docs - -D srclist=${PROJECT_SOURCE_DIR}/framework/drmf.dox\;${PROJECT_SOURCE_DIR}/drsyscall/drsyscall.dox\;${PROJECT_SOURCE_DIR}/drsymcache/drsymcache.dox\;${PROJECT_SOURCE_DIR}/umbra/umbra.dox + -D + srclist=${PROJECT_SOURCE_DIR}/framework/drmf.dox\;${PROJECT_SOURCE_DIR}/drsyscall/drsyscall.dox\;${PROJECT_SOURCE_DIR}/drsymcache/drsymcache.dox\;${PROJECT_SOURCE_DIR}/umbra/umbra.dox\;${PROJECT_SOURCE_DIR}/drfuzz/drfuzz.dox -Dcommondir=${CMAKE_CURRENT_SOURCE_DIR} -Doutfile=${doxyfile} -Dversion_number=${TOOL_VERSION_NUMBER} diff --git a/docs/CMake_doxyfile.cmake b/docs/CMake_doxyfile.cmake index bf6670e0a..5f39274ef 100644 --- a/docs/CMake_doxyfile.cmake +++ b/docs/CMake_doxyfile.cmake @@ -205,7 +205,7 @@ if (PACKAGED_WITH_DYNAMORIO) endif() # XXX: share w/ list of source paths in CMakeLists.txt ${headers} -set(headers "${commondir}/../drsyscall/drsyscall.h ${commondir}/../umbra/umbra.h") +set(headers "${commondir}/../drsyscall/drsyscall.h ${commondir}/../umbra/umbra.h ${commondir}/../drfuzz/drfuzz.h") set(headers "${headers} ${commondir}/../drsymcache/drsymcache.h") set(headers "${headers} ${outdir}/../drmf/include/drmemory_framework.h") if (TOOL_DR_MEMORY) diff --git a/docs/Doxyfile.in b/docs/Doxyfile.in index dc4683978..777d019f9 100644 --- a/docs/Doxyfile.in +++ b/docs/Doxyfile.in @@ -106,7 +106,7 @@ WARN_LOGFILE = # to DR's api/docs/ext.gendox. INPUT = using.dox \ options-docs.dox release.dox main.dox \ - drmf.dox drsyscall.dox drsymcache.dox umbra.dox + drmf.dox drsyscall.dox drsymcache.dox umbra.dox drfuzz.dox FILE_PATTERNS = RECURSIVE = NO EXCLUDE = diff --git a/drfuzz/CMakeLists.txt b/drfuzz/CMakeLists.txt index 7d542f82f..7ba49c3d8 100644 --- a/drfuzz/CMakeLists.txt +++ b/drfuzz/CMakeLists.txt @@ -51,6 +51,10 @@ if (WIN32) set(DEFINES_NO_D ${DEFINES_NO_D} RC_IS_DRFUZZ) endif () +# We set DR libc option on b/c this is needed to link with drsyms_static +# on VS2010. It has a downside of increase in pdb and dll size (xref DRi#714). +set(DynamoRIO_USE_LIBC ON) + macro(configure_drfuzz_target target drwrap drmgr drsyms) use_DynamoRIO_extension(drfuzz ${drwrap}) use_DynamoRIO_extension(drfuzz ${drmgr}) diff --git a/drfuzz/drfuzz.c b/drfuzz/drfuzz.c index 1bb35fca6..40dabc6c6 100644 --- a/drfuzz/drfuzz.c +++ b/drfuzz/drfuzz.c @@ -39,15 +39,17 @@ #include "utils.h" #include "drfuzz.h" +#ifdef UNIX +# include +#endif + #define ARGSIZE(target) ((target)->arg_count * sizeof(reg_t)) #ifdef UNIX -typedef dr_siginfo_t drfuzz_crash_info_t; -typedef dr_signal_action_t drfuzz_crash_action_t; +typedef dr_signal_action_t drfuzz_fault_action_t; # define CRASH_CONTINUE DR_SIGNAL_DELIVER #else -typedef dr_exception_t drfuzz_crash_info_t; -typedef bool drfuzz_crash_action_t; +typedef bool drfuzz_fault_action_t; # define CRASH_CONTINUE true #endif @@ -56,8 +58,10 @@ typedef struct _fuzz_target_t { app_pc func_pc; uint arg_count; drfuzz_flags_t flags; - void (*pre_fuzz_cb)(generic_func_t, void *, INOUT void **); - bool (*post_fuzz_cb)(generic_func_t, void *, void *); + void *user_data; /* see drfuzz_{g,s}et_target_user_data() */ + void (*delete_user_data_cb)(void *user_data); + void (*pre_fuzz_cb)(void *, generic_func_t); + bool (*post_fuzz_cb)(void *, generic_func_t); } fuzz_target_t; /* Restores the return address corresponding to the normal call stack in case it was @@ -88,16 +92,35 @@ typedef struct _retaddr_unclobber_t { * iterations of all nested targets encountered. */ +/* Snapshot of a fuzz_pass_context_t */ +typedef struct _target_iterator_t { + void *dcontext; /* the dcontext corresponding to the captured fuzz_pass_context_t */ + uint index; /* iteration index */ + uint target_count; + drfuzz_target_frame_t *targets; +} target_iterator_t; + +/* max size of the recorded chain of faults for a single fuzz target (stored in + * drfuzz_fault_thread_state_t.faults) in the current implementation. + */ +#define FAULT_CHAIN_ARRAY_MAX 2 +#define FIRST_FAULT(fp) ((fp)->thread_state->faults[0]) +#define LAST_FAULT(fp) ((fp)->thread_state->faults[1]) +#define SIZEOF_FAULT_CHAIN_ARRAY (FAULT_CHAIN_ARRAY_MAX * sizeof(drfuzz_fault_t)) + /* Stores thread-specific state for an executing fuzz target, which is required for * repeating the target and for reporting a crash (can't use the drwrap `user_data` b/c * it is deleted in post-wrap, and we must hold these values from post-wrap to pre-wrap). */ typedef struct _pass_target_t { + void *wrapcxt; fuzz_target_t *target; reg_t xsp; /* stack level at entry to the fuzz target */ retaddr_unclobber_t unclobber; /* see comment on retaddr_unclobber_t */ reg_t *original_args; /* original arg values passed by the app to the fuzz target */ reg_t *current_args; /* fuzzed argument values for the current iteration */ + void *user_data; /* see drfuzz_{g,s}et_target_per_thread_user_data() */ + void (*delete_user_data_cb)(void *user_data); struct _pass_target_t *next; /* chains either stack in fuzz_pass_context_t */ } pass_target_t; @@ -105,6 +128,10 @@ typedef struct _pass_target_t { * live on the call stack, and a cache of targets that have been live in this fuzz pass. */ typedef struct _fuzz_pass_context_t { + /* + * dcontext of the thread + */ + void *dcontext; /* Stack of fuzz targets that are live on this thread; i.e., the subset of the call * stack which are fuzz targets. Chained in pass_target_t.next. */ @@ -114,14 +141,41 @@ typedef struct _fuzz_pass_context_t { * pass diverges from its cached target stack. Chained in pass_target_t.next. */ pass_target_t *cached_targets; + /* Stores thread state information about live fuzz targets and chained faults whenever + * a critical fault occurs on this context's thread. Also stores the live fuzz targets + * when this thread is terminated by an application crash. + */ + drfuzz_fault_thread_state_t *thread_state; } fuzz_pass_context_t; +typedef void (*fault_event_t)(void *fuzzcxt, + drfuzz_fault_t *fault, + drfuzz_fault_ex_t *fault_ex); + +typedef void (*fault_delete_callback_t)(void *fuzzcxt, + drfuzz_fault_t *fault); + +typedef void (*crash_thread_event_t)(void *fuzzcxt, + drfuzz_fault_thread_state_t *state); + +typedef void (*crash_process_event_t)(drfuzz_crash_state_t *state); + +/* Container for client-registered callback lists */ +typedef struct _drfuzz_callbacks_t { + fault_event_t fault_event; + fault_delete_callback_t fault_delete_callback; + crash_thread_event_t crash_thread_event; + crash_process_event_t crash_process_event; +} drfuzz_callbacks_t; + static int drfuzz_init_count; static int tls_idx_fuzzer; static hashtable_t fuzz_target_htable; +static drfuzz_callbacks_t *callbacks; + static void thread_init(void *dcontext); @@ -138,19 +192,34 @@ static pass_target_t * lookup_live_target(fuzz_pass_context_t *fp, app_pc target_pc); static pass_target_t * -activate_cached_target(void *dcontext, fuzz_pass_context_t *fp, app_pc target_pc); +activate_cached_target(fuzz_pass_context_t *fp, app_pc target_pc); static pass_target_t * -create_pass_target(void *dcontext, app_pc target_pc); +create_pass_target(void *dcontext, void *wrapcxt); + +static drfuzz_fault_thread_state_t * +create_fault_state(void *dcontext); + +static drfuzz_target_iterator_t * +create_target_iterator(fuzz_pass_context_t *fp); + +static void +capture_fault(void *dcontext, drfuzz_fault_t *fault, drfuzz_fault_ex_t *fault_ex); -static drfuzz_crash_action_t -crash_handler(void *dcontext, drfuzz_crash_info_t *crash); +static drfuzz_fault_action_t +fault_handler(void *dcontext, drfuzz_fault_ex_t *fault_ex); + +static bool +is_critical_fault(drfuzz_fault_ex_t *fault); + +static void +clear_cached_targets(fuzz_pass_context_t *fp); static void -clear_cached_targets(void *dcontext, fuzz_pass_context_t *fp); +clear_pass_targets(fuzz_pass_context_t *fp); static void -clear_pass_targets(void *dcontext, fuzz_pass_context_t *fp); +clear_thread_state(fuzz_pass_context_t *fp); static void free_fuzz_target(void *p); @@ -158,6 +227,9 @@ free_fuzz_target(void *p); static void free_pass_target(void *dcontext, pass_target_t *target); +static void +free_thread_state(fuzz_pass_context_t *fp); + DR_EXPORT drmf_status_t drfuzz_init(client_id_t client_id) { @@ -170,13 +242,16 @@ drfuzz_init(client_id_t client_id) if (res != DRMF_SUCCESS) return res; + callbacks = global_alloc(sizeof(drfuzz_callbacks_t), HEAPSTAT_MISC); + memset(callbacks, 0, sizeof(drfuzz_callbacks_t)); + drmgr_init(); drwrap_init(); #ifdef UNIX - drmgr_register_signal_event(crash_handler); + drmgr_register_signal_event(fault_handler); #else /* WINDOWS */ - drmgr_register_exception_event(crash_handler); + drmgr_register_exception_event(fault_handler); #endif drmgr_register_thread_init_event(thread_init); drmgr_register_thread_exit_event(thread_exit); @@ -206,8 +281,10 @@ drfuzz_exit(void) if (count < 0) return DRMF_ERROR; - drwrap_exit(); + global_free(callbacks, sizeof(drfuzz_callbacks_t), HEAPSTAT_MISC); + drmgr_exit(); + drwrap_exit(); hashtable_delete(&fuzz_target_htable); @@ -220,6 +297,8 @@ thread_init(void *dcontext) fuzz_pass_context_t *fp = thread_alloc(dcontext, sizeof(fuzz_pass_context_t), HEAPSTAT_MISC); memset(fp, 0, sizeof(fuzz_pass_context_t)); + fp->dcontext = dcontext; + fp->thread_state = create_fault_state(dcontext); drmgr_set_tls_field(dcontext, tls_idx_fuzzer, (void *) fp); } @@ -228,16 +307,29 @@ thread_exit(void *dcontext) { fuzz_pass_context_t *fp = (fuzz_pass_context_t *) drmgr_get_tls_field(dcontext, tls_idx_fuzzer); - clear_pass_targets(dcontext, fp); + + /* crash is indicated by aborted fuzz targets, even if the app did a hard exit() */ + if (fp->live_targets != NULL) { + if (callbacks->crash_thread_event != NULL) { + /* There may be targets already captured by a fault event. If not, and if fuzz + * targets were evidently aborted, then make them available in an iterator. + */ + if (fp->thread_state->targets == NULL && fp->live_targets != NULL) + fp->thread_state->targets = create_target_iterator(fp); + + callbacks->crash_thread_event(fp, fp->thread_state); + } + } + + free_thread_state(fp); + clear_pass_targets(fp); thread_free(dcontext, fp, sizeof(fuzz_pass_context_t), HEAPSTAT_MISC); } DR_EXPORT drmf_status_t drfuzz_fuzz_target(generic_func_t func_pc, uint arg_count, drfuzz_flags_t flags, - void (*pre_fuzz_cb)(generic_func_t target_pc, void *fuzzcxt, - INOUT void **user_data), - bool (*post_fuzz_cb)(generic_func_t target_pc, void *fuzzcxt, - void *user_data)) + void (*pre_fuzz_cb)(void *fuzzcxt, generic_func_t target_pc), + bool (*post_fuzz_cb)(void *fuzzcxt, generic_func_t target_pc)) { fuzz_target_t *target; @@ -248,6 +340,7 @@ drfuzz_fuzz_target(generic_func_t func_pc, uint arg_count, drfuzz_flags_t flags, return DRMF_ERROR_INVALID_PARAMETER; target = global_alloc(sizeof(fuzz_target_t), HEAPSTAT_MISC); + memset(target, 0, sizeof(fuzz_target_t)); target->func_pc = (app_pc) func_pc; target->arg_count = arg_count; target->flags = flags; @@ -268,30 +361,198 @@ drfuzz_fuzz_target(generic_func_t func_pc, uint arg_count, drfuzz_flags_t flags, } DR_EXPORT drmf_status_t -drfuzz_get_arg(generic_func_t target_pc, int arg, bool original, OUT void **arg_value) +drfuzz_register_fault_event(void (*event)(void *fuzzcxt, + drfuzz_fault_t *fault, + drfuzz_fault_ex_t *fault_ex)) { - void *dcontext = dr_get_current_drcontext(); - fuzz_pass_context_t *fp = (fuzz_pass_context_t *) drmgr_get_tls_field(dcontext, - tls_idx_fuzzer); - pass_target_t *target = lookup_live_target(fp, (app_pc) target_pc); + if (callbacks->fault_event != NULL) + return DRMF_ERROR; + callbacks->fault_event = event; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_unregister_fault_event(void (*event)(void *fuzzcxt, + drfuzz_fault_t *fault, + drfuzz_fault_ex_t *fault_ex)) +{ + if (callbacks->fault_event != event) + return DRMF_ERROR_INVALID_PARAMETER; + callbacks->fault_event = NULL; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_register_fault_delete_callback(void (*callback)(void *fuzzcxt, + drfuzz_fault_t *fault)) +{ + if (callbacks->fault_delete_callback != NULL) + return DRMF_ERROR; + callbacks->fault_delete_callback = callback; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_unregister_fault_delete_callback(void (*callback)(void *fuzzcxt, + drfuzz_fault_t *fault)) +{ + if (callbacks->fault_delete_callback != callback) + return DRMF_ERROR_INVALID_PARAMETER; + callbacks->fault_delete_callback = NULL; + return DRMF_SUCCESS; +} + +DR_EXPORT +drmf_status_t +drfuzz_register_crash_thread_event(void (*event)(void *fuzzcxt, + drfuzz_fault_thread_state_t *state)) +{ + if (callbacks->crash_thread_event != NULL) + return DRMF_ERROR; + callbacks->crash_thread_event = event; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_unregister_crash_thread_event(void (*event)(void *fuzzcxt, + drfuzz_fault_thread_state_t *state)) +{ + if (callbacks->crash_thread_event != event) + return DRMF_ERROR_INVALID_PARAMETER; + callbacks->crash_thread_event = NULL; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_register_crash_process_event(void (*event)(drfuzz_crash_state_t *state)) +{ + if (callbacks->crash_process_event != NULL) + return DRMF_ERROR; + callbacks->crash_process_event = event; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_unregister_crash_process_event(void (*event)(drfuzz_crash_state_t *state)) +{ + if (callbacks->crash_process_event != event) + return DRMF_ERROR_INVALID_PARAMETER; + callbacks->crash_process_event = NULL; + return DRMF_SUCCESS; +} + +DR_EXPORT void * +drfuzz_get_fuzzcxt(void) +{ + return drmgr_get_tls_field(dr_get_current_drcontext(), tls_idx_fuzzer); +} + +DR_EXPORT void * +drfuzz_get_drcontext(void *fuzzcxt) +{ + return ((fuzz_pass_context_t *) fuzzcxt)->dcontext; +} + +DR_EXPORT drmf_status_t +drfuzz_get_arg(void *fuzzcxt, generic_func_t target_pc, int arg, bool original, + OUT void **arg_value) +{ + fuzz_pass_context_t *fp = (fuzz_pass_context_t *) fuzzcxt; + pass_target_t *target; + + if (target_pc == NULL) + target = fp->live_targets; + else + target = lookup_live_target(fp, (app_pc) target_pc); if (target == NULL || arg >= target->target->arg_count) return DRMF_ERROR_INVALID_PARAMETER; + if (original) *arg_value = (void *) target->original_args[arg]; else *arg_value = (void *) target->current_args[arg]; + return DRMF_SUCCESS; } DR_EXPORT drmf_status_t drfuzz_set_arg(void *fuzzcxt, int arg, void *val) { - if (drwrap_set_arg(fuzzcxt, arg, val)) + fuzz_pass_context_t *fp = (fuzz_pass_context_t *) fuzzcxt; + + if (drwrap_set_arg(fp->live_targets->wrapcxt, arg, val)) return DRMF_SUCCESS; else return DRMF_ERROR; - /* XXX i#1734: NYI return DRMF_ERROR when called outside pre_fuzz_handler */ +} + +DR_EXPORT drmf_status_t +drfuzz_get_target_user_data(IN generic_func_t target_pc, OUT void **user_data) +{ + fuzz_target_t *target = hashtable_lookup(&fuzz_target_htable, target_pc); + + if (target == NULL) + return DRMF_ERROR_INVALID_PARAMETER; + + *user_data = target->user_data; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_set_target_user_data(IN generic_func_t target_pc, IN void *user_data, + IN void (*delete_callback)(void *user_data)) +{ + fuzz_target_t *target = hashtable_lookup(&fuzz_target_htable, target_pc); + + if (target == NULL) + return DRMF_ERROR_INVALID_PARAMETER; + + target->user_data = user_data; + target->delete_user_data_cb = delete_callback; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_get_target_per_thread_user_data(IN void *fuzzcxt, IN generic_func_t target_pc, + OUT void **user_data) +{ + fuzz_pass_context_t *fp = (fuzz_pass_context_t *) fuzzcxt; + pass_target_t *target; + + if (fp == NULL) { + void *dcontext = dr_get_current_drcontext(); + fp = (fuzz_pass_context_t *) drmgr_get_tls_field(dcontext, tls_idx_fuzzer); + } + + target = lookup_live_target(fp, (app_pc) target_pc); + if (target == NULL) + return DRMF_ERROR_INVALID_PARAMETER; + + *user_data = target->user_data; + return DRMF_SUCCESS; +} + +DR_EXPORT drmf_status_t +drfuzz_set_target_per_thread_user_data(IN void *fuzzcxt, IN generic_func_t target_pc, + IN void *user_data, + IN void (*delete_callback)(void *user_data)) +{ + fuzz_pass_context_t *fp = (fuzz_pass_context_t *) fuzzcxt; + pass_target_t *target; + + if (fp == NULL) { + void *dcontext = dr_get_current_drcontext(); + fp = (fuzz_pass_context_t *) drmgr_get_tls_field(dcontext, tls_idx_fuzzer); + } + + target = lookup_live_target(fp, (app_pc) target_pc); + if (target == NULL) + return DRMF_ERROR_INVALID_PARAMETER; + + target->user_data = user_data; + target->delete_user_data_cb = delete_callback; + return DRMF_SUCCESS; } static void @@ -312,14 +573,25 @@ pre_fuzz_handler(void *wrapcxt, INOUT void **user_data) LOG(3, "pre_fuzz() for target "PFX" with %d args\n", target_to_fuzz, target->arg_count); + /* XXX i#1734: this heuristic may be incorrect when a handled fault occurs during + * the very last iteration of the last fuzz pass on any thread. + */ + clear_thread_state(fp); + + /* Stop the target iterator that was captured at the last critical fault, because + * the fact that we are in pre-fuzz implies the fault was handled and doesn't matter. + */ + if (fp->thread_state->targets != NULL) + drfuzz_target_iterator_stop(fp->thread_state->targets); + /* XXX: assumes the fuzz target is never called recursively */ if (fp->live_targets != NULL && fp->live_targets->target->func_pc == target_to_fuzz) { live = fp->live_targets; /* this is a repetition of the last live target */ } else { is_target_entry = true; /* this is a new invocation of a target */ - live = activate_cached_target(dcontext, fp, target_to_fuzz); /* check the cache */ + live = activate_cached_target(fp, target_to_fuzz); /* check the cache */ if (live == NULL) - live = create_pass_target(dcontext, drwrap_get_func(wrapcxt)); + live = create_pass_target(dcontext, wrapcxt); live->next = fp->live_targets; /* push to live stack */ fp->live_targets = live; } @@ -357,8 +629,7 @@ pre_fuzz_handler(void *wrapcxt, INOUT void **user_data) *live->unclobber.retaddr_loc = live->unclobber.retaddr; /* restore retaddr to stack */ #endif - target->pre_fuzz_cb((generic_func_t) target_to_fuzz, wrapcxt, - NULL/*XXX i#1734: NYI*/); + target->pre_fuzz_cb(fp, (generic_func_t) target_to_fuzz); drwrap_set_mcontext(wrapcxt); for (i = 0; i < target->arg_count; i++) live->current_args[i] = (reg_t) drwrap_get_arg(wrapcxt, i); @@ -371,8 +642,7 @@ post_fuzz_handler(void *wrapcxt, void *user_data) { fuzz_pass_context_t *fp = (fuzz_pass_context_t *) user_data; pass_target_t *live = fp->live_targets; - bool repeat = live->target->post_fuzz_cb((generic_func_t) live->target->func_pc, - wrapcxt, NULL/*XXX i#1734: NYI*/); + bool repeat = live->target->post_fuzz_cb(fp, (generic_func_t) live->target->func_pc); LOG(3, "post_fuzz() for target "PFX" (%s)\n", live->target->func_pc, repeat ? "repeat" : "stop"); @@ -394,7 +664,7 @@ post_fuzz_handler(void *wrapcxt, void *user_data) fp->cached_targets = live; if (fp->live_targets == NULL) /* clear cached targets after fuzz pass has ended */ - clear_cached_targets(drwrap_get_drcontext(wrapcxt), fp); + clear_cached_targets(fp); } } @@ -411,7 +681,7 @@ lookup_live_target(fuzz_pass_context_t *fp, app_pc target_pc) } static pass_target_t * -activate_cached_target(void *dcontext, fuzz_pass_context_t *fp, app_pc target_pc) +activate_cached_target(fuzz_pass_context_t *fp, app_pc target_pc) { if (fp->cached_targets != NULL) { if (fp->cached_targets->target->func_pc == target_pc) { @@ -419,63 +689,242 @@ activate_cached_target(void *dcontext, fuzz_pass_context_t *fp, app_pc target_pc fp->cached_targets = fp->cached_targets->next; return live; } else { /* call stack diverges from cached stack, so clear it out */ - clear_cached_targets(dcontext, fp); + clear_cached_targets(fp); } } return NULL; } static pass_target_t * -create_pass_target(void *dcontext, app_pc target_pc) +create_pass_target(void *dcontext, void *wrapcxt) { + app_pc target_pc = drwrap_get_func(wrapcxt); fuzz_target_t *target = hashtable_lookup(&fuzz_target_htable, target_pc); pass_target_t *live = thread_alloc(dcontext, sizeof(pass_target_t), HEAPSTAT_MISC); + memset(live, 0, sizeof(pass_target_t)); + live->wrapcxt = wrapcxt; live->original_args = thread_alloc(dcontext, ARGSIZE(target), HEAPSTAT_MISC); live->current_args = thread_alloc(dcontext, ARGSIZE(target), HEAPSTAT_MISC); live->target = target; return live; } -static drfuzz_crash_action_t -crash_handler(void *dcontext, drfuzz_crash_info_t *crash) +static drfuzz_fault_thread_state_t * +create_fault_state(void *dcontext) +{ + drfuzz_fault_thread_state_t *state; + + state = thread_alloc(dcontext, sizeof(drfuzz_fault_thread_state_t), HEAPSTAT_MISC); + memset(state, 0, sizeof(drfuzz_fault_thread_state_t)); + state->faults_observed = 0; + state->fault_count = 0; + /* allocate first and last now */ + state->faults = thread_alloc(dcontext, SIZEOF_FAULT_CHAIN_ARRAY, HEAPSTAT_MISC); + memset(state->faults, 0, SIZEOF_FAULT_CHAIN_ARRAY); + return state; +} + +static drfuzz_target_iterator_t * +create_target_iterator(fuzz_pass_context_t *fp) +{ + uint i, j; + pass_target_t *target; + target_iterator_t *iter; + drfuzz_target_frame_t *frame; + + iter = thread_alloc(fp->dcontext, sizeof(target_iterator_t), HEAPSTAT_MISC); + memset(iter, 0, sizeof(target_iterator_t)); + iter->dcontext = fp->dcontext; + for (target = fp->live_targets; target != NULL; target = target->next) + iter->target_count++; + iter->targets = thread_alloc(fp->dcontext, + sizeof(drfuzz_target_frame_t) * iter->target_count, + HEAPSTAT_MISC); + + for (i = 0, target = fp->live_targets; target != NULL; i++, target = target->next) { + frame = &iter->targets[i]; + frame->func_pc = target->target->func_pc; + frame->arg_count = target->target->arg_count; + frame->arg_values = thread_alloc(fp->dcontext, sizeof(reg_t) * frame->arg_count, + HEAPSTAT_MISC); + for (j = 0; j < frame->arg_count; j++) + frame->arg_values[j] = target->current_args[i]; + } + + return (drfuzz_target_iterator_t *) iter; +} + +DR_EXPORT drfuzz_target_iterator_t * +drfuzz_target_iterator_start(void *fuzzcxt) +{ + return (void *) create_target_iterator((fuzz_pass_context_t *) fuzzcxt); +} + +DR_EXPORT drfuzz_target_frame_t * +drfuzz_target_iterator_next(drfuzz_target_iterator_t *iter_in) +{ + target_iterator_t *iter = (target_iterator_t *) iter_in; + if (iter->index < iter->target_count) + return (void *) &iter->targets[iter->index++]; + else + return NULL; +} + +DR_EXPORT drmf_status_t +drfuzz_target_iterator_stop(drfuzz_target_iterator_t *iter_in) +{ + uint i; + target_iterator_t *iter = (target_iterator_t *) iter_in; + + for (i = 0; i < iter->target_count; i++) { + thread_free(iter->dcontext, iter->targets[i].arg_values, + sizeof(iter->targets[i].arg_values[0]), HEAPSTAT_MISC); + } + thread_free(iter->dcontext, iter->targets, + sizeof(drfuzz_target_frame_t) * iter->target_count, HEAPSTAT_MISC); + thread_free(iter->dcontext, iter, sizeof(target_iterator_t), HEAPSTAT_MISC); + + return DRMF_SUCCESS; +} + +static void +capture_fault(void *dcontext, drfuzz_fault_t *fault, drfuzz_fault_ex_t *fault_ex) +{ +#ifdef UNIX + fault->fault_code = fault_ex->sig; + fault->fault_pc = fault_ex->mcontext->pc; + fault->access_address = fault_ex->access_address; +#else /* WINDOWS */ + fault->fault_code = fault_ex->record->ExceptionCode; + fault->fault_pc = fault_ex->record->ExceptionAddress; + fault->access_address = (byte *) fault_ex->record->ExceptionInformation[1]; +#endif + fault->thread_id = dr_get_thread_id(dcontext); +} + +static drfuzz_fault_action_t +fault_handler(void *dcontext, drfuzz_fault_ex_t *fault_ex) { - /* XXX i#1734: NYI */ + if (is_critical_fault(fault_ex) && callbacks->fault_event != NULL) { + drfuzz_fault_t *fault; + fuzz_pass_context_t *fp; + + fp = (fuzz_pass_context_t *) drmgr_get_tls_field(dcontext, tls_idx_fuzzer); + if (fp->live_targets == NULL) { + /* Only keep one fault on a thread having no live fuzz targets, because we + * have no easy way to tell when the fault has been handled (given at least + * one fuzz target, we can assume that the next re-entry to pre-fuzz implies + * the fault must have been properly handled by the app somewhere. + */ + clear_thread_state(fp); + } else { + /* Capture the fuzz targets in case the app crashes before the fault is + * handled (which does not necessarily mean this fault caused the crash. + * This iterator will be automatically stopped (and freed) on either + * pre-fuzz or during thread_exit(). The documentation instructs the client + * not to stop this iterator. + */ + fp->thread_state->targets = create_target_iterator(fp); + } + + if (fp->thread_state->fault_count == FAULT_CHAIN_ARRAY_MAX) { + if (callbacks->fault_delete_callback != NULL) /* remove the last one */ + callbacks->fault_delete_callback(fp, &LAST_FAULT(fp)); + fp->thread_state->fault_count--; + } + + fp->thread_state->faults_observed++; + fault = &fp->thread_state->faults[fp->thread_state->fault_count++]; + capture_fault(dcontext, fault, fault_ex); + callbacks->fault_event(fp, fault, fault_ex); + } return CRASH_CONTINUE; } +static inline bool +is_critical_fault(drfuzz_fault_ex_t *fault) +{ + /* XXX i#1734: allow the client to configure the set of faults that are considered + * critical, and extend the default set to include e.g. SIGILL, SIGABRT, etc. + */ +#ifdef WINDOWS + return (fault->record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION); +#else /* UNIX */ + return (fault->sig == SIGSEGV || fault->sig == SIGBUS); +#endif +} + static void -clear_cached_targets(void *dcontext, fuzz_pass_context_t *fp) +clear_cached_targets(fuzz_pass_context_t *fp) { pass_target_t *cached, *next; for (cached = fp->cached_targets; cached != NULL; cached = next) { next = cached->next; - free_pass_target(dcontext, cached); + free_pass_target(fp->dcontext, cached); } fp->cached_targets = NULL; } static void -clear_pass_targets(void *dcontext, fuzz_pass_context_t *fp) +clear_pass_targets(fuzz_pass_context_t *fp) { pass_target_t *live, *next; for (live = fp->live_targets; live != NULL; live = next) { next = live->next; - free_pass_target(dcontext, live); + free_pass_target(fp->dcontext, live); } fp->live_targets = NULL; - clear_cached_targets(dcontext, fp); + clear_cached_targets(fp); +} + +static void +clear_thread_state(fuzz_pass_context_t *fp) +{ + uint i; + + if (callbacks->fault_delete_callback != NULL) { + for (i = 0; i < fp->thread_state->fault_count; i++) + callbacks->fault_delete_callback(fp, &fp->thread_state->faults[i]); + } + fp->thread_state->fault_count = 0; + fp->thread_state->faults_observed = 0; } static void free_fuzz_target(void *p) { - global_free(p, sizeof(fuzz_target_t), HEAPSTAT_MISC); + fuzz_target_t *target = (fuzz_target_t *) p; + + if (target->delete_user_data_cb != NULL && target->user_data != NULL) + target->delete_user_data_cb(target->user_data); + global_free(target, sizeof(fuzz_target_t), HEAPSTAT_MISC); } static void -free_pass_target(void *dcontext, pass_target_t *pass) +free_pass_target(void *dcontext, pass_target_t *target) { - thread_free(dcontext, pass->original_args, ARGSIZE(pass->target), HEAPSTAT_MISC); - thread_free(dcontext, pass->current_args, ARGSIZE(pass->target), HEAPSTAT_MISC); - thread_free(dcontext, pass, sizeof(*pass), HEAPSTAT_MISC); + if (target->delete_user_data_cb != NULL && target->user_data != NULL) + target->delete_user_data_cb(target->user_data); + thread_free(dcontext, target->original_args, ARGSIZE(target->target), HEAPSTAT_MISC); + thread_free(dcontext, target->current_args, ARGSIZE(target->target), HEAPSTAT_MISC); + thread_free(dcontext, target, sizeof(*target), HEAPSTAT_MISC); +} + +static void +free_thread_state(fuzz_pass_context_t *fp) +{ + if (fp->thread_state == NULL) + return; + + if (fp->thread_state->targets != NULL) + drfuzz_target_iterator_stop((void *) fp->thread_state->targets); + if (callbacks->fault_delete_callback != NULL && fp->thread_state->fault_count > 0) { + callbacks->fault_delete_callback(fp, &FIRST_FAULT(fp)); + if (fp->thread_state->fault_count == 2) + callbacks->fault_delete_callback(fp, &LAST_FAULT(fp)); + } + thread_free(fp->dcontext, fp->thread_state->faults, SIZEOF_FAULT_CHAIN_ARRAY, + HEAPSTAT_MISC); + thread_free(fp->dcontext, fp->thread_state, sizeof(drfuzz_fault_thread_state_t), + HEAPSTAT_MISC); } diff --git a/drfuzz/drfuzz.dox b/drfuzz/drfuzz.dox new file mode 100644 index 000000000..93960582a --- /dev/null +++ b/drfuzz/drfuzz.dox @@ -0,0 +1,85 @@ +/* ********************************************************** + * Copyright (c) 2014 Google, Inc. All rights reserved. + * **********************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/** +*************************************************************************** +*************************************************************************** +\page page_drfuzz Dr. Fuzz: Dynamic Fuzz Testing Extension + +The \p Dr. Fuzz DynamoRIO Extension provides fuzz testing features. +\p Dr. Fuzz is part of the Dr. Memory Framework. + + - \ref sec_drfuzz_setup + - \ref sec_drfuzz_API + +\section sec_drfuzz_setup Setup + +To use \p Dr. Fuzz with your client, first locate the Dr. Memory +Framework. Then use the standard method of using an Extension with the +name \p drfuzz. The two steps will look like this in your client's +\p CMakeLists.txt file: + +\code +find_package(DrMemoryFramework) +use_DynamoRIO_extension(clientname drfuzz) +\endcode + +To point CMake at the framework, set the DrMemoryFramework_DIR variable to +point at the \p drmf subdirectory of the Dr. Memory package that you are +using. For example: + +\code +cmake -G"Ninja" -DDynamoRIO_DIR=c:/path/to/DynamoRIO-Windows-4.1.0-8/cmake -DDrMemoryFramework_DIR=c:/path/to/DrMemory-Windows-1.6.0-2/drmf ../mysrcs/ +\endcode + +That will automatically set up the include path and library dependence. + +Your client must call \p drfuzz_init() prior to accessing any API +routines in \p drfuzz, and should call \p drfuzz_exit() at process exit +time. + +\section sec_drfuzz_API Dr. Fuzz API + +\p Dr. Fuzz provides the following key features: + -# Repeat execution of the test target function with fuzzed arguments + -# Mutate argument values using bit flipping or random number algorithms + -# Schedule fuzz iterations for a target function and set of arguments + -# Report state information on a crash caused by fuzz inputs + +The client can use the provided Dr. Fuzz APIs to fuzz test the target application. The +most flexible approach is to register fuzz target functions and directly control the +fuzzing cycle using the callbacks. This approach also requires the most effort, so +Dr. Fuzz additionally provides mutator and scheduler modules which can be leveraged +to simplify the fuzzing procedure into a few higher level operations (pending completion +of issue #1734). + +*/ diff --git a/drfuzz/drfuzz.h b/drfuzz/drfuzz.h index 1a1696247..21dacdfca 100644 --- a/drfuzz/drfuzz.h +++ b/drfuzz/drfuzz.h @@ -35,7 +35,7 @@ /* Dr. Fuzz: DynamoRIO Fuzz Testing Extension */ -/* Framework-shared header */ +/* Framework-shared headers */ #include "drmemory_framework.h" #include "../framework/drmf.h" @@ -53,6 +53,121 @@ extern "C" { */ /*@{*/ /* begin doxygen group */ +/** + * Represents the stack frame of a fuzz target during one fuzz iteration, including + * the argument values from that iteration. To access the target frames for any thread, + * call drfuzz_target_iterator_start() from that thread. When a crash occurs, the set + * of target frames that were live at the time of the critical fault are copied into + * a #drfuzz_fault_thread_state_t, which is provided in the crash events. + * + * \note For pointer arguments, the pointed value may change, or may no longer be + * accessible, after the stack frame returns (or aborts on crash). Clients requiring + * post-mortem access to such pointed values will need to store them at the beginning + * of each fuzz iteration. + */ +typedef struct _drfuzz_target_frame_t { + app_pc func_pc; /* the target function */ + uint arg_count; /* the number of arguments for this target */ + reg_t *arg_values; /* the argument values */ +} drfuzz_target_frame_t; + +/** + * An opaque iterator of fuzz targets, representing the set of targets that were live on + * the call stack of one thread at a specific point in time. + */ +typedef void * drfuzz_target_iterator_t; + +/** + * Provides basic information about an occurrence of a "critical fault", which in drfuzz + * refers to a subset of signals (Unix) or exceptions (Windows) that (a) are likely to be + * caused by fuzz testing, (b) imply errors and/or vulnerabilities in the code of the + * target application, and (c) terminate execution if they are not caught and handled. + * By default, the set of "critical faults" is SIGSEGV and SIGBUS on Unix, and Access + * Violation on Windows. (In a future release, the "critical faults" will be configurable + * by the drfuzz client.) Also see comments on drfuzz_register_fault_event(). + * + * Where provided by callbacks from this API, this struct may be retained indefinitely. + * Additional information about a critical fault is provided by #drfuzz_fault_ex_t, which + * duplicates some data from this struct, but may not be retained after a callback. + */ +typedef struct _drfuzz_fault_t { + /** + * Signal number (Unix) or exception code (Windows). + */ + int fault_code; + /** + * The address within the target application where the fault occurred. + */ + app_pc fault_pc; + /** + * For memory access faults only, the address of the failed access attempt. + */ + byte *access_address; + /** + * The thread that executed the fault. + */ + thread_id_t thread_id; + /** + * Available for custom user data. To free the custom data when drfuzz deletes an + * instance of #drfuzz_fault_t, use drfuzz_register_fault_delete_callback(). + */ + void *user_data; +} drfuzz_fault_t; + +/** + * Provides extended information about a critical fault. On Unix cast to dr_siginfo_t, + * or on Windows cast to dr_exception_t. See documentation on those structs for details. + * + * Where provided by callbacks from this API, instances of this struct are temporary and + * may not be accessed after the callback function returns. Copy the struct as necessary. + */ +#ifdef UNIX +typedef dr_siginfo_t drfuzz_fault_ex_t; +#else +typedef dr_exception_t drfuzz_fault_ex_t; +#endif + +/** + * Records the state of a thread at the time a fault occurred on that thread, or at the + * time the thread is aborted due to application crash. Faults recorded in the state at + * the time of a crash are not necessarily responsible for the crash. + * + * \note Currently only the first and last faults will be provided in the \p faults array. + */ +typedef struct _drfuzz_fault_thread_state_t { + /** + * The thread ID. + */ + thread_id_t thread_id; + /** + * The number of critical faults that occurred in the chain, starting with the + * first fault that occurred while executing a fuzz target, and including all + * faults that occurred until the thread exited. + */ + uint faults_observed; + /** + * The number of elements in the faults array. + */ + uint fault_count; + /** + * The array of faults. + */ + drfuzz_fault_t *faults; + /** + * An iterable list of the fuzz targets that were live on the call stack when the + * first fault in the chain occurred (innermost stack frame first). + */ + drfuzz_target_iterator_t *targets; +} drfuzz_fault_thread_state_t; + +/** + * Records the state of all application threads at the time of a crash. + */ +typedef struct _drfuzz_crash_state_t { + uint thread_count; /**< The number of states in the \p thread_states array. */ + drfuzz_fault_thread_state_t **thread_states; /**< An array of thread states. */ +} drfuzz_crash_state_t; + /** * Values for the flags parameter to drfuzz_fuzz_target(). */ typedef enum _drfuzz_flags_t { @@ -138,18 +253,132 @@ DR_EXPORT * the client must know how many args are actually used). * @param[in] flags Flags are optional, except that the calling convention * must be specified (using one of DRFUZZ_CALLCONV_*). - * @param[in] pre_fuzz_callback Called prior to each fuzz iteration of the target + * @param[in] pre_fuzz_cb Called prior to each fuzz iteration of the target * function (must not be NULL). - * @param[in] post_fuzz_callback Called following each fuzz iteration of the target + * @param[in] post_fuzz_cb Called following each fuzz iteration of the target * function (must not be NULL). */ -/* XXX i#1734: describe what happens on crash, or when client detects an error (NYI) */ drmf_status_t drfuzz_fuzz_target(generic_func_t func_pc, uint arg_count, drfuzz_flags_t flags, - void (*pre_fuzz_cb)(generic_func_t target_pc, void *fuzzcxt, - INOUT void **user_data), - bool (*post_fuzz_cb)(generic_func_t target_pc, void *fuzzcxt, - void *user_data)); + void (*pre_fuzz_cb)(void *fuzzcxt, generic_func_t target_pc), + bool (*post_fuzz_cb)(void *fuzzcxt, generic_func_t target_pc)); + +DR_EXPORT +/** + * Register for notification of a fault event, which occurs when the execution of any + * fuzz target encounters a critical fault (as defined at #drfuzz_fault_t). Since the app + * may handle the fault, drfuzz does not report it to the user at the time this event + * occurs. Instead, it maintains a chain of faults that occur during a single fuzz + * iteration, and only reports them if the application crashes before starting the next + * fuzz iteration. Use drfuzz_register_crash_thread_event() to receive crash notification. + * + * Event parameters + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + *
    [in]fuzzcxt The drfuzz thread-local context of the fault.
    [in]fault  + * Provides basic information about the kind and location of the + * fault, and may be retained by the callee until delete (to be + * notified, use drfuzz_register_fault_delete_callback()). The + * field user_data is available for attaching custom data to the + * fault for later use during the crash events (if any) via + * #drfuzz_fault_thread_state_t and #drfuzz_crash_state_t. + *
    [in]fault_ex  + * Provides extended information about the memory state at the + * fault. This struct is transitory and must be copied if any of + * its data needs to be retained. + *
+ * + * \note Does not allow multiple registration. + */ +drmf_status_t +drfuzz_register_fault_event(void (*event)(void *fuzzcxt, + drfuzz_fault_t *fault, + drfuzz_fault_ex_t *fault_ex)); + +DR_EXPORT +/** + * Unregister the fault notification event. + */ +drmf_status_t +drfuzz_unregister_fault_event(void (*event)(void *fuzzcxt, + drfuzz_fault_t *fault, + drfuzz_fault_ex_t *fault_ex)); + +DR_EXPORT +/** + * Register to be notified when drfuzz deletes a fault object, indicating it is no longer + * safe to retain it. If user_data has been attached, it should be disposed at this time. + * + * \note Does not allow multiple registration. + */ +drmf_status_t +drfuzz_register_fault_delete_callback(void (*callback)(void *fuzzcxt, + drfuzz_fault_t *fault)); + +DR_EXPORT +/** + * Unregister the fault delete notification callback. + */ +drmf_status_t +drfuzz_unregister_fault_delete_callback(void (*callback)(void *fuzzcxt, + drfuzz_fault_t *fault)); + +DR_EXPORT +/** + * Register to be notified of an application crash on each thread that is running at + * the time of the crash. The event contains the state of the current thread at the + * time of the fault. If the crash was not caused by this thread, the state may contain + * faults that would have been handled by the app (had it continued to run). + * + * The state also contains a list of fuzz targets. If the state contains faults, these + * targets represent the state of fuzzing at the time the first fault occurred. Otherwise + * these targets were live at the time the thread was aborted by the crash. + * + * Access the targets using the provided iterator, but do not stop the iterator + * (drfuzz_target_iterator_stop() will be called internally). + * + * \note Does not allow multiple registration. + */ +drmf_status_t +drfuzz_register_crash_thread_event(void (*event)(void *fuzzcxt, + drfuzz_fault_thread_state_t *state)); + +DR_EXPORT +/** + * Unregister the crash notification callback. + */ +drmf_status_t +drfuzz_unregister_crash_thread_event(void (*event)(void *fuzzcxt, + drfuzz_fault_thread_state_t *state)); + +DR_EXPORT +/** + * Get the drfuzz thread context for the current thread. This function has significant + * overhead, and should not be used repetitively in performance-sensitive applications. + */ +void * +drfuzz_get_fuzzcxt(void); + +DR_EXPORT +/** + * Get the dcontext associated with this fuzzcxt. + */ +void * +drfuzz_get_drcontext(void *fuzzcxt); DR_EXPORT /** @@ -157,14 +386,17 @@ DR_EXPORT * called while fuzzing of this target is in progress. Will retrieve the arg value for * the current fuzz iteration on the current thread. Returns DRMF_SUCCESS on success. * - * @param[in] target The target function. + * @param[in] fuzzcxt The drfuzz thread context. + * @param[in] target_pc The target function. May be NULL, which implies that the target + * is the most recently called target on the app call stack. * @param[in] arg Identifies the argument by its index. * @param[in] original Specifies whether to get the original value of the argument * passed by the app, or the currently applied fuzz value. * @param[out] arg_value Returns the value of the argument (when successful). */ drmf_status_t -drfuzz_get_arg(generic_func_t target_pc, int arg, bool original, OUT void **arg_value); +drfuzz_get_arg(void *fuzzcxt, generic_func_t target_pc, int arg, bool original, + OUT void **arg_value); DR_EXPORT /** @@ -177,6 +409,70 @@ DR_EXPORT drmf_status_t drfuzz_set_arg(void *fuzzcxt, int arg, void *val); +DR_EXPORT +/** + * Get the user data associated with the \p target_pc. + */ +drmf_status_t +drfuzz_get_target_user_data(IN generic_func_t target_pc, OUT void **user_data); + +DR_EXPORT +/** + * Set the user data associated with the specified \p target_pc. If the \p delete_callback + * is not NULL, it will be called when drfuzz deletes the internal target data structure. + * + * \note: Only one slot is provided for the data, so multiple writes will overwrite. + */ +drmf_status_t +drfuzz_set_target_user_data(IN generic_func_t target_pc, IN void *user_data, + IN void (*delete_callback)(void *user_data)); + +DR_EXPORT +/** + * Get the user data associated with the specified \p target_pc and \p fuzzcxt. If the + * \p fuzzcxt is NULL, the fuzzcxt for the current thread will be used (if any). + */ +drmf_status_t +drfuzz_get_target_per_thread_user_data(IN void *fuzzcxt, IN generic_func_t target_pc, + OUT void **user_data); + +DR_EXPORT +/** + * Set the user data associated with the specified \p target_pc and \p fuzzcxt. If the + * \p fuzzcxt is NULL, the fuzzcxt for the current thread will be used (if any). If the + * \p delete_callback is not NULL, it will be called when drfuzz deletes the internal + * target data structure (after completing a fuzz pass), or when the thread exits. + * + * \note: Only one slot is provided for the data, so multiple writes will overwrite. + */ +drmf_status_t +drfuzz_set_target_per_thread_user_data(IN void *fuzzcxt, IN generic_func_t target_pc, + IN void *user_data, + IN void (*delete_callback)(void *user_data)); + +DR_EXPORT +/** + * Initiates an iterator over the set of fuzz targets that are live on the current + * thread's call stack. Use drfuzz_target_iterator_next() to traverse the fuzz target + * frames, and use drfuzz_target_iterator_stop() to free the iterator and all frames. + */ +drfuzz_target_iterator_t * +drfuzz_target_iterator_start(void *fuzzcxt); + +DR_EXPORT +/** + * Returns the next fuzz target frame in the iteration set, or NULL after the last frame. + */ +drfuzz_target_frame_t * +drfuzz_target_iterator_next(drfuzz_target_iterator_t *iter); + +DR_EXPORT +/** + * Stop a fuzz target iterator and free its allocated resources (including target frames). + */ +drmf_status_t +drfuzz_target_iterator_stop(drfuzz_target_iterator_t *iter); + /*@}*/ /* end doxygen group */ #ifdef __cplusplus diff --git a/drmemory/docs/release.dox b/drmemory/docs/release.dox index 668f7c6ce..111233bd9 100644 --- a/drmemory/docs/release.dox +++ b/drmemory/docs/release.dox @@ -101,14 +101,14 @@ The changes between version 1.7.0 and version 1.6.1 include: which provides persistent caching of symbol lookup data to reduce startup time overhead on large applications. - Clarified the final parameter to umbra_get_shadow_memory_type() and - umbra_shadow_memory_is_shared() to be #umbra_shadow_memory_type_t + umbra_shadow_memory_is_shared() to be umbra_shadow_memory_type_t rather than uint. - Clarified the 2nd parameter to umbra_create_shadow_memory() to be - #umbra_shadow_memory_flags_t rather than uint. - - Clarified the #umbra_shadow_memory_info_t shadow_type field to be - #umbra_shadow_memory_type_t rather than uint. - - Clarified the #umbra_map_options_t flags field to be - #umbra_map_flags_t rather than uint. + umbra_shadow_memory_flags_t rather than uint. + - Clarified the umbra_shadow_memory_info_t shadow_type field to be + umbra_shadow_memory_type_t rather than uint. + - Clarified the umbra_map_options_t flags field to be + umbra_map_flags_t rather than uint. - Added static library versions of drsyscall and umbra. - On Windows, handle leak detection (-check_handle_leaks) is now on by default in full mode. However, it is still experimental and conservative, diff --git a/framework/drmf.dox b/framework/drmf.dox index bc32f55e3..e096d7b52 100644 --- a/framework/drmf.dox +++ b/framework/drmf.dox @@ -46,6 +46,8 @@ DynamoRIO Extension libraries:
Shadow memory mapping - \subpage page_drsymcache
Persistent caching of symbol lookup data + - \subpage page_drfuzz +
Fuzz testing - Dr. Malloc
Memory allocation tracking (coming soon) - Dr. Callstack diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt index 3fb60fbe2..a801de715 100644 --- a/tests/framework/CMakeLists.txt +++ b/tests/framework/CMakeLists.txt @@ -59,7 +59,7 @@ endfunction(add_drmf_test_app) # We only expect a few tests, so we simplify things by having a simple # regex output for whether they passed. # ext_list should be a list of extensions minus the drmf_ prefix. -function(add_drmf_test test_name app_name src_client ext_list pass_regex) +function(add_drmf_test test_name app_name src_client ext_list client_options pass_regex) set(client_name ${test_name}.client) add_library(${client_name} SHARED ${src_client}) set_property(TARGET ${client_name} PROPERTY COMPILE_DEFINITIONS ${arch_defs}) @@ -85,7 +85,7 @@ function(add_drmf_test test_name app_name src_client ext_list pass_regex) endif () add_test(${test_name} ${DynamoRIO_DIR}/../${BIN_ARCH}/drrun ${drrun_extra} - -client ${client_path} 0 "" + -client ${client_path} 0 "${client_options}" -msgbox_mask 0 -stderr_mask 0xc "--" ${app_path}) set_tests_properties(${test_name} PROPERTIES PASS_REGULAR_EXPRESSION "${pass_regex}") @@ -93,17 +93,37 @@ endfunction(add_drmf_test) add_drmf_test_app(drsyscall_app drsyscall_app.c) add_drmf_test(drsyscall_test drsyscall_app drsyscall_client.c - drsyscall "done\nTEST PASSED") + drsyscall "" "done\nTEST PASSED") add_drmf_test(strace_test drsyscall_app strace_client.c - drsyscall "done\n.*TEST PASSED") + drsyscall "" "done\n.*TEST PASSED") # drfuzz tests add_drmf_test_app(drfuzz_app_empty drfuzz_app_empty.c) add_drmf_test(drfuzz_test_empty drfuzz_app_empty drfuzz_client_empty.c - drfuzz "done\nTEST PASSED") + drfuzz "" "done\nTEST PASSED") add_drmf_test(drfuzz_test_mutator drfuzz_app_empty drfuzz_client_mutator.c - drfuzz "TEST PASSED\n.*done") + drfuzz "" "TEST PASSED\n.*done") add_drmf_test_app(drfuzz_app_repeat drfuzz_app_repeat.c) add_drmf_test(drfuzz_test_repeat drfuzz_app_repeat drfuzz_client_repeat.c - drfuzz "hello 1\nhello 2\nhello 3\nhello 4\nhello 5\ndone\nTEST PASSED") + drfuzz "" "hello 1\nhello 2\nhello 3\nhello 4\nhello 5\ndone\nTEST PASSED") + +add_drmf_test_app(drfuzz_app_segfault drfuzz_app_segfault.c) + +set(segfault_regex_1 "Fault occured in target.*with 1 args.*") +set(segfault_regex_2 "Crash originated in target.*with 1 args.*") +set(segfault_regex_3 "TEST PASSED") + +add_drmf_test(drfuzz_test_segfault drfuzz_app_segfault drfuzz_client_segfault.c + drfuzz "-crash" "${segfault_regex_1}${segfault_regex_2}${segfault_regex_3}") +use_DynamoRIO_extension(drfuzz_test_segfault.client drsyms) + +add_drmf_test(drfuzz_test_app_abort drfuzz_app_segfault drfuzz_client_segfault.c + drfuzz "-abort" "${segfault_regex_2}${segfault_regex_3}") +use_DynamoRIO_extension(drfuzz_test_app_abort.client drsyms) + +add_drmf_test(drfuzz_test_no_crash drfuzz_app_segfault drfuzz_client_segfault.c + drfuzz "" ".*TEST PASSED") +use_DynamoRIO_extension(drfuzz_test_no_crash.client drsyms) + +# XXX i#1734: add multi-threaded test, checking for aborted fuzz targets on all threads diff --git a/tests/framework/drfuzz_app_segfault.c b/tests/framework/drfuzz_app_segfault.c new file mode 100644 index 000000000..8471b494a --- /dev/null +++ b/tests/framework/drfuzz_app_segfault.c @@ -0,0 +1,67 @@ +/* ************************************************************** + * Copyright (c) 2015 Google, Inc. All rights reserved. + * **************************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* Test of fault handling in the Dr. Fuzz Extension */ + +#include + +#ifdef WINDOWS +# define EXPORT __declspec(dllexport) +#else +# define EXPORT +#endif + +static int data[10]; + +/* A crash prone function. Segfaults with index out of range. */ +EXPORT void +print_data(unsigned int index) +{ + if (index == 11) { + printf("Index out of bounds! Exiting now.\n"); + exit(1); + } + + printf("data[%d]: %d\n", index, data[index]); +} + +/* Trivial test driver. */ +int +main(int argc, char **argv) +{ + unsigned int i; + + for (i = 0; i < 10; i++) + print_data(i); + printf("done\n"); + return 0; +} diff --git a/tests/framework/drfuzz_client_repeat.c b/tests/framework/drfuzz_client_repeat.c index ea95a5451..2d1935709 100644 --- a/tests/framework/drfuzz_client_repeat.c +++ b/tests/framework/drfuzz_client_repeat.c @@ -36,23 +36,34 @@ #include "drmgr.h" #include "drfuzz.h" +#undef EXPECT /* we don't want msgbox */ +#define EXPECT(cond, msg) \ + ((void)((!(cond)) ? \ + (dr_fprintf(STDERR, "EXPECT FAILURE: %s:%d: %s (%s)", \ + __FILE__, __LINE__, #cond, msg), \ + dr_abort(), 0) : 0)) + static void -pre_fuzz_cb(generic_func_t target_pc, void *fuzzcxt, void **user_data) +pre_fuzz_cb(void *fuzzcxt, generic_func_t target_pc) { ptr_uint_t arg_value; - if (drfuzz_get_arg(target_pc, 0, false/*cur*/, (void **)&arg_value) != DRMF_SUCCESS) - DR_ASSERT_MSG(false, "drfuzz failed to get arg"); + + if (drfuzz_get_arg(fuzzcxt, target_pc, 0, false/*cur*/, + (void **) &arg_value) != DRMF_SUCCESS) + EXPECT(false, "drfuzz failed to get arg"); arg_value = (arg_value + 1); if (drfuzz_set_arg(fuzzcxt, 0, (void *)arg_value) != DRMF_SUCCESS) - DR_ASSERT_MSG(false, "drfuzz failed to set arg"); + EXPECT(false, "drfuzz failed to set arg"); } static bool -post_fuzz_cb(generic_func_t target_pc, void *fuzzcxt, void *user_data) +post_fuzz_cb(void *fuzzcxt, generic_func_t target_pc) { ptr_uint_t arg_value; - if (drfuzz_get_arg(target_pc, 0, false/*cur*/, (void **)&arg_value) != DRMF_SUCCESS) - DR_ASSERT_MSG(false, "drfuzz failed to get arg"); + + if (drfuzz_get_arg(fuzzcxt, target_pc, 0, false/*cur*/, + (void **) &arg_value) != DRMF_SUCCESS) + EXPECT(false, "drfuzz failed to get arg"); if (arg_value == 5) return false; /* stop */ return true; /* repeat */ @@ -62,7 +73,7 @@ static void exit_event(void) { if (drfuzz_exit() != DRMF_SUCCESS) - DR_ASSERT_MSG(false, "drfuzz failed to exit"); + EXPECT(false, "drfuzz failed to exit"); dr_fprintf(STDERR, "TEST PASSED\n"); drmgr_exit(); } @@ -74,18 +85,18 @@ dr_client_main(client_id_t id, int argc, const char *argv[]) generic_func_t repeatme_addr; drmgr_init(); if (drfuzz_init(id) != DRMF_SUCCESS) - DR_ASSERT_MSG(false, "drfuzz failed to init"); + EXPECT(false, "drfuzz failed to init"); dr_register_exit_event(exit_event); /* fuzz repeatme */ app = dr_get_main_module(); if (app == NULL) - DR_ASSERT_MSG(false, "failed to get application module"); + EXPECT(false, "failed to get application module"); repeatme_addr = dr_get_proc_address(app->handle, "repeatme"); if (repeatme_addr == NULL) - DR_ASSERT_MSG(false, "failed to find function repeatme"); + EXPECT(false, "failed to find function repeatme"); if (drfuzz_fuzz_target(repeatme_addr, 1, DRFUZZ_CALLCONV_CDECL, pre_fuzz_cb, post_fuzz_cb) != DRMF_SUCCESS) - DR_ASSERT_MSG(false, "drfuzz failed to fuzz function repeatme"); + EXPECT(false, "drfuzz failed to fuzz function repeatme"); dr_free_module_data(app); } diff --git a/tests/framework/drfuzz_client_segfault.c b/tests/framework/drfuzz_client_segfault.c new file mode 100644 index 000000000..1088a3cb4 --- /dev/null +++ b/tests/framework/drfuzz_client_segfault.c @@ -0,0 +1,266 @@ +/* ************************************************************** + * Copyright (c) 2015 Google, Inc. All rights reserved. + * **************************************************************/ + +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * * Neither the name of Google, Inc. nor the names of its contributors may be + * used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE, INC. OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +/* Test of fault handling in the Dr. Fuzz Extension */ + +#include "dr_api.h" +#include +#include "drmgr.h" +#include "drsyms.h" +#include "drfuzz.h" + +/* Finds the print_data() function in the target app. */ +#ifdef UNIX +# define TARGET_SYMBOL "drfuzz_app_segfault!print_data" +#else +# define TARGET_SYMBOL "print_data" +#endif + +#undef EXPECT /* we don't want msgbox */ +#define EXPECT(cond, msg) \ + ((void)((!(cond)) ? \ + (dr_fprintf(STDERR, "EXPECT FAILURE: %s:%d: %s (%s)", \ + __FILE__, __LINE__, #cond, msg), \ + dr_abort(), 0) : 0)) + +/* Some fake user data to attach to fuzzer data structures. */ +static const char *fake_stack_trace = "fake stack trace"; +static const char *fake_per_thread_data = "fake per thread data"; +static const char *fake_target_data = "fake target data"; + +/* Trivial state for driving the test. */ +static bool invoke_crash; +static bool invoke_abort; +static bool per_thread_is_null = true; +static bool fault_delete_occurred = false; +static bool repeating_target = false; +static uint repeat_index = 0; + +/* Report to STDERR when a critical fault occurs in a fuzz target. */ +static void +fault_event(void *fuzzcxt, drfuzz_fault_t *fault, drfuzz_fault_ex_t *fault_ex) +{ + drfuzz_target_frame_t *target_frame; + drfuzz_target_iterator_t *iter = drfuzz_target_iterator_start(fuzzcxt); + + while ((target_frame = drfuzz_target_iterator_next(iter)) != NULL) { + uint i; + dr_fprintf(STDERR, "Fault occured in target "PIFX" with %d args: ", + (ptr_uint_t) target_frame->func_pc, target_frame->arg_count); + for (i = 0; i < target_frame->arg_count; i++) { + dr_fprintf(STDERR, PIFX, target_frame->arg_values[i]); + if (i < (target_frame->arg_count - 1)) + dr_fprintf(STDERR, ", "); + } + dr_fprintf(STDERR, "\n"); + } + drfuzz_target_iterator_stop(iter); + + fault->user_data = (void *) fake_stack_trace; +} + +/* Make sure the fake user data is still set on the segfault instance. */ +static void +fault_deleted(void *fuzzcxt, drfuzz_fault_t *fault) +{ + EXPECT(fault->user_data == fake_stack_trace, "user data is incorrect"); + fault_delete_occurred = true; +} + +/* Make sure the fake user data is still set on the fuzz target per-thread instance. */ +static void +delete_per_thread_data(void *per_thread_data) +{ + EXPECT(per_thread_data == (per_thread_is_null ? NULL : fake_per_thread_data), + "per-thread user data is incorrect"); +} + +/* Make sure the fake user data is still set on the fuzz target instance. */ +static void +delete_per_target_data(void *per_target_data) +{ + EXPECT(per_target_data == fake_target_data, "per-target user data is incorrect"); +} + +/* Find the fuzz target. */ +static generic_func_t +find_target_pc() +{ + size_t symbol_offset; + drsym_debug_kind_t kind; + drsym_error_t result; + generic_func_t target; + module_data_t *module = dr_get_main_module(); + + EXPECT(module != NULL, "Main module not initialized"); + + /* give a meaningful error message if the module doesn't have symbols */ + if (drsym_get_module_debug_kind(module->full_path, &kind) != DRSYM_SUCCESS) + EXPECT(false, "module does not have symbols"); + + result = drsym_lookup_symbol(module->full_path, TARGET_SYMBOL, &symbol_offset, 0); + EXPECT(result == DRSYM_SUCCESS && symbol_offset > 0, "cannot find symbol"); + + target = (generic_func_t) (module->start + symbol_offset); + dr_free_module_data(module); + return target; +} + +/* When the app crashes, report the live fuzz targets and args to STDERR. */ +static void +thread_crash(void *fuzzcxt, drfuzz_fault_thread_state_t *state) +{ + drfuzz_target_frame_t *target_frame; + + EXPECT(state->targets != NULL, "fuzz targets are missing from the crash state"); + while ((target_frame = drfuzz_target_iterator_next(state->targets)) != NULL) { + uint i; + dr_fprintf(STDERR, "Crash originated in target "PIFX" with %d args: ", + (ptr_uint_t) target_frame->func_pc, target_frame->arg_count); + for (i = 0; i < target_frame->arg_count; i++) { + dr_fprintf(STDERR, PIFX, (ptr_uint_t) target_frame->arg_values[i]); + if (i < (target_frame->arg_count - 1)) + dr_fprintf(STDERR, ", "); + } + dr_fprintf(STDERR, "\n"); + } +} + +/* Fuzz driver. When the target app is executed natively, it will invoke the fuzz target + * (print_data()) 10 times. This driver repeats each of those invocations 3 times without + * changing the arguments, to test the scope and release of the per-target per-thread user + * data. Then on the last iteration (where the app's index arg is 9), the index is changed + * to 100000, causing a segfault by attempting to read a static array way out of bounds. + */ +static void +pre_fuzz(void *fuzzcxt, generic_func_t target_pc) +{ + drmf_status_t res; + const char *user_data; + ptr_uint_t index_arg; + + if (drfuzz_get_arg(fuzzcxt, target_pc, 0, true, (void *) &index_arg) != DRMF_SUCCESS) + EXPECT(false, "failed to get fuzz target's arg value"); + + if (repeating_target) { + res = drfuzz_get_target_per_thread_user_data(fuzzcxt, target_pc, + (void *) &user_data); + EXPECT(res == DRMF_SUCCESS, "failed to get target per-thread data"); + EXPECT(user_data == fake_per_thread_data, "incorrect target per-thread data"); + } else { + res = drfuzz_set_target_per_thread_user_data(fuzzcxt, target_pc, + (void *) fake_per_thread_data, + delete_per_thread_data); + EXPECT(res == DRMF_SUCCESS, "failed to set target per-thread data"); + per_thread_is_null = false; + } + + repeating_target = (repeat_index++ < 3); + + if (!repeating_target) { + res = drfuzz_set_target_per_thread_user_data(fuzzcxt, target_pc, NULL, + delete_per_thread_data); + EXPECT(res == DRMF_SUCCESS, "failed to clear the target per-thread data"); + per_thread_is_null = true; + res = drfuzz_get_target_per_thread_user_data(fuzzcxt, target_pc, + (void *) &user_data); + EXPECT(res == DRMF_SUCCESS, "failed to get the cleared target per-thread data"); + EXPECT(user_data == NULL, "incorrect target per-thread data"); + } + + if (invoke_crash && index_arg == 9) + drfuzz_set_arg(fuzzcxt, 0, (void *) 10000000); + if (invoke_abort && index_arg == 9) + drfuzz_set_arg(fuzzcxt, 0, (void *) 11); +} + +/* Just checks the per-target user data and directs drfuzz as decided in pre-fuzz. */ +static bool +post_fuzz(void *fuzzcxt, generic_func_t target_pc) +{ + drmf_status_t res; + const char *user_data; + + res = drfuzz_get_target_user_data(target_pc, (void *) &user_data); + EXPECT(res == DRMF_SUCCESS, "failed to get the target data"); + EXPECT(user_data == fake_target_data, "failed to get the target data"); + + return repeating_target; +} + +/* Test passes if none of the EXPECT statements fail. */ +static +void exit_event(void) +{ + EXPECT(!invoke_crash || fault_delete_occurred, "fault user data was not deleted"); + EXPECT(invoke_crash || !fault_delete_occurred, "unexpected fault delete callback"); + if (drfuzz_exit() != DRMF_SUCCESS) + EXPECT(false, "drfuzz failed to exit"); + dr_fprintf(STDERR, "TEST PASSED\n"); + drmgr_exit(); + drsym_exit(); + + dr_exit_process(0); /* change the exit code so ctest believes the test passed */ +} + +/* Initialize drfuzz, register the fuzz target and related callbacks, and set some + * fake data in the per-target user data field. + */ +DR_EXPORT +void dr_client_main(client_id_t id, int argc, const char *argv[]) +{ + generic_func_t target_pc; + + invoke_crash = (argc > 1 && strcmp(argv[1], "-crash") == 0); + invoke_abort = (argc > 1 && strcmp(argv[1], "-abort") == 0); + + drmgr_init(); + drsym_init(0); + dr_register_exit_event(exit_event); + + if (drfuzz_init(id) != DRMF_SUCCESS) + EXPECT(false, "drfuzz failed to init"); + if (drfuzz_register_fault_event(fault_event) != DRMF_SUCCESS) + EXPECT(false, "failed to register the fault event"); + if (drfuzz_register_fault_delete_callback(fault_deleted) != DRMF_SUCCESS) + EXPECT(false, "failed to register the fault delete callback"); + if (drfuzz_register_crash_thread_event(thread_crash) != DRMF_SUCCESS) + EXPECT(false, "failed to register the thread crash event"); + + target_pc = find_target_pc(); + if (drfuzz_fuzz_target(target_pc, 1, DRFUZZ_CALLCONV_CDECL, + pre_fuzz, post_fuzz) != DRMF_SUCCESS) + EXPECT(false, "failed to register the fuzz target"); + + drfuzz_set_target_user_data(target_pc, (void *) fake_target_data, + delete_per_target_data); +} diff --git a/umbra/umbra.dox b/umbra/umbra.dox index a8e358da8..4f5595ea8 100644 --- a/umbra/umbra.dox +++ b/umbra/umbra.dox @@ -100,7 +100,7 @@ translation values to the instructions. Some shadow mapping schemes do not support shadow memory for invalid application addresses (i.e., addresses for which no application memory is allocated). If such application addresses are passed to shadow API -routines, they will fail with #DRMF_ERROR_INVALID_ADDRESS, while shadow -memory type queries will indicate #UMBRA_SHADOW_MEMORY_TYPE_NOT_SHADOW. +routines, they will fail with DRMF_ERROR_INVALID_ADDRESS, while shadow +memory type queries will indicate UMBRA_SHADOW_MEMORY_TYPE_NOT_SHADOW. */