Wednesday, September 8, 2021

Fuzzing annocheck with Radamsa

Recently I heard that annocheck was crashing when scanning some files. That gave me an idea - fuzz it! I got the latest code in rawhide which at the time was 9.93, built it using rpmbuild, and cd'ed into it's source directory. Then:

make clean
CFLAGS="-fsanitize=address,undefined,null,return,object-size,nonnull-
attribute,returns-nonnull-attribute,bool,enum -ggdb -fno-sanitize-
recover=signed-integer-overflow" ./configure --without-tests --without-docs
make -j `nproc`

This sets it up for the address sanitizer so that we can spot fuzzing induced problems. Running make fails unexpectedly:

In function ‘follow_debuglink’,
    inlined from ‘annocheck_walk_dwarf’ at annocheck.c:1092:18:
annocheck.c:776:7: error: ‘%s’ directive argument is null [-Werror=format-
overflow=]
  776 |       einfo (VERBOSE2, "%s:  try: %s", data->filename, debugfile);      
\
      |       ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
annocheck.c:805:7: note: in expansion of macro ‘TRY_DEBUG’
  805 |       TRY_DEBUG ("%s", debug_file);
      |       ^~~~~~~~~
annocheck.c: In function ‘annocheck_walk_dwarf’:
annocheck.c:776:35: note: format string is defined here
  776 |       einfo (VERBOSE2, "%s:  try: %s", data->filename, debugfile);

This goes on an on. As I understand it, this is a bug in gcc and will be fixed in an upcoming release. However, what is stopping the build is the -Werror flag in the Makefile. So, you want to edit annocheck/Makefile and remove -Werror from the make file. Now running make will produce the binaries. Prior to working with annocheck, I wrote and compiled a little "Hello World" program in a test directory. In doing this, I left it unstripped. To prepare for fuzzing, I did this:

cd annocheck
mkdir in
cp ~/test/hello   in/test
mkdir -p /tmp/out
ln -s /tmp/out out

Then I used a script similar to the one discussed in the Fuzzing with Radamsa article from a couple days ago.

#!/bin/sh
LOG="in/test"
TLOG="out/test"

while true
do
        cat $LOG | radamsa > $TLOG
        ./annocheck $TLOG >/dev/null
        rc="$?"
        if [ "$rc" == "1" ] ; then
                exit 1
        fi
        rm -f $TLOG
        echo "==="
done

The basic idea is put a seed program into the "in" directory. Radamsa mutates it and writes it to the "out" directory, which is a symlink to /tmp. As explained in the previous article, you want to do fuzzing writes to a tmpfs file system so that you don't wear out real hardware. Running the script found this on the first test case:

hardened.c:1081:7: runtime error: null pointer passed as argument 1, which is
declared to never be null
AddressSanitizer:DEADLYSIGNAL
=================================================================
==48860==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc
0x7ffb239405bb bp 0x7fff6413a7d0 sp 0x7fff64139f60 T0)
==48860==The signal is caused by a READ memory access.
==48860==Hint: address points to the zero page.
    #0 0x7ffb239405bb in __interceptor_strcmp.part.0 (/lib64/libasan.so.
6+0x8d5bb)
    #1 0x42b9eb in interesting_sec /home/builder/working/BUILD/annobin-9.93/
annocheck/hardened.c:1081
    #2 0x42b9eb in interesting_sec /home/builder/working/BUILD/annobin-9.93/
annocheck/hardened.c:1074
    #3 0x40dd23 in run_checkers /home/builder/working/BUILD/annobin-9.93/
annocheck/annocheck.c:618
<snip>

I re-run the sanitizer it stops a lot on this error. But if you keep restarting it, eventually you may get this one:

=================================================================
==49841==ERROR: AddressSanitizer: heap-use-after-free on address
0x603000035d40 at pc 0x7fd46885170c bp 0x7ffcf350afc0 sp 0x7ffcf350a770
READ of size 1 at 0x603000035d40 thread T0
    #0 0x7fd46885170b in __interceptor_strcmp.part.0 (/lib64/libasan.so.
6+0x8d70b)
    #1 0x42637d in check_for_gaps /home/builder/working/BUILD/annobin-9.93/
annocheck/hardened.c:3709
    #2 0x42637d in finish /home/builder/working/BUILD/annobin-9.93/annocheck/
hardened.c:3889
    #3 0x42637d in finish /home/builder/working/BUILD/annobin-9.93/annocheck/
hardened.c:3844
    #4 0x40e3f6 in run_checkers /home/builder/working/BUILD/annobin-9.93/
annocheck/annocheck.c:691
    #5 0x40e3f6 in process_elf /home/builder/working/BUILD/annobin-9.93/
annocheck/annocheck.c:1517
    #6 0x40f690 in process_file /home/builder/working/BUILD/annobin-9.93/
annocheck/annocheck.c:1732
    #7 0x408880 in process_files /home/builder/working/BUILD/annobin-9.93/
annocheck/annocheck.c:1890
    #8 0x408880 in main /home/builder/working/BUILD/annobin-9.93/annocheck/
annocheck.c:1982
    #9 0x7fd467b36b74 in __libc_start_main (/lib64/libc.so.6+0x27b74)
    #10 0x409dad in _start (/home/builder/working/BUILD/annobin-9.93/
annocheck/annocheck+0x409dad)

0x603000035d40 is located 0 bytes inside of 25-byte region
[0x603000035d40,0x603000035d59)
freed by thread T0 here:
    #0 0x7fd468872647 in free (/lib64/libasan.so.6+0xae647)
    #1 0x412cfb in annocheck_get_symbol_name_and_type /home/builder/working/
BUILD/annobin-9.93/annocheck/annocheck.c:1463

previously allocated by thread T0 here:
    #0 0x7fd46881d967 in strdup (/lib64/libasan.so.6+0x59967)
    #1 0x412d25 in annocheck_get_symbol_name_and_type /home/builder/working/
BUILD/annobin-9.93/annocheck/annocheck.c:1466

SUMMARY: AddressSanitizer: heap-use-after-free (/lib64/libasan.so.6+0x8d70b)
in __interceptor_strcmp.part.0



This is a good one. Use after free can be exploitable under the right conditions. I reported these to the annobin developer. He replicated the results, fixed it up, and released 9.94 to Fedora the next day. I checked it and can confirm that Radamsa finds no other problems.

Note that if you actually wanted to fix the bug, the test case is in out/test. Just make the patch, recompile, and manually run the test case to confirm it's fixed. If you would like to preserve the test cases for later, remember that if you shut down the system they are gone unless you moved it from out to a more permanent location.

Radamsa is a good first fuzzer to reach for when starting to fuzz a new program. It's simple to setup and get running. And it finds all the low hanging fruit. But to go deeper, you need a guided fuzzer like AFL++. And that is exactly what we'll do in a future blog post.

No comments: