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:
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
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.
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.
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.
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.