blob: e299b9122fce0386cd493b796c84096bf785e5ca [file] [view]
# Getting started with fuzzing in Chromium
This document walks you through the basic steps to start fuzzing and suggestions
for improving your fuzz targets. If you're looking for more advanced fuzzing
topics, see the [main page](README.md).
[TOC]
## Getting started
### Setting up your build environment
Generate build files by using the `use_libfuzzer` [GN] argument together with a
sanitizer:
```bash
# AddressSanitizer is the default config we recommend testing with.
# Linux:
tools/mb/mb.py gen -m chromium.fuzz -b 'Libfuzzer Upload Linux ASan' out/libfuzzer
# Chrome OS:
tools/mb/mb.py gen -m chromium.fuzz -b 'Libfuzzer Upload Chrome OS ASan' out/libfuzzer
# Mac:
tools/mb/mb.py gen -m chromium.fuzz -b 'Libfuzzer Upload Mac ASan' out/libfuzzer
# Windows:
python tools\mb\mb.py gen -m chromium.fuzz -b "Libfuzzer Upload Windows ASan" out\libfuzzer
```
*** note
**Note:** You can also invoke [AFL] by using the `use_afl` GN argument, but we
recommend libFuzzer for local development. Running libFuzzer locally doesn't
require any special configuration and gives quick, meaningful output for speed,
coverage, and other parameters.
***
It’s possible to run fuzz targets without sanitizers, but not recommended, as
sanitizers help to detect errors which may not result in a crash otherwise.
`use_libfuzzer` is supported in the following sanitizer configurations.
| GN Argument | Description | Supported OS |
|-------------|-------------|--------------|
| `is_asan=true` | Enables [AddressSanitizer] to catch problems like buffer overruns. | Linux, Windows, Mac, Chrome OS |
| `is_msan=true` | Enables [MemorySanitizer] to catch problems like uninitialized reads<sup>\[[\*](reference.md#MSan)\]</sup>. | Linux |
| `is_ubsan_security=true` | Enables [UndefinedBehaviorSanitizer] to catch<sup>\[[\*](reference.md#UBSan)\]</sup> undefined behavior like integer overflow.| Linux |
For more on builder and sanitizer configurations, see the [Integration
Reference] page.
*** note
**Hint**: Fuzz targets are built with minimal symbols by default. You can adjust
the symbol level by setting the `symbol_level` attribute.
***
### Creating your first fuzz target
After you set up your build environment, you can create your first fuzz target:
1. In the same directory as the code you are going to fuzz (or next to the tests
for that code), create a new `<my_fuzzer>.cc` file.
*** note
**Note:** Do not use the `testing/libfuzzer/fuzzers` directory. This
directory was used for initial sample fuzz targets but is no longer
recommended for landing new targets.
***
2. In the new file, define a `LLVMFuzzerTestOneInput` function:
```cpp
#include <stddef.h>
#include <stdint.h>
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Put your fuzzing code here and use |data| and |size| as input.
return 0;
}
```
3. In `BUILD.gn` file, define a `fuzzer_test` GN target:
```python
import("//testing/libfuzzer/fuzzer_test.gni")
fuzzer_test("my_fuzzer") {
sources = [ "my_fuzzer.cc" ]
deps = [ ... ]
}
```
*** note
**Note:** Most of the targets are small. They may perform one or a few API calls
using the data provided by the fuzzing engine as an argument. However, fuzz
targets may be more complex if a certain initialization procedure needs to be
performed. [quic_stream_factory_fuzzer.cc] is a good example of a complex fuzz
target.
***
### Running the fuzz target
After you create your fuzz target, build it with ninja and run it locally:
```bash
# Build the fuzz target.
ninja -C out/libfuzzer url_parse_fuzzer
# Create an empty corpus directory.
mkdir corpus
# Run the fuzz target.
./out/libfuzzer/url_parse_fuzzer corpus
# If have other corpus directories, pass their paths as well:
./out/libfuzzer/url_parse_fuzzer corpus seed_corpus_dir_1 seed_corpus_dir_N
```
Your fuzz target should produce output like this:
```
INFO: Seed: 1511722356
INFO: Loaded 2 modules (115485 guards): 22572 [0x7fe8acddf560, 0x7fe8acdf5610), 92913 [0xaa05d0, 0xafb194),
INFO: -max_len is not provided; libFuzzer will not generate inputs larger than 4096 bytes
INFO: A corpus is not provided, starting from an empty corpus
#2 INITED cov: 961 ft: 48 corp: 1/1b exec/s: 0 rss: 48Mb
#3 NEW cov: 986 ft: 70 corp: 2/104b exec/s: 0 rss: 48Mb L: 103/103 MS: 1 InsertRepeatedBytes-
#4 NEW cov: 989 ft: 74 corp: 3/106b exec/s: 0 rss: 48Mb L: 2/103 MS: 1 InsertByte-
#6 NEW cov: 991 ft: 76 corp: 4/184b exec/s: 0 rss: 48Mb L: 78/103 MS: 2 CopyPart-InsertRepeatedBytes-
```
A `... NEW ...` line appears when libFuzzer finds new and interesting inputs. If
your fuzz target is efficient, it will find a lot of them quickly. A `... pulse
...` line appears periodically to show the current status.
For more information about the output, see [libFuzzer's output documentation].
*** note
**Note:** If you observe an `odr-violation` error in the log, please try setting
the following environment variable: `ASAN_OPTIONS=detect_odr_violation=0` and
running the fuzz target again.
***
#### Symbolizing a stacktrace
If your fuzz target crashes when running locally and you see non-symbolized
stacktrace, make sure you add the `third_party/llvm-build/Release+Asserts/bin/`
directory from Chromium’s Clang package in `$PATH`. This directory contains the
`llvm-symbolizer` binary.
Alternatively, you can set an `external_symbolizer_path` via the `ASAN_OPTIONS`
environment variable:
```bash
ASAN_OPTIONS=external_symbolizer_path=/my/local/llvm/build/llvm-symbolizer \
./fuzzer ./crash-input
```
The same approach works with other sanitizers via `MSAN_OPTIONS`,
`UBSAN_OPTIONS`, etc.
### Submitting your fuzz target
ClusterFuzz and the build infrastructure automatically discover, build and
execute all `fuzzer_test` targets in the Chromium repository. Once you land your
fuzz target, ClusterFuzz will run it at scale. Check the [ClusterFuzz status]
page after a day or two.
If you want to better understand and optimize your fuzz target’s performance,
see the [Efficient Fuzzing Guide].
*** note
**Note:** It’s important to run fuzzers at scale, not just in your own
environment, because local fuzzing will catch fewer issues. If you run fuzz
targets at scale continuously, you’ll catch regressions and improve code
coverage over time.
***
## Optional improvements
### Common tricks
Your fuzz target may immediately discover interesting (i.e. crashing) inputs.
You can make it more effective with several easy steps:
* **Create a seed corpus**. You can guide the fuzzing engine to generate more
relevant inputs by adding the `seed_corpus = "src/fuzz-testcases/"` attribute
to your fuzz target and adding example files to the appropriate directory. For
more, see the [Seed Corpus] section of the [Efficient Fuzzing Guide].
*** note
**Note:** make sure your corpus files are appropriately licensed.
***
* **Create a mutation dictionary**. You can make mutations more effective by
providing the fuzzer with a `dict = "protocol.dict"` GN attribute and a
dictionary file that contains interesting strings / byte sequences for the
target API. For more, see the [Fuzzer Dictionary] section of the [Efficient
Fuzzer Guide].
* **Specify testcase length limits**. Long inputs can be problematic, because
they are more slowly processed by the fuzz target and increase the search
space. By default, libFuzzer uses `-max_len=4096` or takes the longest
testcase in the corpus if `-max_len` is not specified.
ClusterFuzz uses different strategies for different fuzzing sessions,
including different random values. Also, ClusterFuzz uses different fuzzing
engines (e.g. AFL that doesn't have `-max_len` option). If your target has an
input length limit that you would like to *strictly enforce*, add a sanity
check to the beginning of your `LLVMFuzzerTestOneInput` function:
```cpp
if (size < kMinInputLength || size > kMaxInputLength)
return 0;
```
* **Generate a [code coverage report]**. See which code the fuzzer covered in
recent runs, so you can gauge whether it hits the important code parts or not.
**Note:** Since the code coverage of a fuzz target depends heavily on the
corpus provided when running the target, we recommend running the fuzz target
built with ASan locally for a little while (several minutes / hours) first.
This will produce some corpus, which should be used for generating a code
coverage report.
#### Disabling noisy error message logging
If the code you’re fuzzing generates a lot of error messages when encountering
incorrect or invalid data, the fuzz target will be slow and inefficient.
If the target uses Chromium logging APIs, you can silence errors by overriding
the environment used for logging in your fuzz target:
```cpp
struct Environment {
Environment() {
logging::SetMinLogLevel(logging::LOG_FATAL);
}
};
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
static Environment env;
// Put your fuzzing code here and use data+size as input.
return 0;
}
```
### Mutating Multiple Inputs
By default, a fuzzing engine such as libFuzzer mutates a single input (`uint8_t*
data, size_t size`). However, APIs often accept multiple arguments of various
types, rather than a single buffer. You can use three different methods to
mutate multiple inputs at once.
#### libprotobuf-mutator
If you need to mutate multiple inputs of various types and length, see [Getting
Started with libprotobuf-mutator in Chromium].
*** note
**Note:** This method requires extra effort, but works with APIs and data
structures of any complexity.
***
#### Hash-based argument
If your API accepts a buffer with data and some integer value (i.e., a bitwise
combination of flags), we recommend calculating a hash value from (`data, size`)
and using that value to fuzz an additional integer argument. For example:
```cpp
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
std::string str = std::string(reinterpret_cast<const char*>(data), size);
std::size_t data_hash = std::hash<std::string>()(str);
APIToBeFuzzed(data, size, data_hash);
return 0;
}
```
*** note
**Note:** The hash value derived from the data is a random value, rather than a
meaningful one controlled by the fuzzing engine. A single bit mutation might
lead to a new code coverage, but the next mutation would generate a new hash
value and trigger another code path, without providing any real guidance to the
fuzzing engine.
***
#### Bytes taken from (data, size)
You can extract one or more bytes from the data provided by fuzzing engine and
use that value for fuzzing other arguments of the target API or making other
decisions (e.g. number of iterations or attempts for calling some function).
Note that those bytes should not be used as data for any other arguments, e.g.:
```cpp
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Don't forget to enforce minimal data length.
if (size < 1)
return 0;
// Extract single byte for fuzzing "flags" value.
uint8_t flags = data[0];
// Wrong, there is a bias between flags and API input.
APIToBeFuzzed(data, size, flags);
// Good, API input and flags are independent.
APIToBeFuzzed(data + 1, size - 1, flags);
return 0;
}
```
This approach addresses the problem of the *hash-based argument* approach, but
has its own **caveats**:
* If you extract any bytes from the input (either first or last ones), you
cannot use valid samples as seed corpus. In that case, you'll have to generate
seed corpus manually, i.e. append necessary bytes to the valid sample inputs.
* Imagine that `APIToBeFuzzed()` had a bug, something like the following:
```cpp
void APIToBeFuzzed(uint8_t* buffer, size_t length, uint8_t options) {
...
if (options == 0x66) {
// Yes, looks ridiculous, but things like that did happen in the real world.
*(buffer - 1) = -1;
}
...
}
```
Assuming we used the fuzz target listed above, neither ASan nor other sanitizers
would detect a buffer underwrite vulnerability, as the byte addressed by
`buffer - 1` is actually a mapped memory allocated inside the fuzzing engine as
`data[0]`.
To avoid issues like that one, we would have to allocate a separate buffer and
copy API input into it, or use a container object e.g.:
```cpp
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
// Don't forget to enforce minimal data length.
if (size < 1)
return 0;
// Extract single byte for fuzzing flags value.
uint8_t flags = data[0];
// Put API input into a separate container.
std::vector<uint8_t> buffer(data + 1, data + size);
APIToBeFuzzed(buffer.data(), buffer.size(), flags);
return 0;
}
```
There is [FuzzedDataProvider] class that might be helpful for writing fuzz
targets using that approach. In order to use it, add
`#include <fuzzer/FuzzedDataProvider.h>` to your fuzz target source file. To
learn more about `FuzzedDataProvider`, check out the
[google/fuzzing documentation page] on it.
Note that it's not recommended to use `FuzzedDataProvider` unless you actually
need to split the fuzz input. If you need to convert the fuzz input into a
vector or string object, for example, simply initialize that object by passing
`const uint8_t* data, size_t size` to its constructor.
[AFL]: AFL_integration.md
[AddressSanitizer]: http://clang.llvm.org/docs/AddressSanitizer.html
[ClusterFuzz status]: libFuzzer_integration.md#Status-Links
[Efficient Fuzzing Guide]: efficient_fuzzing.md
[FuzzedDataProvider]: https://cs.chromium.org/chromium/src/third_party/libFuzzer/src/utils/FuzzedDataProvider.h
[Fuzzer Dictionary]: efficient_fuzzing.md#Fuzzer-dictionary
[GN]: https://gn.googlesource.com/gn/+/master/README.md
[Getting Started with libprotobuf-mutator in Chromium]: libprotobuf-mutator.md
[Integration Reference]: reference.md
[MemorySanitizer]: http://clang.llvm.org/docs/MemorySanitizer.html
[Seed Corpus]: efficient_fuzzing.md#Seed-corpus
[UndefinedBehaviorSanitizer]: http://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html
[code coverage report]: efficient_fuzzing.md#Code-coverage
[crbug/598448]: https://bugs.chromium.org/p/chromium/issues/detail?id=598448
[google/fuzzing documentation page]: https://github.com/google/fuzzing/blob/master/docs/split-inputs.md#fuzzed-data-provider
[libFuzzer's output documentation]: http://llvm.org/docs/LibFuzzer.html#output
[quic_stream_factory_fuzzer.cc]: https://cs.chromium.org/chromium/src/net/quic/quic_stream_factory_fuzzer.cc
[sanitizers]: https://github.com/google/sanitizers