Using syzkaller, part 2: Detecting programming bugs in the Linux kernel

Andre Almeida April 17, 2020

Share this post:

In my previous blog post, we discussed the importance of testing, what is fuzzing, and how the syzkaller fuzzes the kernel in order to find bugs. Now, let’s install the tool and starting using it to improve our code base.

The kernel source will be expected to be found in the $KSRC directory. The syscall descriptions are based on linux-next, so if something fails or triggers a warning that a specific syscall isn’t defined, consider changing to the current linux-next/master branch.

Your kernel should be specifically configured to enhance the performance of syzkaller and to enable it to be properly fuzzed. The following configs are necessary:

To be able to navigate through the code coverage in the web interface, CONFIG_DEBUG_INFO must also be enabled. To get the most of the power of syzkaller, there are more configuration options that can be enabled, the more the better honorable mention for KASAN. After modifying the configuration, build the kernel again, since we will need a compiled kernel in next steps. Also, it’s required to have the following tools:

Go

QEMU

git

OpenSSH

A recent C compiler with coverage support (both current GCC and Clang versions should work)

This doesn’t seem a very canonical way of downloading and installing a package, but since this project is created by Go developers, I believe that their suggestion is currently the best approach. Syzkaller should now be at ~/go/src/github.com/google/syzkaller. Not a very convenient path, but this can be solved with a symlink or the PATH variable. In this example, it will be defined by the following variable:

export SYZPATH=~/go/src/github.com/google/syzkaller

A basic rootfs for the virtual machines can be created using a script provided by syzkaller that requires the debootstrap package. Choose a directory in which to store rootfs images (which is stored as `$IMAGES` below), then run:

export IMAGES=$(pwd)
bash $SYZPATH/tools/create-image.sh

Use -h to explore the other options provided by this tool. In summary, it will create a basic Debian image with the tools that we need and a SSH server to copy and run commands across virtual machines.

Verifying your setup

Just to make sure everything is working fine and avoid some troubleshooting in the future, check if the ssh is properly working. First, boot the machine:

Build and configure syzkaller and its tools

Now we have a working rootfs, we are almost ready. Go ahead and build syzkaller and its tools:

cd $SYZPATH/
make -j8

We need to create a work directory, where the tool will create a database and store results. Having a separate directory is a good way to organize information as it allows us to have different databases for different syscalls or for different configurations. To do that, just create different directories and set the one you want to work with in the configuration file. Any name works, but those starting with workdir* will be ignored by git, as per a rule in .gitignore file.

mkdir workdir

To define the behavior of the tool, create a configuration file somewhere, here called as config.cfg. This example should be enough for an initial run:

Open the URL provided and you should be able to check the web interface. It’s a convenient way to navigate through the features, check coverage reports, crashes and corpus. Notice that the server may seem unresponsive for a few seconds while the tool is performing some heavy operations, so be patient.

This is what the main dashboard looks like. In the first section, you can read the current status fo the tool, like the uptime, number of syscalls enabled, crashes and fuzzer execution statistics. In the second one, you have a list of crashes, how many times it happened. It may also export a report and a C reproducer of the crash. The last section is the log, the same log that the tool outputs in the terminal with live information about the tool progress.

Clicking in corpus, you can see the set of syscalls that produces the current coverage. If a syscall can't increase the coverage, it will not be saved in this database.

The list shows a link for the coverage that this syscall had produce as well as the name of the syscall description. Clicking on the name, you can see the input of this corpus. For instance, this is an exemple of syz_mount_image$ext4:

Clicking in a coverage report in the corpus page will show the coverage for this particular input. Clicking in the coverage link in the main dashboard will show the global coverage, for the current corpus.

This is the current coverage of the file usercopy.c for the current corpus. An explanation about the coverage colors can be found in the documentation.

And we are done! This is the basic usage of syzkaller. Dig into the documentation for more details and see this source file for more options for the configuration. I found the following ones to be very useful to add at config.cfg file:

"enable_syscalls": [ "eventfd", "read$eventfd", ... ]: only those syscalls will be fuzzed. If they depend on another syscall to work (e.g. open()) syzkaller will ask you to enable it. Using read will trigger all definitions for this syscalls, including the eventfd one. Using read$eventfd will trigger just the eventfd one.

Those new options may be added anywhere at the config file, with the exception inside the vm curly braces.

Summary

Now you should be ready to starting using syzkaller. Remember that the tool needs some some time to starting showing some results. Join the mailing list to get in touch with developers, see new bugs found by the tool and to report the crashes that you found.

In the final part of this series, we are going to see how to modify syzkaller in order to stress your own changes and improve testing in our development process.