EF/CF はスマートコントラクトファジングへの新しいアプローチです。新しい カスタムファザーを使う代わりに、C/C++ コードの既存のファジング基盤を スマートコントラクト向けに再利用します。現在、AFL++ が主にサポートされているファザーですが、 libfuzzer と honggfuzz の非常に基本的なサポートもいくつかあります。
なぜ既存のファジング基盤を使うのか?
途中で遭遇する問題は何でしょうか?
./src/ethmutator/./src/evm2cpp/このリポジトリは EF/CF プロジェクトの主要なエントリポイントです。関連するすべての
コードが ./src/ 内のサブプロジェクトとして含まれており、インストール用の便利なスクリプト、
ファジングキャンペーンを開始するためのスクリプト、およびファザーをテストする(他のツールと比較する)ためのさまざまなデータセットが含まれています。
./src/ - EF/CF のビルドと実行に必要なすべてのソースが含まれています。
再現性のため、すべての直接依存関係は git サブモジュールとして追加されています。./data/ - 評価で使用されたデータセットが含まれています./scripts - 実験の実行、インストールなどのスクリプトが含まれています./docker - コンテナベースのワークフロー用の Dockerfile
./docker/tools/ には、EF/CF を評価したツールの dockerfile が含まれています。
論文で評価したバージョンを dockerfile に固定するよう最善を尽くしました。./EXPERIMENTS.md - 論文の実験を再現するための
ガイドが含まれています。./examples - EF/CF によって生成された出力例が含まれていますEF/CF のアーキテクチャ、実装、および評価結果の概要は、論文で説明しています: arxiv.org preprint
学術研究で EF/CF を参照する場合は、引用に以下の bibtex エントリを使用してください。```bibtex @InProceedings{efcf2023, author = "Michael Rodler and David Paaßen and Wenting Li and Lukas Bernhard and Thorsten Holz and Ghassan Karame and Lucas Davi", title = "EF/CF: High Performance Smart Contract Fuzzing for Exploit Generation", booktitle = "{IEEE} European Symposium on Security and Privacy ({EuroS&P})", publisher = "{IEEE}", year = "2023", }
## クイックスタート
推奨される方法は、EF/CFをインタラクティブなDockerコンテナとして実行することです。
1. シェルでコンテナに入る ```
docker run --rm -it ghcr.io/uni-due-syssec/efcf-framework
または、クローンしたリポジトリからコンテナをビルドする ``` make gitmodules # to fetch the git submodules make container-enter
1. solidityコントラクトをコンパイルしてから、最初のクラッシュ/バグが
発見されるまでファズする: ```
efcfuzz --until-crash --out ./baby_bank_results/ --source ./data/examples/baby_bank.sol
git をお持ちでない場合? tarball/docker リリースを使用する場合は、これは無視してください。
git submodule update --init を実行して、既にクローン済みのリポジトリで最新のサブモジュールコミットを取得してください。
./src/eEVM 内でもこれを実行することを忘れないでください。```
git submodule update --init; cd src/eEVM/; git submodule update --init; cd ../../
*警告:* `git clone --recursive $repo` を実行するか、`--recursive` 引数を `git sumbodule (update|init)` に渡すと、git が AFL++ リポジトリのサブモジュールを再帰的に取得しますが、これらはこのプロジェクトには必要ありません。スペースを節約するため、再帰的なサブモジュールのチェックアウトは避けることをお勧めします。
### コンテナ
コンテナベースのワークフロー向けに、以下の便利な make ターゲットを提供しています。```sh
make container-build # build default efcf container
make container-enter # enter default efcf container in current working dir
クリーンビルドを確実にしたい場合は、以下のコマンドを使用できます。```sh make container-build CLEAN_CHECKOUT=1
あるいは、コンテナは以下のdockerコマンドで構築できます:```sh
docker build \
-f docker/ubuntu.Dockerfile \
-t efcf:latest \
.
Archlinux と Fedora ベースの Dockerfile もあることに注意してください。これらは 同様に動作するはずですが、あまりテストされていません。
Docker イメージを手動で配布する場合(例: ローカルの変更を含める場合)、次を使用します:``` make container-release docker load -i ./efcf*.tar
以下を推奨するDocker起動オプションです:
* `--security-opt seccomp=unconfined` - ファジングパフォーマンスの向上
* `--net=host` - ローカルのEthereumノードに簡単にアクセスするため
* `--tmpfs "/tmp/efcf/":exec,size=6g` - 可能であればEF/CFの一時ファイルをRAMディスクに置く(ディスクの摩耗を軽減)
* `--privileged` - `afl-system-config` または `efcfuzz --configure-system` を実行するため
* `-v` - EF/CFの出力データを永続化するため
### VM / ベアメタル
VM またはベアメタルベースのワークフローの場合:```sh
make system-install # install efcf to current system (requires root or sudo rights)
なお、スクリプトの多くは相対ディレクトリ構造で動作するため、
これは主に依存関係と、自身の
PATH にあると便利ないくつかのツールをインストールします。EF/CF は以下の Linux ディストリビューションでテストしています:
(ディストリビューションはあまり重要ではありません。LLVM 13 と 14 でテストしており、14 が
推奨の選択肢です。LLVM 11 や 12 もまだ動作するかもしれません。しかし、いつものように、新しい方が
良いです。重要なのは、AFL++ のフォークと互換性のある LLVM が存在することです。)
EF/CF は Mac OS 上ではネイティブにテストしていません。おそらく動作しないでしょう (例: Mac OS 上の afl-clang-lto は動作しないようです)。最良の選択肢は docker を利用することです。```sh
make gitmodules
docker pull ubuntu:jammy --platform linux/amd64
docker build -t efcf:latest -f docker/ubuntu.Dockerfile --platform linux/amd64 .
docker run --tmpfs "/tmp/efcf/":exec,size=8g --platform linux/amd64 --rm -it -v $(pwd):$(pwd) -w $(pwd) efcf:latest
docker desktop v4.21.1 と基本的な EF/CF の使用でテストしましたが、問題なく動作します。ただし、以下に注意してください。
* ビルド中にセグメンテーション違反が発生した場合は、Mac OS で docker が使用する VM のメモリ制限を増やしてみてください。
* docker で rosetta を使ったアクセラレーションを有効にしてみてください。うまくいけば少し速くなるはずです。
### 開発環境のセットアップ
これらのツールは通常インストールする必要はありません。必要な依存関係は、
`system-install.sh` スクリプトまたはDockerfileに記載されているとおりにインストールしてください。
便宜上、`PATH` を更新するスクリプトをいくつか用意しています。```sh
# POSIX-like shells (i.e., bash, ...)
source ./scripts/env.sh
# for the fish shell
source ./scripts/env.fish
一部のスクリプトは、Etherscan サービスからメタデータ(例:ABI)を取得するために API キーを必要とします。API キーをお持ちの場合は、ETHERSCAN_API_KEY 環境変数を設定してスクリプトに渡す必要があります。docker ベースのワークフローでは、--env フラグを指定して docker コンテナを起動するか、.etherscan_api_key ファイルに API キーを入れることで、API キーを docker コンテナに組み込むことができます。
便利なように、EF/CF ファザーを起動する際にすべての詳細を処理するラッパースクリプト efcfuzz を利用しています。
ビルドおよびファジングプロセスに関するファザーの動作を設定するために、多くのコマンドラインオプションを設定できます。オプションの一覧は efcfuzz --help を参照してください。
例
Solidity のソースコードを EF/CF ネイティブコードにコンパイルし、5 分間(つまり 300 秒間)ファジングを開始します。```bash efcfuzz --timeout 300 --source ./data/examples/baby_bank.sol
あるいは、ファジング出力を抑えて起動する(`--quiet` はベースファザーの出力を抑制し、
`--print-progress` はファジングの進捗の短い要約を表示する)、そしてファザーを
4コアで起動する。```bash
efcfuzz --quiet --print-progress --cores 4 --timeout 300 --source ./data/examples/baby_bank.sol
既にコンパイル済みのバイトコードを使用し、そのバイトコードをEF/CFネイティブコードにコンパイルして、ファジングを開始します。```bash
pushd ./data/examples/; make baby_bank.combined.json; popd efcfuzz --timeout 300 --bin-runtime ./data/examples/baby_bank.combined.json
pushd ./data/examples/; make baby_bank; popd
efcfuzz --timeout 300
--bin-runtime ./data/examples/baby_bank.bin-runtime
--bin-deploy ./data/examples/baby_bank.bin
--abi ./data/examples/baby_bank.abi
ラッパーは go-ethereum/erigon ノードからコントラクトの状態をエクスポートし、そこから
ファジングを開始できます。```bash
$ efcfuzz --timeout 300 --live-state 0xfffF8D17CB019E0825c478c666B251A7099df3FD
さらに、--include-address-deps=y を渡すと、エクスポートされたコントラクトのストレージ内にある他のアカウントのアドレスを再帰的に検索し、それらもステートエクスポートに含めることができます。ただし、これには Solidity の mapping 型に格納された他のコントラクトは含まれません。実際にステート全体を再帰的にエクスポートするには、--include-mapping-deps=y フラグも渡してください。
ただし注意してください。この再帰的なルックアップは、コンパイル時間の長期化やファジング性能の低下を招く可能性があります。特に、頻繁に使用されるコントラクトは内部状態が多く、そのエクスポートされたステートを使用するとファジングが遅くなる可能性があります。ファザーが 1k execs/sec 以上を達成できるかどうかを確認してください。達成できない場合は、人工的でより小さなステートを作成することをお勧めします。ローカルの go-ethereum ノードを --dev モードで実行し、そこにコントラクトをデプロイしてみてください。そして、そこからライブステートをエクスポートしてください。
ラッパーはビルドをキャッシュするため、2回目のファジング実行は、最初のコンパイル時間が不要になるため、はるかに速く起動できるはずです。ビルドのみを行い、それをキャッシュに配置したい場合は、--build-only 引数を渡すことができます。
例: プロパティを使ったファジング
EF/CF は、echidna ファザーと同じプロパティ定義を使用した、プロパティベースのファジングもサポートしています。プロパティ(または不変条件)は、ファザーに対するバグのオラクルとして機能する Solidity 関数として表現されます。例えば、次のような Solidity 関数を追加できます:```solidity function test_property_balance() public view returns (bool) { return total_balance < 1000; }
これは、total_balance が常に 1000 未満であるべきというプロパティを表しています。EF/CF は、何らかのトランザクションシーケンスを使用してこのプロパティを違反できた場合、すなわちオラクルが `false` を返した場合にバグを報告します。
EF/CF にこれがプロパティであることを伝えるには、ファジング中にチェックすべきプロパティのリストとして EF/CF が読み込む関数シグネチャのリストをファイルに指定する必要があります。
最も簡単な方法は、Solidity コンパイラの `--hashes` フラグを使用して関連するシグネチャを取得することです。例:```
solc --hashes ./path/to/your.sol | grep test_property > property_list
これでファザーを起動できます:``` efcfuzz --source ./path/to/your.sol --properties ./property_list -C
また、`--disable-detectors` を追加して、組み込みのetherベースのバグオラクルを無効にすることもできます。
プロパティベースのファジングの例は、以下で試すことができます:```
efcfuzz \
--properties ./data/examples/harvey_baz_properties.signatures
--disable-detectors \
--until-crash --timeout 120 \
--source ./data/examples/harvey_baz.sol \
例: イベントのファジング
EF/CF は、イベントを通じて表現されるアサーション違反のファジングをサポートしています。実際に、
任意のカスタムイベントをバグオラクルとして使用することもサポートしています。デフォルトでは、EF/CF は、
対象コントラクトが次のいずれかのイベントをログに記録した場合にバグを特定します:
AssertionFailed()、AssertionFailed(uint256)、
AssertionFailed(string)、Panic(uint256)。```
efcfuzz --event-assertions
--timeout 120 --until-crash
--source ./data/properties-assertions-tests/verifyfunwithnumbers.sol
また、監視対象の追加のカスタムイベントトピック/ハッシュを
`--event-assertions-list ./path/to/eventslist.txt` を使ってファイルで指定することもできます。前述の
プロパティリストと同様に、`solc --hashes` を使用して
イベントハッシュと名前をイベントリストファイルにコピーすることで、形式を取得できます。
デフォルトでは、EF/CF はターゲットコントラクトが発行しなかったイベントを
無視します。変更したい場合は `--event-assertions-target-only=n` を使用してください。
(注: `--assertions` を使用すると、イベントと Solidity アサーションの両方の
チェックを有効にできます)
**例: Solidity ^0.8 アサーションのファジング**
現在、0.8 未満の Solidity バージョンでは、Solidity コード内の任意のアサーションの
ファジングには対応していません。以前は、Solidity のアサーションは単に
`invalid` オペコードをトリガーし、その結果、かなり強制的なリバートが発生していました。
Solidity バージョン 0.8 では、トランザクションをリバートするために `invalid` オペコードを
使用するのではなく、`revert` メカニズムを利用して
呼び出し元にエラーを通知するように動作が変更されました。EF/CF では、この種のエラー伝播をバグ
オラクルとして利用できます。現在、EF/CF は Solidity エラータイプ
`Panic(uint256)` のチェックをサポートしています。[Solidity エラーの詳細
情報。](https://docs.soliditylang.org/en/v0.8.0/control-structures.html?highlight=assert#panic-via-assert-and-error-via-require)```
efcfuzz --sol-assertions \
--timeout 120 --until-crash \
--source ./data/assertions-tests/overflow.sol
(注: --assertions を使用すると、イベントとSolidityのアサーションチェックの両方を
有効にできます)
システム仕様と構成
4〜16コアと、コアあたり約1 GBのメモリを割り当てることをお勧めします。
--configure-system フラグを使用して、システムを高速ファジング用に構成する
か、自分で構成することもできます。Dockerコンテナでは、最適なパフォーマンスの
ためにホストも構成する必要があります。重要でないホストの場合は、
コンテナを --privileged として起動し、
/usr/local/bin/afl-system-config を使用してシステムを高速ファジング用に
構成できます(実質的にコンテナをrootとして実行することに注意してください)。```
docker run --rm -it --privileged efcf afl-system-config
docker run --rm -it
--security-opt seccomp=unconfined
--tmpfs "/tmp/efcf/":exec,size=6g
efcf
## ファジング実験の実行
`data/tests/` データセットで実験を実行するには、次のコマンドを使用して、
コントラクトとそのファジングハーネスをビルドし、その後、さまざまな設定や
複数の繰り返しなどでファザーを実行できます。これにはかなり時間がかかるため、
これらの実験は並行して実行できます。ファジング実験をビルドステップと
ファズステップに分割します。ビルドステップは、すべてのスマートコントラクトを
順次ビルドします(ただし、ビルド自体は複数のコアを使用します)。
次に、バックグラウンドで8つのファザーインスタンスを起動し、ビルドステップの
ビルド成果物を取得してファジング実行を開始します。Makefileは、
`docker` または `podman` のいずれかが利用可能な場合、適切なコンテナ内で
自動的にすべてを起動しようとします。```bash
make build-tests
make fuzz-tests CONTAINER_BACKGROUND=1 FUZZER_INSTANCES=8
コンテナをバックグラウンドで起動する際、seccompとネットワークサンドボックスを無効にしています。seccompサンドボックスを無効にすると、ファジングのパフォーマンスが向上します。ホストネットワークを使用することで、EF/CFは追加設定なしでローカルネットワーク上のEthereumノードにアクセスできます。
これらのデータセットのdockerコンテナ内で他のツールを実行するために、./scripts/run-tools-on-dataset.py スクリプトを使用しました。例えば、マルチデータセットでは次のコマンドを使用します:```bash
python3 ./scripts/run-tools-on-dataset.py ./data/multi/
cd ./results/tools-multi/
python3 ../../scripts/get-tools-on-dataset-stats.py
head stats.csv
You need to adapt the script to configure the tools and number of runs.
### ファジング実験のセットアップ
ここでは、`tests` 実験を例として使用します。次の手順では、文字列
`tests` を実験の名前に置き換えるだけです。
1. `./data/` にデータセットを置きます。例として、テストコントラクトを含む `./data/tests` データセットがあります。Solidity コントラクト用に、コントラクトをビルドする汎用の `Makefile` (`sol.Makefile`) を用意しています。必要に応じてこれを再利用できます。例については `./data/tests/Makefile` を参照してください。
2. 必要な前処理/スクレイピング手順を含め、ビルド成果物を構築するスクリプトを作成します。たとえば、`tests` データセット用には `./scripts/build-tests.sh` スクリプトがあります。ビルド成果物は `./builds/tests/${contract}.build.tar.xz` に保存する必要があります。
3. ファジングキャンペーンを起動するスクリプトを作成します。たとえば、`tests` データセット用には `./scripts/fuzz-tests.sh` というスクリプトを作成します。通常は、`./scripts/common.sh` の共通のファジングキャンペーン関数を使用できます。テンプレートとして `fuzz-tests.sh` を参照してください。
4. `fuzz-tests.sh` の結果は `./results/run-fuzz-tests/` に保存されます。
5. 結果を要約するために、bash ベースのランチャースクリプト用の `./scripts/summarize.py` と、Python ランチャースクリプト用の `./scripts/summarize_l.py` (`efcfuzz` ツール) を提供しています。
手順 3 に応じて、これらのスクリプトを適応させる必要があるかもしれません。
### 既存のファジング実験
#### ベンチマーク
* <a href="./data/multi/">`./data/multi`</a> には、分析ツールがより長いトランザクションシーケンスにどの程度スケールするかを評価するために使用したスケーラビリティベンチマークが含まれています。これは次の3種類のコントラクトで構成されています:
* `multi_gen_*.sol` - 自動的に合成されたコントラクトで、多数の `require(input <= MAGIC)` を実行してから内部状態変数を設定します。すべての状態変数が設定されると、`selfdestruct`(または echidna oracle)をトリガーできます。
* `multi_man_complex_*.sol` - `multi_gen` 型のコントラクトと同様に動作する手動作成のバリアントですが、もう少しトリッキーな制約(例:マジック値との等値・不等値以外のもの)を備えています。
* `justlen_*.sol` - これらは [echidna-parade example](https://github.com/crytic/echidna-parade/blob/main/examples/justlen.sol) から取得したものです。
* `multi_simple_*.sol` - ファザー/ツールが理論的には9または10のトランザクションを必要とするバグを見つけられることを検証する健全性チェックです。ここでは、アナライザは引数なしで10個の関数を正しい順序で呼び出すだけです。これはほとんどの分析ツールにとってかなり簡単です。
* <a href="./data/throughput/">`./data/throughput`</a> には、スループットを評価するために使用したコントラクトが含まれています。これはさまざまなサイズのコントラクトを集めたものです。これらのコントラクトのすべての脆弱性にパッチを適用しているため、検出された脆弱性がスループット測定に影響を与えないことに注意してください。
* <a href="./data/cov-max-testset">`./data/cov-max-testset`</a> には、コードカバレッジに基づくファザーの比較に使用したコントラクトが含まれています。
#### バグ検出
* <a href="./data/ethbmc-vuln">`./data/ethbmc-vuln`</a> EthBMC が脆弱として検出したコントラクトのリスト。
* <a href="./data/ethbmc-timeouts">`./data/ethbmc-timeouts`</a> EthBMC がタイムアウトにより解析を停止したコントラクトのリスト。
* <a href="./data/reentrancy">`./data/reentrancy`</a> 再入攻撃に対して脆弱なコントラクトのセット。
* <a href="./data/sailfish-dao-tp">`./data/sailfish-dao-tp`</a> [sailfish study](https://github.com/ucsb-seclab/sailfish/tree/master/data/ground-truth) の一部として再入バグを含むことが検証されたコントラクトのセット。
* <a href="./data/sailfish-dao">`./data/sailfish-dao`</a> [sailfish](https://github.com/ucsb-seclab/sailfish/tree/master/data/bugs) が再入バグを発見したすべてのコントラクトのリスト。
* <a href="./data/sereum">`./data/sereum`</a> [Sereum](https://github.com/uni-due-syssec/sereum-results) によると再入攻撃に対して脆弱なコントラクトのリスト。
* <a href="./data/smartbugs-curated-accesscontrol">`./data/smartbugs-curated-accesscontrol`</a>
「アクセス制御」バグに分類された、キュレーションされた smartbugs からのコントラクト
([smartbugs github](https://github.com/smartbugs/smartbugs/tree/master/dataset/access_control))
* <a href="./data/smartbugs-curated-reentrancy">`./data/smartbugs-curated-reentrancy`</a>
「再入」バグに分類された、キュレーションされた smartbugs からのコントラクト
([smartbugs github](https://github.com/smartbugs/smartbugs/tree/master/dataset/reentrancy))
#### テスト
次のデータセットには、ファザーの機能をテストするための基本的な合成テストコントラクトが含まれています:
* <a href="./data/tests">`./data/tests`</a> ファザーの基本的な機能をチェックする、複数のソースから収集された基本的なテスト。すべて `selfdestruct` オラクルを使用しています。
* <a href="./data/tests-not-vuln">`./data/tests-not-vuln`</a> `tests` と同じですが、脆弱として検出されるべきではありません。
* <a href="./data/properties-tests">`./data/properties-tests`</a> プロパティベースのファジング用のテスト
* <a href="./data/assertions-tests">`./data/assertions-tests`</a> アサーションのファジング用のテスト
## ファジングの詳細
実際のファザー(この場合は AFL++)を起動するために、ラッパースクリプトを使用します。これは `efcfuzz` ランチャーを使用すると自動的に行われます。```bash
$ cd data/tests
$ make SimpleDAO.evm2cpp
$ cd ../../src/eEVM/
$ env AFL_BENCH_UNTIL_CRASH=1 ./fuzz/launch-aflfuzz.sh SimpleDAO
tmux と tmuxp がインストールされている場合、開発と検査にはスクリプトの 対話型バージョンが役立つかもしれません:```bash $ ./fuzz/interactive-aflfuzz.sh -b SimpleDAO
This will then compile and fuzz for quite a while. You can then
`cd ./fuzz/out/SimpleDAO*` to view the fuzzing results. Our wrapper scripts do
some extra work on top of launching the `afl-fuzz` program, i.e., mostly
post-processing the results. Additionally it will generated several convenience
scripts to analyze the generated test cases.
* `./a.sh` - Print human-readable form of a test case, wrapper around
`efuzzcaseanalyzer`.
* `./r.sh` - run a test case with the same settings as when the fuzzer was run.
* `./m.sh` - minimize a testcase with the same settings as when the fuzzer was
run.
* `./c.sh` - analyze the "chain" of test cases that lead to the given test
case. Useful for analyzing/optimizng the fuzzer. You can quickly see, which
test case was produced by which chain of mutations on which queue entries.
Requires `fzf`.
There are also some other convenience reports, such as
* `./bugs` and `./bugtypes` that summarize any bugs that were identified.
* `./crashes_min`, which contains minimized crashes of all the `afl-fuzz`
instances.
**View EVM Basic Block Code Coverage**```bash
$ cat coverage-percent-all.evmcov
70.73170731707317
The script fuzz/evm-bb-coverage.sh は、AFL出力ディレクトリを指定して基本ブロックカバレッジを計算します。ハーネスはオプションで基本ブロックのトレースをダンプでき、そのトレースは evm2cpp が出力する基本ブロックのリスト(すなわち eEVM/contracts/ 内の .bb_list ファイル)と比較されます。
デフォルトでは、デフォルトの汎用シード(eEVM/fuzz/generic_seeds を参照)が生成するカバレッジも計算します:```bash
$ cat coverage-percent-seeds.evmcov
10.5890
カバーされた基本ブロックのリストは `all.evmcov` ファイルに保存されます。
**生成されたテストケースのサマリーを表示**
`efuzzcaseanalyzer` は、生成されたテストケースを表示/要約するために使用できます。
例:```
$ efuzzcaseanalyzer -a ./contract.abi -s ./crashes_min/
Transactions Sequences:
--------------------------------------------------------------
TX [🪙]
deposit()[🪙];
withdraw(uint256)[↕️ ↩️ ];
withdraw(uint256)[];
--------------------------------------------------------------
Number of fuzzcases: 1
Average number of TXs: 3
Number of unique TX sequences: 1
Number of unique TX sequences (consecutive deduplicated): 1
サマリーは通常、ファイル crashes_tx_summary と queue_tx_summary に保存されますが、後者は少し冗長になることがあります。
単一のクラッシュするテストケースを分析する``` $ ./a.sh default/crashes/id:000000,...
$ efuzzcaseanalyzer -a ./contract.abi default/crashes/id:000000,... Block header: number: 0 difficulty: 0 gas_limit: 0 timestamp: 0 initial_ether: 0
TX with tx_sender: 54 (selector); call_value: 0x0; length: 36; block+=1; #returns=0 func: withdraw(uint256) input: { Uint(80), } TX with tx_sender: 238 (selector); call_value: 0x246ddf979; length: 4; block+=1; #returns=0 func: deposit() input: { } TX with tx_sender: 153 (selector); call_value: 0x3860e6373; length: 4; block+=1; #returns=0 func: deposit() input: { } TX with tx_sender: 166 (selector); call_value: 0x0; length: 36; block+=1; #returns=1 func: withdraw(uint256) input: { Uint(37000000000000000000), } returns: return val: 1; allows reenter: 2; data: 0x0000000000000000000000000000000000000000000000000000000000000001 TX with tx_sender: 166 (selector); call_value: 0x0; length: 36; block+=1; #returns=0 func: withdraw(uint256) input: { Uint(37000000000000000000), }
そして、fuzz targetの実際の結果を取得するには、次のように実行できます:```
$ ./r.sh default/crashes/id:000000,sig:06,src:000000+000010,time:1584,EM-________SAO_______AD
# roughly equivalent to running
$ env EVM_DEBUG_PRINT=1 ./build/fuzz_multitx default/crashes/id:000000,sig:06,src:000000+000010,time:1584,EM-________SAO_______AD
[...]
account 0xc4b803ea8bc30894cc4672a9159ca000d377d9a3 has balance 0x100000000000000000000000000000001bc16d67562e80000( > 0x1000000000000000000000000000000000000000000000000)
Aborted (core dumped)
これにより、多くの冗長な出力が得られます。コントラクトの実行トレースの一部や、ハーネスが行う残高チェックの結果も含まれます。
クラッシュする入力を最小化する
クラッシュする入力には、ランダム化されたテストアプローチのため、無関係なトランザクションが含まれていることがよくあります。これは、クラッシュする入力に対して最小化を実行することで軽減できます(つまり、クラッシュが発生し続ける限り入力を削減します)。
クラッシュしない入力を最小化したい場合は、-M フラグを使用して、最小化基準としてカバレッジに応じた最小化を有効にできます。
次のコマンドはテストケースを削減し、ファイルを上書きします:``` $ efuzzcaseminimizer -oa ./contract.abi ./build/fuzz_multitx ./default/crashes/id:000000,sig:06,src:000000+000010,time:1584,EM-________SAO_______AD
[..]
=== Before minimizing: === Block header: number: 0 difficulty: 0 gas_limit: 0 timestamp: 0 initial_ether: 0
TX with tx_sender: 54 (selector); call_value: 0x0; length: 36; block+=1; #returns=0 func: withdraw(uint256) input: { Uint(80), } TX with tx_sender: 238 (selector); call_value: 0x246ddf979; length: 4; block+=1; #returns=0 func: deposit() input: { } TX with tx_sender: 153 (selector); call_value: 0x3860e6373; length: 4; block+=1; #returns=0 func: deposit() input: { } TX with tx_sender: 166 (selector); call_value: 0x0; length: 36; block+=1; #returns=1 func: withdraw(uint256) input: { Uint(37000000000000000000), } returns: return val: 1; allows reenter: 2; data: 0x0000000000000000000000000000000000000000000000000000000000000001 TX with tx_sender: 166 (selector); call_value: 0x0; length: 36; block+=1; #returns=0 func: withdraw(uint256) input: { Uint(37000000000000000000), }
=== After minimizing: === Block header: number: 0 difficulty: 0 gas_limit: 0 timestamp: 0 initial_ether: 15133991795
TX with tx_sender: 4 (selector); call_value: 0x246ddf979; length: 4; block+=0; #returns=0 func: deposit() input: { } TX with tx_sender: 4 (selector); call_value: 0x0; length: 36; block+=1; #returns=1 func: withdraw(uint256) input: { Uint(37000000000000000000), } returns: return val: 1; allows reenter: 2; data: 0x TX with tx_sender: 0 (selector); call_value: 0x0; length: 36; block+=1; #returns=0 func: withdraw(uint256) input: { Uint(37000000000000000000), }
## テストケース形式の読み方
テストケース形式はファジングを想定して設計されており、読むのはそれほど簡単ではありません。注意すべきいくつかの微妙な点があります。
* テストケース形式は、実行可能なトランザクションの「キュー」と見なされます。何らかの問題が発生すると、テストケースの処理はそこで停止します。これには以下が含まれます:
* トランザクションがリバートした場合。
* ハーネスコードでエラーが発生した場合。
* 何らかのバグがトリガーされ検出された場合。
その結果、出力されたテストケースは実際に実行される内容と必ずしも対応しません。末尾に実行されないトランザクションが存在することがあります。詳細出力を確認し、ミニマイザを使用してそれらを除去してください!
* 同様に、`returns` が多すぎたり、余分な `reenter` フラグが付いたりすることがあります。これらを取り除くには、テストケースミニマイザを使用してください。
* コントラクトがリエントランシーされるのは、リエントランシーを実行するはずのトランザクションの後に、リスト内に別のトランザクションが続く場合(つまり、キューに後続のエントリがもう1つある場合)のみです。
* `reenter` フラグに何らかの値が設定されていても、必ずしもコントラクトがリエントランシーされるとは限りません。ハーネスコードが可能な場合にそれを試みるというだけです。例えば、コントラクトがコールを行わない場合、リエントランシーする可能性がないため `reenter` フラグは無視されます。通常、ミニマイザは余分な reenter フラグを削除します。
一般に、これらの問題の多くはテストケースミニマイザを使用することで解消されます。そのため、生成されたテストケースを分析する前には、常にこれを使用することをお勧めします。
## 既知の誤検知
EF/CF でコントラクトをファジングする際に繰り返し発生すると思われる、いくつかのタイプの誤検知を確認しました。
* 設計上 Ether を支払うコントラクト。EF/CF の Ether-gains バグオラクルは、これらのコントラクトが設計どおりに動作しているにもかかわらず、脆弱であるとして検出します:
* ギャンブルコントラクト: 多くのギャンブルコントラクトには何らかの形のランダム性が含まれており、これはイーサリアムではすでに悪い慣行です。ただし、一部のギャンブルコントラクトは、例えば次のブロックハッシュの最後の2桁などを推測することを強制するように実装されています。これはコミットメントスキームを使用して実現できます。つまり、最初のトランザクションでユーザーが特定の値にコミットし、2番目のトランザクションで推測と勝利時の支払いがトリガーされます。これらのコントラクトは通常、実際のブロックチェーンでは悪用できません。しかし、EF/CF のシミュレートされたブロックチェーンでは、ファザーは2番目のトランザクションの値が観測された後にコミットメントを変更できます。この事実は、EF/CF がより良いコードカバレッジを達成するために重要です。しかし、その一方で、ファザーがギャンブルコントラクトで決定的に勝利できる TX シーケンスを EF/CF が簡単に特定できるようにもなります。
* 利息を支払うコントラクト: Ether を投資して、`N` ブロックごとに一定割合の利息を支払う小さなコントラクトが多数存在します。EF/CF のシミュレートされた攻撃者は `N` ブロック待機し、その後利息の支払いを受け取ることができるため、これも Ether-gains バグオラクルによって検出されます。
* エアドロップ: 一部のトークンコントラクトは、エアドロップを有効にしています。つまり、一定の上限に達するまで、要求した人にトークンをただで配布します。例えば、エアドロップは多くの場合、短期間だけ有効です。このようなコントラクトが EF/CF 内にデプロイされると、エアドロップがまだ有効になるように時間制限が設定されている可能性が高くなります。エアドロップされたトークンを再度売却できる場合、EF/CF は Ether-gains として検出します。
* 制御可能な `DELEGATECALL` の早期報告: 現在、制御可能な delegatecall は呼び出されるとすぐに報告されます。しかし、呼び出し元が任意のアドレスへの delegatecall を意図的に実行できるようにする関数を備えたコントラクトが複数存在します。ただし、これらの関数は delegatecall の直後にトランザクションを無条件にリバートします。これにより、状態の更新や ether の転送が永続化するのを防ぎます。通常、このような関数は関数名に "simulate" のような語が含まれているため、簡単に見つけられます。
* EF/CF では、報告を実行終了まで延期することで修正できる可能性があります。ただし、これによりバグオラクルがかなり複雑になります。
* 現在、これを修正する予定はありません。
* イニシャライザが呼び出し可能: ブロックチェーンからエクスポートされたコントラクトをファジングする際、EF/CF はコントラクトがすでに初期化されているにもかかわらず、イニシャライザ関数を呼び出せる場合があることを確認しました。通常、これはリバートを引き起こすはずですが、EF/CF の EVM 環境ではそうなりません。イニシャライザを再度呼び出すと、例えばイニシャライザが *owner* 変数などを設定するため、簡単に Ether を獲得できることがよくあります。
* この問題の根本原因はまだ特定できていません。ただし、イニシャライザ関数は通常 `initializer`、`init` などと呼ばれるため、通常は簡単に見つけられます。
## よくある落とし穴
ある程度使いやすくするよう最善を尽くしましたが、これはまだ研究プロトタイプです。壊れることを想定してください。以下に、私たちが確認したよくある問題をいくつか示します:
* *Q: `TOKENPASTE` マクロが原因で奇妙なコンパイルエラーが発生します。*
A: これは多くの場合、`efcfuzz` が誤ったコントラクト名を推測したときに発生します(つまり、抽象コントラクトを推測した場合です)。`--name YourContract` を渡して対象のコントラクトを指定してみてください。