This document explains how to add fuzz tests to Chromium using FuzzTest.
If you need to maintain an existing libFuzzer target, see Getting Started with libFuzzer. libFuzzer is deprecated for new fuzz targets.
You should fuzz any function that takes input from an untrusted source, such as the internet. If the code parses, decodes, or otherwise manipulates that input, you should write a fuzz test for it.
To view directories that currently lack fuzz coverage, see go/chrome-fuzzing-dashboard.
We recommend writing your fuzz tests in the same file as your standard unit tests. Open your existing unit test file and include the FuzzTest header:
#include "third_party/fuzztest/src/fuzztest/fuzztest.h"`
Create a function with assertions that verify the expected behavior. For example:
void MyApiCanSuccessfullyParseAnyString(std::string input) { bool success = MyApi(input); EXPECT_TRUE(success); }
In the same file, declare the FUZZ_TEST macro directly below your function:
FUZZ_TEST(MyApiTest, MyApiCanSuccessfullyParseAnyString);
The fuzzing infrastructure mutates the input based on code coverage to explore branches within your API. Because generating all possible strings requires infinite time, the fuzzer crafts inputs designed to maximize code coverage and find bugs efficiently.
Fuzz tests do not replace traditional unit tests. Use fuzzing as a supplementary testing strategy to help prevent unexpected security flaws.
For complex cases, specify the expected domains of valid input. For example:
void MyApiAlwaysSucceedsOnPositiveIntegers(int i) { bool success = MyApi(i); EXPECT_TRUE(success); } FUZZ_TEST(MyApiTest, MyApiAlwaysSucceedsOnPositiveIntegers) .WithDomains(/*i:*/fuzztest::Positive<int>());
For more information about configuring input domains, see the FuzzTest Domain Reference.
Now that your test is written, add it to your existing unit test target. Open your BUILD.gn file, locate your existing test target, and add the fuzztests list containing the name of your new test.
import("//testing/test.gni") test("my_component_unittests") { sources = [ "my_component_unittest.cc", # Your FUZZ_TEST macros are in this file. ] # Add your FuzzTest names here: fuzztests = [ "MyApiTest.MyApiCanSuccessfullyParseAnyString" ] deps = [ ":my_component", # Chromium unit tests usually already include this dependency. # It provides the standard test main() that initializes FuzzTest. "//base/test:test_support", ] }
Because Chromium's test template automatically injects FuzzTest dependencies and initializes the framework, you do not need to add any specific FuzzTest libraries to your deps.
If your component's directory enforces include rules, you must add +third_party/fuzztest to your local DEPS file so that your C++ files can successfully include the FuzzTest headers.
For small seed lists or dictionaries, prefer inline literals (such as .WithSeeds({...}) or fuzztest::String().WithDictionary({...})), which require no file packaging.
To load on-disk seed files (fuzztest::ReadFilesFromDirectory) or a dictionary (fuzztest::ReadDictionaryFromFile), resolve paths from base::DIR_SRC_TEST_DATA_ROOT inside helper functions:
#include "base/base_paths.h" #include "base/files/file_path.h" #include "base/path_service.h" std::vector<std::string> ReadMyDictionary() { return fuzztest::ReadDictionaryFromFile( base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT) .AppendASCII("my_component/fuzzer.dict") .AsUTF8Unsafe()); } std::vector<std::tuple<std::string>> ReadMySeeds() { return fuzztest::ReadFilesFromDirectory( base::PathService::CheckedGet(base::DIR_SRC_TEST_DATA_ROOT) .AppendASCII("my_component/fuzz_corpus") .AsUTF8Unsafe()); } void ParseDoesNotCrash(const std::string& input) { MyApi(input); } FUZZ_TEST(MyApiTest, ParseDoesNotCrash) .WithDomains(fuzztest::String().WithDictionary(ReadMyDictionary)) .WithSeeds(ReadMySeeds);
Keep two cross-platform rules in mind:
FUZZ_TEST registration runs during C++ static initialization before main()—before the Android test launcher configures DIR_SRC_TEST_DATA_ROOT. Passing a function pointer defers file loading until test setup.data) and iOS (bundle_data): Add data = [ "fuzz_corpus/", "fuzzer.dict" ] to your target in BUILD.gn for desktop, Android, and Fuchsia. On iOS, DIR_SRC_TEST_DATA_ROOT resolves to the .app bundle root and GN data entries are not copied into .app bundles. If your unit test suite runs on iOS (such as base_unittests, net_unittests, components_unittests, content_unittests, or blink_platform_unittests), also package the files into the bundle:bundle_data (such as base_unittests_bundle_data) or a new bundle_data target with outputs = [ "{{bundle_resources_dir}}/{{source_root_relative_dir}}/{{source_file_part}}" ]..globlist (such as net/data/test_bundle_data.globlist or per-component lists in components/test/BUILD.gn), add the glob and regenerate its .filelist using build/ios/update_bundle_filelist.py.if (is_ios), import //build/config/ios/bundle_data_from_filelist.gni, define a bundle_data_from_filelist target and add it to deps, and add presubmit_support.CheckBundleData in PRESUBMIT.py.In a local terminal, build and run your unit tests. If your platform supports FuzzTest, the test runs for one second and produces the following output:
[==========] Running 1 test from 1 test suite. [----------] Global test environment set-up. [----------] 1 test from ScaleFuzz [ RUN ] ApiTest.MyApiCanSuccessfullyParseAnyString [ OK ] ApiTest.MyApiCanSuccessfullyParseAnyString (1000 ms) [----------] 1 test from ScaleFuzz (1000 ms total) [----------] Global test environment tear-down [==========] 1 test from 1 test suite ran. (1000 ms total) [ PASSED ] 1 test.
On unsupported platforms, the system ignores the test.
To perform continuous fuzzing with FuzzTest locally, add the following to your gn arguments:
enable_fuzztest_fuzz=true is_component_build=false
Run your unit test and append the --fuzz= argument. You can optionally specify a target test name by using --fuzz=TEST_NAME. The command produces the following output as it explores your code:
[*] Corpus size: 1 | Edges covered: 73 | Fuzzing time: 1.60482ms | Total runs: 1.00e+00 | Runs/secs: 623 | Max stack usage: 0 [*] Corpus size: 2 | Edges covered: 103 | Fuzzing time: 1.844ms | Total runs: 2.00e+00 | Runs/secs: 1084 | Max stack usage: 0 [*] Corpus size: 3 | Edges covered: 111 | Fuzzing time: 2.747931ms | Total runs: 3.00e+00 | Runs/secs: 1091 | Max stack usage: 0 [*] Corpus size: 4 | Edges covered: 135 | Fuzzing time: 2.92305ms | Total runs: 4.00e+00 | Runs/secs: 1368 | Max stack usage: 0 [*] Corpus size: 5 | Edges covered: 173 | Fuzzing time: 3.35237ms | Total runs: 5.00e+00 | Runs/secs: 1491 | Max stack usage: 0 [*] Corpus size: 6 | Edges covered: 178 | Fuzzing time: 4.15666ms | Total runs: 6.00e+00 | Runs/secs: 1443 | Max stack usage: 0
The Edges covered metric indicates the number of unique code blocks the fuzzer explores. Over time, the fuzzer explores more blocks until it exhausts new edges.
Submit your changelist by using standard Chromium contribution processes.
ClusterFuzz typically begins running your new fuzzer within two days. To view the fuzzer, go to ClusterFuzz Fuzzer Stats and search for your test target's name. FuzzTest targets typically use the centipede_ or libfuzzer_ prefix in the dashboard.
Thanks very much for doing your part in making Chromium more secure!
If FuzzTest and libFuzzer do not meet your requirements, you can explore advanced options, such as uploading blackbox fuzzers to ClusterFuzz or running external fuzzers that upload results to ClusterFuzz for triage. To discuss these options, contact the fuzzing team at chrome-fuzzing-core@google.com.