Integrating fuzzing into your open source project with OSS-Fuzz

OSS-Fuzz is a free service that continuously runs fuzzers for open source projects.
36 readers like this
36 readers like this
magnifying glass on computer screen, finding a bug in the code

Opensource.com

OSS-Fuzz is a free service that continuously runs fuzzers for open source projects. This GitHub repository manages the service and enrolling in it is handled by pull requests.

Once a project has integrated with OSS-Fuzz, the fuzzers affiliated with that project run daily—continuously and indefinitely. OSS-Fuzz emails maintainers when a bug is found and also has a dashboard with details about all issues found (stack traces, artifacts for reproducing issues, and so on).

The benefits of integrating with OSS-Fuzz are that most aspects of managing fuzzer execution and analyzing the results are done by OSS-Fuzz itself. This is important in fuzzing because fuzzers build up a historical profile over time, meaning that continuous analysis is essential to maximize the results. On one project, which we detail in a blog post, fuzzing had been run on just an ad hoc basis for months, with no reports of any specific issue. However, after integration with OSS-Fuzz, the service reported an issue within about a week of continuous execution. In this case, a severe security issue was only discovered because of the continuous analysis done by OSS-Fuzz.

Which projects can integrate into OSS-fuzz?

To qualify for integration, an open source project must serve a critical purpose to global infrastructure. This usually means larger user groups rely on the project or other essential open source projects depend on it. The verdict on whether a project is security-critical is made on a project-by-project basis by the OSS-Fuzz maintainers. To find out whether the maintainers will accept your project, make a pull request to integrate your project, and they will let you know through the PR if the project is accepted or not.

Furthermore, you must write the project in one or more of the supported languages by OSS-Fuzz. At the time of writing, these are C, C++, Go, Rust, Python, Java, and Swift.

OSS-Fuzz manages fuzzing of more than 500 projects at this stage, including Kubernetes, Istio, Envoy, VLC, OpenSSL, Containerd, Binutils, Spidermonkey, and systemd.

Integrate a project into OSS-Fuzz

The two main ingredients for integrating a project into OSS-Fuzz are a set of fuzzers that can analyze your open source project and the required infrastructure glue to build your fuzzers in the OSS-Fuzz environment.

Create a set of fuzzers

Creating a set of fuzzers for a given open source project largely depends on the project itself. Here's a simple example, demonstrating a self-contained library in a single file called char_lib.c, that exposes a single function:

// Count the number of lowercase letters
// input must be a null-terminated string.
int count_lowercase_letters(char *input);

Write a simple fuzzer for this library:

int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  // wrap input in null-terminated string
  char *ns = malloc(size+1);
  memcpy(ns, data, size);
  ns[size] = '\0';
 
  count_lowercase_letters(ns);
 
  free(ns);
}

Place this function in a file called fuzz_char_lib.c at the root of the Git repository. At this point, you can fuzz your library by compiling char_lib.c and fuzz_char_lib.c:

clang -fsanitize=fuzzer-no-link char_lib.c -c -o char_lib.o
clang -fsanitize=fuzzer-no-link fuzz_char_lib.c -c -o fuzz_char_lib.o
clang -fsanitize=fuzzer fuzz_char_lib.o char_lib.o -o fuzzer

Finally, run the fuzzer:

$ ./fuzzer

OSS-Fuzz infrastructure setup

To integrate the sample library into OSS-Fuzz, you must create three files: project.yaml, Dockerfile, and build.sh. These are the required components to create a project folder on OSS-Fuzz (see this Kubernetes integration as a detailed example.)

Project.yaml

This YAML file holds management data about the project. The two most important parts of the file are the list of contacts (primary_contact and auto_ccs) and the project's programming language. Here is the file:

homepage: "https://github.com/AdaLogics/oss-fuzz-example"
main_repo
: 'https://github.com/AdaLogics/oss-fuzz-example'
primary_contact
: "adam@example.com"
auto_ccs
:
 - "david@example.com"
language
: c

Dockerfile

The Dockerfile is responsible for building a container that downloads the relevant source repositories and downloads and configures dependencies. OSS-Fuzz performs several builds to accommodate different sanitizers and various fuzz engines, so the container only creates a build script that can build the project but doesn't run the build script itself. Here is a sample Dockerfile:

FROM gcr.io/oss-fuzz-base/base-builder

RUN git clone https://github.com/AdaLogics/oss-fuzz-example
COPY build.sh $SRC/build.sh
WORKDIR $SRC/oss-fuzz-example

Build.sh

The build.sh script builds the project. It needs to use some specific environment variables for the compiler and compiler flags rather than specifying the compiler because OSS-Fuzz builds each project in many different ways (different fuzzers, different flags, and so on). The most important environment variables are CC, CXX, CFLAGS, CXXFLAGS, and LIB_FUZZING_ENGINE. The first four of these flags are common compiler variables. However, LIB_FUZZING_ENGINE is a flag that must be used in the linking step of a fuzzer build.

$CC $CFLAGS char_lib.c -c -o char_lib.o
$CC $CFLAGS fuzz_char_lib.c -c -o fuzz_char_lib.o
$CC $LIB_FUZZING_ENGINE fuzz_char_lib.o char_lib.o -o $OUT/simple-fuzzer

The OSS-Fuzz run-time environment doesn't run the fuzzers from within the container, and for this reason, the binaries must be statically linked to most of its dependencies. The build script must place the fuzzing binaries in the directory defined by the OUT environment variable.

Place these three scripts in a folder called oss-fuzz-example within the projects folder in the OSS-Fuzz repository, and then you're ready to test the OSS-Fuzz integration.

Testing OSS-Fuzz integration

To test the OSS-Fuzz integration, use the infra/helper.py script from the OSS-Fuzz repository. This script accepts various commands. The most important commands for this example are build_fuzzers, run_fuzzer, and check_build. These commands are all you need to test the integration.

These commands do the following:

  • build_fuzzers builds the necessary containers for the project and runs the build.sh script from within these containers.
  • run_fuzzer runs a given fuzzer for a given project. This command must run after build_fuzzers.
  • check_build performs various checks to ensure the setup works properly. This check must be passed for the CI of OSS-Fuzz to succeed.

The best way to visualize these commands is to run them in series.

First, clone both repositories:

$ git clone https://github.com/AdaLogics/oss-fuzz-example
$ git clone https://github.com/google/oss-fuzz

Next, make a project directory in the OSS-Fuzz repo:

$ mkdir oss-fuzz/projects/oss-fuzz-example

Copy over OSS-Fuzz artifacts to the directory:

$ cp oss-fuzz-example/oss-fuzz-example/Dockerfile oss-fuzz/projects/oss-fuzz-example/Dockerfile
$ cp oss-fuzz-example/oss-fuzz-example/build.sh oss-fuzz/projects/oss-fuzz-example/build.sh
$ cp oss-fuzz-example/oss-fuzz-example/project.yaml oss-fuzz/projects/oss-fuzz-example/project.yaml

Then navigate into OSS-Fuzz and build the fuzzers:

$ cd oss-fuzz
$ python3 infra/helper.py build_fuzzers oss-fuzz-example

Now run the fuzzer. Use CTRL+C to exit this step:

$ python3 infra/helper.py run_fuzzer oss-fuzz-example simple-fuzzer

Check the build:

$ python3 infra/helper.py check_build oss-fuzz-example


INFO:root:Check build passed.

Submit OSS-Fuzz integration

At this stage, you have all the artifacts needed for integration. The remaining step before completing the integration is to make a pull request on the OSS-Fuzz repository with the OSS-Fuzz artifacts; specifically, the Dockerfile, build.sh, and project.yaml files.

If the maintainers approve that pull request, the integration is complete, and OSS-Fuzz starts running fuzzers continuously. It reports the progress on a dashboard. In addition, if OSS-Fuzz finds bugs in your library, it emails a detailed report to the list of maintainers in the primary_contact and auto_ccs fields of the project.yaml file, e.g., stack trace and issues found.

The importance of fuzzing

The proof-of-concept integration is a minimal example. A more complex project can be difficult to configure for fuzzing, but continuous fuzzing pays dividends over time.

There are many reasons why a project should integrate with OSS-Fuzz. It's a free service with significant support behind it, and many projects have benefited from it tremendously. As of January 2022, OSS-Fuzz has found over 36,000 bugs in 550 open source projects.

Integrating fuzzing into a project is time-consuming. Be prepared to devote effort to this, and do not expect a mature integration to happen overnight. Many projects have benefited from fuzzing and OSS-Fuzz, but most have invested many hours to ensure the integration and fuzzing setup functions well. It's hard work, but it's well worth it.

Happy OSS-Fuzz integration and happy bug hunting!

What to read next
Adam is a security researcher at Ada Logics where he leads many of their fuzzing efforts. He is passionate about fuzzing the open source eco system where he has contributed to projects like Kubernetes, Containerd, Runc, Istio, VLC, Vitess, Ethereum, Argo, Etcd and many more.
David is a security researcher at Ada Logics. His focus is on security automation and program analysis techniques, including fuzzing, symbolic execution and static analysis techniques. To read more about his works, please visit https://adalogics.com

Comments are closed.

Creative Commons LicenseThis work is licensed under a Creative Commons Attribution-Share Alike 4.0 International License.