Merge pull request #1965 from CodeLinaro/stateful

replay mode support
This commit is contained in:
van Hauser 2024-02-08 10:29:33 +01:00 committed by GitHub
commit 42c663e7c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 528 additions and 32 deletions

4
.gitignore vendored
View File

@ -103,6 +103,10 @@ utils/optimin/build
utils/optimin/optimin
utils/persistent_mode/persistent_demo
utils/persistent_mode/persistent_demo_new
utils/persistent_mode/persistent_demo_new_compat
utils/persistent_mode/test-instr
utils/replay_record/persistent_demo_replay
utils/replay_record/persistent_demo_replay_compat
utils/replay_record/persistent_demo_replay_argparse
utils/plot_ui/afl-plot-ui
vuln_prog

View File

@ -124,6 +124,10 @@
#define CASE_PREFIX "id_"
#endif /* ^!SIMPLE_FILES */
#ifdef AFL_PERSISTENT_RECORD
#define RECORD_PREFIX "RECORD:"
#endif
#define STAGE_BUF_SIZE (64) /* usable size for stage name buf in afl_state */
// Little helper to access the ptr to afl->##name_buf - for use in afl_realloc.

View File

@ -0,0 +1,131 @@
#ifndef _HAVE_PERSISTENT_REPLAY_H
#define _HAVE_PERSISTENT_REPLAY_H
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifndef PATH_MAX
#define PATH_MAX 4096
#endif
static unsigned short int is_replay_record;
static unsigned int replay_record;
static unsigned int replay_record_cnt;
static char replay_record_path[PATH_MAX];
static char *replay_record_dir;
static struct dirent **record_list;
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
static char **record_arg = NULL;
#endif // AFL_PERSISTENT_REPLAY_ARGPARSE
static int select_files(const struct dirent *dirbuf) {
char fn[PATH_MAX];
if (dirbuf->d_name[0] == '.') {
return 0;
} else {
snprintf(fn, sizeof(fn), "RECORD:%06u", replay_record);
return !!strstr(dirbuf->d_name, fn);
}
}
static int compare_files(const struct dirent **da, const struct dirent **db) {
unsigned int c1 = 0, c2 = 0;
sscanf((*da)->d_name, "RECORD:%*u,cnt:%06u", &c1);
sscanf((*db)->d_name, "RECORD:%*u,cnt:%06u", &c2);
return c1 - c2;
}
__attribute__((destructor)) static void __afl_record_replay_destroy(void) {
for (int i = 0; i < replay_record_cnt; i++) {
free(record_list[i]);
}
free(record_list);
}
__attribute__((constructor)) static void __afl_record_replay_init(
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
int argc, char **argv
#endif // AFL_PERSISTENT_REPLAY_ARGPARSE
) {
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
char **argp;
#endif // AFL_PERSISTENT_REPLAY_ARGPARSE
struct stat sb;
/* caveat: if harness uses @@ and we don't pass it, it will regardless loop
* the number of iterations defined for AFL_LOOP (on the same file)*/
if (!(is_replay_record = !!getenv("AFL_PERSISTENT_REPLAY"))) {
// printf("[warning] AFL_PERSISTENT_REPLAY not set.\n");
return;
}
replay_record = atoi(getenv("AFL_PERSISTENT_REPLAY"));
replay_record_dir = getenv("AFL_PERSISTENT_DIR");
if (!(stat(replay_record_dir, &sb) == 0 && S_ISDIR(sb.st_mode))) {
fprintf(stderr, "[error] Can't find the requested record directory!\n");
is_replay_record = 0;
return;
}
replay_record_cnt = scandir(replay_record_dir ? replay_record_dir : "./",
&record_list, select_files, compare_files);
if (!replay_record_cnt) {
fprintf(stderr, "[error] Can't find the requested record!\n");
is_replay_record = 0;
}
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
argp = argv;
while (*argp) {
if (!strcmp(*argp, "@@")) {
record_arg = argp;
*record_arg = replay_record_path;
break;
}
++argp;
}
#endif // AFL_PERSISTENT_REPLAY_ARGPARSE
}
#endif // _HAVE_PERSISTENT_REPLAY_H

View File

@ -0,0 +1,67 @@
#ifndef _HAVE_AFL_COMPAT_H
#define _HAVE_AFL_COMPAT_H
#include <afl-persistent-replay.h>
#define FUZZ_BUF_SIZE 1024000
// extern ssize_t read(int fildes, void *buf, size_t nbyte);
// extern int __afl_persistent_loop(unsigned int max_cnt);
// extern unsigned char fuzz_buf[];
#ifndef __AFL_HAVE_MANUAL_CONTROL
#define __AFL_HAVE_MANUAL_CONTROL
#endif
#define __AFL_FUZZ_TESTCASE_LEN (read(0, fuzz_buf, FUZZ_BUF_SIZE))
#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
#define __AFL_FUZZ_INIT() void sync(void);
#define __AFL_INIT() sync()
#define __AFL_LOOP(x) __afl_persistent_loop(x)
unsigned char fuzz_buf[FUZZ_BUF_SIZE];
int __afl_persistent_loop(unsigned int max_cnt) {
static unsigned int cycle_cnt = 1;
static unsigned short int inited = 0;
char tcase[PATH_MAX];
if (is_replay_record) {
if (!inited) {
cycle_cnt = replay_record_cnt;
inited = 1;
}
snprintf(tcase, PATH_MAX, "%s/%s",
replay_record_dir ? replay_record_dir : "./",
record_list[replay_record_cnt - cycle_cnt]->d_name);
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
if (record_arg) {
*record_arg = tcase;
} else
#endif // AFL_PERSISTENT_REPLAY_ARGPARSE
{
int fd = open(tcase, O_RDONLY);
dup2(fd, 0);
close(fd);
}
}
return --cycle_cnt;
}
#endif // _HAVE_AFL_COMPAT_H

View File

@ -97,6 +97,11 @@
// #define AFL_PERSISTENT_RECORD
/* Adds support in compiler-rt to replay persistent records in @@-style
* harnesses */
// #define AFL_PERSISTENT_REPLAY_ARGPARSE
/* console output colors: There are three ways to configure its behavior
* 1. default: colored outputs fixed on: defined USE_COLOR && defined
* ALWAYS_COLORED The env var. AFL_NO_COLOR will have no effect

View File

@ -195,4 +195,34 @@ Then as first line after the `__AFL_LOOP` while loop:
int len = __AFL_FUZZ_TESTCASE_LEN;
```
And that is all!
And that is all!
## 6) Persistent record, and replay
If your software under test requires keeping a state between persistent loop iterations (i.e., a stateful network stack), you can use the `AFL_PERSISTENT_RECORD` variable as described in the [environment variables documentation](../docs/env_variables.md).
When `AFL_PERSISTENT_RECORD` is enabled, replay functionality is also included in the compiler-rt library. To replay a specific record, assign the record number to the AFL_PERSISTENT_REPLAY environment variable (i.e., `RECORD:XXXXX`` -> `AFL_PERSISTENT_REPLAY=XXXXX`), and run the test binary as you would normally do.
The directory where the record files live can be specified via the `AFL_PERSISTENT_DIR` environment varilable, otherwise by default it will be considered the current directory (`./`).
If your harness reads the input files from arguments using the special `@@` argument you will need to include support by enabling `AFL_PERSISTENT_ARGPARSE` in `config.h`.
In order to offer transparent support to harnesses using the `@@` command line argument, arguments are parsed by the `__afl_record_replay_init` init function. Since not all systems support passing arguments to initializers, this functionality is disabled by default, it's recommendable to use the `__AFL_FUZZ_TESTCASE_BUF/__AFL_FUZZ_TESTCASE_LEN` shared memory mechanism instead.
## 7) Drop-in persistent loop replay replacement
To use the replay functionality without having to use `afl-cc`, include the [include/record_compat.h](../include/afl-record_compat.h) header file. Together with the [include/afl-persistent-replay.h](../include/afl-persistent-replay.h) header included in it, `afl-record-compat.h` provides a drop-in replacement for the persistent loop mechanism.
```c
#ifndef __AFL_FUZZ_TESTCASE_LEN
// #define AFL_PERSISTENT_REPLAY_ARGPARSE
#include "afl-record-compat.h"
#endif
__AFL_FUZZ_INIT();
```
A simple example is provided in [persistent_demo_replay.c](../utils/replay_record/persistent_demo_replay.c).
Be aware that the [afl-record-compat.h](../include/afl-record-compat.h) header should only be included in a single compilation unit, or you will end up with clobbered functions and variables.
If you need a cleaner solution, you'll have to move the functions and variables defined in [include/record_compat.h](../include/afl-record-compat.h) and [include/afl-persistent-replay.h](../include/afl-persistent-replay.h) in a C file, and add the relevant declarations to a header file. After including the new header file, the compilation unit resulting from compiling the C file can then be linked with your program.

View File

@ -87,6 +87,10 @@ __attribute__((weak)) void __sanitizer_symbolize_pc(void *, const char *fmt,
#include <sys/mman.h>
#include <fcntl.h>
#ifdef AFL_PERSISTENT_RECORD
#include "afl-persistent-replay.h"
#endif
/* Globals needed by the injected instrumentation. The __afl_area_initial region
is used for instrumentation output before __afl_map_shm() has a chance to
run. It will end up as .comm, so it shouldn't be too wasteful. */
@ -1354,6 +1358,10 @@ int __afl_persistent_loop(unsigned int max_cnt) {
static u8 first_pass = 1;
static u32 cycle_cnt;
#ifdef AFL_PERSISTENT_RECORD
char tcase[PATH_MAX];
#endif
if (first_pass) {
/* Make sure that every iteration of __AFL_LOOP() starts with a clean slate.
@ -1365,14 +1373,59 @@ int __afl_persistent_loop(unsigned int max_cnt) {
__afl_area_ptr[0] = 1;
memset(__afl_prev_loc, 0, NGRAM_SIZE_MAX * sizeof(PREV_LOC_T));
cycle_cnt = max_cnt;
first_pass = 0;
__afl_selective_coverage_temp = 1;
#ifdef AFL_PERSISTENT_RECORD
if (unlikely(is_replay_record)) {
cycle_cnt = replay_record_cnt;
goto persistent_record;
} else
#endif
{
cycle_cnt = max_cnt;
}
return 1;
} else if (--cycle_cnt) {
#ifdef AFL_PERSISTENT_RECORD
if (unlikely(is_replay_record)) {
persistent_record:
snprintf(tcase, PATH_MAX, "%s/%s",
replay_record_dir ? replay_record_dir : "./",
record_list[replay_record_cnt - cycle_cnt]->d_name);
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
if (unlikely(record_arg)) {
*record_arg = tcase;
} else
#endif // AFL_PERSISTENT_REPLAY_ARGPARSE
{
int fd = open(tcase, O_RDONLY);
dup2(fd, 0);
close(fd);
}
return 1;
}
#endif
raise(SIGSTOP);
__afl_area_ptr[0] = 1;

View File

@ -1599,6 +1599,11 @@ afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
u32 exec_ms;
u32 write_value = fsrv->last_run_timed_out;
#ifdef AFL_PERSISTENT_RECORD
fsrv_run_result_t retval = FSRV_RUN_OK;
char *persistent_out_fmt;
#endif
#ifdef __linux__
if (fsrv->nyx_mode) {
@ -1798,6 +1803,18 @@ afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
if (unlikely(fsrv->last_run_timed_out)) {
fsrv->last_kill_signal = fsrv->child_kill_signal;
#ifdef AFL_PERSISTENT_RECORD
if (unlikely(fsrv->persistent_record)) {
retval = FSRV_RUN_TMOUT;
persistent_out_fmt = "%s/hangs/RECORD:%06u,cnt:%06u";
goto store_persistent_record;
}
#endif
return FSRV_RUN_TMOUT;
}
@ -1819,42 +1836,21 @@ afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
(fsrv->uses_crash_exitcode &&
WEXITSTATUS(fsrv->child_status) == fsrv->crash_exitcode))) {
/* For a proper crash, set last_kill_signal to WTERMSIG, else set it to 0 */
fsrv->last_kill_signal =
WIFSIGNALED(fsrv->child_status) ? WTERMSIG(fsrv->child_status) : 0;
#ifdef AFL_PERSISTENT_RECORD
if (unlikely(fsrv->persistent_record)) {
char fn[PATH_MAX];
u32 i, writecnt = 0;
for (i = 0; i < fsrv->persistent_record; ++i) {
u32 entry = (i + fsrv->persistent_record_idx) % fsrv->persistent_record;
u8 *data = fsrv->persistent_record_data[entry];
u32 len = fsrv->persistent_record_len[entry];
if (likely(len && data)) {
snprintf(fn, sizeof(fn), "%s/RECORD:%06u,cnt:%06u",
fsrv->persistent_record_dir, fsrv->persistent_record_cnt,
writecnt++);
int fd = open(fn, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd >= 0) {
ck_write(fd, data, len, fn);
close(fd);
}
}
}
++fsrv->persistent_record_cnt;
retval = FSRV_RUN_CRASH;
persistent_out_fmt = "%s/crashes/RECORD:%06u,cnt:%06u";
goto store_persistent_record;
}
#endif
/* For a proper crash, set last_kill_signal to WTERMSIG, else set it to 0 */
fsrv->last_kill_signal =
WIFSIGNALED(fsrv->child_status) ? WTERMSIG(fsrv->child_status) : 0;
return FSRV_RUN_CRASH;
}
@ -1862,6 +1858,40 @@ afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
/* success :) */
return FSRV_RUN_OK;
#ifdef AFL_PERSISTENT_RECORD
store_persistent_record: {
char fn[PATH_MAX];
u32 i, writecnt = 0;
for (i = 0; i < fsrv->persistent_record; ++i) {
u32 entry = (i + fsrv->persistent_record_idx) % fsrv->persistent_record;
u8 *data = fsrv->persistent_record_data[entry];
u32 len = fsrv->persistent_record_len[entry];
if (likely(len && data)) {
snprintf(fn, sizeof(fn), persistent_out_fmt, fsrv->persistent_record_dir,
fsrv->persistent_record_cnt, writecnt++);
int fd = open(fn, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd >= 0) {
ck_write(fd, data, len, fn);
close(fd);
}
}
}
++fsrv->persistent_record_cnt;
return retval;
}
#endif
}
void afl_fsrv_killall() {

View File

@ -1921,6 +1921,9 @@ static void handle_existing_out_dir(afl_state_t *afl) {
}
#ifdef AFL_PERSISTENT_RECORD
delete_files(fn, RECORD_PREFIX);
#endif
if (delete_files(fn, CASE_PREFIX)) { goto dir_cleanup_failed; }
ck_free(fn);
@ -1953,6 +1956,9 @@ static void handle_existing_out_dir(afl_state_t *afl) {
}
#ifdef AFL_PERSISTENT_RECORD
delete_files(fn, RECORD_PREFIX);
#endif
if (delete_files(fn, CASE_PREFIX)) { goto dir_cleanup_failed; }
ck_free(fn);

View File

@ -2182,7 +2182,7 @@ int main(int argc, char **argv_orig, char **envp) {
}
afl->fsrv.persistent_record_dir = alloc_printf("%s/crashes", afl->out_dir);
afl->fsrv.persistent_record_dir = alloc_printf("%s", afl->out_dir);
}

View File

@ -7,4 +7,4 @@ document:
AFL_DONT_OPTIMIZE=1 ../../afl-clang-fast -D_AFL_DOCUMENT_MUTATIONS -o test-instr test-instr.c
clean:
rm -f persistent_demo persistent_demo_new test-instr
rm -f persistent_demo persistent_demo_new persistent_demo_new_compat test-instr

View File

@ -0,0 +1,8 @@
all:
test `grep '//[\s\t ]*#define[\s\t ]*AFL_PERSISTENT_RECORD' ../../include/config.h | wc -l` -eq 0 || (echo "AFL_PERSISTENT_RECORD must be enabled in config.h"; exit 1)
../../afl-clang-fast -o persistent_demo_replay persistent_demo_replay.c
${CC} -I ../../include -o persistent_demo_replay_compat persistent_demo_replay.c
${CC} -g -I ../../include -DAFL_PERSISTENT_REPLAY_ARGPARSE -o persistent_demo_replay_argparse persistent_demo_replay.c
clean:
rm -f persistent_demo_replay persistent_demo_replay_argparse persistent_demo_replay_compat

View File

@ -0,0 +1,10 @@
# AFL++ persistent record replay
This persistent record replay demo showcases the `AFL_PERSISTENT_RECORD` replay functionality.
The [Makefile](Makefile) will produce three binaries:
+ persistent_demo_replay: uses afl-cc and makes use of the replay functionality included in the compiler runtime library
+ persistent_demo_replay_compat: uses the [afl-record-compat.h](../../include/afl-record-compat.h) compatibility header to compile the same example without `afl-cc`
+ persistent_demo_replay_argparse: makes use of `afl-record-compat.h`, and the Makefile defines `AFL_PERSISTENT_REPLAY_ARGPARSE` to test the replay functionality but parses the input file via a command-line argument (`@@`-style harness).
For more information see [README.persistent_mode.md](../../instrumentation/README.persistent_mode.md).

View File

@ -0,0 +1,148 @@
/*
american fuzzy lop++ - persistent mode example
--------------------------------------------
Originally written by Michal Zalewski
Copyright 2015 Google Inc. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
This file demonstrates the high-performance "persistent mode" that may be
suitable for fuzzing certain fast and well-behaved libraries, provided that
they are stateless or that their internal state can be easily reset
across runs.
To make this work, the library and this shim need to be compiled in LLVM
mode using afl-clang-fast (other compiler wrappers will *not* work).
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <limits.h>
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
#include <sys/stat.h>
#include <fcntl.h>
#endif
/* this lets the source compile without afl-clang-fast/lto */
#ifndef __AFL_FUZZ_TESTCASE_LEN
#include "afl-record-compat.h"
#endif
__AFL_FUZZ_INIT();
/* Main entry point. */
/* To ensure checks are not optimized out it is recommended to disable
code optimization for the fuzzer harness main() */
#pragma clang optimize off
#pragma GCC optimize("O0")
int main(int argc, char **argv) {
ssize_t len; /* how much input did we read? */
unsigned char *buf; /* test case buffer pointer */
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
int fd;
if (argc < 2) { printf("Need an input file!"); }
#endif
/* The number passed to __AFL_LOOP() controls the maximum number of
iterations before the loop exits and the program is allowed to
terminate normally. This limits the impact of accidental memory leaks
and similar hiccups. */
__AFL_INIT();
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
buf = malloc(1000);
#else
buf = __AFL_FUZZ_TESTCASE_BUF; // this must be assigned before __AFL_LOOP!
#endif
while (__AFL_LOOP(UINT_MAX)) { // increase if you have good stability
#ifdef AFL_PERSISTENT_REPLAY_ARGPARSE
fd = open(argv[1], O_RDONLY);
len = read(fd, buf, 1000);
close(fd);
#else
len = __AFL_FUZZ_TESTCASE_LEN; // do not use the macro directly in a call!
#endif
// fprintf(stderr, "input: %zd \"%s\"\n", len, buf);
/* do we have enough data? */
if (len < 8) continue;
if (strcmp((char *)buf, "thisisateststring") == 0) printf("teststring\n");
if (buf[0] == 'f') {
printf("one\n");
if (buf[1] == 'o') {
printf("two\n");
if (buf[2] == 'o') {
printf("three\n");
if (buf[3] == '!') {
printf("four\n");
if (buf[4] == '!') {
printf("five\n");
if (buf[5] == '!') {
printf("six\n");
abort();
} else {
if (buf[5] == 'O') {
// hang
while (1) {
continue;
};
}
}
}
}
}
}
}
/*** END PLACEHOLDER CODE ***/
}
/* Once the loop is exited, terminate normally - AFL will restart the process
when this happens, with a clean slate when it comes to allocated memory,
leftover file descriptors, etc. */
return 0;
}