
afl.rsを使ったファジングを自動化するためのRustマクロとCargoサブコマンド。コーパス生成とハーネスの実装を含み、Rustのテストフレームワークと統合されています。
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