What is fuzzing?
Fuzzing or fuzz testing is an automated software technique that involves providing semi-random data as input to the test program in order to uncover bugs and crashes. In this short tutorial we will discuss cargo-fuzz.
Why fuzz Rust code?
Rust is a safe language (mostly) and memory corruption issues are a thing of the past so we don’t need to fuzz our code, right? wrong:). Any code where stability quality and coverage are important is usually worth fuzzing. Also fuzzing can uncover logical bugs and Denial-of-service where in critical components can lead to security issues as well.
As a reference to almost infinite amount of bugs found with cargo-fuzz (only the documented one) you can look here. Also, you can check-out our previous discussion on why to fuzz go which is also a safe language.
cargo-fuzz is the current de-facto standard fuzzer for rust and essentially it is just a proxy layer to the well tested libFuzzer engine. This means the algorithm and the interface is all based on libFuzzer which wildly-used coverage guided fuzzer for c/c++ and some other languages that implemented a proxy layer just like cargo-fuzz.
libFuzzer (cargo-fuzz) and coverage-guided fuzzers in general have the following algorithm:
Building & Running
If you are already familiar with this part you can skip to Continuous Fuzzing section.
we will use https://github.com/fuzzitdev/example-rust as a simple example.
For the sake of the example we have a simple function with an off-by-one bug:
our fuzz function will look like this and will be called by libFuzzer in an infinite loop with the generated data according to the coverage-guided algorithm.
To run the fuzzer we need to build an instrumented version of the code together with the fuzz function. cargo-fuzz is doing for us the heavy lifting so it can be done with the following simple steps:
This find the bug in a few seconds, prints the “FUZZI” string that triggers the vulnerability and saves it to a file.
From our experience integrating continuous fuzzing into OSS projects we believe the following two workflows brings the most value:
The fuzzing workflow helps find new bugs, generate new test-cases and guarantee that your most recent version of the code was fuzzed.
The regression workflow helps validate pull-requests and find bugs before merging. Essentially adding another (free!) layer of unit-tests.
Now let’s look at a concrete example of how .travis.yml looks like though the same logic can be implemented in any CI (Circle,GithubActions, Jenkins, etc…)
in .travis.yml you can see exactly the two workflows:
- fuzzing – which builds the fuzzers and uploads them to fuzzit so they can run asynchrounsly without blocking the CI.
- local regression – which run the fuzzers through all generated test-cases and the fixed crashes inline in your CI so it will fail the CI and immediately notify about the bugs.
Fuzzing is a great way to improve your code. Wanna join the fuzzing revolution? try out fuzzit for free for your OSS project.