FLIMP!

Tutorial: Fuzzing GIMP

GIMP has a lot of input file parsers. Parsers for complex binary formats are a very typical target for fuzz testing. We will explain how to fuzz GIMP with American Fuzzy Lop (AFL) and Address Sanitizer (ASAN). However fuzzing GIMP comes with some challenges:

Enabling Address Sanitizer

ASAN makes many memory safety bugs visible that would otherwise stay undetected, particularly out of bounds reads and use after free bugs. Therefore we enable ASAN in AFL by setting the appropriate environment variable:

export AFL_USE_ASAN=1

Setting ASAN options

To properly use ASAN with the GIMP we need to set a couple of options, also via an environment variable:

export ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:allow_user_segv_handler=0:handle_abort=1:symbolize=0"

detect_leaks=0 disables ASAN's memory leak detection. We're not going to hunt for memory leaks for now and GIMP compilation already breaks if we don't disable this.

abort_on_error=1 makes sure each detected ASAN error causes the application to abort with a proper error code that AFL can detect as a crash.

allow_user_segv_handler=0 prevents GIMP from installing its own segfault handlers. These also prevent proper detection of crashes.

handle_abort=1 makes sure abort calls are handled as crashes by ASAN.

symbolize=0 prevents symbol resolution for the crash dumps. While this is useful for debugging the crashes later it is not needed for crash detection and AFL requires it to be off in order to avoid unnecessary performance costs.

Compiling GIMP

Next we get a recent version of GIMP, unpack it and patch it with this patch. We run configure while setting CC to the AFL compiler wrapper, compile it and install it:

patch -p1 < [path_to]/gimp-abort-on-plugin-error.diff
./configure CC=afl-clang-fast
make
make install

AFL provides several compiler wrappers, but if available afl-clang-fast is the fastest and thus preferrable. Some distributions rename GEGL (e.g. Gentoo), so you may have to prefix the configure call with something like GEGL=/usr/bin/gegl-0.3.

The patch makes sure that GIMP will abort itself if a crashing plug-in was detected.

We're installing GIMP into the system. This means we shouldn't already have GIMP installed. However we need its dependencies like babl and gegl already installed. We recommend to do this in a chroot environment or a virtual machine.

Fuzzing

Now we need a sample file to start our fuzzing. Most promising are obscure file formats that GIMP still supports. A collection of potential input files can be found here. If possible we can also choose several input files that expose different features of the file format. However it's not trivially possible to fuzz several different file formats at once.

For our example we will use tga files (Example file with no RLE compression, example file with RLE compression). We place them into the directory in-tga.

Finally we run AFL to fuzz GIMP:

afl-fuzz -m none -i in-tga -o out-tga -t 5000 -f test.tga /usr/local/bin/gimp-console-2.9 -idfsn test.tga -b (gimp-quit 0)

The -m none disables the memory limit, it is required to make AFL work with ASAN. The parameter -t 5000 increases the timeout (fuzzing GIMP is very slow). After a while example files triggering bugs will be collected in out-tga/crashes.

We will run gimp-console

To analyze them we can run gimp-console manually. We need to rename the crashing input files to a filename with the right extension, otherwise GIMP won't detect it.

In order to get a more useful stack trace for this step we should remove the symbolize=0 part of our ASAN_OPTIONS variable:

export ASAN_OPTIONS="detect_leaks=0:abort_on_error=1:allow_user_segv_handler=0"

We can then test our file with this command:

/usr/local/bin/gimp-console-2.9 -idfsn crash.tga -b '(gimp-quit 0)'

The -b '(gimp-quit 0)' parameter will make sure GIMP quits after parsing the file. -i makes sure no GUI is started, -d speeds up the start a bit by avoiding to load unneeded data, -f avoids loading fonts, -s disables the splash screen, -n makes sure a new instance is run so different parallel instances don't interact with each other.

Ideas for better Fuzzing

While the method described in this tutorial works it is also painfully slow (around 5 executions per second on a test server). It is far from ideal, as it needs to start the whole GIMP application in order to test a small parser.

Other fuzzing strategies would be more efficient, but they would require refactoring parts of the GIMP code in order to make it more testable. Ideally one would change the GIMP plug-ins so they can be used as a library and thus are testable with a tool like LibFuzzer.

Hanno Böck