OSS-Fuzz Integration Guide: Continuous Fuzzing for Open Source Projects
OSS-Fuzz is Google's free continuous fuzzing service for open source software. Once integrated, your project gets fuzzed 24/7 on Google's infrastructure using libFuzzer, AFL++, and Honggfuzz. Crashes are automatically deduplicated, tracked, and reported. As of 2024, OSS-Fuzz has found over 10,000 vulnerabilities in 1,000+ projects.
What OSS-Fuzz Provides
- Continuous fuzzing on thousands of cores, running indefinitely
- Multiple fuzzing engines: libFuzzer, AFL++, Honggfuzz — all in parallel
- Sanitizer coverage: ASan, UBSan, MSan, ThreadSanitizer
- Automatic deduplication: Similar crashes are grouped
- Bug tracking: Automated filing of issues when crashes are found
- Disclosure policy: Security bugs are embargoed for 90 days; non-security bugs for 30 days
- Integration dashboard: View coverage, crash counts, and bug status
Requirements
- Open source project (publicly accessible repository)
- Build system that works in Docker
- At least one libFuzzer or AFL harness function
Project Structure
OSS-Fuzz stores project configurations in the google/oss-fuzz repository:
oss-fuzz/projects/
myproject/
Dockerfile
build.sh
project.yamlYour fuzzing harnesses live in your project's own repository.
Writing Fuzzing Harnesses
Before submitting to OSS-Fuzz, write harnesses locally.
C/C++ harness:
// fuzz/parse_input_fuzzer.cc
#include <stdint.h>
#include <stdlib.h>
#include "mylib/parser.h"
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
// Exercise the parsing code
ParserContext ctx;
ctx.Parse(data, size);
return 0;
}Go harness:
// fuzz/fuzz_parse.go
package fuzz
import "github.com/org/mylib/parser"
func FuzzParse(data []byte) int {
p := parser.New()
_, err := p.Parse(data)
if err != nil {
return 0
}
return 1
}Python harness (via atheris):
# fuzz/fuzz_parse.py
import sys
import atheris
from mylib import parse
def TestOneInput(data):
fdp = atheris.FuzzedDataProvider(data)
input_str = fdp.ConsumeUnicodeNoSurrogates(256)
try:
parse(input_str)
except ValueError:
pass
if __name__ == "__main__":
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()Creating the OSS-Fuzz Configuration
1. Dockerfile
# projects/myproject/Dockerfile
FROM gcr.io/oss-fuzz-base/base-builder
# Install build dependencies
RUN apt-get update && apt-get install -y cmake libssl-dev
# Clone your project
RUN git clone --depth 1 https://github.com/org/myproject.git $SRC/myproject
# Copy the build script
COPY build.sh $SRC/
WORKDIR $SRC/myproject2. build.sh
#!/bin/bash -eu
<span class="hljs-comment"># projects/myproject/build.sh
<span class="hljs-built_in">cd <span class="hljs-variable">$SRC/myproject
<span class="hljs-comment"># Build with OSS-Fuzz's provided flags
<span class="hljs-built_in">mkdir build && <span class="hljs-built_in">cd build
cmake .. \
-DCMAKE_CXX_COMPILER=<span class="hljs-variable">$CXX \
-DCMAKE_C_COMPILER=<span class="hljs-variable">$CC \
-DCMAKE_CXX_FLAGS=<span class="hljs-string">"$CXXFLAGS" \
-DCMAKE_C_FLAGS=<span class="hljs-string">"$CFLAGS" \
-DBUILD_FUZZING=ON
make -j$(<span class="hljs-built_in">nproc)
<span class="hljs-comment"># Copy fuzzer binaries to $OUT
<span class="hljs-keyword">for fuzzer <span class="hljs-keyword">in fuzz_parser fuzz_decoder fuzz_encoder; <span class="hljs-keyword">do
<span class="hljs-built_in">cp build/fuzzers/<span class="hljs-variable">$fuzzer <span class="hljs-variable">$OUT/
<span class="hljs-keyword">done
<span class="hljs-comment"># Copy seed corpus (as a zip named {fuzzer}_seed_corpus.zip)
zip -r <span class="hljs-variable">$OUT/fuzz_parser_seed_corpus.zip fuzz/corpus/parser/
zip -r <span class="hljs-variable">$OUT/fuzz_decoder_seed_corpus.zip fuzz/corpus/decoder/3. project.yaml
# projects/myproject/project.yaml
homepage: "https://github.com/org/myproject"
language: c++
# Security contact for embargoed bug reports
primary_contact: "security@example.com"
# Additional CCs for non-security findings
auto_ccs:
- "maintainer@example.com"
# Optional: coverage reporting config
coverage_extra_args: "-ignore_filename_regex=.*/fuzz/.*"Testing Locally with OSS-Fuzz
Before submitting, test your build locally:
# Clone OSS-Fuzz
git <span class="hljs-built_in">clone https://github.com/google/oss-fuzz
<span class="hljs-built_in">cd oss-fuzz
<span class="hljs-comment"># Build your project's Docker image
python3 infra/helper.py build_image myproject
<span class="hljs-comment"># Build the fuzzers
python3 infra/helper.py build_fuzzers myproject
<span class="hljs-comment"># Run one fuzzer for 60 seconds
python3 infra/helper.py run_fuzzer myproject fuzz_parser -- -max_total_time=60
<span class="hljs-comment"># Reproduce a specific crash
python3 infra/helper.py reproduce myproject fuzz_parser /path/to/crash
<span class="hljs-comment"># Check coverage
python3 infra/helper.py coverage myproject --fuzz-target=fuzz_parserThe local build exactly mirrors what OSS-Fuzz will run. If it works locally, it works in production.
Submitting to OSS-Fuzz
- Fork
google/oss-fuzzon GitHub - Create
projects/myproject/with your three files - Submit a pull request
The OSS-Fuzz team reviews the PR and runs a build test. Once merged, your project is added to the continuous fuzzing queue.
What the OSS-Fuzz team checks:
- Build succeeds in the Docker environment
- At least one fuzzer binary is produced
- No obvious issues with the configuration
Handling OSS-Fuzz Findings
When OSS-Fuzz finds a bug:
- You receive an email with a report
- Security bugs: embargoed for 90 days, visible only to you and OSS-Fuzz
- Non-security bugs: embargoed for 30 days
- After embargo: the bug is made public in the OSS-Fuzz issue tracker
Reproducing a bug from the report:
# Download the crashing input from the report
<span class="hljs-comment"># Reproduce locally
./fuzz_parser path/to/crash_input
<span class="hljs-comment"># Or with OSS-Fuzz tools
python3 infra/helper.py reproduce myproject fuzz_parser path/to/crash_inputFixing and closing:
Once you've fixed the bug:
- Create a fix in your project
- Update your OSS-Fuzz build to use the fixed version (or update your repo)
- Comment on the OSS-Fuzz issue with the fix
- OSS-Fuzz verifies the fix and closes the issue
Language-Specific Notes
Go Projects
Go fuzzing uses a different harness format:
// fuzz/fuzz_parse_test.go
package fuzz
import "github.com/org/mylib"
func FuzzParse(f *testing.F) {
f.Add([]byte("example input"))
f.Fuzz(func(t *testing.T, data []byte) {
mylib.Parse(data)
})
}# build.sh for Go
<span class="hljs-built_in">cd <span class="hljs-variable">$SRC/myproject
go build ./...
<span class="hljs-comment"># For Go native fuzzing
compile_go_fuzzer github.com/org/myproject/fuzz FuzzParse fuzz_parsePython Projects
# build.sh for Python with atheris
pip3 install atheris
compile_python_fuzzer <span class="hljs-variable">$SRC/myproject/fuzz/fuzz_parse.py fuzz_parseRust Projects
// fuzz/fuzz_targets/fuzz_parse.rs
#![no_main]
use libfuzzer_sys::fuzz_target;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let _ = mylib::parse(s);
}
});# build.sh for Rust
<span class="hljs-built_in">cd <span class="hljs-variable">$SRC/myproject
cargo fuzz build --release
<span class="hljs-built_in">cp fuzz/target/x86_64-unknown-linux-gnu/release/fuzz_parse <span class="hljs-variable">$OUT/Coverage Reporting
OSS-Fuzz generates coverage reports for each project. These show which code paths the fuzzer has reached.
Access at: https://oss-fuzz.com/coverage (requires project access)
Low coverage on a code path means the fuzzer hasn't reached it. Improve coverage by:
- Adding seeds that exercise that path
- Writing a more targeted harness
- Adding a custom mutator
Maintaining Your Integration
After initial integration, maintenance involves:
- Keeping the Dockerfile updated when your build dependencies change
- Adding new harnesses as your codebase grows
- Improving seeds when coverage reports show uncovered areas
- Responding to findings within the embargo window
A stale OSS-Fuzz integration that doesn't build is worse than no integration — it consumes project slots without producing results. Check your project's build status at https://oss-fuzz.com/build-status periodically.
For Private Projects
OSS-Fuzz is public-only. For private projects, the equivalent is:
- ClusterFuzz: Google's open-source fuzzing infrastructure that powers OSS-Fuzz, self-hostable
- FuzzBench: Google's fuzzer benchmarking service
- GitHub Actions / CI: Run libFuzzer or AFL directly in CI with time-limited runs
- Commercial services: Several companies offer hosted fuzzing for private repos
The tooling (libFuzzer, AFL, harness writing) is identical regardless of whether you use OSS-Fuzz or a self-hosted setup.