This is the final post on AFL usage for a while, and we will cover updating the basic FreeType fuzzer from the previous post to use the semi-recently added persistent mode. The first post covered basic usage of AFL in order to fuzz tcpdump. The second post covered how to fuzz tcpdump and achieve more executions per second with advanced AFL features. The third and previous post covered instrumenting libfreetype, then writing a small test program consuming the instrumented libfreetype in order to fuzz the TTF font parsing.
This post will assume the previous posts has been followed and that you have already successfully built and instrumented libfreetype. With all of the optimization covered in all of the previous posts covering AFL, after this post, you can easily sustain thousands of executions per second per core.
In order to use persistent mode with AFL, we need to build a test
program with a very specific pattern. Using a bit of spaghetti code, we
will essentially perform the fuzzing in-process so we avoid calling
fork()
for each execution of a new test file being fed by AFL.
This is what gives us such a massive speedboost for a small bit more of
work.
#include <ft2build.h>
#include FT_FREETYPE_H
int main(int argc, char *argv[]){
FT_Library lib = NULL;
FT_Face face = NULL;
FT_Init_FreeType(&lib);
while(__AFL_LOOP(1000)) {
face = NULL;
FT_New_Face(lib, argv[1], 0, &face);
FT_Done_Face(face);
}
return 0;
}
This should be compiled exactly the same way we compiled the previous
test harness, with afl-clang-fast
and statically linked with the
instrumented libfreetype. One modification that could be made is that
you can alternatively read the test file directly from standard in using
read(0,buf, buf_size)
but I find it a bit easier for this
particular case to just use the filename passed as the argument and pass
it to FT_New_Face()
. If you wanted to read stdin instead, you
could replace the FT_New_Face()
call with FT_New_Memory_Face()
.
# afl-clang-fast font_parser_persistent.c -I freetype-2.6/include/ \
> -L freetype-2.6/objs/.libs/ -lfreetype -lz -static \
> -o font_parser_persistent
Now, when we have the binary compiled, we need to call afl-fuzz
in
a slightly different way. We must set AFL_PERSISTENT=1
.
# AFL_PERSISTENT=1 afl-fuzz -i testcases/ -o syncdir/ -M fuzzer1 \
> ./font_parser_persistent @@
With all of the optimizations covered in the previous posts, as well as using persistent mode covered in the post, my 6-core AMD Athlon sustains about 21k executions per second across 5 of the cores.
status check tool for afl-fuzz by <lcamtuf@google.com>
Individual fuzzers
==================
>>> fuzzer5 (0 days, 20 hrs) <<<
cycle 45, lifetime speed 4680 execs/sec, path 2889/3274 (88%)
pending 0/219, coverage 6.70%, no crashes yet
>>> fuzzer4 (0 days, 20 hrs) <<<
cycle 50, lifetime speed 4673 execs/sec, path 2610/3282 (79%)
pending 0/264, coverage 6.75%, no crashes yet
>>> fuzzer1 (0 days, 20 hrs) <<<
cycle 3, lifetime speed 2068 execs/sec, path 968/2403 (40%)
pending 0/1915, coverage 6.67%, no crashes yet
>>> fuzzer3 (0 days, 20 hrs) <<<
cycle 47, lifetime speed 4690 execs/sec, path 1728/3283 (52%)
pending 0/286, coverage 6.75%, no crashes yet
>>> fuzzer2 (0 days, 20 hrs) <<<
cycle 50, lifetime speed 4718 execs/sec, path 2646/3257 (81%)
pending 0/260, coverage 6.73%, no crashes yet
Summary stats
=============
Fuzzers alive : 5
Total run time : 4 days, 6 hours
Total execs : 1533 million
Cumulative speed : 20830 execs/sec
Pending paths : 0 faves, 2944 total
Pending per fuzzer : 0 faves, 588 total (on average)
Crashes found : 0 locally unique