# Fuzz testing Halide has a set of fuzz-testing harnesses that can be used to find those tricky to find, edge cases and bugs that would otherwise not be caught by a regular unit-testing suite. At the moment these fuzz-tests are housed in the `test/fuzz` directory. The fuzz testing suite use the common, [libfuzzer](https://www.llvm.org/docs/LibFuzzer.html) interface for fuzz-tests. ## Building fuzz tests Fuzz testing requires specific instrumentation across the entire build; to do this we make use of a fuzzing-specific-toolchain/preset. e.g. ``` cmake -B build --preset linux-x64-fuzzer -DLLVM_ROOT=/path/to/llvminstall cmake --build ./build -j$(nproc) ``` Note that the LLVM install that you use must be built with `-D LLVM_ENABLE_RUNTIMES="compiler-rt"` set if you want to build the fuzzer tests (failing to do so will fail at configure time); not all prebuilt LLVM installs include this, so you may need to build LLVM from source to run the fuzz tests locally. ## Using the fuzz-harnesses Fuzz-testing harnesses are a little different to a more traditional unit-test and don't have a definitive end of test. In other words, a fuzz test will run: - for an infinite amount of time (the default), - for a user specified maximum amount of time, - until the fuzzer finds a bug and crashes, - you manually kill the process e.g. (ctrl-C). Once you have built the fuzz testing suite using the commands listed above you can list the fuzz testing harnesses using the command: ``` ls ./build/test/fuzz/fuzz_* ``` To run a fuzzer simply run the fuzz-testing harness with no arguments. e.g. `./build/test/fuzz/fuzz_simplify` By default this will run the fuzz test on a single core and discard whatever. temporary corpus is created. To reuse a given corpus (recommended) create a new directory to store the corpus generated by your fuzz testing harness and pass that directory into your fuzzer e.g. ``` mkdir fuzz_simplify_corpus -p ./build/test/fuzz/fuzz_simplify fuzz_simplify_corpus ``` This will save the state of the fuzzer between runs, this way any progress that your fuzzer makes improving code-coverage will remain persistent on your disk. Up until this point the fuzzer has only been running on a single core. To speed things up a little, let's run the fuzzer in parallel across all available cores on our machine. ``` ./build/test/fuzz/fuzz_simplify fuzz_simplify_corpus -fork=$(nproc) ``` ## Reproducing crashes An important part of fuzz testing is reproducing the crashing input. To handle this, a libfuzzer-based fuzz harness will create a crash file whenever the fuzzer exits unexpectedly. This will look something like: `crash-` To reproduce a crash we simply rerun our fuzz harness with our crash file as the first argument. `./build/test/fuzz/fuzz_simplify crash-` So long as your fuzz harness and library are deterministic this should reproduce the original crash. ## Adding new fuzz tests A bare-bones fuzzer will look something like the following: ```cpp #include #include #include extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { // Randomly throw data at our function and hope it doesn't crash. foo(data, size); return 0; } ``` This assumes that our function foo takes in a buffer and the size of said buffer. But in many cases we would like to make use of more structured data. e.g. a string or a vector of integers etc. Thankfully libfuzzer provides a handy helper to convert a raw buffer into common structured data types, the [FuzzedDataProvider class](https://github.com/llvm/llvm-project/blob/main/compiler-rt/include/fuzzer/FuzzedDataProvider.h). For examples on how to use this class see `test/fuzz/simplify.cpp`. ## Other useful materials - [The official libfuzzer docs](https://www.llvm.org/docs/LibFuzzer.html) - [The libfuzzer tutorial](https://github.com/google/fuzzing/blob/master/tutorial/libFuzzerTutorial.md)