
ソフトウェアプロキシからテスト入力を生成してCPU実装をファズし、実際のハードウェア上で実行することで、マイクロアーキテクチャの欠陥やエラータを検出します。
SiliFuzzは、CPUシミュレータや逆アセンブラなどのソフトウェアプロキシをファジングし、蓄積したテスト入力(コーパス と呼ばれます)を実際のCPU上で大規模に実行することでCPUの欠陥を発見するシステムです。SiliFuzzは開発途上であり、詳細は論文を参照してください。
ファジングとは、動的に生成された多数のテスト入力を用いてターゲット(アプリケーションやAPI)をテストする手法です。その目的は、コーナーケースを引き起こすために、これらの入力を可能な限り興味深く多様なものにすることです。言い換えれば、ファジングは複合的なコードカバレッジを最大化することを目指します。コードカバレッジ は、プログラム内でどの基本ブロックが実行されたか、どのパスが取られたかなど、さまざまな意味を持つ場合があります。
SiliFuzzの目的において、プロキシ とは、ターゲットCPUの一部の側面と同様に動作するソフトウェアまたはハードウェアシステムのことです。例えば、CPUエミュレータや逆アセンブラなどです。プロキシは、ターゲットから直接カバレッジ情報を収集できない場合に必要です。
プロキシにファジング技術を適用することで、プロキシ内で興味深い動作を生み出す一連のテスト入力(コーパス)を生成できます。根本的な前提は、これがターゲットでも同様に興味深い動作につながるというものです。詳細はドキュメントを参照してください。
ターゲットのテストに使用される入力の集合はコーパスと呼ばれます。
十分な大きさのコーパスには数百万の入力が含まれ、通常はシャードと呼ばれる複数の重複しないチャンクに分割されます。
SiliFuzzスナップショットは、CPU命令の短いシーケンスと、そのシーケンスを決定的に実行するためのCPUレジスタとメモリの初期状態を記述します。典型的なスナップショットは100バイト未満のコードを含み、マイクロ秒で実行されますが、任意に大きくすることもできます。スナップショットはsilifuzz.proto.Snapshotプロトコルバッファとして保存されます。
スナップショットは通常、ファジングエンジンによって生成された入力から作成されます。CPUテストの目的では、これらの入力は非決定的なスナップショットを排除するためにフィルタリングされます。詳細はドキュメントを参照してください。
終了状態は、スナップショット実行の終了時に存在することが期待されるレジスタとメモリの内容を記述します。スナップショットが異なるCPUマイクロアーキテクチャで異なる動作をする場合、複数の期待される終了状態を持つことになります。
Snapは、Runnerによって簡単にロードおよび実行できるSnapshotのインメモリ表現です。Snapは通常、読み取りランナーによってディスクからロードされます。Snapのディスク上のフォーマットは、ネイティブポインタがオフセットに置き換えられていることを除いて、インメモリのものと基本的に同じです。詳細はこのヘッダを参照してください。このフォーマットはしばしばリロケータブルと呼ばれます。各Snapには正確に1つの期待される終了状態が含まれます。つまり、Snapはマイクロアーキテクチャ固有です。詳細はドキュメントを参照してください。
Runnerは、単一のCPUコアをテストするためのバイナリです。ランナーはコーパスシャードを消費し、その中のランダムなSnapを繰り返し実行して、期待される終了状態に到達するかをチェックします。Runnerはシングルスレッドプロセスです。
オーケストレータは、複数のランナーを駆動するプロセスです。典型的な設定では、オーケストレータは論理CPUコアごとに1つのランナーを継続的に実行し、個々のランナープロセスによって生成された障害を蓄積して報告します。
サポートされているマイクロアーキテクチャの一覧については、このファイルを参照してください。
SiliFuzzはx86_64およびaarch64 Linuxシステムで動作します。Linuxカーネルバージョン5.xおよび6.xでテストされています。それ以前のカーネルバージョンとの互換性は保証されていません。誤検知を避けるために、レガシーvsyscall ABIはオフにする必要があります。
SiliFuzzが発見したバグと欠陥の不完全なリストです。
ロジックバグとは、特定のCPUマイクロアーキテクチャまたはステッピングに固有の無効なCPU動作です。SiliFuzzは以下のバグを特定しています。
(電気的)欠陥とは、1つまたはいくつかのチップでのみ発生する無効なCPU動作です。SiliFuzzは、論文で説明した以下の欠陥を発見しました。
git clone https://github.com/google/silifuzz.git && cd silifuzz
SILIFUZZ_SRC_DIR=`pwd`
./install_build_dependencies.sh # Currently, works for the latest Ubuntu only.
bazel build -c opt @silifuzz//tools:{snap_corpus_tool,fuzz_filter_tool,snap_tool,silifuzz_platform_id,simple_fix_tool_main} \
@silifuzz//runner:reading_runner_main_nolibc \
@silifuzz//orchestrator:silifuzz_orchestrator_main
SILIFUZZ_BIN_DIR=`pwd`/bazel-bin
cd "${SILIFUZZ_BIN_DIR}"
NOTE: ホストシステムを汚染しないようにDockerコンテナを使用できます: docker run -it --tty --security-opt seccomp=unconfined --mount type=bind,source=${SILIFUZZ_SRC_DIR},target=/app ubuntu:noble /bin/bash -c "cd /app && ./install_build_dependencies.sh && bazel build ... && bazel test ..."
Bazelの場合、以下のコマンドを使用します。
cd "${SILIFUZZ_SRC_DIR}"
COV_FLAGS_FILE="$(bazel info output_base)/external/fuzztest+/centipede/clang-flags.txt"
bazel build -c opt --copt=-UNDEBUG --dynamic_mode=off \
--per_file_copt=unicorn/.*@$(xargs < "${COV_FLAGS_FILE}" |sed -e 's/,/\\,/g' -e 's/ /,/g') @//proxies:unicorn_x86_64
bazel build -c opt @fuzztest//centipede:centipede
mkdir -p /tmp/wd
# Fuzz the Unicorn proxy under Centipede 1000 times with parallelism of 30.
"${SILIFUZZ_BIN_DIR}/external/fuzztest+/centipede/centipede" \
--binary="${SILIFUZZ_BIN_DIR}/proxies/unicorn_x86_64" \
--workdir=/tmp/wd \
-j=30 --num_runs=1000
NOTE: ファジングエンジンを効率的に実行する方法については、Centipedeのドキュメントを参照してください。
このヘルパーツールは、実行中のマシンがサポートされているかどうかを確認するためのものです。
$ ${SILIFUZZ_BIN_DIR}/tools/silifuzz_platform_id --short
intel-skylake
NOTE: SiliFuzzのCPU検出ロジックは、サポートされているCPUの特定のデスクトップバリアントを考慮していません。そのような場合、ツールは「Unsupported platform」と報告します。
fuzz_filter_toolは、生の命令をSnap互換のスナップショットに変換します。変換が可能な場合は0を返し、それ以外の場合は1を返します。このインターフェースはCentipedeのinput_filterと互換性があります。
fuzz_filter_tool raw_input_sequence
raw_input_sequenceファイルには生の命令が含まれており、InstructionsToSnapshotを使用してSnapshot形式に変換されます。
使用例:
# INC EAX
echo -en '\xFF\xC0' > /tmp/inc_eax && ./tools/fuzz_filter_tool /tmp/inc_eax
echo $?
0
snap_toolはバイナリのSnapshotプロトを検査および操作します。オプションで生の命令をロードしてSnapshotに変換することもできます。
echo -en '\xFF\xC0' > /tmp/inc_eax
./tools/snap_tool --raw print /tmp/inc_eax
Metadata:
Id: inc_eax
Architecture: x86_64 Linux
Completeness: complete
Registers:
gregs (non-0 only)
rax = 0x20000000
....
simple fix toolは、Centipedeからのファジング結果を受け取り、生の命令を終了状態のないスナップショットに変換し、スナップショットに終了状態を追加し、最後にスナップショットをシャード化されたリロケータブルスナップコーパスにパッケージ化します。
現在、これは単一ホスト上で再起動不可能なプロセスとして実行され、すべてがメモリに置かれるため、処理できるコーパスのサイズはホストの利用可能メモリによって制限されます。終了状態はホスト上で生成されるため、結果のコーパスは単一アーキテクチャになります。
実験的: ハッシュテストは、ランダムに生成された命令にエントロピーを注入し、結果の出力を可能な限り効率的にキャプチャするランダム化された構造化テストです。このアプローチは、適切な入力で適切な命令を呼び出すことで、かなりの割合の欠陥を検出できるという観察に基づいています。ハッシュテストはこの単純なクラスの欠陥を積極的にターゲットにして、Silifuzzの実験的な比較ポイントを提供します。現在はx86_64のみがサポートされています。
たとえば、Skylakeプロセッサでサポートされている命令を含む30kのハッシュテストスナップショットを/tmp/hashtestディレクトリに生成したい場合は、次のコマンドを実行できます。
mkdir -p /tmp/hashtest && bazel run -c opt @silifuzz//fuzzer/hashtest:hashtest_generator -- --platform=intel-skylake -n 30000 --outdir /tmp/hashtest
このドキュメントの残りの部分はハウツー形式で構成されており、各質問が典型的なユースケースを説明しています。質問の進行は、達成しようとしているタスクの複雑さの増加を表しています。各ステップでは通常、前のステップで得られた理解または成果物(場合によっては両方)が必要です。
NOTE: このドキュメントはx86_64ホスト/ターゲットCPUを前提としています。正確な出力はCPUベンダー/ステッピングなどや環境(例: Docker/KVM)によって異なる場合があります。
警告: 以下の手順の多くは、実行中のユーザーの権限で任意のバイナリコードを実行します。ツールはseccomp(2)を使用してコードをサンドボックス化するよう最善を尽くします。自己責任で使用してください。
# INC EAX
$ echo -en '\xFF\xC0' > /tmp/inc_eax
$ ./tools/snap_tool --raw --out=/tmp/inc_eax.pb make /tmp/inc_eax
# CPUID
$ echo -en '\x0F\xA2' > /tmp/cpuid
$ ./tools/snap_tool --raw --out=/tmp/cpuid.pb make /tmp/cpuid
<error log omitted>
Could not load snapshot: INTERNAL: Tracing failed: Banned instruction: CPUID
NOTE: 非決定的な結果を避けるため、SiliFuzzのさまざまな部分が特定のクラスの命令(例: 上記のCPUID)を除外しています。
$ ./tools/snap_tool print /tmp/inc_eax.pb
Metadata:
Id: inc_eax
Architecture: x86_64 Linux
Completeness: complete
Registers:
gregs (non-0 only):
rax = 0x20000000
rip = 0xeb85c12b000
<omitted>
End states (1):
Endpoint:
Instruction address: 0xeb85c12b002
Platforms:
intel-skylake
Registers (diff vs snapshot's initial values):
gregs (modified only):
rax = 0x20000001
rip = 0xeb85c12b002
<omitted>
RAXレジスタの終了状態の値が0x20000001(0x20000000+1、これはまさにINC EAXが行うことです)であることに注目してください。また、RIP値が元の+2(INC EAX命令のサイズ)であることにも注目してください。
$ ./tools/snap_tool play /tmp/inc_eax.pb
Snapshot played successfully.
注: コーパスを生成するには、ターゲットプラットフォームを指定する必要があります。この例では、結果のコーパスは生成されたプラットフォームをターゲットにします。
$ cd "${SILIFUZZ_BIN_DIR}"
$ PLATFORM_ID=$(./tools/silifuzz_platform_id --short)
$ ./tools/snap_tool generate_corpus /tmp/inc_eax.pb \
--target_platform="${PLATFORM_ID}" > /tmp/inc_eax.corpus
# Will play the same "INC EAX" snapshot 1M times
$ ./runner/reading_runner_main_nolibc /tmp/inc_eax.corpus
gdbでプロセスを検査できます:
$ gdb ./runner/reading_runner_main_nolibc
(gdb) b RestoreUContextNoSyscalls
(gdb) run /tmp/inc_eax.corpus
Starting program: .../reading_runner_main_nolibc /tmp/inc_eax.corpus
Breakpoint 1, 0x0000456700010598 in RestoreUContextNoSyscalls ()
(gdb) x/i 0xeb85c12b000 # same as the rip value produced by snap_tool print above
0xeb85c12b000: inc %eax
NOTE: このステップは、前述の「Unicornターゲットのファジング」のステップに依存しています。
ファジング結果のcorpus.*を、現在のアーキテクチャ用の10シャードの実行可能なコーパスに変換します。
cd "${SILIFUZZ_BIN_DIR}"
"${SILIFUZZ_BIN_DIR}/tools/simple_fix_tool_main" \
--num_output_shards=10 \
--output_path_prefix=/tmp/wd/runnable-corpus \
--runner="${SILIFUZZ_BIN_DIR}/runner/reading_runner_main_nolibc" \
/tmp/wd/corpus.*
コーパスシャードは/tmp/wd/runnable-corpus.*に配置されます。
$ ./tools/snap_corpus_tool list_snaps /tmp/inc_eax.corpus
...
I0000 00:00:1661887744.019079 4074672 snap_corpus_tool.cc:155] inc_eax
NOTE: これは現時点では非常に基本的なツールであり、いくつかのコマンドしか提供していません。
# Will play the same "INC EAX" snapshot on CPU#1 10k times.
$ ./runner/reading_runner_main_nolibc \
--cpu=1 --num_iterations=10000 /tmp/inc_eax.corpus
オーケストレータは、--shard_list_file引数で渡されたファイルにリストされているすべてのシャードを循環します。
$ ls -1 /tmp/wd/runnable-corpus.* > /tmp/wd/shard_list
$ echo 'version: "local_corpus"' > /tmp/wd/corpus_metadata
# Will repeatedly run the corpus on all available CPU cores for 30s using
# /tmp/wd/runnable-corpus.* selected randomly.
$ ${SILIFUZZ_BIN_DIR}/orchestrator/silifuzz_orchestrator_main --duration=30s \
--runner=${SILIFUZZ_BIN_DIR}/runner/reading_runner_main_nolibc \
--shard_list_file=/tmp/wd/shard_list \
--corpus_metadata_file=/tmp/wd/corpus_metadata
NOTE: オーケストレータは、.xzで終わるファイルからXZ圧縮されたコーパスシャードをロードすることもできます。