At a high-level, test-fuzz is a convenient front end for afl.rs. In more concrete terms, test-fuzz is a collection of Rust macros and a Cargo subcommand that automate certain fuzzing-related tasks, most notably:
test-fuzz accomplishes these (in part) using Rust's testing facilities. For example, to generate a fuzzing corpus, test-fuzz records a target's arguments each time it is called during an invocation of cargo test. Similarly, test-fuzz implements a fuzzing harness as an additional test in a cargo-test-generated binary. This tight integration with Rust's testing facilities is what motivates the name test-fuzz.
Contents
test-fuzz package featuresInstall cargo-test-fuzz and afl.rs with the following command:
sh
cargo install cargo-test-fuzz afl
Fuzzing with test-fuzz is essentially three steps:*
Identify a fuzz target:
dependencies to the target crate's Cargo.toml file:
toml
serde = "1.0"
test-fuzz = "4.0"
test_fuzz macro:
rust
#[test_fuzz::test_fuzz]
fn foo(...) {
...
}
Generate a corpus by running cargo test:
$ cargo test
Fuzz your target by running cargo test-fuzz:
$ cargo test-fuzz foo
* Some additional steps may be necessary following a reboot. AFL requires the following commands to be run as root:
Linux
sh
echo core >/proc/sys/kernel/core_pattern
cd /sys/devices/system/cpu
echo performance | tee cpu*/cpufreq/scaling_governor
OSX
sh
SL=/System/Library; PL=com.apple.ReportCrash
launchctl unload -w ${SL}/LaunchAgents/${PL}.plist
sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist
test_fuzz macroPreceding a function with the test_fuzz macro indicates that the function is a fuzz target.
The primary effects of the test_fuzz macro are:
#[cfg(test)] so that corpus files are generated only when running tests (however, see enable_in_production below).cargo test-fuzz, so that the test does not block trying to read from standard input during a normal invocation of cargo test. The test is enclosed in a module to reduce the likelihood of a name collision. Currently, the name of the module is target_fuzz, where target is the name of the target (however, see rename below).bounds = "where_predicates" - Impose where_predicates (e.g., trait bounds) on the struct used to serialize/deserialize arguments. This may be necessary, e.g., if a target's argument type is an associated type. For an example, see associated_type.rs in this repository.
concretize = "parameters" - Use parameters as the target's type parameters when fuzzing. Example:
```rust
fn foo
Note: The target's arguments must be serializable for every instantiation of its type parameters. But the target's arguments are required to be deserializable only when the target is instantiated with parameters.
concretize_impl = "parameters" - Use parameters as the target's Self type parameters when fuzzing. Example:
```rust
impl
Note: The target's arguments must be serializable for every instantiation of its Self type parameters. But the target's arguments are required to be deserializable only when the target's Self is instantiated with parameters.
convert = "X, Y" - When serializing the target's arguments, convert values of type X to type Y using Y's implementation of From<X>, or of type &X to type Y using Y's implementation of the non-standard trait test_fuzz::FromRef<X>. When deserializing, convert those values back to type X using Y's implementation of the non-standard trait test_fuzz::Into<X>.
That is, use of convert = "X, Y" must be accompanied by certain implementations. If X implements Clone, then Y may implement the following:
rust
impl From<X> for Y {
fn from(x: X) -> Self {
...
}
}
If X does not implement Clone, then Y must implement the following:
rust
impl test_fuzz::FromRef<X> for Y {
fn from_ref(x: &X) -> Self {
...
}
}
Additionally, Y must implement the following (regardless of whether X implements Clone):
rust
impl test_fuzz::Into<X> for Y {
fn into(self) -> X {
...
}
}
The definition of test_fuzz::Into is identical to that of std::convert::Into. The reason for using a non-standard trait is to avoid conflicts that could arise from blanket implementations of standard traits.
enable_in_production - Generate corpus files when not running tests, provided the environment variable TEST_FUZZ_WRITE is set. The default is to generate corpus files only when running tests, regardless of whether TEST_FUZZ_WRITE is set. When running a target from outside its package directory, set TEST_FUZZ_MANIFEST_PATH to the path of the package's Cargo.toml file.
WARNING: Setting enable_in_production could introduce a denial-of-service vector. For example, setting this option for a function that is called many times with different arguments could fill up the disk. The check of TEST_FUZZ_WRITE is meant to provide some defense against this possibility. Nonetheless, consider this option carefully before using it.
execute_with = "function" - Rather than call the target directly:
FnOnce() -> R, where R is the target's return type, so that calling the closure calls the target;function with the closure.Calling the target in this way allows function to set up the call's environment. This can be useful, e.g., for fuzzing Substrate externalities.
no_auto_generate - Do not try to auto-generate corpus files for the target.
only_concretizations - Record the target's concretizations when running tests, but do not generate corpus files and do not implement a fuzzing harness. This can be useful when the target is a generic function, but it is unclear what type parameters should be used for fuzzing.
The intended workflow is: enable only_concretizations, then run cargo test followed by cargo test-fuzz --display-concretizations. One of the resulting concretizations might be usable as concretize's parameters. Similarly, a concretization resulting from cargo test-fuzz --display-imply-concretizations might be usable as concretize_impl's parameters.
Note, however, that just because a target was concretized with certain parameters during tests, it does not imply the target's arguments are serializable/deserializable when so concretized. The results of --display-concretizations/--display-impl-concretizations are merely suggestive.
rename = "name" - Treat the target as though its name is name when adding a module to the enclosing scope. Expansion of the test_fuzz macro adds a module definition to the enclosing scope. Currently, the module is named target_fuzz, where target is the name of the target. Use of this option causes the module to instead be be named name_fuzz. Example:
```rust
fn foo() {}
// Without the use of rename, a name collision and compile error would result.
mod foo_fuzz {}
```
test_fuzz_impl macrocargo test-fuzz command