Our latest addition to the Trail of Bits Testing Handbook is a comprehensive guide to fuzzing: an essential, effective, low-effort method to find bugs in software that involves repeatedly running a program with random inputs to cause unexpected results.

At Trail of Bits, we don’t just rely on standard static analysis. We tailor our approach to each project, fine-tuning our methods to rigorously fuzz critical code segments. We’ve seen how challenging it can be to start with fuzzing; it’s a field with diverse methodologies and no one-size-fits-all solution. We believe that distilling our knowledge into this handbook will help those seeking to integrate fuzzing into their methodology do so quickly and easily, with better results.

Designed for developers eager to integrate fuzzing into their workflow, this chapter demystifies the fuzzing process. Within a jungle of fuzzer forks, each with numerous variations, it’s easy to get lost. Our guide focuses on the most proven and widely used fuzzers, providing a solid foundation to get you results.

This chapter focuses on how to fuzz C/C++ and Rust projects. We describe how to install and start using three of the most mature fuzzers commonly used for C/C++ and Rust projects: libFuzzer, AFL++, and cargo-fuzz. We discuss common challenges when fuzzing, using an example C/C++ project. One of the challenges of starting your fuzzing is that there is no uniform way to set up fuzzing; some developers use CMake, while others use Autotools or plain Makefiles. We will also go through several real-world examples that use different build systems to demonstrate how to fuzz real projects.

For every language and technology stack, and throughout the chapter, we will show you how to discover the following exemplary bug using each of the discussed fuzzers.

void check_buf(char *buf, size_t buf_len) {
    if(buf_len > 0 && buf[0] == 'a') {
        if(buf_len > 1 && buf[1] == 'b') {
            if(buf_len > 2 && buf[2] == 'c') {
                abort();
            }
        }
    }
}

We also describe more advanced techniques, like using AddressSanitizer, a memory sanitizer that detects memory corruption bugs, with each fuzzer. We also detail how to use fuzzing dictionaries efficiently, and how to write good fuzzing harnesses.

Our goal is to continuously update the handbook—including this chapter— so that it remains a key resource for security practitioners and developers in configuring, deploying, and automating the tools we use at Trail of Bits. We plan on keeping this chapter updated to reflect future changes to the fuzzing ecosystem and to include the most advanced fuzzing techniques.