From 862c725ab1d230fad69d47838c3702fa2a14c846 Mon Sep 17 00:00:00 2001 From: ex0dus-0x Date: Wed, 18 Dec 2019 16:08:36 -0500 Subject: [PATCH 1/2] Add initial support for crash backtracing --- src/include/deepstate/DeepState.h | 36 ++++++++++++++++++++++++++++++- src/lib/DeepState.c | 14 ++++++------ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/include/deepstate/DeepState.h b/src/include/deepstate/DeepState.h index 67fa862e..b531c92f 100644 --- a/src/include/deepstate/DeepState.h +++ b/src/include/deepstate/DeepState.h @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -90,6 +91,7 @@ DECLARE_bool(fork); DECLARE_bool(list_tests); DECLARE_bool(boring_only); DECLARE_bool(run_disabled); +DECLARE_bool(verbose_crash_trace); DECLARE_int(min_log_level); DECLARE_int(seed); @@ -521,6 +523,25 @@ extern void DeepState_SaveFailingTest(void); /* Save a crashing test to the output test directory. */ extern void DeepState_SaveCrashingTest(void); +/* Emit test function backtrace after test crashes. */ +static void DeepState_EmitBacktrace(int signum, siginfo_t *sig, void *context) { + + DeepState_LogFormat(DeepState_LogInfo, "Test crashed with: %s", sys_siglist[sig->si_status]); + + void *array[10]; + size_t size; + char **strings; + + size = backtrace(array, 10); + strings = backtrace_symbols(array, size); + + for (size_t i = 0; i < size; i++) + DeepState_LogFormat(DeepState_LogTrace, "%s", strings[i]); + + free(strings); +} + + /* Jump buffer for returning to `DeepState_Run`. */ extern jmp_buf DeepState_ReturnToRun; @@ -650,7 +671,6 @@ static int DeepState_RunTestNoFork(struct DeepState_TestInfo *test) { #if defined(__cplusplus) && defined(__cpp_exceptions) try { #endif /* __cplusplus */ - test->test_func(); /* Run the test function. */ return(DeepState_TestRunPass); @@ -692,6 +712,19 @@ static int DeepState_RunTestNoFork(struct DeepState_TestInfo *test) { /* Fork and run `test`. */ static enum DeepState_TestRunResult DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { + + /* If flag is set, install a signal handler for SIGCHLD */ + /* TODO(alan): use handler as "multiplexer" and handle child signal */ + if (FLAGS_verbose_crash_trace) { + struct sigaction sigact, oldact; + + sigact.sa_flags = SA_SIGINFO | SA_NOCLDWAIT; + sigact.sa_sigaction = DeepState_EmitBacktrace; + + sigaction(SIGCHLD, &sigact, &oldact); + } + + pid_t test_pid; if (FLAGS_fork) { test_pid = fork(); @@ -754,6 +787,7 @@ DeepState_RunSavedTestCase(struct DeepState_TestInfo *test, const char *dir, DeepState_LogFormat(DeepState_LogError, "Crashed: %s", test->test_name); DeepState_LogFormat(DeepState_LogError, "Test case %s crashed", path); free(path); + if (HAS_FLAG_output_test_dir) { DeepState_SaveCrashingTest(); } diff --git a/src/lib/DeepState.c b/src/lib/DeepState.c index 7b275de8..5cbf281f 100644 --- a/src/lib/DeepState.c +++ b/src/lib/DeepState.c @@ -41,10 +41,11 @@ DEFINE_string(output_test_dir, InputOutputGroup, "", "Directory where tests will DEFINE_bool(take_over, ExecutionGroup, false, "Replay test cases in take-over mode."); DEFINE_bool(abort_on_fail, ExecutionGroup, false, "Abort on file replay failure (useful in file fuzzing)."); DEFINE_bool(exit_on_fail, ExecutionGroup, false, "Exit with status 255 on test failure."); -DEFINE_bool(verbose_reads, ExecutionGroup, false, "Report on bytes being read during execution of test."); DEFINE_int(min_log_level, ExecutionGroup, 0, "Minimum level of logging to output (default 2, 0=debug, 1=trace, 2=info, ...)."); DEFINE_int(timeout, ExecutionGroup, 120, "Timeout for brute force fuzzing."); DEFINE_uint(num_workers, ExecutionGroup, 1, "Number of workers to spawn for testing and test generation."); +DEFINE_bool(verbose_reads, ExecutionGroup, false, "Report on bytes being read during execution of test."); +DEFINE_bool(verbose_crash_trace, ExecutionGroup, false, "If test crashes, report an execution backtrace after abrupt exit."); /* Fuzzing and symex related options, baked in to perform analysis-related tasks without auxiliary tools */ DEFINE_bool(fuzz, AnalysisGroup, false, "Perform brute force unguided fuzzing."); @@ -254,7 +255,7 @@ void DeepState_SwarmAssignCStr_C(const char* file, unsigned line, int stype, if (NULL == str) { DeepState_Abandon("Attempted to populate null pointer."); } - char swarm_allowed[256]; + char swarm_allowed[256]; if (allowed == 0) { /* In swarm mode, if there is no allowed string, create one over all chars. */ for (int i = 0; i < 255; i++) { @@ -306,7 +307,7 @@ char *DeepState_SwarmCStr_C(const char* file, unsigned line, int stype, if (NULL == str) { DeepState_Abandon("Can't allocate memory"); } - char swarm_allowed[256]; + char swarm_allowed[256]; if (allowed == 0) { /* In swarm mode, if there is no allowed string, create one over all chars. */ for (int i = 0; i < 255; i++) { @@ -347,14 +348,14 @@ void DeepState_SymbolizeCStr_C(char *begin, const char* allowed) { void DeepState_SwarmSymbolizeCStr_C(const char* file, unsigned line, int stype, char *begin, const char* allowed) { if (begin && begin[0]) { - char swarm_allowed[256]; + char swarm_allowed[256]; if (allowed == 0) { /* In swarm mode, if there is no allowed string, create one over all chars. */ for (int i = 0; i < 255; i++) { swarm_allowed[i] = i+1; } swarm_allowed[255] = 0; - allowed = (const char*)&swarm_allowed; + allowed = (const char*)&swarm_allowed; } uint32_t allowed_size = strlen(allowed); struct DeepState_SwarmConfig* sc = DeepState_GetSwarmConfig(allowed_size, file, line, stype); @@ -668,7 +669,7 @@ extern void DeepState_CleanUp() { free(DeepState_GeneratedStrings[i]); } DeepState_GeneratedStringsIndex = 0; - + for (int i = 0; i < DeepState_SwarmConfigsIndex; i++) { free(DeepState_SwarmConfigs[i]->file); free(DeepState_SwarmConfigs[i]->fmap); @@ -1116,6 +1117,7 @@ enum DeepState_TestRunResult DeepState_FuzzOneTestCase(struct DeepState_TestInfo enum DeepState_TestRunResult result = DeepState_ForkAndRunTest(test); + if (result == DeepState_TestRunCrash) { DeepState_LogFormat(DeepState_LogError, "Crashed: %s", test->test_name); From 5e9d0d31df76cb9ca7b78d9de7c8f377a606c903 Mon Sep 17 00:00:00 2001 From: ex0dus-0x Date: Fri, 27 Dec 2019 10:44:13 -0500 Subject: [PATCH 2/2] Improve backtrace routine --- src/include/deepstate/DeepState.h | 62 ++++++++++++++++++++----------- src/lib/DeepState.c | 3 +- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/src/include/deepstate/DeepState.h b/src/include/deepstate/DeepState.h index b531c92f..d16cd0ce 100644 --- a/src/include/deepstate/DeepState.h +++ b/src/include/deepstate/DeepState.h @@ -59,6 +59,10 @@ #define DEEPSTATE_SIZE 8192 #endif +#ifndef DEEPSTATE_CRASH_MAX_FRAMES +#define DEEPSTATE_CRASH_MAX_FRAMES 63 +#endif + #ifndef DEEPSTATE_MAX_SWARM_CONFIGS #define DEEPSTATE_MAX_SWARM_CONFIGS 1024 #endif @@ -524,21 +528,38 @@ extern void DeepState_SaveFailingTest(void); extern void DeepState_SaveCrashingTest(void); /* Emit test function backtrace after test crashes. */ -static void DeepState_EmitBacktrace(int signum, siginfo_t *sig, void *context) { +static void DeepState_EmitBacktrace(int signum, siginfo_t *sig, void *_context) { - DeepState_LogFormat(DeepState_LogInfo, "Test crashed with: %s", sys_siglist[sig->si_status]); + /* output information about the signal caught and the exception that occurred */ + const char *result; + if (!sig->si_status) + result = sys_siglist[signum]; + else + result = sys_siglist[sig->si_status]; + DeepState_LogFormat(DeepState_LogError, "Signal caught in test: %s (error: %d)", result, sig->si_signo); - void *array[10]; + /* return a backtrace */ size_t size; - char **strings; + void *back_addrs[DEEPSTATE_CRASH_MAX_FRAMES]; + char **symbols; - size = backtrace(array, 10); - strings = backtrace_symbols(array, size); + size = backtrace(back_addrs, DEEPSTATE_CRASH_MAX_FRAMES); + if (size == 0) + DeepState_Abandon("Cannot retrieve backtrace stack addresses"); + symbols = backtrace_symbols(back_addrs, size); + if (symbols == NULL) + DeepState_Abandon("Cannot retrieve symbols for stack addresses"); + + DeepState_LogFormat(DeepState_LogTrace, "======= Backtrace: ========="); for (size_t i = 0; i < size; i++) - DeepState_LogFormat(DeepState_LogTrace, "%s", strings[i]); + DeepState_LogFormat(DeepState_LogTrace, "%s", symbols[i]); + DeepState_LogFormat(DeepState_LogTrace, "==========================="); - free(strings); + /* cleanup resources and exit */ + free(symbols); + DeepState_CleanUp(); + exit(DeepState_TestRunCrash); } @@ -712,19 +733,6 @@ static int DeepState_RunTestNoFork(struct DeepState_TestInfo *test) { /* Fork and run `test`. */ static enum DeepState_TestRunResult DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { - - /* If flag is set, install a signal handler for SIGCHLD */ - /* TODO(alan): use handler as "multiplexer" and handle child signal */ - if (FLAGS_verbose_crash_trace) { - struct sigaction sigact, oldact; - - sigact.sa_flags = SA_SIGINFO | SA_NOCLDWAIT; - sigact.sa_sigaction = DeepState_EmitBacktrace; - - sigaction(SIGCHLD, &sigact, &oldact); - } - - pid_t test_pid; if (FLAGS_fork) { test_pid = fork(); @@ -737,6 +745,18 @@ DeepState_ForkAndRunTest(struct DeepState_TestInfo *test) { if (FLAGS_fork) { waitpid(test_pid, &wstatus, 0); } else { + + /* If flag is set, install a "multiplexed" signal handler + * in order to backtrace and cleanup without an abrupt exit. */ + if (FLAGS_verbose_crash_trace) { + struct sigaction sigact; + sigact.sa_flags = SA_SIGINFO; + sigact.sa_sigaction = DeepState_EmitBacktrace; + + for (int i = 0; i <= sizeof(sys_siglist); i++) + sigaction(i, &sigact, 0); + } + wstatus = DeepState_RunTestNoFork(test); DeepState_CleanUp(); } diff --git a/src/lib/DeepState.c b/src/lib/DeepState.c index 5cbf281f..cb7161b4 100644 --- a/src/lib/DeepState.c +++ b/src/lib/DeepState.c @@ -43,9 +43,8 @@ DEFINE_bool(abort_on_fail, ExecutionGroup, false, "Abort on file replay failure DEFINE_bool(exit_on_fail, ExecutionGroup, false, "Exit with status 255 on test failure."); DEFINE_int(min_log_level, ExecutionGroup, 0, "Minimum level of logging to output (default 2, 0=debug, 1=trace, 2=info, ...)."); DEFINE_int(timeout, ExecutionGroup, 120, "Timeout for brute force fuzzing."); -DEFINE_uint(num_workers, ExecutionGroup, 1, "Number of workers to spawn for testing and test generation."); DEFINE_bool(verbose_reads, ExecutionGroup, false, "Report on bytes being read during execution of test."); -DEFINE_bool(verbose_crash_trace, ExecutionGroup, false, "If test crashes, report an execution backtrace after abrupt exit."); +DEFINE_bool(verbose_crash_trace, ExecutionGroup, false, "If unforked test crashes, report crash information and backtrace."); /* Fuzzing and symex related options, baked in to perform analysis-related tasks without auxiliary tools */ DEFINE_bool(fuzz, AnalysisGroup, false, "Perform brute force unguided fuzzing.");