- File descriptor offsets are shared between processes created with fork(). This means that any descriptors that are open at the time that our shim is executed may need to be rewound to their original position; not a significant concern if we are stopping at main() - we can just as well rewind stdin by doing lseek() in the fuzzer itself, since that's where the descriptor originates - but it can become a hurdle if we ever aim at locations further down the line.
- In the same vein, there are some types of file descriptors we can't fix up. The shim needs to be executed before any access to pipes, character devices, sockets, and similar non-resettable I/O. Again, not a big concern for main().
- The task of duplicating threads is more complicated and would require the shim to keep track of them all. So, in simple implementations, the shim needs to be injected before any additional threads are spawned in the binary. (Of course, threads are rare in file parser libraries, but may be more common in more heavyweight tools.)
- The fuzzer is no longer an immediate parent of the fuzzed process, and as a grandparent, it can't directly use waitpid(); there is also no other simple, portable API to get notified about the process' exit status. We fix that simply by having the shim do the waiting, then send the status code to the fuzzer. In theory, we should simply call the clone() syscall with the CLONE_PARENT flag, which would make the new process "inherit" the original PPID. Unfortunately, calling the syscall directly confuses glibc, because the library caches the result of getpid() when initializing - and without a way to make it reconsider, PID-dependent calls such as abort() or raise() will go astray. There is also a library wrapper for the clone() call that does update the cached PID - but the wrapper is unwieldy and insists on messing with the process' stack. (To be fair, PTRACE_ATTACH offers a way to temporarily adopt a process and be notified of its exit status, but it also changes process semantics in a couple of ways that need a fair amount of code to fully undo.)
__afl_forkserver: /* Phone home and tell the parent that we're OK. */ pushl $4 /* length */ pushl $__afl_temp /* data */ pushl $199 /* file desc */ call write addl $12, %esp __afl_fork_wait_loop: /* Wait for parent by reading from the pipe. This will block until the parent sends us something. Abort if read fails. */ pushl $4 /* length */ pushl $__afl_temp /* data */ pushl $198 /* file desc */ call read addl $12, %esp cmpl $4, %eax jne __afl_die /* Once woken up, create a clone of our process. */ call fork cmpl $0, %eax jl __afl_die je __afl_fork_resume /* In parent process: write PID to pipe, then wait for child. Parent will handle timeouts and SIGKILL the child as needed. */ movl %eax, __afl_fork_pid pushl $4 /* length */ pushl $__afl_fork_pid /* data */ pushl $199 /* file desc */ call write addl $12, %esp pushl $2 /* WUNTRACED */ pushl $__afl_temp /* status */ pushl __afl_fork_pid /* PID */ call waitpid addl $12, %esp cmpl $0, %eax jle __afl_die /* Relay wait status to pipe, then loop back. */ pushl $4 /* length */ pushl $__afl_temp /* data */ pushl $199 /* file desc */ call write addl $12, %esp jmp __afl_fork_wait_loop __afl_fork_resume: /* In child process: close fds, resume execution. */ pushl $198 call close pushl $199 call close addl $8, %esp retBut, was it worth it? The answer is a resounding "yes": the stop-at-main() logic, already shipping with afl 0.36b, can speed up the fuzzing of many common image libraries by a factor of two or more. It's actually almost unexpected, given that we still keep doing fork(), a syscall with a lingering reputation for being very slow. The next challenge is devising a way to move the shim down the stream, so that we can also skip any common program initialization steps, such as reading config files - and stop just few instructions shy of the point where the application tries to read the mutated data we are messing with. Jann's original patch has a solution that relies on ptrace() to detect file access; but we've been brainstorming several other ways. PS. On a related note, some readers might enjoy this.