OSS-Fuzz Integration Guide: Continuous Fuzzing for Open Source Projects

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.yaml

Your 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/myproject

2. 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_parser

The local build exactly mirrors what OSS-Fuzz will run. If it works locally, it works in production.

Submitting to OSS-Fuzz

  1. Fork google/oss-fuzz on GitHub
  2. Create projects/myproject/ with your three files
  3. 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:

  1. You receive an email with a report
  2. Security bugs: embargoed for 90 days, visible only to you and OSS-Fuzz
  3. Non-security bugs: embargoed for 30 days
  4. 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_input

Fixing and closing:

Once you've fixed the bug:

  1. Create a fix in your project
  2. Update your OSS-Fuzz build to use the fixed version (or update your repo)
  3. Comment on the OSS-Fuzz issue with the fix
  4. 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_parse

Python Projects

# build.sh for Python with atheris
pip3 install atheris

compile_python_fuzzer <span class="hljs-variable">$SRC/myproject/fuzz/fuzz_parse.py fuzz_parse

Rust 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:

  1. Keeping the Dockerfile updated when your build dependencies change
  2. Adding new harnesses as your codebase grows
  3. Improving seeds when coverage reports show uncovered areas
  4. 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.

Read more