
test-fuzz は、afl.rs を使ったファジングに関連する特定のタスクを自動化するための Cargo サブコマンドおよび Rust マクロのコレクションです。具体的には以下を含みます:
test-fuzz は、これらのタスクを部分的に Rust のテスト機能を利用して実現します。たとえば、ファジングコーパスを生成するために、test-fuzz は cargo test の実行中に対象関数が呼び出されるたびに、その引数を記録します。同様に、test-fuzz はファジングハーネスを、cargo-test が生成するバイナリ内の追加テストとして実装します。Rust のテスト機能とのこの緊密な統合が、test-fuzz という名前の由来です。
目次
test_fuzz マクロ]test_fuzz_impl マクロ]cargo test-fuzz コマンド]test-fuzz パッケージの機能]次のコマンドで cargo-test-fuzz と afl.rs をインストールします:```sh
cargo install cargo-test-fuzz cargo-afl
## Overview
`test-fuzz` を使ったファジングは基本的に3つのステップです:\*
1. **ファズターゲットを特定する**:
- ターゲットクレートの `Cargo.toml` ファイルに以下の `dependencies` を追加します:
```toml
serde = "*"
test-fuzz = "*"
```
- ターゲット関数の前に [`test_fuzz`] マクロを置きます:
```rust
#[test_fuzz::test_fuzz]
fn foo(...) {
...
}
```
2. **コーパスを生成する**: `cargo test` を実行します: ```
cargo test
cargo test-fuzz を実行します: ```
cargo test-fuzz foo
* 再起動後には、追加の予備的な手順が必要になる場合があります:```sh cargo afl system-config
上記のコマンドは内部で `sudo` を実行することに注意してください。そのため、パスワードの入力を求められる場合があります。
## Components
### `test_fuzz` macro
関数の前に `test_fuzz` マクロを付けると、その関数がファズターゲットであることを示します。
`test_fuzz` マクロの主な効果は次のとおりです。
- ターゲットに計測を追加し、ターゲットが呼び出されるたびにその引数をシリアライズしてコーパスファイルに書き込みます。この計測は `#[cfg(test)]` でガードされているため、コーパスファイルはテスト実行時のみ生成されます(ただし、下記の [`enable_in_production`] を参照)。
- 標準入力から引数を読み取ってデシリアライズし、ターゲットに適用するテストを追加します。このテストは [`cargo test-fuzz`] によって設定される環境変数をチェックするため、通常の `cargo test` 実行時には標準入力からの読み取りを待ってブロックすることはありません。このテストは名前の衝突の可能性を減らすためにモジュール内に囲まれています。現在、モジュール名は `target_fuzz` です。ここで `target` はターゲットの名前です(ただし、下記の [`rename`] を参照)。
#### Arguments
##### `bounds = "where_predicates"`
引数のシリアライズ/デシリアライズに使用される構造体に `where_predicates`(例: トレイト境界)を課します。これは、例えばターゲットの引数型が関連型である場合に必要になることがあります。例については、このリポジトリの [associated_type.rs] を参照してください。
##### `generic_args = "parameters"`
ファジング時にターゲットの型パラメータとして `parameters` を使用します。例:```rust
#[test_fuzz(generic_args = "String")]
fn foo<T: Clone + Debug + Serialize>(x: &T) {
...
}
注記: ターゲットの引数は、その型パラメータのすべてのインスタンス化に対してシリアライズ可能である必要があります。ただし、ターゲットの引数がデシリアライズ可能であることが必要なのは、ターゲットがparametersでインスタンス化される場合のみです。
impl_generic_args = "parameters"ファジング時に、ターゲットのSelf型パラメータとしてparametersを使用します。例:```rust
#[test_fuzz_impl]
impl<T: Clone + Debug + Serialize> for Foo {
#[test_fuzz(impl_generic_args = "String")]
fn bar(&self, x: &T) {
...
}
}
注: ターゲットの引数は、その`Self`型パラメータの**すべての**インスタンス化に対してシリアライズ可能でなければなりません。しかし、ターゲットの引数がデシリアライズ可能である必要があるのは、ターゲットの`Self`が`parameters`でインスタンス化される場合のみです。
##### `convert = "X, Y"`
ターゲットの引数をシリアライズするとき、型`X`の値を型`Y`に変換するには、`Y`による`From<X>`の実装を使用するか、型`&X`の値を型`Y`に変換するには、`Y`による非標準トレイト`test_fuzz::FromRef<X>`の実装を使用します。デシリアライズ時には、`Y`による非標準トレイト`test_fuzz::Into<X>`の実装を使用して、それらの値を型`X`に戻します。
つまり、`convert = "X, Y"`の使用には特定の実装が伴わなければなりません。`X`が[`Clone`]を実装している場合、`Y`は以下を実装してもよいです:```rust
impl From<X> for Y {
fn from(x: X) -> Self {
...
}
}
X が Clone を実装していない場合、Y は以下を実装する必要があります:```rust
impl test_fuzz::FromRef for Y {
fn from_ref(x: &X) -> Self {
...
}
}
さらに、`Y` は (`X` が [`Clone`] を実装しているかどうかに関係なく) 以下を実装する必要があります:```rust
impl test_fuzz::Into<X> for Y {
fn into(self) -> X {
...
}
}
test_fuzz::Into の定義は std::convert::Into の定義と同一です。標準外のトレイトを使用する理由は、標準トレイトの包括実装から生じる競合を回避するためです。
enable_in_productionテストを実行していないときでも、環境変数 TEST_FUZZ_WRITE が設定されていれば、コーパスファイルを生成します。デフォルトでは、TEST_FUZZ_WRITE が設定されているかどうかに関係なく、テスト実行時のみコーパスファイルを生成します。パッケージディレクトリの外部からターゲットを実行する場合は、TEST_FUZZ_MANIFEST_PATH をパッケージの Cargo.toml ファイルのパスに設定してください。
WARNING: enable_in_production を設定すると、サービス拒否(denial-of-service)のベクターが導入される可能性があります。たとえば、異なる引数で多数回呼び出される関数にこのオプションを設定すると、ディスクを埋め尽くす可能性があります。TEST_FUZZ_WRITE のチェックは、この可能性に対する防御策を提供することを意図しています。それでも、このオプションを使用する前に注意深く検討してください。
execute_with = "function"ターゲットを直接呼び出す代わりに:
R として、FnOnce() -> R 型のクロージャを構築します。これにより、そのクロージャを呼び出すとターゲットが呼び出されます。function を呼び出します。この方法でターゲットを呼び出すことで、function が呼び出し環境をセットアップできます。これは、例えば Substrate externalities のファジングに役立ちます。
no_auto_generateターゲットに対して auto-generate corpus files を試みません。
only_generic_argsテスト実行時にターゲットのジェネリック引数を記録しますが、コーパスファイルは生成せず、ファジングハーネスも実装しません。これは、ターゲットがジェネリック関数であるものの、ファジングにどの型パラメータを使用すべきかが不明な場合に役立ちます。
意図されたワークフローは次のとおりです。only_generic_args を有効にして、cargo test を実行し、続けて cargo test-fuzz --display generic-args を実行します。得られたジェネリック引数のうちの1つが、generic_args の parameters として使用できるかもしれません。同様に、cargo test-fuzz --display impl-generic-args から得られたジェネリック引数が、impl_generic_args の parameters として使用できるかもしれません。
ただし、テスト中にターゲットが特定のパラメータで呼び出されたからといって、それらのパラメータを使用したときにターゲットの引数がシリアライズ/デシリアライズ可能であることを意味するわけではないことに注意してください。--display generic-args/--display impl-generic-args の結果は単なる参考です。
rename = "name"外側のスコープにモジュールを追加する際、ターゲットの名前が name であるかのように扱います。test_fuzz マクロの展開により、外側のスコープにモジュール定義が追加されます。デフォルトでは、モジュールは次のように命名されます。
impl ブロック内に現れない場合、モジュール名は target_fuzz__ となり、target はターゲットの名前です。impl ブロック内に現れる場合、モジュール名は path_target_fuzz__ となり、path は impl の Self 型パスの最後のセグメントです。ただし、このオプションを使用すると、モジュールは代わりに name_fuzz__ と命名されます。例:```rust
#[test_fuzz(rename = "bar")]
fn foo() {}
// Without the use of rename, a name collision and compile error would result.
mod foo_fuzz__ {}
#### 関数引数における Serde フィールド属性
`test_fuzz` マクロは、[Serde フィールド属性] を関数引数に適用することを可能にします。これは、扱いにくい型を扱うためのもう1つのツールを提供します。
以下はその例です。`Context` は `Mutex` を含むため、トレイト `serde::Serialize` と `serde::Deserialize` を導出できません。しかし、`Context` は `Default` を実装しています。そのため、`Context` 引数に `#[serde(skip)]` を適用すると、シリアライズ時にはスキップされ、デシリアライズ時にはデフォルト値が使用されます。```rust
use std::sync::Mutex;
// Traits `serde::Serialize` and `serde::Deserialize` cannot be derived for `Context` because it
// contains a `Mutex`.
#[derive(Default)]
struct Context {
lock: Mutex<()>,
}
impl Clone for Context {
fn clone(&self) -> Self {
Self {
lock: Mutex::new(()),
}
}
}
#[test_fuzz::test_fuzz]
fn target(#[serde(skip)] context: Context, x: i32) {
assert!(x >= 0);
}
Serde フィールド属性が引数に適用される場合、test_fuzz マクロはその引数に対して他のconversionsを実行しないことに注意してください。
test_fuzz_impl マクロtest_fuzz マクロが impl ブロック内で使用される場合は常に、
impl の前に test_fuzz_impl マクロを置く必要があります。例:```rust
#[test_fuzz_impl]
impl Foo {
#[test_fuzz]
fn bar(&self, x: &str) {
...
}
}
この要件の理由は以下の通りです。[`test_fuzz`] マクロの展開により、モジュール定義が囲んでいるスコープに追加されます。しかし、モジュール定義は `impl` ブロック内に置くことはできません。`impl` の前に `test_fuzz_impl` マクロを置くと、モジュールは `impl` ブロックの外に追加されます。
次のようなエラーが表示される場合、`test_fuzz_impl` マクロの使用が欠落している可能性があります:```
error: module is not supported in `trait`s or `impl`s
test_fuzz_impl には現在、オプションがありません。
cargo test-fuzz コマンドcargo test-fuzz コマンドは、ファズターゲットと対話し、それらのコーパス、クラッシュ、ハング、ワークキューを操作するために使用されます。呼び出しの例は次のとおりです。
foo のコーパスを表示する ```
cargo test-fuzz foo --display corpus
foo ```
cargo test-fuzz foo
foo に対して見つかったクラッシュのリプレイ ```
cargo test-fuzz foo --replay crashes
Usage: cargo test-fuzz [OPTIONS] [TARGETNAME] [-- ...]
Arguments: [TARGETNAME] String that fuzz target's name must contain [ARGS]... Arguments for the fuzzer
Options:
--backtrace Display backtraces
--consolidate Move one target's crashes, hangs, and work queue to its corpus; to
consolidate all targets, use --consolidate-all
--coverage Generate coverage for corpus, crashes, hangs, or work queue. Note
that generating coverage for instrumented fuzz targets is not
supported.
--cpus Fuzz using at most cpus; default is all but one
--display Display corpus, crashes, generic args, impl generic args, hangs,
or work queue. By default, an uninstrumented fuzz target is used.
To display with instrumentation, append -instrumented to
, e.g., --display corpus-instrumented.
--exact Target name is an exact name rather than a substring
--exit-code Exit with 0 if the time limit was reached, 1 for other
programmatic aborts, and 2 if an error occurred; implies --no-ui,
does not imply --run-until-crash or --max-total-time
--features Space or comma separated list of features to activate
--list List fuzz targets
--manifest-path Path to Cargo.toml
--max-total-time Fuzz at most of time (equivalent to -- -V )
--no-default-features Do not activate the default feature
--no-run Compile, but don't fuzz
--no-ui Disable user interface
-p, --package Package containing fuzz target
--persistent Enable persistent mode fuzzing
--pretty Pretty-print debug output when generating coverage, displaying, or
replaying
--release Build in release mode
--replay Replay corpus, crashes, hangs, or work queue. By default, an
uninstrumented fuzz target is used. To replay with
instrumentation, append -instrumented to , e.g.,
--replay corpus-instrumented.
--reset Clear fuzzing data for one target, but leave corpus intact; to
reset all targets, use --reset-all
--resume Resume target's last fuzzing session
--run-until-crash Stop fuzzing once a crash is found
--slice If there are not sufficiently many cpus to fuzz all targets
simultaneously, fuzz them in intervals of [default:
1200]
--test Integration test containing fuzz target
--timeout Number of seconds to consider a hang when fuzzing or replaying
(equivalent to -- -t <TIMEOUT * 1000> when fuzzing)
--verbose Show build output when generating coverage, displaying, or
replaying
-h, --help Print help
-V, --version Print version
Try cargo afl fuzz --help to see additional fuzzer options.
`--display` オプションを使用すると、ターゲットが stderr に書き出した出力が表示されます。これには、`eprintln!` ステートメントからの出力や、`dbg!` などのデバッグ用マクロからの出力も含まれます。これは、特定の入力を処理する際に、コード内で何が起こっているかを理解するのに役立ちます。
`--display` オプションと `--replay` オプションは一緒に指定でき、1つのコマンドでコーパスエントリの表示とリプレイの両方を行うことができます。例:```
cargo test-fuzz foo --display corpus --replay corpus
警告: これらのユーティリティはセマンティックバージョニングの対象外であり、将来のバージョンのtest-fuzzでは削除される可能性があります。
dont_care!dont_care!マクロは、構築が簡単で、その値を記録する必要がない型に対してserde::Serialize/serde::Deserializeを実装するために使用できます。直感的には、dont_care!($ty, $expr)は次のことを意味します:
$ty型の値をスキップします。$ty型の値を$exprで初期化します。より具体的には、dont_care!($ty, $expr)は次のように展開されます:```rust
impl serde::Serialize for $ty {
fn serialize(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
().serialize(serializer)
}
}
impl<'de> serde::Deserialize<'de> for $ty { fn deserialize(deserializer: D) -> std::result::Result<Self, D::Error> where D: serde::Deserializer<'de>, { <()>::deserialize(deserializer).map(|_| $expr) } }
`$ty` がユニット構造体の場合、`$expr` は省略できます。つまり、`dont_care!($ty)` は `dont_care!($ty, $ty)` と同等です。
#### `leak!`
`leak!` マクロは、参照であり、その型が [`ToOwned`] トレイトを実装しているターゲット引数を直列化するのに役立ちます。[`convert`] オプションと一緒に使用することを想定しています。
具体的には、次の形式の呼び出しは、型 `LeakedX` を宣言し、それに対して `From` トレイトと `test_fuzz::Into` トレイトを実装します。```rust
leak!(X, LeakedX);
その後、LeakedXをconvertオプションとともに次のように使用できます:```rust
#[test_fuzz::test_fuzz(convert = "&X, LeakedX")
`X` が [`Path`] である例は、このリポジトリの [conversion.rs] にあります。
より一般的には、`leak!($ty, $ident)` という形式の呼び出しは、次のように展開されます:```rust
#[derive(Clone, std::fmt::Debug, serde::Deserialize, serde::Serialize)]
struct $ident(<$ty as ToOwned>::Owned);
impl From<&$ty> for $ident {
fn from(ty: &$ty) -> Self {
Self(ty.to_owned())
}
}
impl test_fuzz::Into<&$ty> for $ident {
fn into(self) -> &'static $ty {
Box::leak(Box::new(self.0))
}
}
serialize_ref / deserialize_refserialize_ref と deserialize_ref は leak! と同様に機能しますが、それぞれ Serde の serialize_with および deserialize_with フィールド属性で使用することを目的としています。```rust
fn serialize_ref<S, T>(x: &&T, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: serde::Serialize,
{
::serialize(*x, serializer)
}
fn deserialize_ref<'de, D, T>(deserializer: D) -> Result<&'static T, D::Error> where D: serde::Deserializer<'de>, T: serde:🇩🇪:DeserializeOwned + std::fmt::Debug, { let x = ::deserialize(deserializer)?; Ok(Box::leak(Box::new(x))) }
#### `serialize_ref_mut` / `deserialize_ref_mut`
`serialize_ref_mut` と `deserialize_ref_mut` は、それぞれ `serialize_ref` と `deserialize_ref` に似ていますが、不変参照ではなく可変参照に対して動作する点が異なります。
## `test-fuzz` パッケージのフィーチャー
このセクションのフィーチャーは、`test-fuzz` パッケージ全体に適用されます。[The Cargo Book] で説明されているように、`test-fuzz` の依存関係仕様でこれらを有効にしてください。たとえば、`cast_checks` フィーチャーを有効にするには、次のようにします。```toml
test-fuzz = { version = "*", features = ["cast_checks"] }
The test-fuzz package currently supports the following features:
cast_checksUse cast_checks to automatically check target functions for invalid casts.
Note that this feature enables cast_checks only for functions annotated with the test_fuzz macro, not for the functions they call.
test-fuzz can serialize target arguments in multiple Serde formats. The following are the features used to select a format.
cargo-test-fuzz can auto-generate values for types that implement certain traits. If all of a target's argument types implement such traits, cargo-test-fuzz can auto-generate corpus files for the target.
The traits that cargo-test-fuzz currently supports and the values generated for them are as follows:
Key
Add - core::ops::AddBounded - num_traits::bounds::BoundedDefault - std::default::DefaultDiv - core::ops::DivOne - num_traits::OneSub - core::ops::SubTEST_FUZZ_LOGDuring macro expansion:
TEST_FUZZ_LOG is set to 1, write all instrumented fuzz targets and module definitions to standard output.TEST_FUZZ_LOG is set to a crate name, write that crate's instrumented fuzz targets and module definitions to standard output.This can be useful for debugging.
TEST_FUZZ_MANIFEST_PATHWhen running a target from outside its package directory, find the package's Cargo.toml file at this location. One may need to set this environment variable when enable_in_production is used.
TEST_FUZZ_WRITEGenerate corpus files when not running tests for those targets for which enable_in_production is set.
A target's arguments must implement the Clone trait. The reason for this requirement is that the arguments are needed in two places: in a test-fuzz-internal function that writes corpus files, and in the body of the target function. To resolve this conflict, the arguments are cloned before being passed to the former.
In general, a target's arguments must implement the serde::Serialize and serde::Deserialize traits, e.g., by deriving them. We say "in general" because test-fuzz knows how to handle certain special cases that wouldn't normally be serializable/deserializable. For example, an argument of type &str is converted to String when serializing, and back to a &str when deserializing. See also generic_args and impl_generic_args above.
The fuzzing harnesses that test-fuzz implements do not initialize global variables. While execute_with provides some remedy, it is not a complete solution. In general, fuzzing a function that relies on global variables requires ad-hoc methods.
convert and generic_args / impl_generic_argsThese options are incompatible in the following sense. If a fuzz target's argument type is a type parameter, convert will try to match the type parameter, not the type to which the parameter is set. Supporting the latter would seem to require simulating type substitution as the compiler would perform it. However, this is not currently implemented.
#[cfg(test)] is not enabled for integration tests. If your target is tested only by integration tests, then consider using enable_in_production and TEST_FUZZ_WRITE to generate a corpus. (Note the warning accompanying enable_in_production, however.)
If you know the package in which your target resides, passing -p <package> to cargo test/cargo test-fuzz can significantly reduce build times. Similarly, if you know your target is called from only one integration test, passing --test <name> can reduce build times.
Rust won't allow you to implement serde::Serialize for other repositories' types. But you may be able to other repositories to make their types serializable. Also, can be useful for grabbing dependencies' repositories.
We reserve the right to change the format of corpora, crashes, hangs, and work queues, and to consider such changes non-breaking.
test-fuzz is licensed and distributed under the AGPLv3 license with the Macros and Inline Functions Exception. In plain language, using the test_fuzz macro, the test_fuzz_impl macro, or test-fuzz's convenience functions and macros in your software does not require it to be covered by the AGPLv3 license.
| Trait(s) | Value(s) |
|---|
Bounded | T::min_value(), T::max_value() |
Bounded + Add + One | T::min_value() + T::one() |
Bounded + Add + Div + Two | T::min_value() / T::two() + T::max_value() / T::two() |
Bounded + Add + Div + Two + One | T::min_value() / T::two() + T::max_value() / T::two() + T::one() |
Bounded + Sub + One | T::max_value() - T::one() |
Default | T::default() |
Two - test_fuzz::runtime::traits::Two (essentially Add + One)Serde attributes can be helpful in implementing serde::Serialize/serde::Deserialize for difficult types.