关于 atheris
Atheris 是基于 libFuzzer 的覆盖率引导型 Python 模糊测试工具,可测试纯 Python 代码和 Python C 扩展,并集成 AddressSanitizer。它适用于需要覆盖率反馈或检查扩展内存错误的开发者。
主要功能
- 01对 Python 代码进行覆盖率引导测试
- 02测试 Python C 扩展
- 03集成 AddressSanitizer
- 04使用 FuzzedDataProvider 生成结构化输入
- 05管理和精简测试语料
适用场景
- 01模糊测试 Python 函数或库
- 02检查 C 扩展的内存错误
- 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__)"
编写测试 Harness
纯 Python 的 Harness 结构
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 处理结构化输入
如果目标函数接收多个有类型的参数,而 Harness 手动切分 data,模糊测试器的大部分输入就会被浪费,因为每次变异都会改变其后所有内容的字节偏移量。atheris.FuzzedDataProvider 会将一个 bytes 输入拆分为多个有类型的值:
fdp = atheris.FuzzedDataProvider(data)
name = fdp.ConsumeUnicodeNoSurrogates(fdp.ConsumeIntInRange(0, 64))
strict = fdp.ConsumeBool()
有关完整的方法参考、固定读取顺序规则,以及缓冲区耗尽后每个方法的返回值,请参阅 structured-input.md。
Harness 规则
| 应做 | 不要做 |
|---|---|
使用 @atheris.instrument_func 进行覆盖率插桩 | 忘记对目标代码进行插桩 |
| 捕获预期的异常 | 不加区分地捕获所有异常 |
对库使用 atheris.instrument_imports() | 在 atheris.Setup() 之后导入模块 |
| 保持 Harness 的确定性 | 使用随机性或基于时间的行为 |
另请参阅: 有关详细的 Harness 编写技巧、处理复杂输入的模式和高级策略,请参阅 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 扩展需要使用特定标志进行编译,以支持插桩和 Sanitizer。
环境配置
如果使用提供的 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 | 每秒执行次数(吞吐量) |
corp: X/Yb | 语料库大小:X 个输入,共 Y 字节 |
ERROR: libFuzzer | 检测到崩溃 |
Sanitizer 集成
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"))')"
另请参阅: 有关 Sanitizer 的详细配置、常见问题和高级标志, 请参阅 address-sanitizer 和 undefined-behavior-sanitizer 技术技能。
常见 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 中的标志。
实际示例
examples.md 中提供了两个完整的测试桩——一个纯 Python 解析器和一个 HTTP 响应解析器。
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 覆盖率没有增加 | 初始语料库质量差,或目标未插桩 | 添加更好的初始样本,验证 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 扩展进行模糊测试 Trail of Bits 博客文章,介绍 CI/CD 集成、ClusterFuzzLite 设置,以及在持续集成流水线中对 Python C 扩展进行模糊测试的实际示例。
ClusterFuzzLite Python 集成 介绍如何使用 ClusterFuzzLite 将 Atheris 模糊测试集成到 CI/CD 流水线,实现自动化的持续模糊测试。
视频资源
Atheris 主文档和 libFuzzer 资源中提供了视频和教程。