VolatileMinds

Application Security and Software Consulting



Advanced AFL usage with real-world examples -- Persistent mode

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