Solve empty and non-performing seed case #5
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Sorry in advance, this is a bit longer...(instructions to reproduce at the end)
TLDR: If every seed has
perf_score==0
, AFLFast (default) will stop fuzzing and only cycle through seeds.Longer version:
Why
I ran some experiments with AFLFast (default schedule) with the empty seed, as described in the paper. While this works with several targets just fine (e.g. binutils), it implodes on targets that are "a bit harder to explore", due to an edge case in the
performance_score
calculation.By "imploding" I mean:
AFLFast runs for a few thousand iterations fine (depending on the target ~5-10k total execs), then suddenly
exec speed
drops to 0/sec, andcycles done
explodes into millions.As of now I've hit this problem with 2/8 targets (
tcpdump
anddjpeg
are causing issues).Problem
I believe the problem lies in the performance calculation:
Here are the relevant code snippets:
inside
calculate_score()
:using
calculate_score()
If we happen to find something with our empty seed, new paths will be scheduled,
q->fuzz_level
remains fairly small andfactor
is most likely > 0. Even iffactor==0
and therefore resultingperf_score==0
, we have no problem, as we simply skip to another seed.If we happen not to find any new seeds,
q->fuzz_level
steadily increases and as soon asq->fuzz_level >=16
we go in the else statement:factor = MAX_FACTOR / (fuzz == 0 ? 1 : next_p2 (fuzz)); //will almost always be 0, as fuzz is already high
perf_score *= factor / POWER_BETA; //will then always be 0
if (perf_score == 0) goto abandon_entry //will then always be true
this means we are constantly skipping to the next seed, but as we only have 1 empty seed, we will constantly skip to the same one. This will explode in cycles and always skip fuzzing altogether.
This could be solved in 2 ways:
factor = MAX_FACTOR
2.1 Check whether all seeds have
performance_score==0
, and if so, don't skip. This is probably expensive2.2 Check for
queued_paths
, and only skip if we have a few. To be accurate: this doesn't completely solve the edge case. If every seed has a performance score of 0, this still occurs. However, with this fix the chances of it occurring should be much lower, and additionally, the changes to the original implementation are minimal. This is the suggestion of this PR.2.3 Similar checks are implemented https://github.com/derdav3/aflfast/blob/master/afl-fuzz.c#L5045, so another solution would be to check for
pending_favorites
, but I didnt investigate this further.With the suggested code change, all my targets run just fine.
Reproduction
echo "" > empty
~/aflfast/afl-fuzz -i ./empty/ -o ./out -- ./tcpdump -nr @@
PS: Depending on your clang/llvm version, you might need [https://github.com/derdav3/aflfast/commit/2793c0920f3db51fb9bcb80301018d7e032fafc5](this patch) to compile aflfast.