Last active
August 29, 2015 14:24
-
-
Save thejh/617ee67aa43e236ee266 to your computer and use it in GitHub Desktop.
old AFL forkserver patch
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. | |
+ 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) [email protected] -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) [email protected] 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) [email protected] -o $@ | |
ln -s afl-as as 2>/dev/null || true | |
-afl-fuzz: afl-fuzz.c $(COMM_HDR) | |
- $(CC) $(CFLAGS) $(LDFLAGS) [email protected] -o $@ | |
+afl-fuzz: afl-fuzz.c $(COMM_HDR) common.o common.h | |
+ $(CC) $(CFLAGS) $(LDFLAGS) [email protected] common.o -o $@ | |
afl-showmap: afl-showmap.c $(COMM_HDR) | |
$(CC) $(CFLAGS) $(LDFLAGS) [email protected] -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