How to Fuzz Rust Code with Cargo-Fuzz (Continuously)

cargo-fuzz

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

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.


Continuous Fuzzing

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.

the fuzzit.sh itself contains the bash script itself that build the fuzz targets and either uploads them to fuzzit or runs locally for regression via fuzzit open-source CLI.


Summary

Fuzzing is a great way to improve your code. Wanna join the fuzzing revolution? try out fuzzit for free for your OSS project.

Comments are closed.