atheris について
AtherisはlibFuzzerを基盤とする、カバレッジガイド型のPythonファザーです。純粋なPythonコードとPython C拡張を対象とし、AddressSanitizerにも対応してメモリ破損の検出を支援するため、ライブラリやネイティブ拡張をテストする開発者に適しています。
主な機能
- 01Pythonコードのカバレッジガイド型ファジング
- 02Python C拡張のファジング
- 03AddressSanitizerとの統合
- 04FuzzedDataProviderによる構造化入力生成
- 05コーパスの管理と最小化
活用シーン
- 01Python関数やライブラリのファジング
- 02C拡張のメモリエラー検査
- 03シードコーパスを用いたパーサーのテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trailofbits/ skills --skill atherisインストール後、Claude Code で/atherisを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples.mdstructured-input.mdagents/openai.yamlassets/trail-of-bits-mark.svg詳細
Atheris
Atheris は libFuzzer を基盤とする、カバレッジガイド型の Python ファザーです。純粋な Python コードと Python C 拡張の両方をファジングでき、AddressSanitizer が統合されているため、メモリ破損の問題を検出できます。
使用する場面
| ファザー | 最適な用途 | 複雑さ |
|---|---|---|
| Atheris | Python コードと C 拡張 | 低~中 |
| Hypothesis | プロパティベーステスト | 低 |
| python-afl | AFL スタイルのファジング | 中 |
次の場合は Atheris を選択してください:
- カバレッジガイドを使って純粋な Python コードをファジングする場合
- メモリ破損の有無を調べるために Python C 拡張をテストする場合
- libFuzzer エコシステムとの統合が必要な場合
- AddressSanitizer のサポートが必要な場合
クイックスタート
import sys
import atheris
@atheris.instrument_func
def TestOneInput(data: bytes):
if len(data) == 4:
if data[0] == 0x46: # "F"
if data[1] == 0x55: # "U"
if data[2] == 0x5A: # "Z"
if data[3] == 0x5A: # "Z"
raise RuntimeError("You caught me")
def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()
実行:
uv run python fuzz.py
インストール
Atheris は 32 ビットおよび 64 ビットの Linux と macOS をサポートしています。管理が簡単で、多くの場合より高速な Linux でのファジングをおすすめします。
前提条件
- Python 3.7 以降
- 最近の clang(できれば最新リリース)
- Docker を使用する場合:Docker Desktop
Linux/macOS
uv init --bare # once, if the harness directory is not yet a uv project
uv add atheris
Docker 環境(推奨)
すべての依存関係が設定された、すぐに使用できる Linux 環境を用意するには:
# https://hub.docker.com/_/python
ARG PYTHON_VERSION=3.11
FROM python:$PYTHON_VERSION-slim-bookworm
RUN python --version
RUN apt update && apt install -y \
ca-certificates \
wget \
&& rm -rf /var/lib/apt/lists/*
# LLVM builds version 15-19 for Debian 12 (Bookworm)
# https://apt.llvm.org/bookworm/dists/
ARG LLVM_VERSION=19
RUN echo "deb http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" > /etc/apt/sources.list.d/llvm.list
RUN echo "deb-src http://apt.llvm.org/bookworm/ llvm-toolchain-bookworm-$LLVM_VERSION main" >> /etc/apt/sources.list.d/llvm.list
RUN wget -qO- https://apt.llvm.org/llvm-snapshot.gpg.key > /etc/apt/trusted.gpg.d/apt.llvm.org.asc
RUN apt update && apt install -y \
build-essential \
clang-$LLVM_VERSION \
&& rm -rf /var/lib/apt/lists/*
ENV APP_DIR "/app"
RUN mkdir $APP_DIR
WORKDIR $APP_DIR
ENV VIRTUAL_ENV "/opt/venv"
RUN python -m venv $VIRTUAL_ENV
ENV PATH "$VIRTUAL_ENV/bin:$PATH"
# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#step-1-compiling-your-extension
ENV CC="clang-$LLVM_VERSION"
ENV CFLAGS "-fsanitize=address,fuzzer-no-link"
ENV CXX="clang++-$LLVM_VERSION"
ENV CXXFLAGS "-fsanitize=address,fuzzer-no-link"
ENV LDSHARED="clang-$LLVM_VERSION -shared"
ENV LDSHAREDXX="clang++-$LLVM_VERSION -shared"
ENV ASAN_SYMBOLIZER_PATH="/usr/bin/llvm-symbolizer-$LLVM_VERSION"
# Allow Atheris to find fuzzer sanitizer shared libs
# https://github.com/google/atheris#building-from-source
RUN LIBFUZZER_LIB=$($CC -print-file-name=libclang_rt.fuzzer_no_main-$(uname -m).a) \
python -m pip install --no-binary atheris atheris
# https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#option-a-sanitizerlibfuzzer-preloads
ENV LD_PRELOAD "$VIRTUAL_ENV/lib/python3.11/site-packages/asan_with_fuzzer.so"
# 1. Skip memory allocation failures for now, they are common, and low impact (DoS)
# 2. https://github.com/google/atheris/blob/master/native_extension_fuzzing.md#leak-detection
ENV ASAN_OPTIONS "allocator_may_return_null=1,detect_leaks=0"
CMD ["/bin/bash"]
ビルドして実行:
docker build -t atheris .
docker run -it atheris
動作確認
python -c "import atheris; print(atheris.__version__)"
ハーネスの作成
純粋な Python 用のハーネス構造
import sys
import atheris
@atheris.instrument_func
def TestOneInput(data: bytes):
"""
Fuzzing entry point. Called with random byte sequences.
Args:
data: Random bytes generated by the fuzzer
"""
# Add input validation if needed
if len(data) < 1:
return
# Call your target function
try:
your_target_function(data)
except ValueError:
# Expected exceptions should be caught
pass
# Let unexpected exceptions crash (that's what we're looking for!)
def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()
FuzzedDataProvider による構造化入力
複数の型付き引数を受け取るターゲットで、ハーネスが data を手作業でスライスすると、ファザーの入力の大半が無駄になります。ミューテーションのたびに、それ以降のすべてのバイトオフセットがずれるためです。atheris.FuzzedDataProvider を使うと、1 つの bytes 入力を型付きの値に分割できます。
fdp = atheris.FuzzedDataProvider(data)
name = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 64))
strict = fdp.ConsumeBool()
すべてのメソッドのリファレンス、固定された読み取り順序のルール、バッファが空になったときに各メソッドが返す値については、structured-input.md を参照してください。
ハーネスのルール
| すること | しないこと |
|---|---|
カバレッジ計測には @atheris.instrument_func を使う | ターゲットコードの計測を忘れる |
| 想定される例外を捕捉する | 無差別にすべての例外を捕捉する |
ライブラリには atheris.instrument_imports() を使う | atheris.Setup() の後にモジュールをインポートする |
| ハーネスの動作を決定的に保つ | 乱数や時刻に依存する動作を使う |
関連項目: 詳細なハーネス作成の手法、複雑な入力を扱うパターン、高度な戦略については、fuzz-harness-writing 技術スキルを参照してください。
純粋な Python コードのファジング
アプリケーションやライブラリのより広い範囲をファジングするには、計測関数を使います。
import atheris
with atheris.instrument_imports():
import your_module
from another_module import target_function
def TestOneInput(data: bytes):
target_function(data)
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
計測オプション:
atheris.instrument_func- 単一の関数を計測するデコレーターatheris.instrument_imports()- インポートされるすべてのモジュールを計測するコンテキストマネージャーatheris.instrument_all()- システム全体のすべての Python コードを計測
Python C 拡張のファジング
Python C 拡張の計測とサニタイザーのサポートには、特定のフラグを使ったコンパイルが必要です。
環境設定
付属の Dockerfile を使う場合、これらはすでに設定されています。ローカル環境で設定する場合:
export CC="clang"
export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXX="clang++"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
export LDSHARED="clang -shared"
例:cbor2 のファジング
ソースから拡張機能をインストールします:
CBOR2_BUILD_C_EXTENSION=1 uv add --no-binary-package cbor2 'cbor2==5.6.4'
--no-binary-package フラグを指定すると、C 拡張がビルド済みの wheel から取得されず、計測を有効にしてローカルでコンパイルされます。この設定を維持するには、pyproject.toml の [tool.uv] の下に no-binary-package = ["cbor2"] を設定してください。そうしないと、後続の uv sync により、計測されていない wheel に気付かないまま置き換えられることがあります。
cbor2-fuzz.py を作成します:
import sys
import atheris
# _cbor2 ensures the C library is imported
from _cbor2 import loads
def TestOneInput(data: bytes):
try:
loads(data)
except Exception:
# We're searching for memory corruption, not Python exceptions
pass
def main():
atheris.Setup(sys.argv, TestOneInput)
atheris.Fuzz()
if __name__ == "__main__":
main()
実行:
uv run python cbor2-fuzz.py
重要: ローカルで実行する場合(Docker 内ではない場合)は、
LD_PRELOADを手動で設定する必要があります。
コーパスの管理
初期コーパスの作成
mkdir corpus
# Add seed inputs
echo "test data" > corpus/seed1
echo '{"key": "value"}' > corpus/seed2
コーパスを指定して実行:
uv run python fuzz.py corpus/
コーパスの最小化
Atheris は libFuzzer のコーパス最小化機能を引き継いでいます:
uv run python fuzz.py -merge=1 new_corpus/ old_corpus/
関連項目: コーパスの作成方法、辞書、シードの選定については、fuzzing-corpus 技術スキルを参照してください。
キャンペーンの実行
基本的な実行
uv run python fuzz.py
コーパスディレクトリを指定して実行
uv run python fuzz.py corpus/
よく使うオプション
# Run for 10 minutes
uv run python fuzz.py -max_total_time=600
# Limit input size
uv run python fuzz.py -max_len=1024
# Run with multiple workers
uv run python fuzz.py -workers=4 -jobs=4
出力の読み方
| 出力 | 意味 |
|---|---|
NEW cov: X | 新しいカバレッジが見つかり、コーパスが拡張されました |
pulse cov: X | 定期的なステータス更新 |
exec/s: X | 1 秒あたりの実行数(スループット) |
corp: X/Yb | コーパスのサイズ:入力 X 件、合計 Y バイト |
ERROR: libFuzzer | クラッシュが検出されました |
サニタイザーの統合
AddressSanitizer(ASan)
付属の Docker 環境を使用する場合、または適切なフラグを指定してコンパイルする場合、AddressSanitizer は自動的に統合されます。
ローカル環境を設定するには:
export CFLAGS="-fsanitize=address,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,fuzzer-no-link"
ASan の動作を設定します:
export ASAN_OPTIONS="allocator_may_return_null=1,detect_leaks=0"
LD_PRELOAD の設定
ネイティブ拡張のファジングでは:
export LD_PRELOAD="$(python -c 'import atheris; import os; print(os.path.join(os.path.dirname(atheris.__file__), "asan_with_fuzzer.so"))')"
関連項目: サニタイザーの詳細な設定、よくある問題、高度なフラグについては、 address-sanitizer および undefined-behavior-sanitizer のテクニックスキルを参照してください。
よくあるサニタイザーの問題
| 問題 | 解決策 |
|---|---|
LD_PRELOAD が設定されていない | LD_PRELOAD をエクスポートし、asan_with_fuzzer.so を指定します |
| メモリ割り当ての失敗 | ASAN_OPTIONS=allocator_may_return_null=1 を設定します |
| リーク検出によるノイズ | ASAN_OPTIONS=detect_leaks=0 を設定します |
| シンボライザーが見つからない | ASAN_SYMBOLIZER_PATH に llvm-symbolizer を設定します |
高度な使い方
ヒントとコツ
| ヒント | 効果 |
|---|---|
atheris.instrument_imports() を早い段階で使用する | すべてのインポートがカバレッジ計測用にインストルメントされます |
小さな max_len から始める | 最初のファジングが速くなり、段階的に増やせます |
| 構造化形式には辞書を使用する | ファザーが形式のトークンを理解しやすくなります |
| 複数のインスタンスを並行して実行する | カバレッジの探索が向上します |
カスタムインストルメンテーション
インストルメントする対象を細かく調整します:
import atheris
# Instrument only specific modules
with atheris.instrument_imports():
import target_module
# Don't instrument test harness code
def TestOneInput(data: bytes):
target_module.parse(data)
パフォーマンスの調整
| 設定 | 影響 |
|---|---|
-max_len=N | 値が小さいほど実行が速くなります |
-workers=N -jobs=N | 並列ファジングにより、カバレッジの獲得が速くなります |
ASAN_OPTIONS=fast_unwind_on_malloc=0 | スタックトレースの精度が向上しますが、実行は遅くなります |
UndefinedBehaviorSanitizer(UBSan)
UBSan を追加して、さらに多くのバグを検出します:
export CFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
export CXXFLAGS="-fsanitize=address,undefined,fuzzer-no-link"
注:コンテナ化された環境を使用する場合は、Dockerfile 内のフラグを変更してください。
実際の使用例
純粋な Python パーサーと HTTP レスポンスパーサーの、完全なハーネスが 2 つ、 examples.md にあります。
トラブルシューティング
| 問題 | 原因 | 解決策 |
|---|---|---|
| カバレッジが増加しない | シードコーパスの質が低い、またはターゲットがインストルメントされていない | より良いシードを追加し、instrument_imports() を確認します |
| 実行が遅い | ASan のオーバーヘッド、または入力が大きい | max_len を小さくし、ASAN_OPTIONS=fast_unwind_on_malloc=1 を使用します |
| インポートエラー | インストルメンテーションより前にモジュールがインポートされた | インポートを instrument_imports() コンテキスト内に移動します |
| ASan の出力なしでセグメンテーション違反が発生する | LD_PRELOAD がない | LD_PRELOAD に asan_with_fuzzer.so のパスを設定します |
| ビルドに失敗する | コンパイラーが間違っている、またはフラグが不足している | CC、CFLAGS、および clang のバージョンを確認します |
関連スキル
テクニックスキル
| スキル | 用途 |
|---|---|
| fuzz-harness-writing | 効果的なハーネスの作成に関する詳細なガイダンス |
| address-sanitizer | ファジング中のメモリエラー検出 |
| undefined-behavior-sanitizer | C 拡張における未定義動作の検出 |
| coverage-analysis | コードカバレッジの測定と改善 |
| fuzzing-corpus | シードコーパスの構築と管理 |
関連ファザー
| スキル | 検討するタイミング |
|---|---|
| hypothesis | 型を考慮した生成によるプロパティベーステスト |
| python-afl | Atheris が利用できない場合の、Python 向け AFL 形式のファジング |
リソース
主な外部リソース
Atheris GitHub リポジトリ 純粋な Python とネイティブ拡張の両方をファジングするための、インストール手順、サンプル、ドキュメントを掲載した公式リポジトリです。
ネイティブ拡張のファジングガイド Python C 拡張向けのコンパイルフラグ、LD_PRELOAD の設定、サニタイザーの構成、トラブルシューティングを網羅した包括的なガイドです。
Python C 拡張を継続的にファジングする CI/CD との統合、ClusterFuzzLite のセットアップ、継続的インテグレーションパイプラインで Python C 拡張をファジングする実例を紹介する、Trail of Bits のブログ記事です。
ClusterFuzzLite の Python 統合 ClusterFuzzLite を使用して Atheris のファジングを CI/CD パイプラインに統合し、ファジングを自動化するためのガイドです。
動画リソース
動画やチュートリアルは、Atheris のメインドキュメントおよび libFuzzer のリソースでご覧いただけます。