Merge pull request #49 from trailofbits/child-proc-mem-share

Use shared memory to determine results of forked test runs
This commit is contained in:
Joe Ranweiler
2018-02-22 09:35:35 -08:00
committed by GitHub
2 changed files with 88 additions and 36 deletions

View File

@@ -28,6 +28,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
@@ -55,7 +56,8 @@ DEEPSTATE_BEGIN_EXTERN_C
DECLARE_string(input_test_dir);
DECLARE_string(output_test_dir);
DECLARE_string(take_over);
DECLARE_bool(take_over);
enum {
DeepState_InputSize = 8192
@@ -307,6 +309,12 @@ struct DeepState_TestInfo {
unsigned line_number;
};
struct DeepState_TestRunInfo {
struct DeepState_TestInfo *test;
enum DeepState_TestRunResult result;
const char *reason;
};
/* Pointer to the last registered `TestInfo` structure. */
extern struct DeepState_TestInfo *DeepState_LastTestInfo;
@@ -350,11 +358,11 @@ extern void DeepState_Begin(struct DeepState_TestInfo *info);
/* Return the first test case to run. */
extern struct DeepState_TestInfo *DeepState_FirstTest(void);
/* Returns 1 if a failure was caught, otherwise 0. */
extern int DeepState_CatchFail(void);
/* Returns `true` if a failure was caught for the current test case. */
extern bool DeepState_CatchFail(void);
/* Returns 1 if this test case was abandoned. */
extern int DeepState_CatchAbandoned(void);
/* Returns `true` if the current test case was abandoned. */
extern bool DeepState_CatchAbandoned(void);
/* Save a passing test to the output test directory. */
extern void DeepState_SavePassingTest(void);
@@ -371,7 +379,7 @@ extern jmp_buf DeepState_ReturnToRun;
/* Checks a filename to see if might be a saved test case.
*
* Valid saved test cases have the suffix `.pass` or `.fail`. */
static bool IsTestCaseFile(const char *name) {
static bool DeepState_IsTestCaseFile(const char *name) {
const char *suffix = strchr(name, '.');
if (suffix == NULL) {
return false;
@@ -395,7 +403,7 @@ static bool IsTestCaseFile(const char *name) {
/* Resets the global `DeepState_Input` buffer, then fills it with the
* data found in the file `path`. */
static void InitializeInputFromFile(const char *path) {
static void DeepState_InitInputFromFile(const char *path) {
struct stat stat_buf;
FILE *fp = fopen(path, "r");
@@ -512,7 +520,7 @@ DeepState_RunSavedTestCase(struct DeepState_TestInfo *test, const char *dir,
}
snprintf(path, path_len, "%s/%s", dir, name);
InitializeInputFromFile(path);
DeepState_InitInputFromFile(path);
free(path);
@@ -562,7 +570,7 @@ static int DeepState_RunSavedCasesForTest(struct DeepState_TestInfo *test) {
/* Read generated test cases and run a test for each file found. */
while ((dp = readdir(dir_fd)) != NULL) {
if (IsTestCaseFile(dp->d_name)) {
if (DeepState_IsTestCaseFile(dp->d_name)) {
enum DeepState_TestRunResult result =
DeepState_RunSavedTestCase(test, test_case_dir, dp->d_name);

View File

@@ -30,7 +30,8 @@ DEFINE_uint(num_workers, 1,
DEFINE_string(input_test_dir, "", "Directory of saved tests to run.");
DEFINE_string(output_test_dir, "", "Directory where tests will be saved.");
DEFINE_string(take_over, "", "Replay test cases in take-over mode.");
DEFINE_bool(take_over, false, "Replay test cases in take-over mode.");
/* Pointer to the last registers DeepState_TestInfo data structure */
struct DeepState_TestInfo *DeepState_LastTestInfo = NULL;
@@ -45,26 +46,62 @@ uint32_t DeepState_InputIndex = 0;
/* Jump buffer for returning to `DeepState_Run`. */
jmp_buf DeepState_ReturnToRun = {};
static const char *DeepState_TestAbandoned = NULL;
static int DeepState_TestFailed = 0;
/* Information about the current test run, if any. */
static struct DeepState_TestRunInfo *DeepState_CurrentTestRun = NULL;
static void DeepState_SetTestPassed(void) {
DeepState_CurrentTestRun->result = DeepState_TestRunPass;
}
static void DeepState_SetTestFailed(void) {
DeepState_CurrentTestRun->result = DeepState_TestRunFail;
}
static void DeepState_SetTestAbandoned(const char *reason) {
DeepState_CurrentTestRun->result = DeepState_TestRunAbandon;
DeepState_CurrentTestRun->reason = reason;
}
void DeepState_AllocCurrentTestRun(void) {
int mem_prot = PROT_READ | PROT_WRITE;
int mem_vis = MAP_ANONYMOUS | MAP_SHARED;
void *shared_mem = mmap(NULL, sizeof(struct DeepState_TestRunInfo), mem_prot,
mem_vis, 0, 0);
if (shared_mem == MAP_FAILED) {
DeepState_Log(DeepState_LogError, "Unable to map shared memory.");
exit(1);
}
DeepState_CurrentTestRun = (struct DeepState_TestRunInfo *) shared_mem;
}
static void DeepState_InitCurrentTestRun(struct DeepState_TestInfo *test) {
DeepState_CurrentTestRun->test = test;
DeepState_CurrentTestRun->result = DeepState_TestRunPass;
DeepState_CurrentTestRun->reason = NULL;
}
/* Abandon this test. We've hit some kind of internal problem. */
DEEPSTATE_NORETURN
void DeepState_Abandon(const char *reason) {
DeepState_Log(DeepState_LogFatal, reason);
DeepState_TestAbandoned = reason;
DeepState_CurrentTestRun->result = DeepState_TestRunAbandon;
DeepState_CurrentTestRun->reason = reason;
longjmp(DeepState_ReturnToRun, 1);
}
/* Mark this test as having crashed. */
void DeepState_Crash(void) {
DeepState_TestFailed = 1;
DeepState_SetTestFailed();
}
/* Mark this test as failing. */
DEEPSTATE_NORETURN
void DeepState_Fail(void) {
DeepState_TestFailed = 1;
DeepState_SetTestFailed();
if (FLAGS_take_over) {
// We want to communicate the failure to a parent process, so exit.
@@ -81,7 +118,7 @@ void DeepState_Pass(void) {
}
void DeepState_SoftFail(void) {
DeepState_TestFailed = 1;
DeepState_SetTestFailed();
}
/* Symbolize the data in the exclusive range `[begin, end)`. */
@@ -309,6 +346,8 @@ const struct DeepState_IndexEntry DeepState_API[] = {
/* Set up DeepState. */
void DeepState_Setup(void) {
DeepState_AllocCurrentTestRun();
/* TODO(pag): Sort the test cases by file name and line number. */
}
@@ -318,11 +357,10 @@ void DeepState_Teardown(void) {
}
/* Notify that we're about to begin a test. */
void DeepState_Begin(struct DeepState_TestInfo *info) {
DeepState_TestFailed = 0;
DeepState_TestAbandoned = NULL;
void DeepState_Begin(struct DeepState_TestInfo *test) {
DeepState_InitCurrentTestRun(test);
DeepState_LogFormat(DeepState_LogInfo, "Running: %s from %s(%u)",
info->test_name, info->file_name, info->line_number);
test->test_name, test->file_name, test->line_number);
}
/* Save a failing test. */
@@ -330,9 +368,8 @@ void DeepState_Begin(struct DeepState_TestInfo *info) {
/* Runs in a child process, under the control of Dr. Memory */
void DrMemFuzzFunc(volatile uint8_t *buff, size_t size) {
struct DeepState_TestInfo *test = DeepState_DrFuzzTest;
DeepState_TestFailed = 0;
DeepState_InputIndex = 0;
DeepState_TestAbandoned = NULL;
DeepState_InitCurrentTestRun(test);
DeepState_LogFormat(DeepState_LogInfo, "Running: %s from %s(%u)",
test->test_name, test->file_name, test->line_number);
@@ -388,7 +425,9 @@ void DeepState_RunSavedTakeOverCases(jmp_buf env,
/* Read generated test cases and run a test for each file found. */
while ((dp = readdir(dir_fd)) != NULL) {
if (IsTestCaseFile(dp->d_name)) {
if (DeepState_IsTestCaseFile(dp->d_name)) {
DeepState_InitCurrentTestRun(test);
pid_t case_pid = fork();
if (!case_pid) {
DeepState_Begin(test);
@@ -400,7 +439,7 @@ void DeepState_RunSavedTakeOverCases(jmp_buf env,
DeepState_Abandon("Error allocating memory");
}
snprintf(path, path_len, "%s/%s", test_case_dir, dp->d_name);
InitializeInputFromFile(path);
DeepState_InitInputFromFile(path);
free(path);
longjmp(env, 1);
@@ -411,9 +450,7 @@ void DeepState_RunSavedTakeOverCases(jmp_buf env,
/* If we exited normally, the status code tells us if the test passed. */
if (WIFEXITED(wstatus)) {
uint8_t status = WEXITSTATUS(wstatus);
switch (status) {
switch (DeepState_CurrentTestRun->result) {
case DeepState_TestRunPass:
DeepState_LogFormat(DeepState_LogInfo,
"Passed: TakeOver test with data from `%s`",
@@ -424,15 +461,20 @@ void DeepState_RunSavedTakeOverCases(jmp_buf env,
"Failed: TakeOver test with data from `%s`",
dp->d_name);
break;
case DeepState_TestRunCrash:
DeepState_LogFormat(DeepState_LogError,
"Crashed: TakeOver test with data from `%s`",
dp->d_name);
break;
case DeepState_TestRunAbandon:
DeepState_LogFormat(DeepState_LogError,
"Abandoned: TakeOver test with data from `%s`",
dp->d_name);
break;
default:
default: /* Should never happen */
DeepState_LogFormat(DeepState_LogError,
"Unknown exit code from test with data from `%s`",
dp->d_name);
"Error: Invalid test run result %d from `%s`",
DeepState_CurrentTestRun->result, dp->d_name);
}
} else {
/* If here, we exited abnormally but didn't catch it in the signal
@@ -455,6 +497,8 @@ int DeepState_TakeOver(void) {
.line_number = 0,
};
DeepState_AllocCurrentTestRun();
jmp_buf env;
if (!setjmp(env)) {
DeepState_RunSavedTakeOverCases(env, &test);
@@ -484,14 +528,14 @@ struct DeepState_TestInfo *DeepState_FirstTest(void) {
return DeepState_LastTestInfo;
}
/* Returns 1 if a failure was caught, otherwise 0. */
int DeepState_CatchFail(void) {
return DeepState_TestFailed;
/* Returns `true` if a failure was caught for the current test case. */
bool DeepState_CatchFail(void) {
return DeepState_CurrentTestRun->result == DeepState_TestRunFail;
}
/* Returns 1 if this test case was abandoned. */
int DeepState_CatchAbandoned(void) {
return DeepState_TestAbandoned != NULL;
/* Returns `true` if the current test case was abandoned. */
bool DeepState_CatchAbandoned(void) {
return DeepState_CurrentTestRun->result == DeepState_TestRunAbandon;
}
/* Overwrite libc's abort. */