mcpField
A
atheris

atheris

github.com/trailofbits测试与质量7.9k5.2k 次安装今天更新
代码仓库

关于 atheris

Atheris 是基于 libFuzzer 的覆盖率引导型 Python 模糊测试工具,可测试纯 Python 代码和 Python C 扩展,并集成 AddressSanitizer。它适用于需要覆盖率反馈或检查扩展内存错误的开发者。

主要功能

  1. 01对 Python 代码进行覆盖率引导测试
  2. 02测试 Python C 扩展
  3. 03集成 AddressSanitizer
  4. 04使用 FuzzedDataProvider 生成结构化输入
  5. 05管理和精简测试语料

适用场景

  1. 01模糊测试 Python 函数或库
  2. 02检查 C 扩展的内存错误
  3. 03为解析器准备种子语料并持续测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/trailofbits/skills --skill atheris

安装后在 Claude Code 中输入/atheris确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

examples.md
structured-input.md
agents/
openai.yaml
assets/
trail-of-bits-mark.svg

详细介绍

Atheris

Atheris 是一个基于 libFuzzer 构建的覆盖率引导型 Python 模糊测试器。它支持对纯 Python 代码和 Python C 扩展进行模糊测试,并集成了 AddressSanitizer,可用于检测内存损坏问题。

适用场景

模糊测试器最适合复杂度
AtherisPython 代码和 C 扩展低至中
Hypothesis基于属性的测试低
python-aflAFL 风格的模糊测试中

在以下情况下选择 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 上进行模糊测试,因为环境管理更简单,而且通常速度更快。

先决条件
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-aflAtheris 不可用时,对 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 资源中提供了视频和教程。

评论

支持 Markdown