| # Getting started with fuzzing in Chromium | |
| 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. | |
| [TOC] | |
| ## What to fuzz | |
| 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]. | |
| ## Write the FuzzTest code | |
| 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: | |
| ```c++ | |
| #include "third_party/fuzztest/src/fuzztest/fuzztest.h"` | |
| ``` | |
| Create a function with assertions that verify the expected behavior. For example: | |
| ```c++ | |
| 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: | |
| ```c++ | |
| 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: | |
| ```c++ | |
| 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]. | |
| ## Configure the GN build target | |
| 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. | |
| ```gn | |
| 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`. | |
| ## Update your DEPS file | |
| 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. | |
| ## Seed corpora and dictionaries | |
| 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: | |
| ```c++ | |
| #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: | |
| 1. **Pass a function pointer (do not call file readers inline):** | |
| `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. | |
| 2. **Package files for both Swarming (`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: | |
| * For single files, add the file to the suite's `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}}" ]`. | |
| * For corpus directories in suites with an existing `.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`. | |
| * Otherwise, under `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`. | |
| ## Run the FuzzTest locally | |
| 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: | |
| ```shell | |
| [==========] 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: | |
| ```gn | |
| 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: | |
| ```shell | |
| [*] 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 the Fuzztest | |
| 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! | |
| ## Get help with advanced cases | |
| 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. | |
| [ClusterFuzz]: https://clusterfuzz.com/ | |
| [FuzzTest Domain Reference]: https://github.com/google/fuzztest/blob/main/doc/domains-reference.md | |
| [ClusterFuzz Fuzzer Stats]: https://clusterfuzz.com/fuzzer-stats/by-fuzzer/fuzzer/libFuzzer/job/libfuzzer_chrome_asan | |
| [fuzzer builders]: https://ci.chromium.org/p/chromium/g/chromium.fuzz/console | |
| [FuzzTest]: https://github.com/google/fuzztest | |
| [Getting Started with libFuzzer]: getting_started_with_libfuzzer.md | |
| [go/chrome-fuzzing-dashboard]: https://analysis.chromium.org/coverage/p/chromium?platform=fuzz | |
| [`test` template]: https://source.chromium.org/chromium/chromium/src/+/main:testing/test.gni?q=test.gni |