Fuzz testing SPDK

Fuzzing is a software testing technique that incorporates feeding random, unexpected input to a computer program to test unexpected paths of software execution. The SPDK project uses two approaches to fuzz testing:

  • Brute force randomization of inputs

  • LLVM coverage-guided randomization

NVMe fuzz

The SPDK fuzzer application tries to fuzz the NVMe-oF target or a physical NVMe drive by submitting randomized NVMe commands through the SPDK NVMe initiator. To achieve this, NVMe commands are sent through the spdk_nvme_ctrlr_cmd_io_raw interface, or in the case of admin commands spdk_nvme_ctrlr_cmd_admin_raw. The data in those commands is completely random, generated using the current time as a seed.

This application saves generated commands in a JSON formatted file that allows for further debugging. But using random input has some drawbacks: the fuzzer can spend lots of time testing inputs that do not increase coverage and even after a long run, there may still be code paths left untested.

For more information take a look at test/app/fuzz/nvme_fuzz/README.md

LLVM libfuzzer

To overcome this issue, another tool to fuzz test SPDK apps is used. LibFuzzer is a coverage guided fuzzer that comes from the LLVM project. Every input sent to SPDK is also tested against code coverage data to see if it increased. If a random input increases code coverage, it is saved and used as the basis for further mutation, allowing to check nodes placed deeper in the execution tree and to save time on testing inputs that can be rejected early.

llvm_nvme_fuzz

Similar to nvme_fuzz this application submits NVMe commands through the SPDK NVMe initiator, but this time random input is provided by LLVMFuzzerRunDriver and used to perform the chosen test defined in g_fuzzers array.

sudo CC=clang-13 CXX=clang++-13 ./configure --with-fuzzer='/usr/lib/llvm-13/lib/clang/13.0.1/lib/linux/libclang_rt.fuzzer_no_main-x86_64.a'
sudo ./test/fuzz/llvm/nvmf/run.sh 1

Since LibFuzzer is a part of the LLVM project, to compile this application, it’s necessary to set CC and CXX variables to clang and provide the path to the fuzzer library.

llvm_vfio_fuzz

The LLVM fuzzer is also used to test the vfio-user transport. This application sends unexpected input to mimic misbehaving and/or malicious virtual machines using the spdk_vfio_user_pci_bar_access interface.

sudo CC=clang-13 CXX=clang++-13 ./configure --with-fuzzer='/usr/lib/llvm-13/lib/clang/13.0.1/lib/linux/libclang_rt.fuzzer_no_main-x86_64.a' --with-vfio-user
sudo ./test/fuzz/llvm/vfio/run.sh 1

Running fuzz tests

An important advantage of the LLVM fuzzer is the corpus file. Each random input generated by the fuzzer that explores a new path is saved, allowing resumption of testing from the point where it was stopped and avoiding lost progress.

First existing data from a corpus is processed to check if it is still valid and then fuzzing continues from the last random input. It is also possible to provide predetermined data to recreate issues found by the fuzzer or to start from valid samples.

The SPDK CI system has two jobs, long-fuzz-nvmf and long-fuzz-vfio that utilize persistent corpus files.

This way every run is a continuation of the previous one, continually increasing code coverage, which can be observed by looking at coverage data expose in the job artifacts.

To try it for yourself just set SPDK_TEST_FUZZER and all tests should start in parallel.

sudo SPDK_TEST_FUZZER=1 ./test/fuzz/llvm/vfio/run.sh 1

For more information about LLVM Fuzzer please visit LibFuzzer

Thanks for reading!