はじめに
以前afl.rsを使ったファジングの手順を紹介したのですが、今回はlibFuzzerベースのcargo-fuzzを使ったファジングの手順を紹介します。
前提条件
項目 | 説明 |
---|---|
検証日 | 2017.08.18 |
OS | macOS 10.11.6 |
Rust | rustc 1.21.0-nightly (52a330969 2017-07-27) |
cargo-fuzz | 0.4.3 |
現在はx86-64 Linuxとx86-64 macOSのみサポートしている模様
手順
今回は例として、 woothee-rustプロジェクトを使って説明します。
まずは cargo-fuzz
自体をインストールします。
$ cargo install --force cargo-fuzz
インストールが終わったら、ターゲットとなるRustプロジェクトのディレクトリルートでcargo fuzz init
を実行し、ファジングテストのセットアップを行います。
$ cd woothee-rust
$ cargo fuzz init
$ tree
.
├── Cargo.toml
├── LICENSE
├── README.md
├── benches
│ └── benchmark.rs
├── build.rs
├── fuzz
│ ├── Cargo.toml
│ └── fuzz_targets
│ └── fuzz_target_1.rs
├── rustfmt.toml
├── src
│ ├── dataset.rs
│ ├── lib.rs
│ ├── parser.rs
│ └── woothee.rs
├── templates
│ ├── dataset.tmpl
│ └── tests.tmpl
└── tests
├── appliance.rs
├── blank.rs
├── crawler.rs
├── crawler_google.rs
├── crawler_nonmajor.rs
├── misc.rs
├── mobilephone_au.rs
├── mobilephone_docomo.rs
├── mobilephone_misc.rs
├── mobilephone_softbank.rs
├── mobilephone_willcom.rs
├── pc_lowpriority.rs
├── pc_misc.rs
├── pc_windows.rs
├── smartphone_android.rs
├── smartphone_ios.rs
└── smartphone_misc.rs
6 directories, 31 files
ディレクトリルートにfuzz
ディレクトリが新たに作成され、ファジングに必要な最低限のファイルができあがります。
fuzz/fuzz_targets/fuzz_target_1.rs
が実行の起点になるファイルになります。はじめは以下のような内容になっています。
# ![no_main]
# [macro_use] extern crate libfuzzer_sys;
extern crate woothee;
fuzz_target!(|data: &[u8]| {
// fuzzed code goes here
});
これを以下のように変更します。
# ![no_main]
# [macro_use] extern crate libfuzzer_sys;
extern crate woothee;
use woothee::parser::Parser;
fuzz_target!(|data: &[u8]| {
if let Ok(s) = std::str::from_utf8(data) {
let parser = Parser::new();
let _ = parser.parse(s);
}
});
これでテストの準備ができました。実行してみましょう。
$ cargo fuzz run fuzz_target_1
Compiling arbitrary v0.1.0
Compiling utf8-ranges v1.0.0
Compiling regex-syntax v0.4.1
Compiling void v1.0.2
Running `rustc --crate-name arbitrary /Users/hattori-h/.cargo/registry/src/github.com-1ecc6299db9ec823/arbitrary-0.1.0/src/lib.rs --crate-type lib --emit=dep-info,link -C debuginfo=2 -C metadata=ed02362ebbd857c6 -C extra-filename=-ed02362ebbd857c6 --out-dir /Users/hattori-h/work/woothee-rust.new/fuzz/target/x86_64-apple-darwin/debug/deps --target x86_64-apple-darwin -L dependency=/Users/hattori-h/work/woothee-rust.new/fuzz/target/x86_64-apple-darwin/debug/deps -L dependency=/Users/hattori-h/work/woothee-rust.new/fuzz/target/debug/deps --cap-lints allow -Cpasses=sancov -Cllvm-args=-sanitizer-coverage-level=3 -Zsanitizer=address -Cpanic=abort`
:
: ビルド
:
Compiling woothee-fuzz v0.0.1 (file:///Users/hattori-h/work/woothee-rust.tmp/fuzz)
Running `rustc --crate-name fuzz_target_1 fuzz/fuzz_targets/fuzz_target_1.rs --crate-type bin --emit=dep-info,link -C debuginfo=2 -C metadata=61313f395163699b -C extra-filename=-61313f395163699b --out-dir /Users/hattori-h/work/woothee-rust.tmp/fuzz/target/x86_64-apple-darwin/debug/deps --target x86_64-apple-darwin -L dependency=/Users/hattori-h/work/woothee-rust.tmp/fuzz/target/x86_64-apple-darwin/debug/deps -L dependency=/Users/hattori-h/work/woothee-rust.tmp/fuzz/target/debug/deps --extern woothee=/Users/hattori-h/work/woothee-rust.tmp/fuzz/target/x86_64-apple-darwin/debug/deps/libwoothee-c8ac09f4115f38f8.rlib --extern libfuzzer_sys=/Users/hattori-h/work/woothee-rust.tmp/fuzz/target/x86_64-apple-darwin/debug/deps/liblibfuzzer_sys-881433e46785513a.rlib -Cpasses=sancov -Cllvm-args=-sanitizer-coverage-level=3 -Zsanitizer=address -Cpanic=abort -L native=/Users/hattori-h/work/woothee-rust.tmp/fuzz/target/x86_64-apple-darwin/debug/build/libfuzzer-sys-111c832b98e6acbd/out`
Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
Running `fuzz/target/x86_64-apple-darwin/debug/fuzz_target_1 -artifact_prefix=/Users/hattori-h/work/woothee-rust.tmp/fuzz/artifacts/fuzz_target_1/ /Users/hattori-h/work/woothee-rust.tmp/fuzz/corpus/fuzz_target_1`
INFO: Seed: 2815031522
INFO: Loaded 0 modules (0 guards):
Loading corpus dir: /Users/hattori-h/work/woothee-rust.tmp/fuzz/corpus/fuzz_target_1
INFO: -max_len is not provided, using 64
INFO: A corpus is not provided, starting from an empty corpus
# 0 READ units: 1
# 1 INITED cov: 16670 corp: 1/1b exec/s: 0 rss: 34Mb
# 2 NEW cov: 16679 corp: 2/3b exec/s: 0 rss: 34Mb L: 2 MS: 1 InsertByte-
# 7 NEW cov: 16681 corp: 3/4b exec/s: 0 rss: 34Mb L: 1 MS: 1 ChangeByte-
# 19 NEW cov: 16755 corp: 4/5b exec/s: 0 rss: 34Mb L: 1 MS: 3 ChangeByte-EraseBytes-ChangeBinInt-
# 37 NEW cov: 16831 corp: 5/10b exec/s: 0 rss: 34Mb L: 5 MS: 1 CMP- DE: "\x00\x00\x00\x00"-
# 38 NEW cov: 16859 corp: 6/15b exec/s: 0 rss: 34Mb L: 5 MS: 2 CMP-ChangeBit- DE: "\x00\x00\x00\x00"-
# 39 NEW cov: 16876 corp: 7/24b exec/s: 0 rss: 34Mb L: 9 MS: 3 CMP-ChangeBit-PersAutoDict- DE: "\x00\x00\x00\x00"-"\x00\x00\x00\x00"-
# 48 NEW cov: 16892 corp: 8/43b exec/s: 0 rss: 35Mb L: 19 MS: 2 ChangeByte-InsertRepeatedBytes-
# 53 NEW cov: 16894 corp: 9/62b exec/s: 0 rss: 35Mb L: 19 MS: 2 PersAutoDict-ChangeBinInt- DE: "\x00\x00\x00\x00"-
# 90 NEW cov: 16897 corp: 10/69b exec/s: 0 rss: 35Mb L: 7 MS: 4 PersAutoDict-ChangeByte-ChangeASCIIInt-EraseBytes- DE: "\x00\x00\x00\x00"-
# 94 NEW cov: 16898 corp: 11/72b exec/s: 0 rss: 36Mb L: 3 MS: 3 CopyPart-ShuffleBytes-InsertByte-
# 95 NEW cov: 16903 corp: 12/94b exec/s: 0 rss: 36Mb L: 22 MS: 4 CopyPart-ShuffleBytes-InsertByte-CrossOver-
# 97 NEW cov: 16904 corp: 13/99b exec/s: 0 rss: 36Mb L: 5 MS: 1 CopyPart-
# 110 NEW cov: 16917 corp: 14/101b exec/s: 0 rss: 36Mb L: 2 MS: 4 ChangeBit-InsertByte-ShuffleBytes-ChangeBit-
:
: ファジングテストが続く
:
Ctrl+C等で停止するまでテスト実行が継続されます。
テスト結果
今回のテストではpanicは検出できませんでしたが、panic等が発生した場合は fuzz/artifacts
ディレクトリ以下にテストで検出したデータが格納されます。
panicは検出できなかったのですが、実行時間がかかるパターンを検出しました。
$ ls fuzz/artifacts/fuzz_target_1/
slow-unit-3cbe49cc498d80637afa7f06153cca9c725934d0 slow-unit-e09e5f665aca223dc631b0f9c5ae49badb814b88
slow-unix-XXX
に実行時間がかかったデータが格納されます。(これを使えば後ほど再現させることが可能です。)
その他
テスト追加
別のファジングテストを追加したい場合は cargo fuzz add
を実行します。
$ cargo fuzz add test2
$ cargo fuzz list
fuzz_target_1
test2
fuzz/Cargo.toml
にもビルドターゲットが追加されるので、上記の場合だとcargo fuzz run test2
とすれば新しく追加したテストが実行できます。
コーパスの指定
ファジングテストで使用するデータの元となるデータを指定することができます。woothee-rustの場合、ユーザーエージェントを指定することが多いので、それに類するデータを指定することで少し現実に近い形でテストすることもできます。
サンプルデータは以下のようなデータにしてみます。
Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_4; ja-jp) AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1
別のコーパスとして使いたいので以下のように準備します。
$ mkdir mycorpus
$ echo "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_4; ja-jp) AppleWebKit/525.18 (KHTML, like Gecko) Version/3.1.2 Safari/525.20.1" > mycorpus/sample
コーパスを指定して実行します。またデータサイズも大きなものにして実行してみます。
$ cargo fuzz run fuzz_target_1 mycorpus -- -max_len=1024
:
:
INFO: Seed: 364705934
INFO: Loaded 0 modules (0 guards):
Loading corpus dir: mycorpus
# 0 READ units: 1
# 1 INITED cov: 16517 corp: 1/126b exec/s: 0 rss: 27Mb
# 2 NEW cov: 16599 corp: 2/253b exec/s: 0 rss: 27Mb L: 127 MS: 1 InsertByte-
# 4 NEW cov: 16606 corp: 3/319b exec/s: 0 rss: 27Mb L: 66 MS: 3 InsertByte-ChangeByte-EraseBytes-
指定したコーパス用のディレクトリを確認すると様々なデータが生成されているのがわかります。
$ ls -ltr mycorpus/
total 464
-rw-r--r-- 1 126 8 19 22:15 sample
-rw-r--r-- 1 732 8 19 22:18 e633414fee54285eceb763e861f4d74407822c97
-rw-r--r-- 1 68 8 19 22:18 b2756e02a3ba92cc0f58f27be52ee3b602080f8f
-rw-r--r-- 1 1024 8 19 22:18 795176a87e869f449b3a6430fc103c8fa0549ed9
-rw-r--r-- 1 127 8 19 22:18 741b1197abb7ac8be3193da96ccc8f234d7f5cbf
-rw-r--r-- 1 175 8 19 22:18 57995eafdffdba30a485177485cefd291b693861
-rw-r--r-- 1 66 8 19 22:18 4334a4c31b0fb6865b1f83c0bc687901b766e7c1
-rw-r--r-- 1 67 8 19 22:18 2e7db98cd25debfc333c4b6cada66f2c4bda2e87
-rw-r--r-- 1 731 8 19 22:18 1b59508611eb56f8116308d9eac0f4b075c551ab
-rw-r--r-- 1 613 8 19 22:18 d965544c1c37e7f58876c3bd887344f872453c91
:
他にはコーパスを圧縮するcmin
サブコマンドを使えます。(テストを圧縮する?tmin
というサブコマンドもあるのですが、実行時エラーになったので結果どうなるのかわかりませんでした。)
$ cargo fuzz cmin fuzz_target_1 mycorpus
INFO: Seed: 723930214
INFO: Loaded 0 modules (0 guards):
INFO: -max_len is not provided, using 1048576
=== Merging extra 64 units
# 64 MIN0 cov: 18970 units: 39 exec/s: 0 rss: 39Mb
# 103 MIN1 cov: 4486 units: 36 exec/s: 0 rss: 41Mb
=== Merge: written 36 units