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:
@@ -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);
|
||||
|
||||
|
||||
@@ -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. */
|
||||
|
||||
Reference in New Issue
Block a user