I’m fuzzing a project using AFL++, and I’ve already found a few bugs, which I was able to reproduce, and thus they were fixed.
However, for some of the crash files, I was unable to reproduce the crash. I hypothesized that this was related to the use of AFL++’s persistent mode (using __AFL_LOOP
) and state preserved across different inputs.
Further experiments appear to confirm this, as removing __AFL_LOOP
hasn’t led to any irreproducible crashes. I’ve also tried running in persistent mode with (address, undefined behavior and leak) sanitizers on, but it made no difference: I still can’t reproduce the crashes. So I’m suspecting that whatever is happening with the program state is either outside the scope of the sanitizers, or maybe a “legal” change of state (from the point of view of C semantics) but which nevertheless uncovers a dormant bug.
Therefore, I am considering my options with regard to rendering these bugs reproducible. I can see two ways out of this:
-
Rather than creating testcases corresponding to a single input, I can concatenate many testcases to emulate the effect of
__AFL_LOOP
with the same iteration count. However, I think this is going to reduce the efficacy of fuzzing, as it’ll have to work on much larger inputs, and it’s just going to take much longer before it flips the necessary bits across all copies of the input to trigger the bug. It may also lead to certain dead-ends as my inputs are of fixed size, and trying to do anything which changes the input size is going to break the alignment going from one iteration to the other, which may again reduce fuzzing efficacy. -
Remain in persistent mode, but log to
stdout
every single input fed to the program. In this way, I can just replay the series of inputs that ultimately led to the crash. I’m just worried that this may use too much disk space or I/O operations — I have no idea if AFL++ logs somewhere the output of every run, whether crashing or not, or if it only logs in case of a crash. In terms of performance I don’t know if this is a big issue, since the binary takes a few milliseconds to run per input anyway.
However, as this is the first time I’ve fuzzed something, I may be missing an even easier/better way to handle this scenario. My ideal solution would actually be to have some kind of flag to AFL++ that would make it output not only the input that caused the crash, but every input that came before it since the crashing process was spawned (in my case, I use 1,000 iterations for persistent mode, i.e. __AFL_LOOP(1000)
, so that would require saving at most 1,000 inputs, which are fairly small (a few hundred bytes).
Any suggestions for me?