OSS-Fuzz is most of the most impactful security initiatives for open source software. Over the years, it has found thousands of vulnerabilities. Continuous fuzzing isn't a silver bullet. The author of this post dissects why many bugs still exist in these projects, regardless of the large amounts of fuzzing they have done. Since they have reviewed many of these projects and found bugs, they have a good insight into this to create a process for writing good fuzz tests.
In 2024, the author of this post found 29 vulnerabilities in the default multimedia framework for GNOME desktop GStreamer. The public OSS-Fuzz statistics show that only two active fuzzers had a code coverage of 19%. For a project like OpenSSL, it has 139 fuzzers; for bzip2, it has 93% code coverage. OSS-Fuzz requires human intervention to write new fuzzers for uncovered code, and to be serious about doing so.
Poppler, the default PDF parsing library on Ubuntu, is used to render PDFs. It has 16 fuzzers and a coverage of around 60%. Kevin Backhouse found a 1-click RCE in this. How? They exploited a dependency that wasn't included in the coverage metrics. Software is only as secure as its weakest link.
Exiv2 is a library used for reading, writing, deleting and modifying metadata information from images. It's used by GIMP and LibreOffice. In 2021, the Kevin Backhouse added OSS-Fuzz support to Exiv2, finding multiple vulnerabilities along the way. In 2025, several vulnerabilities were discovered in the project; this is after 3+ years of fuzzing. The discovered vulnerabilities were not in decoding but in encoding. Since the encoding isn't looked at nearly as much for security, this led to a hole in the system.
The first step of five is to prepare the code for fuzzing. Removing checksums and reducing randomness are examples of this. Not all code is written in a way that is easily fuzzable. So, it may take time to do this. They have an entire
article already dedicated to this step.
Step 2 is increasing code coverage to above 90%. This mainly falls into two categories of work: adding new fuzzers and creating new inputs to trigger corner cases. To hit the magical 90% number, sophisticated techniques like fault injection will be needed.
Step 3 is improving context-sensitive coverage. Most fuzzers track code coverage at the edge level, where an edge is the transition between two basic blocks. This works well but has a major limitation: it doesn't cover the order in which these blocks are executed. Context-sensitive coverage not only tracks the edges executed but also the edges directly before them. This can be 2, 3, or N edges. AFL++ has support for this. With edge coverage, it's impossible to hit the 90% number though.
Step 4 is adding Value coverage. This defines the range of values a variable can take. This is necessary because an edge may have many possible states that enter into the basic block, where only a few of them have bugs. Because of the large amount of possible values, these should either be A) done on strategic values that are security sensitive or B) done in buckets of values to limit large amounts of states.
Even with these optimizations, many bugs still fit through the cracks. They have noticed that big input cases tend to slip through. This is because fuzzer inputs sizes are capped or it times out. Some vulnerabilities require a long time to trigger, such as a reference-counter integer overflow. The reality is that not all bugs can be found by fuzzers.
Fuzzing is powerful, but it is really an art rather than a fire-and-forget solution. Overall, this was a great post on the effectiveness of fuzzing and where it sometimes falls short in the real world.