Fuzz testing sudo

Version 1.9.6 of sudo was released recently. This is primarily a bug fix release with almost no user visible changes. One of the changes visible to developers is that support for fuzz testing was added. What is fuzz testing? According to the Wikipedia: “Fuzzing or fuzz testing is an automated software testing technique that involves providing invalid, unexpected, or random data as inputs to a computer program. The program is then monitored for exceptions such as crashes, failing built-in code assertions, or potential memory leaks.”

What does this means for sudo? Random input data can uncover problems that would not otherwise be detected. Some of the fixes in this release are due to problems found by fuzzing. Note the word can. As it is random data, there are no guarantees that it will find all corner cases in your software, or that it will alert you about a problem at the time when the related source code was changed. Still, it can improve the security of software.

If you are not a developer or security researcher, then this is probably all you need to know. On the other hand, if you want to fuzz test sudo yourself then keep reading.

What you need

In order to do fuzz testing you need version 11 or higher of LLVM and you need to pass --enable-fuzzer and --enable-sanitizer to configure when building sudo. Now you can run make fuzz to do the actual fuzzing.

This all sounds easy, but you do need to download sources, install the right compiler environment, use the correct set of parameters. There is a much easier way: oss-fuzz. It runs the tests in a containerized environment.

But of course you can also do it all yourself. For those who are interested, I’ve included instructions for FreeBSD that should also work on Linux with minimal modifications.

Fuzzing sudo using oss-fuzz

The oss-fuzz project is available at https://github.com/google/oss-fuzz.git. It includes tests for many software projects, including sudo. There are many advantages using oss-fuzz. It runs in Docker containers, so the software stack necessary to test a given software does not “pollute” your base operating system. It has everything needed for fuzz testing and is fully automated. All of this means that you can easily perform fuzz testing whenever the code is modified.

Here is a list of commands to check out the latest oss-fuzz source code:

git clone https://github.com/google/oss-fuzz.git
cd oss-fuzz
export PROJECT_NAME=sudoers
python infra/helper.py build_image $PROJECT_NAME
python infra/helper.py build_fuzzers --sanitizer address $PROJECT_NAME
python infra/helper.py check_build $PROJECT_NAME

You now have a ready-to-use test environment. Currently, there are eight fuzz targets you can run, depending on which part of the code you want to test:

  • fuzz_policy
  • fuzz_sudoers
  • fuzz_sudoers_ldif
  • fuzz_iolog_json
  • fuzz_iolog_legacy
  • fuzz_iolog_timing
  • fuzz_sudo_conf

You can start any of these using command similar to the following:

python infra/helper.py run_fuzzer $PROJECT_NAME <fuzz_target>

Replace <fuzz_target> with one of the targets from the above list. By default these tests run forever. You can limit the amount of time the fuzzer runs by passing the -max_total_time option to the fuzzer. For example, to limit the amount of time the fuzzer runs to 5 minutes (300 seconds):

python infra/helper.py run_fuzzer $PROJECT_NAME <fuzz_target> \
    --fuzzer-args -max_total_time=300

Fuzzing sudo on FreeBSD

When I first learned that fuzz testing needs LLVM I immediately tried to test it on FreeBSD, where the whole system is compiled using LLVM. As it turned out, it is not that easy. Sudo’s fuzz testing was originally tested only on Linux, and it failed on FreeBSD. And while testing needs LLVM 11, FreeBSD 12 ships with LLVM 10. After a few e-mails with Todd, make fuzz now also works on FreeBSD. You do need to have the latest sudo source code from git for it to work.

First of all, make sure that git and LLVM 11 are available on your system:

pkg install git llvm11

With git installed, you can check out the latest sudo source code:

git clone https://github.com/sudo-project/sudo/

Change to the freshly created sudo directory and run configure. You have to make sure that it finds the right version of LLVM and that fuzzing related options are enabled:

./configure CC=/usr/local/bin/clang11 --enable-fuzzer --enable-sanitizer

Build sudo:

make

You are now ready to fuzz test sudo:

make fuzz

By default, “make fuzz” will run each of the fuzzers for 8192 iterations. You can adjust this by overriding the FUZZ_RUNS Makefile variable. For example:

make FUZZ_RUNS=1024 fuzz

The same instructions should work with minimal modifications on Linux as well.

Continuous integration

Every time a change in a source file is committed, a limited amount of fuzzing is run automatically via CIFuzz. You can see these fuzzing runs in the actions tab of the sudo GitHub repo. Failed actions usually indicate either a compilation problem or a bug of some kind. If a failure does occur, the attached artifacts zip file will include the output and the failed test input.

Results

In an ideal world the fuzzers will not find anything. When there is a problem, the fuzzer stops, prints a detailed error message and saves a copy of the input that caused the problem. I tested it by reverting the fix for CVE-2021-3156. On the third run make fuzz stopped and displayed a stack trace with the source of the crash. If make fuzz reports a problem to you, be sure to let Todd Miller, maintainer of sudo know about it. You can report crashes found by fuzzing to sudo@sudo.ws. Please include a copy of the input that led to the crash.

If you would like to be notified about new posts and sudo news, sign up for the sudo blog announcement mailing list.