Skip to content

Instantly share code, notes, and snippets.

@thejh
Created September 30, 2014 23:38
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thejh/6339804cfd1cdcd90bc7 to your computer and use it in GitHub Desktop.
Save thejh/6339804cfd1cdcd90bc7 to your computer and use it in GitHub Desktop.
afl-forkserver-4.patch
diff -rupN afl-0.31b/afl-fuzz.c afl-0.31b-modded/afl-fuzz.c
--- afl-0.31b/afl-fuzz.c 2014-09-12 08:33:20.000000000 +0200
+++ afl-0.31b-modded/afl-fuzz.c 2014-09-29 13:35:15.876019069 +0200
@@ -19,12 +19,14 @@
*/
#define AFL_MAIN
+#define _GNU_SOURCE
#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
#include "hash.h"
+#include "common.h"
#include <stdio.h>
#include <unistd.h>
@@ -40,13 +42,19 @@
#include <sys/time.h>
#include <sys/shm.h>
#include <sys/stat.h>
-#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/resource.h>
+#include <sys/user.h>
+#include <sys/syscall.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/ptrace.h>
+#include <sys/uio.h>
static u8 *in_dir, /* Directory with initial testcases */
*out_file, /* File to fuzz, if any */
*out_dir, /* Working & output directory */
+ *afl_argv0, /* Name we were invoked as */
*use_banner, /* Display banner */
*in_bitmap; /* Input bitmap */
@@ -58,13 +66,15 @@ static u8 skip_deterministic, /*
use_splicing, /* Recombine input files? */
dumb_mode, /* Allow non-instrumented code? */
score_changed, /* Path scoring changed? */
+ use_forkserver, /* Turn the fuzzee into a forkserver*/
kill_signal; /* Signal that killed the child */
static s32 out_fd, /* Persistent fd for out_file */
+ command_fd = -1, /* fd for passing commands between afl and child */
dev_urandom, /* Persistent fd for /dev/urandom */
dev_null; /* Persistent fd for /dev/null */
-static s32 child_pid; /* PID of the fuzzed program */
+static volatile s32 child_pid; /* PID of the fuzzed program or forkserver */
static u8* trace_bits; /* SHM with instrumentation bitmap */
static u8 virgin_bits[MAP_SIZE]; /* Regions yet untouched by fuzzing */
@@ -659,14 +669,265 @@ static void read_testcases(void) {
}
+/* Append a library to LD_PRELOAD. */
+
+static void ld_preload_add(char *path) {
+ char *old = getenv("LD_PRELOAD");
+ if (!old) {
+ setenv("LD_PRELOAD", path, 1);
+ } else {
+ u8 *new = alloc_printf("%s:%s", old, path);
+ setenv("LD_PRELOAD", new, 1);
+ ck_free(new);
+ }
+}
+
+
+/* Try to find the forkserver library in AFL_PATH or at the location derived
+ from argv[0] and put it into LD_PRELOAD. If that fails, abort. */
+
+static void use_forkserver_lib(u8* argv0) {
+ char *library_dir = find_component_dir(argv0, "forkserver.so");
+ char *library_path = alloc_printf("%s/forkserver.so", library_dir);
+ ck_free(library_dir);
+ ld_preload_add(library_path);
+ ck_free(library_path);
+}
+
+
+/* Create a child with its own process group, in a way that ensures that we
+ will be able to reliably kill it if we get terminated. */
+
+static s32 fork_newsession(void) {
+ /* Block signals, basically to avoid a race condition between forking and
+ saving the result of fork(). Also, when we kill the child, we want to
+ kill the whole taskgroup, which doesn't exist before the child does
+ setgid() */
+ sigset_t stopsigs;
+ sigemptyset(&stopsigs);
+ sigaddset(&stopsigs, SIGHUP);
+ sigaddset(&stopsigs, SIGINT);
+ sigaddset(&stopsigs, SIGTERM);
+ sigprocmask(SIG_BLOCK, &stopsigs, NULL);
+
+ int command_fds[2];
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, command_fds)) PFATAL("socketpair() failed");
+
+ child_pid = fork();
+ if (child_pid < 0) PFATAL("fork() failed");
+
+ close(command_fds[!child_pid]);
+ command_fd = command_fds[!!child_pid];
+
+ if (!child_pid) {
+ setsid();
+
+ if (write(command_fd, "0", 1) != 1) PFATAL("unable to signal readyness to parent, did the parent get SIGKILLed?");
+ }
+
+ if (child_pid) {
+ char dummy;
+ if (read(command_fd, &dummy, 1) != 1) PFATAL("unable to receive readyness from child, did it get SIGKILLed?");
+ }
+
+ sigprocmask(SIG_UNBLOCK, &stopsigs, NULL);
+
+ return child_pid;
+}
+
+
+#define PFATAL_OR_EXIT(exitcode, x...) do { \
+ if (stop_soon) return exitcode; \
+ PFATAL(x); \
+ } while (0);
+
+
+/* Launch target application in forkserver mode. Requires out_file for now. */
+
+static void launch_forkserver(char **argv) {
+ fork_newsession();
+
+ if (!child_pid) {
+ use_forkserver_lib(afl_argv0);
+ setenv("LD_BIND_NOW", "1", 0); /* given that we will fork a lot, binding everything once makes sense */
+
+ dup2(dev_null, 0);
+ dup2(dev_null, 1);
+ dup2(dev_null, 2);
+ close(dev_null);
+
+ char *command_fd_str = alloc_printf("%d", command_fd);
+ setenv("AFL_FORKSERVER_COMMAND_FD", command_fd_str, 1);
+ ck_free(command_fd_str);
+
+ char *afl_target_pid_str = alloc_printf("%lld", (long long)getpid());
+ setenv("AFL_TARGET_PID", afl_target_pid_str, 1);
+ ck_free(afl_target_pid_str);
+
+ struct rlimit r;
+ r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;
+ setrlimit(RLIMIT_AS, &r); /* Ignore errors */
+
+ if (ptrace(PTRACE_TRACEME, 0, NULL, NULL)) exit(EXEC_FAIL);
+ execvp(argv[0], argv);
+ exit(EXEC_FAIL);
+ }
+
+ char *out_file_basename = strrchr(out_file, '/');
+ if (out_file_basename) {
+ out_file_basename++;
+ } else {
+ out_file_basename = out_file;
+ }
+
+ if (do_atomic(write_, command_fd, &exec_tmout, 4) != 4) {
+ PFATAL_OR_EXIT(, "sending exec_tmout failed");
+ }
+
+ int in_syscall = 1; /* you could say that we start tracing inside the execve() */
+ while (1) {
+ int status;
+ if (waitpid(child_pid, &status, 0) != child_pid) PFATAL("waitpid failed");
+ if (WIFSIGNALED(status) || WIFEXITED(status)) {
+ PFATAL_OR_EXIT(, "forkserver exited before main loop, is the -f parameter correct?");
+ }
+ if (WIFSTOPPED(status) && WSTOPSIG(status) == SIGTRAP) {
+ if (!in_syscall) {
+ /* entering a syscall. check whether it uses the name of the input file. */
+ struct user_regs_struct orig_regs, new_regs;
+ if (ptrace(PTRACE_GETREGS, child_pid, NULL, &orig_regs)) PFATAL_OR_EXIT(, "ptrace getregs failed");
+ long int path_pointer;
+ /* orig_eax is the eax value from before the syscall was started, eax is clobbered by now. */
+ switch (orig_regs.orig_eax) {
+ case SYS_access:
+ case SYS_chmod:
+ case SYS_chown:
+ case SYS_chown32:
+ case SYS_creat:
+ case SYS_getxattr:
+ case SYS_lchown:
+ case SYS_lchown32:
+ case SYS_lgetxattr:
+ case SYS_link:
+ case SYS_listxattr:
+ case SYS_llistxattr:
+ case SYS_lremovexattr:
+ case SYS_lsetxattr:
+ case SYS_lstat:
+ case SYS_lstat64:
+ case SYS_mount:
+ case SYS_oldlstat:
+ case SYS_oldstat:
+ case SYS_open:
+ case SYS_readlink:
+ case SYS_removexattr:
+ case SYS_rename:
+ case SYS_setxattr:
+ case SYS_stat:
+ case SYS_stat64:
+ case SYS_unlink: /* this would be weird */
+ case SYS_uselib:
+ case SYS_utime:
+ case SYS_utimes:
+ path_pointer = orig_regs.ebx;
+ break;
+
+ case SYS_faccessat:
+ case SYS_fchmodat:
+ case SYS_fchownat:
+ case SYS_fstatat64:
+ case SYS_futimesat:
+ case SYS_linkat:
+ case SYS_name_to_handle_at:
+ case SYS_openat:
+ case SYS_readlinkat:
+ case SYS_renameat:
+ case SYS_renameat2:
+ case SYS_unlinkat:
+ case SYS_utimensat:
+ path_pointer = orig_regs.ecx;
+ break;
+
+ default:
+ path_pointer = 0;
+ }
+ if (path_pointer != 0) {
+ /* Try to grab the filename from the child. Yes, this is horribly
+ inefficient, but it only happens at startup, so that should be ok.
+ We only check the filename because the rest of the path might be
+ different than what we expect, e.g. if the target binary does
+ realpath() on the path. */
+ char basename[1024]; /* wheee, fixed-size buffers! */
+ int basename_used = 0;
+ while (1) {
+ if (basename_used == 1024) {
+ PFATAL("the child passed a pointer to an overly long string to a filesystem function");
+ }
+ struct iovec local_iovec = { .iov_base = basename+basename_used, .iov_len = 1 };
+ struct iovec remote_iovec = { .iov_base = (void*)path_pointer, .iov_len = 1 };
+ if (process_vm_readv(child_pid, &local_iovec, 1, &remote_iovec, 1, 0) != 1) {
+ PFATAL_OR_EXIT(, "either the child passed a bad pointer to a filesystem syscall or we made some mistake. make sure that you're tracing a 32bit program");
+ }
+ if (basename[basename_used] == '\0') break;
+ if (basename[basename_used] == '/') {
+ basename_used = 0;
+ } else {
+ basename_used++;
+ }
+ path_pointer++;
+ }
+
+ if (!strcmp(basename, out_file_basename)) {
+ /* This is where we force the child to enter the forkserver code.
+ Apart from changing EIP so that it points at enter_forkserver,
+ we also push the current register values on the child's
+ stack so that it can later return to the old execution flow
+ using a few POPs and a RET. */
+
+ uint32_t enter_forkserver_addr;
+ if (read(command_fd, &enter_forkserver_addr, 4) != 4) {
+ PFATAL_OR_EXIT(, "receiving address of enter_forkserver failed");
+ }
+
+ /* see forkserver_entry.asm - the entries here must match with the pops over there */
+ long int stack_saved_regs[] = {
+ orig_regs.eflags,
+ orig_regs.esi, orig_regs.edi,
+ orig_regs.orig_eax, orig_regs.ebx, orig_regs.ecx, orig_regs.edx,
+ /* EIP is behind the instruction used to perform the syscall, so
+ move it back. Both "int 0x80" and "sysenter" are 2 bytes, so
+ this should be sufficient. */
+ orig_regs.ebp, orig_regs.eip - 2
+ };
+ new_regs = orig_regs;
+ new_regs.eip = enter_forkserver_addr;
+ /* Why are those registers in the user struct declared as signed for x86?
+ Of course, the amd64 regs are unsigned. */
+ new_regs.esp = (long)(((unsigned long)new_regs.esp) - sizeof(stack_saved_regs));
+
+ if (ptrace(PTRACE_SETREGS, child_pid, NULL, &new_regs)) PFATAL_OR_EXIT(, "ptrace setregs failed");
+
+ struct iovec local_iovec = { .iov_base = stack_saved_regs, .iov_len = sizeof(stack_saved_regs) };
+ struct iovec remote_iovec = { .iov_base = (void*)new_regs.esp, .iov_len = sizeof(stack_saved_regs) };
+ if (process_vm_writev(child_pid, &local_iovec, 1, &remote_iovec, 1, 0) != sizeof(stack_saved_regs)) {
+ PFATAL_OR_EXIT(, "remote stack push failed");
+ }
+
+ if (ptrace(PTRACE_DETACH, child_pid, NULL, NULL)) PFATAL_OR_EXIT(, "ptrace detach failed");
+ return;
+ }
+ }
+ }
+ if (ptrace(PTRACE_SYSCALL, child_pid, NULL, NULL)) PFATAL_OR_EXIT(, "ptrace syscall-step failed");
+ in_syscall = !in_syscall;
+ }
+ }
+}
+
+
/* Execute target application, monitoring for timeouts. Return status
information. The called program will update trace_bits[]. */
-#define FAULT_NONE 0
-#define FAULT_HANG 1
-#define FAULT_CRASH 2
-#define FAULT_ERROR 3
-
static u8 run_target(char** argv) {
static struct itimerval it;
@@ -674,79 +935,97 @@ static u8 run_target(char** argv) {
child_timed_out = 0;
- memset(trace_bits, 0, MAP_SIZE);
-
- child_pid = fork();
+ total_execs++;
- if (child_pid < 0) PFATAL("fork() failed");
+ if (use_forkserver && command_fd == -1) {
+ launch_forkserver(argv);
+ }
- if (!child_pid) {
+ memset(trace_bits, 0, MAP_SIZE);
- struct rlimit r;
+ if (!use_forkserver) {
+ fork_newsession();
+ close(command_fd);
- r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;
+ if (!child_pid) {
- setrlimit(RLIMIT_AS, &r); /* Ignore errors */
+ struct rlimit r;
- /* Isolate the process and configure standard descriptors. If out_file is
- specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */
+ r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;
- setsid();
+ setrlimit(RLIMIT_AS, &r); /* Ignore errors */
- dup2(dev_null, 1);
- dup2(dev_null, 2);
+ /* Configure standard descriptors. If out_file is
+ specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */
- if (out_file) {
+ dup2(dev_null, 1);
+ dup2(dev_null, 2);
- dup2(dev_null, 0);
+ if (out_file) {
- } else {
+ dup2(dev_null, 0);
- dup2(out_fd, 0);
- close(out_fd);
+ } else {
- }
+ dup2(out_fd, 0);
+ close(out_fd);
- close(dev_null);
+ }
- execvp(argv[0], argv);
+ close(dev_null);
- /* Use a distinctive return value to tell the parent about execvp()
- falling through. */
+ execvp(argv[0], argv);
- exit(EXEC_FAIL);
+ /* Use a distinctive return value to tell the parent about execvp()
+ falling through. */
- }
+ exit(EXEC_FAIL);
- /* Configure timeout, as requested by user, then wait for child to terminate. */
+ }
+
+ /* Configure timeout, as requested by user, then wait for child to terminate. */
- it.it_value.tv_sec = (exec_tmout / 1000);
- it.it_value.tv_usec = (exec_tmout % 1000) * 1000;
+ it.it_value.tv_sec = (exec_tmout / 1000);
+ it.it_value.tv_usec = (exec_tmout % 1000) * 1000;
- setitimer(ITIMER_REAL, &it, NULL);
+ setitimer(ITIMER_REAL, &it, NULL);
- if (waitpid(child_pid, &status, WUNTRACED) <= 0) PFATAL("waitpid() failed");
+ if (waitpid(child_pid, &status, WUNTRACED) <= 0) PFATAL("waitpid() failed");
- child_pid = 0;
- it.it_value.tv_sec = 0;
- it.it_value.tv_usec = 0;
+ child_pid = 0;
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 0;
- setitimer(ITIMER_REAL, &it, NULL);
+ setitimer(ITIMER_REAL, &it, NULL);
- total_execs++;
+ /* Report outcome to caller. */
- /* Report outcome to caller. */
+ if (child_timed_out) return FAULT_HANG;
- if (child_timed_out) return FAULT_HANG;
+ if (WIFSIGNALED(status) && !stop_soon) {
+ kill_signal = WTERMSIG(status);
+ return FAULT_CRASH;
+ }
- if (WIFSIGNALED(status) && !stop_soon) {
- kill_signal = WTERMSIG(status);
- return FAULT_CRASH;
- }
+ if (WEXITSTATUS(status) == EXEC_FAIL) return FAULT_ERROR;
- if (WEXITSTATUS(status) == EXEC_FAIL) return FAULT_ERROR;
+ return 0;
- return 0;
+ } else {
+ char fork_cmd = 'F';
+ if (do_atomic(write_, command_fd, &fork_cmd, 1) != 1) {
+ PFATAL_OR_EXIT(0, "sending fork command failed");
+ }
+
+ u16 reply;
+ if (do_atomic(read, command_fd, &reply, 2) != 2)
+ PFATAL_OR_EXIT(0, "receiving child reply failed");
+ uint8_t res = reply >> 8;
+ kill_signal = (reply & 0xff);
+
+ if (stop_soon) res = 0;
+ return res;
+ }
}
@@ -2515,7 +2794,7 @@ abandon_entry:
static void handle_stop_sig(int sig) {
stop_soon = 1;
- if (child_pid > 0) kill(child_pid, SIGKILL);
+ if (child_pid > 0) kill(-child_pid, SIGKILL);
}
@@ -2525,7 +2804,7 @@ static void handle_stop_sig(int sig) {
static void handle_timeout(int sig) {
child_timed_out = 1;
- if (child_pid > 0) kill(child_pid, SIGKILL);
+ if (child_pid > 0) kill(-child_pid, SIGKILL);
}
@@ -2545,7 +2824,8 @@ static void usage(u8* argv0) {
" -f file - input file used by the traced application\n"
" -t msec - timeout for each run (%u ms)\n"
- " -m megs - memory limit for child process (%u MB)\n\n"
+ " -m megs - memory limit for child process (%u MB)\n"
+ " -F - use forkserver mode (fast but unreliable; requires -f)\n\n"
"Fuzzing behavior settings:\n\n"
@@ -2642,8 +2922,10 @@ int main(int argc, char** argv) {
signal(SIGTSTP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
+
+ afl_argv0 = argv[0];
- while ((opt = getopt(argc,argv,"+i:o:f:m:t:T:dDnB:")) > 0)
+ while ((opt = getopt(argc,argv,"+i:o:f:m:t:T:dDnB:F")) > 0)
switch (opt) {
@@ -2711,6 +2993,11 @@ int main(int argc, char** argv) {
if (use_banner) FATAL("Multiple -T options not supported");
use_banner = optarg;
break;
+
+ case 'F':
+
+ use_forkserver = 1;
+ break;
default:
@@ -2728,6 +3015,9 @@ int main(int argc, char** argv) {
else use_banner = trim + 1;
}
+
+ if (!out_file && use_forkserver)
+ FATAL("forkserver mode requires an outfile to be used");
if (skip_deterministic && skip_det_input)
FATAL("-d and -D are mutually exclusive");
diff -rupN afl-0.31b/afl-gcc.c afl-0.31b-modded/afl-gcc.c
--- afl-0.31b/afl-gcc.c 2014-06-28 05:15:50.000000000 +0200
+++ afl-0.31b-modded/afl-gcc.c 2014-09-29 13:57:58.286115925 +0200
@@ -32,6 +32,7 @@
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
+#include "common.h"
#include <stdio.h>
#include <unistd.h>
@@ -47,54 +48,7 @@ static u32 gcc_par_cnt = 1; /* P
from argv[0]. If that fails, abort. */
static void find_as(u8* argv0) {
-
- u8 *afl_path = getenv("AFL_PATH");
- u8 *slash, *tmp;
-
- if (afl_path) {
-
- tmp = alloc_printf("%s/as", afl_path);
-
- if (!access(tmp, X_OK)) {
- as_path = afl_path;
- ck_free(tmp);
- return;
- }
-
- ck_free(tmp);
-
- }
-
- if (!access(AFL_PATH "/as", X_OK)) {
- as_path = AFL_PATH;
- return;
- }
-
- slash = strrchr(argv0, '/');
-
- if (slash) {
-
- u8* dir;
-
- *slash = 0;
- dir = ck_strdup(argv0);
- *slash = '/';
-
- tmp = alloc_printf("%s/as", dir);
-
- if (!access(tmp, X_OK)) {
- as_path = dir;
- ck_free(tmp);
- return;
- }
-
- ck_free(tmp);
- ck_free(dir);
-
- }
-
- FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH");
-
+ as_path = find_component_dir(argv0, "as");
}
@@ -153,7 +107,7 @@ static void edit_params(u32 argc, char**
#ifdef USE_ASAN
gcc_params[gcc_par_cnt++] = "-fsanitize=address";
- gcc_params[gcc_par_cnt++] = "-fsanitize=memory";
+ /*gcc_params[gcc_par_cnt++] = "-fsanitize=memory";*/
#endif /* USE_ASAN */
if (!fortify_set)
diff -rupN afl-0.31b/common.c afl-0.31b-modded/common.c
--- afl-0.31b/common.c 1970-01-01 01:00:00.000000000 +0100
+++ afl-0.31b-modded/common.c 2014-09-29 13:35:15.876019069 +0200
@@ -0,0 +1,88 @@
+#include "common.h"
+#include "types.h"
+#include "alloc-inl.h"
+
+#include <unistd.h>
+#include <errno.h>
+
+
+/* wrapper around write() to make gcc shut up about an argument type mismatch
+ without casting function pointers around */
+
+ssize_t write_(int fd, void *buf, size_t count) {
+ return write(fd, buf, count);
+}
+
+
+/* wrapper around read() and write() that retries on partial reads and EINTR */
+
+ssize_t do_atomic(ssize_t (*f)(int fd, void *buf, size_t count), int fd, void *buf, size_t count) {
+ ssize_t done = 0; /* bytes read/written so far */
+ while (count > 0) {
+ ssize_t res = f(fd, buf, count);
+ if (res == -1 && errno == EINTR) continue;
+ if (res <= 0) {
+ return (done > 0) ? done : res;
+ }
+ done += res;
+ buf += res;
+ count -= res;
+ }
+ return done;
+}
+
+
+/* Try to find an AFL component in AFL_PATH or at the location derived
+ from argv[0]. If that fails, abort. Returns the path of the directory
+ containing the file. */
+
+char *find_component_dir(char *argv0, char *filename) {
+
+ u8 *afl_path = getenv("AFL_PATH");
+ u8 *slash, *tmp;
+
+ if (afl_path) {
+
+ tmp = alloc_printf("%s/%s", afl_path, filename);
+
+ if (!access(tmp, X_OK)) {
+ ck_free(tmp);
+ return ck_strdup(afl_path);
+ }
+
+ ck_free(tmp);
+
+ }
+
+ tmp = alloc_printf(AFL_PATH "/%s", filename);
+ if (!access(tmp, X_OK)) {
+ ck_free(tmp);
+ return ck_strdup(AFL_PATH);
+ }
+ ck_free(tmp);
+
+ slash = strrchr(argv0, '/');
+
+ if (slash) {
+
+ u8* dir;
+
+ *slash = 0;
+ dir = ck_strdup(argv0);
+ *slash = '/';
+
+ tmp = alloc_printf("%s/%s", dir, filename);
+
+ if (!access(tmp, X_OK)) {
+ ck_free(tmp);
+ return dir;
+ }
+
+ ck_free(tmp);
+ ck_free(dir);
+
+ }
+
+ FATAL("Unable to find AFL component named '%s'. Please set AFL_PATH", filename);
+
+}
diff -rupN afl-0.31b/common.h afl-0.31b-modded/common.h
--- afl-0.31b/common.h 1970-01-01 01:00:00.000000000 +0100
+++ afl-0.31b-modded/common.h 2014-09-29 13:35:15.876019069 +0200
@@ -0,0 +1,11 @@
+#include <sys/types.h>
+
+#define FAULT_NONE 0
+#define FAULT_HANG 1
+#define FAULT_CRASH 2
+#define FAULT_ERROR 3
+
+ssize_t write_(int fd, void *buf, size_t count);
+ssize_t do_atomic(ssize_t (*f)(int fd, void *buf, size_t count), int fd, void *buf, size_t count);
+char *find_component_dir(char *argv0, char *filename);
+
diff -rupN afl-0.31b/docs/README afl-0.31b-modded/docs/README
--- afl-0.31b/docs/README 2014-08-18 06:48:20.000000000 +0200
+++ afl-0.31b-modded/docs/README 2014-09-29 13:35:15.876019069 +0200
@@ -184,6 +184,21 @@ Ctrl-C is hit.
For large inputs, or when trying to distribute the fuzzing process, you can
use -d to skip the deterministic stages and proceed straight to random tweaks.
+To speed up fuzzing, use the -F flag for forkserver mode. In this mode, AFL
+will only launch the target binary once, then create copies of that instance
+at the point where it tries to access the input file, thereby working around
+the rather large setup cost of a typical process (including loading libraries
+and so on). However, keep in mind that this is not completely reliable - for
+example, if the program opens a config file before accessing the input file
+but reads the config file afterwards, the file offset for reading would
+probably be at the end when instances after the first one try to read the
+config, causing them to perceive the config file as empty. Therefore, if you
+decide to use forkserver mode, check whether the number of paths discovered
+looks sane - if AFL discovers none, it's probably the fault of forkserver
+mode.
+This mode also has the drawback of using the same address space layout every
+time, so there won't be any variable execution paths caused by ASLR.
+
6) Interpreting output
----------------------
diff -rupN afl-0.31b/forkserver.c afl-0.31b-modded/forkserver.c
--- afl-0.31b/forkserver.c 1970-01-01 01:00:00.000000000 +0100
+++ afl-0.31b-modded/forkserver.c 2014-09-29 13:35:15.876019069 +0200
@@ -0,0 +1,121 @@
+#define _GNU_SOURCE
+
+#include "common.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <errno.h>
+#include <stdio.h>
+#include <sys/syscall.h>
+#include <sched.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+
+void enter_forkserver(void);
+
+static int command_fd = -1;
+static uint32_t exec_tmout;
+static uint8_t child_timed_out;
+static int32_t fork_res;
+
+static void handle_timeout(int sig) {
+ child_timed_out = 1;
+ if (fork_res > 0) kill(fork_res, SIGKILL);
+}
+
+
+// This is basically a classic fork server. The server loops forever and stays
+// in this function. However, instead of calling a function to handle the
+// incoming testcase, we go back to the state before entering this loop by
+// returning and letting the assembler helper restore all registers.
+void run_forkserver(void) {
+ while (1) {
+ char command;
+ if (do_atomic(read, command_fd, &command, 1) != 1) exit(1);
+ if (command == 'X' /*exit*/) exit(0);
+ if (command != 'F' /*fork*/) exit(1);
+
+ // This is a really nice usecase for clone(CLONE_PARENT) (the syscall, not the
+ // libc function). Basically fork(), but we could let our parent (afl) handle
+ // the child's death directly. glibc ships no interface to the raw syscall,
+ // but a generic function syscall() that lets us call arbitrary syscalls.
+ // There is a problem with that though: When we kind-of-fork this way,
+ // glibc doesn't notice. That's a problem because glibc
+ // caches process and thread IDs and uses such a cached thread ID in the
+ // code for abort(). When it tries to kill itself that way, the abort()
+ // will instead signal the forkserver with a SIGABRT.
+ // Is it possible to tell glibc to refresh the cached pid/tid? Doesn't
+ // seem so. We could use seccomp to fixup the case of abort(), but if the
+ // pid/tid is used for anything else, things will still break.
+ // So what do we do? Treat SYS_clone as unusable, use fork() instead,
+ // duplicate child-handling code from afl-fuzz. Add additional code for
+ // passing around exec_tmout. *GRMBL*
+ fork_res = fork(); /*syscall(SYS_clone, CLONE_PARENT, NULL, NULL, NULL, NULL);*/
+ if (fork_res == -1) {
+ fputs("clone() failed", stderr);
+ exit(1);
+ } else if (fork_res == 0) {
+ // We are the child - return to enter_forkserver to get back to the
+ // normal program flow.
+ return;
+ } else {
+ // We are the parent.
+
+ child_timed_out = 0;
+ static struct itimerval it;
+ it.it_value.tv_sec = (exec_tmout / 1000);
+ it.it_value.tv_usec = (exec_tmout % 1000) * 1000;
+ sighandler_t ex_handler = signal(SIGALRM, handle_timeout);
+ setitimer(ITIMER_REAL, &it, NULL);
+
+ int status;
+ if (waitpid(fork_res, &status, WUNTRACED) <= 0) {
+ fputs("waitpid() failed", stderr);
+ exit(1);
+ }
+ fork_res = 0;
+
+ it.it_value.tv_sec = 0;
+ it.it_value.tv_usec = 0;
+ setitimer(ITIMER_REAL, &it, NULL);
+ signal(SIGALRM, ex_handler); /* restore for future children */
+
+ uint8_t res = 0,
+ kill_signal = 0;
+ if (child_timed_out) {
+ res = FAULT_HANG;
+ } else if (WIFSIGNALED(status)) {
+ res = FAULT_CRASH;
+ kill_signal = WTERMSIG(status);
+ }
+ uint16_t reply = (res << 8) | kill_signal;
+
+ if (do_atomic(write_, command_fd, &reply, 2) != 2) {
+ fputs("unable to write address of enter_forkserver\n", stderr);
+ exit(1);
+ }
+ }
+ }
+}
+
+__attribute__((constructor)) void prepare_forkserver() {
+ char *command_fd_str = getenv("AFL_FORKSERVER_COMMAND_FD");
+ char *afl_target_pid_str = getenv("AFL_TARGET_PID");
+ if (command_fd_str == NULL || afl_target_pid_str == NULL) return; /* we are not running in forkserver mode */
+ pid_t afl_target_pid = strtoll(afl_target_pid_str, NULL, 10);
+ if (afl_target_pid != getpid()) return; /* some parent is in forkserver mode, but we aren't */
+ command_fd = atoi(command_fd_str);
+
+ uint32_t enter_forkserver_addr = (uint32_t)enter_forkserver;
+ if (do_atomic(write_, command_fd, &enter_forkserver_addr, 4) != 4) {
+ fputs("unable to write address of enter_forkserver\n", stderr);
+ exit(1);
+ }
+
+ if (do_atomic(read, command_fd, &exec_tmout, 4) != 4) {
+ fputs("unable to read exec timeout\n", stderr);
+ exit(1);
+ }
+}
diff -rupN afl-0.31b/forkserver_entry.asm afl-0.31b-modded/forkserver_entry.asm
--- afl-0.31b/forkserver_entry.asm 1970-01-01 01:00:00.000000000 +0100
+++ afl-0.31b-modded/forkserver_entry.asm 2014-09-29 13:35:15.876019069 +0200
@@ -0,0 +1,37 @@
+section .text
+
+global enter_forkserver
+extern run_forkserver
+
+enter_forkserver:
+ ; At this point, the ptracing parent has "pushed" all the interesting
+ ; registers, so we can freely use them and pop them later. Well, not the
+ ; segment registers, but our code should have no reason to touch them.
+ ; Also not the stack pointer - we operate directly under the normal stack
+ ; because that lets us "return" into the old program flow.
+ ; IMPORTANT NOTE: That will need to be done a bit differently in a port
+ ; for amd64 - you'd have to watch out for the red zone.
+ call run_forkserver
+
+ ; At this point, we are inside the child process created using clone().
+ ; Pop the registers so that the normal code can continue running.
+
+ ; If we pop garbage here, we sometimes end up with a SIGTRAP at the
+ ; "pop edi" because the trap flag was set. Fun!
+ ; (Happened because I wrote process_vm_readv instead of
+ ; process_vm_writev in the register-pushing code.)
+ popfd ; pop eflags
+
+ pop esi
+ pop edi
+
+ pop eax
+ pop ebx
+ pop ecx
+ pop edx
+
+ pop ebp
+
+ ; int 3
+
+ ret
diff -rupN afl-0.31b/Makefile afl-0.31b-modded/Makefile
--- afl-0.31b/Makefile 2014-09-29 01:23:06.000000000 +0200
+++ afl-0.31b-modded/Makefile 2014-09-29 13:36:43.748031104 +0200
@@ -21,9 +21,14 @@ HELPER_PATH = /usr/local/lib/afl
PROGS = afl-gcc afl-as afl-fuzz afl-showmap
-CFLAGS += -O3 -Wall -fstack-protector-all -m32 \
- -D_FORTIFY_SOURCE=2 -g -Wno-pointer-sign \
- -DAFL_PATH=\"$(HELPER_PATH)\" \
+# These are used for everything. We don't use -fstack-protector-all here
+# because it would require the target binary to include code for handling
+# stack protector errors.
+COMMON_CFLAGS = -O3 -m32 -g -Wall -Wno-pointer-sign -DAFL_PATH=\"$(HELPER_PATH)\"
+
+# These are only used for the afl standalone binaries, not for injected code.
+CFLAGS += $(COMMON_CFLAGS) -fstack-protector-all \
+ -D_FORTIFY_SOURCE=2 \
-DVERSION=\"$(VERSION)\"
GCC48PLUS := $(shell expr `gcc -dumpversion | cut -f-2 -d.` \>= 4.8)
@@ -34,30 +39,38 @@ endif
COMM_HDR = alloc-inl.h config.h debug.h types.h
-all: $(PROGS)
+all: $(PROGS) forkserver.so
-afl-gcc: afl-gcc.c $(COMM_HDR)
- $(CC) $(CFLAGS) $(LDFLAGS) $@.c -o $@
+common.o: common.c common.h
+ $(CC) -c -fpic $(COMMON_CFLAGS) common.c -o common.o
+
+afl-gcc: afl-gcc.c $(COMM_HDR) common.o common.h
+ $(CC) $(CFLAGS) $(LDFLAGS) $@.c common.o -o $@
ln -s afl-gcc afl-g++ 2>/dev/null || true
afl-as: afl-as.c afl-as.h $(COMM_HDR)
$(CC) $(CFLAGS) $(LDFLAGS) $@.c -o $@
ln -s afl-as as 2>/dev/null || true
-afl-fuzz: afl-fuzz.c $(COMM_HDR)
- $(CC) $(CFLAGS) $(LDFLAGS) $@.c -o $@
+afl-fuzz: afl-fuzz.c $(COMM_HDR) common.o common.h
+ $(CC) $(CFLAGS) $(LDFLAGS) $@.c common.o -o $@
afl-showmap: afl-showmap.c $(COMM_HDR)
$(CC) $(CFLAGS) $(LDFLAGS) $@.c -o $@
+forkserver.so: forkserver.c forkserver_entry.asm common.c common.h
+ $(CC) -c -fpic $(COMMON_CFLAGS) forkserver.c -o forkserver.o
+ nasm -f elf32 forkserver_entry.asm
+ ld -shared -m elf_i386 -o forkserver.so forkserver.o forkserver_entry.o common.o
+
clean:
- rm -f $(PROGS) as afl-g++ *.o *~ a.out core core.[1-9][0-9]* *.stackdump test
+ rm -f $(PROGS) as afl-g++ *.o *.so *~ a.out core core.[1-9][0-9]* *.stackdump test
rm -rf out_dir
install: all
install afl-gcc afl-g++ afl-fuzz afl-showmap $(BIN_PATH)
mkdir -m 755 $(HELPER_PATH) 2>/dev/null || continue
- install afl-as as $(HELPER_PATH)
+ install afl-as as forkserver.so $(HELPER_PATH)
publish: clean
test "`basename $$PWD`" = "afl" || exit 1
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment