NAME

docs/dev/fuzzing.pod - Fuzzing Parrot with afl - the american fuzzy lop

DESCRIPTION

This document describes how to find parrot crashes on invalid input with the american fuzzy lop.

This will not work on MS Windows. It is tested on x86_64 linux, but should also work on darwin and other POSIX intel systems.

Setup afl

Download afl from http://lcamtuf.coredump.cx/afl/

Make and install afl.

    cd ~/

    wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
    tar xfz afl-latest.tgz

    cd afl-<TAB>

    # be sure to unset ASAN_OPTIONS
    grep ASAN | env
    unset ASAN_OPTIONS
    export ASAN_OPTIONS

    make
    sudo make install

Compile parrot with the instrumenting afl-gcc:

    cd <your-parrot>
    perl Configure.pl --cc=afl-gcc --ld=afl-gcc --link=afl-gcc
    make -s -j4

Read the afl documentation

See http://lcamtuf.coredump.cx/afl/ and under afl-VERSION/docs. And maybe also https://en.wikipedia.org/wiki/Fuzz_testing

Some notes:

afl can work with clang, but gcc is preferred.

afl can work with ASAN or MSAN, but since parrot is too slow it is advised to run it without. Our coverage with the normal testsuite is pretty good, so you better smoke the normal testsuite with ASAN or MSAN or VALGRIND=valgrind instead.

afl tries to create bad input files, to crash or hang the process. Bad and uncontrolled input may impact the stability of your testmachine. E.g. it might randomly overwrite files or memory or fill up your logs or harddisc space. Use a fast kvm VM or docker. Only one core is needed if you don't fuzz in parallel.

parrot_old has faster startup, but does not play nice with the afl forkserver. You can use it with -C, the crash mode though. Get a testcase which crashes and find more crashes. This way I found https://github.com/parrot/parrot/issues/1168

With perl5 or potion I got exec speeds of 1500/sec, with parrot_old 150/sec, with parrot 100/sec. This is very slow. So strip your input testcases to the absolute minimum. An afl-fuzz run usually lasts a day or more, but at least a few hours. I did not dare yet to fuzz nqp or perl6 yet, as it would be even slower.

Set your terminal screen to black background, seriously. You hardly see anything on white.

imcc Parser

You can fuzz .pir and .pasm input to check the imcc parser.

    mkdir afl-out afl-testcase
    cp t/op/literal.t testcase/

    afl-fuzz -m4000 -o afl-out -i afl-testcase -- parrot -r @@

With keywords

Adding our regular pir and pasm keywords helps creating more meaningful permutations. One file in a new directory per keyword.

    mkdir -p afl-out2 afl-testcase2/keywords

    perl -e'`echo $_ > afl-testcase2/keywords/$_` for qw(.sym .arg prototyped
      non_prototyped .class
      .endclass .param inc dec new defined global clone .call .result .return
      .local .const .globalconst
      end goto if unless call branch jump jsr ret invoke invokecc throw
      rethrow die_hard .emit .eom .sub .end .begin_call
      .end_call .pcc_sub .begin_return .end_return .begin_yield
      .end_yield .loadlib .namespace .endnamespace .macro .include int float
      string pmc ne eq le lt ge gt == =head1 =end =pod)'

    afl-fuzz -m4000 -i afl-testcase2 -x afl-testcase2/keywords -o afl-out2 -- parrot -r @@

pbc packfiles

Fuzz binary input to check the PackFile reader. Binary files are the typical usecase for afl. See https://github.com/parrot/parrot/issues/1169

    mkdir -p afl-out3 afl-testcase3
    cp t/native_pbc/*_8_le.pbc afl-testcase3/
    afl-fuzz -m4000 -i afl-testcase3 -o afl-out3 -- parrot -r @@

During fuzzing

You can examine the results while running the fuzzer, and you can even fix the found error, recompile, Ctrl-C the fuzzer and resume it with -i-, i.e. afl-fuzz -m4000 -i- -o afl-out3 -- parrot -r @@

It will reinstrument the existing paths and cases, and save away the old results.

Crosscheck the results for false positives

afl-fuzz does sometimes categorize all exit codes > 0 as crash, while it should do only do it with > 128. So you need to verify the found crashes, like this:

    for c in afl-out/crashes/*; do ./parrot $c >/dev/null 2>&1 || echo $? $c; done

and check only the exit codes > 128.

Further investigations - crash explorer

If you found a new crash, try out the crash explorer mode -C by copying this crash result as new testcase and start a new afl-fuzz with -C. This will find other variations of the crash faster than in the normal mode. See http://lcamtuf.blogspot.de/2014/11/afl-fuzz-crash-exploration-mode.html

Other tools

There exist also less intelligent blackbox fuzzers, like csmith.

And more intelligent whitebox fuzzers, which do taint symbolic input and use constraint solvers to track fishy source paths back to the input, e.g. mayhem, cbmc or the mythical smart Microsoft fuzzer (https://en.wikipedia.org/wiki/Fuzz_testing#cite_ref-AutoDO-14_16-0).

However afl is usually the best option for now. It is fast, simple and good enough.

AUTHOR

Reini Urban <rurban@cpan.org>