mcpField
L
libfuzzer

libfuzzer

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

关于 libfuzzer

libFuzzer 技能指南介绍 LLVM 项目的进程内覆盖引导模糊测试器,面向使用 Clang 测试 C/C++ 项目的开发者。指南涵盖测试入口编写、编译、语料管理、运行及覆盖率分析。

主要功能

  1. 01编写 LLVMFuzzerTestOneInput 测试入口
  2. 02使用 Clang 和 Sanitizer 编译
  3. 03创建与最小化语料
  4. 04配置字典、输入长度和超时
  5. 05运行多核任务并分析覆盖率

适用场景

  1. 01为 C/C++ 解析器编写模糊测试
  2. 02用 AddressSanitizer 检查内存错误
  3. 03复现崩溃并缩减测试语料

安装

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

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

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

附带文件agents/openai.yamlassets/trail-of-bits-mark.svg

详细介绍

libFuzzer

libFuzzer 是 LLVM 项目的一款进程内、覆盖率引导型模糊测试工具。由于简单易用,并且与 LLVM 工具链集成,它是对 C/C++ 项目进行模糊测试的推荐起点。虽然 libFuzzer 自 2022 年底以来仅处于维护模式,但它比其他替代方案更容易安装和使用,支持范围广泛,并且在可预见的未来会继续维护。

适用场景

模糊测试工具最适合复杂度
libFuzzer快速设置、单项目模糊测试低
AFL++多核模糊测试、多样化变异中
LibAFL自定义模糊测试工具、研究项目高
Honggfuzz基于硬件的覆盖率中

选择 libFuzzer 的情况:

  • 需要为 C/C++ 代码进行简单、快速的设置
  • 项目使用 Clang 编译
  • 初期使用单核模糊测试即可满足需求
  • 后续可以迁移到 AFL++(两者的测试桩兼容)

**注意:**为 libFuzzer 编写的模糊测试桩与 AFL++ 兼容,因此如果需要更高级的功能(例如更好的多核支持),迁移会很容易。

快速开始

#include <stdint.h>
#include <stddef.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // Validate input if needed
    if (size < 1) return 0;

    // Call your target function with fuzzer-provided data
    my_target_function(data, size);

    return 0;
}

编译并运行:

clang++ -fsanitize=fuzzer,address -g -O2 harness.cc target.cc -o fuzz
mkdir corpus/
./fuzz corpus/

安装

前置条件
  • LLVM/Clang 编译器(包含 libFuzzer)
  • 用于覆盖率分析的 LLVM 工具(可选)
Linux(Ubuntu/Debian)
apt install clang llvm

如需最新的 LLVM 版本:

# Add LLVM repository from apt.llvm.org
# Then install specific version, e.g.:
apt install clang-18 llvm-18
macOS
# Using Homebrew
brew install llvm

# Or using Nix
nix-env -i clang
Windows

通过 Visual Studio 安装 Clang。请参阅 Microsoft 的文档了解设置说明。

**建议:**如果条件允许,请在本地 x86_64 VM 上进行模糊测试,或在 DigitalOcean、AWS 或 Hetzner 上租用一台。Linux 对 libFuzzer 的支持最佳。

验证
clang++ --version
# Should show LLVM version information

编写测试桩

测试桩结构

测试桩是模糊测试工具的入口点。libFuzzer 会使用不同的输入反复调用 LLVMFuzzerTestOneInput 函数。

#include <stdint.h>
#include <stddef.h>

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // 1. Optional: Validate input size
    if (size < MIN_REQUIRED_SIZE) {
        return 0;  // Reject inputs that are too small
    }

    // 2. Optional: Convert raw bytes to structured data
    // Example: Parse two integers from byte array
    if (size >= 2 * sizeof(uint32_t)) {
        uint32_t a = *(uint32_t*)(data);
        uint32_t b = *(uint32_t*)(data + sizeof(uint32_t));
        my_function(a, b);
    }

    // 3. Call target function
    target_function(data, size);

    // 4. Always return 0 (non-zero reserved for future use)
    return 0;
}
测试桩规则
应做事项不应做事项
处理所有类型的输入(空、大型、格式错误)调用 exit()——这会停止模糊测试进程
返回前等待所有线程结束留下仍在运行的线程
保持测试桩快速、简单添加过多日志或复杂逻辑
保持确定性使用随机数生成器或读取 /dev/random
在每次运行之间重置全局状态依赖之前执行时的状态
使用范围狭窄、目标明确的测试目标在一个测试桩中混合无关的数据格式(PNG + TCP)

原因:

  • **速度很重要:**目标是每个核心每秒执行数百至数千次
  • **可复现性:**模糊测试完成后,崩溃必须能够复现
  • **隔离性:**每次执行都应相互独立
使用 FuzzedDataProvider 处理复杂输入

对于复杂输入(字符串、多个参数),请使用 FuzzedDataProvider 助手类:

#include <stdint.h>
#include <stddef.h>
#include "FuzzedDataProvider.h"  // From LLVM project

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    FuzzedDataProvider fuzzed_data(data, size);

    // Extract structured data
    size_t allocation_size = fuzzed_data.ConsumeIntegral<size_t>();
    std::vector<char> str1 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);
    std::vector<char> str2 = fuzzed_data.ConsumeBytesWithTerminator<char>(32, 0xFF);

    // Call target with extracted data
    char* result = concat(&str1[0], str1.size(), &str2[0], str2.size(), allocation_size);
    if (result != NULL) {
        free(result);
    }

    return 0;
}

从 LLVM 仓库下载 FuzzedDataProvider.h。

交错模糊测试

使用单个测试桩测试多个相关函数:

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if (size < 1 + 2 * sizeof(int32_t)) {
        return 0;
    }

    uint8_t mode = data[0];
    int32_t numbers[2];
    memcpy(numbers, data + 1, 2 * sizeof(int32_t));

    // Select function based on first byte
    switch (mode % 4) {
        case 0: add(numbers[0], numbers[1]); break;
        case 1: subtract(numbers[0], numbers[1]); break;
        case 2: multiply(numbers[0], numbers[1]); break;
        case 3: divide(numbers[0], numbers[1]); break;
    }

    return 0;
}

**另请参阅:**如需了解详细的测试桩编写技巧、处理复杂输入的模式、 结构感知模糊测试以及基于 protobuf 的模糊测试,请参阅 fuzz-harness-writing 技术技能。

编译

基本编译

关键标志是 -fsanitize=fuzzer,它会:

  • 链接 libFuzzer 运行时(提供 main 函数)
  • 启用用于跟踪覆盖率的 SanitizerCoverage 插桩
  • 禁用 memcmp 等内置函数
clang++ -fsanitize=fuzzer -g -O2 harness.cc target.cc -o fuzz

标志说明:

  • -fsanitize=fuzzer:启用 libFuzzer
  • -g:添加调试符号(有助于分析崩溃)
  • -O2:生产级优化(推荐用于模糊测试)
  • -DNO_MAIN:如果代码中有 main 函数,则定义此宏
使用 Sanitizer

AddressSanitizer(推荐):

clang++ -fsanitize=fuzzer,address -g -O2 -U_FORTIFY_SOURCE harness.cc target.cc -o fuzz

使用多个 Sanitizer:

clang++ -fsanitize=fuzzer,address,undefined -g -O2 harness.cc target.cc -o fuzz

另请参阅: 有关 Sanitizer 的详细配置、常见问题、ASAN_OPTIONS 标志 和高级用法,请参阅 address-sanitizer 和 undefined-behavior-sanitizer 技术技能。

构建标志
标志用途
-fsanitize=fuzzer启用 libFuzzer 运行时和插桩
-fsanitize=address启用 AddressSanitizer(内存错误检测)
-fsanitize=undefined启用 UndefinedBehaviorSanitizer
-fsanitize=fuzzer-no-link进行插桩但不链接 Fuzzer(用于库)
-g包含调试符号
-O2生产环境优化级别
-U_FORTIFY_SOURCE禁用强化保护(可能会干扰 ASan)
构建静态库

对于生成静态库的项目:

  1. 使用模糊测试插桩构建库:
export CC=clang CFLAGS="-fsanitize=fuzzer-no-link -fsanitize=address"
export CXX=clang++ CXXFLAGS="$CFLAGS"
./configure --enable-shared=no
make
  1. 将静态库与测试驱动程序链接:
clang++ -fsanitize=fuzzer -fsanitize=address harness.cc libmylib.a -o fuzz
CMake 集成
project(FuzzTarget)
cmake_minimum_required(VERSION 3.0)

add_executable(fuzz main.cc harness.cc)
target_compile_definitions(fuzz PRIVATE NO_MAIN=1)
target_compile_options(fuzz PRIVATE -g -O2 -fsanitize=fuzzer -fsanitize=address)
target_link_libraries(fuzz -fsanitize=fuzzer -fsanitize=address)

使用以下命令构建:

cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ .
cmake --build .

语料库管理

创建初始语料库

为语料库创建目录(可以从空目录开始):

mkdir corpus/

可选但推荐: 提供种子输入(有效的示例文件):

# For a PNG parser:
cp examples/*.png corpus/

# For a protocol parser:
cp test_packets/*.bin corpus/

种子输入的好处:

  • Fuzzer 无需从头开始
  • 更快到达有效的代码路径
  • 显著提高效率
语料库结构

语料库目录包含:

  • 能触发独特代码路径的输入文件
  • 最小化后的版本(libFuzzer 会自动进行最小化)
  • 以内容哈希命名(例如,a9993e364706816aba3e25717850c26c9cd0d89d)
语料库最小化

libFuzzer 会在模糊测试期间自动最小化语料库条目。若要显式执行最小化:

mkdir minimized_corpus/
./fuzz -merge=1 minimized_corpus/ corpus/

这会在 minimized_corpus/ 中创建去重后的最小化语料库。

另请参阅: 有关语料库创建策略、种子选择、特定格式的语料库构建 和语料库维护,请参阅 fuzzing-corpus 技术技能。

运行模糊测试

基本运行
./fuzz corpus/

该命令会持续运行,直到发现崩溃或你停止它(Ctrl+C)。

推荐:崩溃后继续运行
./fuzz -fork=1 -ignore_crashes=1 corpus/

-fork 和 -ignore_crashes 标志(实验性,但已广泛使用)允许模糊测试在发现崩溃后继续运行。

常用选项

控制输入大小:

./fuzz -max_len=4000 corpus/

经验法则:最小真实输入大小的 2 倍。

设置超时:

./fuzz -timeout=2 corpus/

中止运行时间超过 2 秒的测试用例。

使用字典:

./fuzz -dict=./format.dict corpus/

关闭 stdout/stderr(加快模糊测试速度):

./fuzz -close_fd_mask=3 corpus/

查看所有选项:

./fuzz -help=1
多核模糊测试

选项 1:Jobs 和 workers(推荐):

./fuzz -jobs=4 -workers=4 -fork=1 -ignore_crashes=1 corpus/
  • -jobs=4:依次运行 4 轮模糊测试
  • -workers=4:使用 4 个进程并行处理任务
  • 各任务之间共享测试用例

选项 2:Fork 模式:

./fuzz -fork=4 -ignore_crashes=1 corpus/

注意: 如需进行严肃的多核模糊测试,请考虑改用 AFL++、Honggfuzz 或 LibAFL。

重新执行测试用例

重新运行单个崩溃用例:

./fuzz ./crash-a9993e364706816aba3e25717850c26c9cd0d89d

不进行模糊测试,直接测试目录中的所有输入:

./fuzz -runs=0 corpus/
解读输出

运行模糊测试时,你会看到如下统计信息:

INFO: Seed: 3517090860
INFO: Loaded 1 modules (9 inline 8-bit counters)
#2      INITED cov: 3 ft: 4 corp: 1/1b exec/s: 0 rss: 26Mb
#57     NEW    cov: 4 ft: 5 corp: 2/4b lim: 4 exec/s: 0 rss: 26Mb
输出含义
INITED模糊测试已初始化
NEW发现新的覆盖率,已添加到语料库
REDUCE在保持覆盖率的同时缩减了输入
cov: N命中的覆盖率边数量
corp: X/Yb语料库大小:X 个条目,共 Y 字节
exec/s: N每秒执行次数
rss: NMb常驻内存用量

发生崩溃时:

==11672== ERROR: libFuzzer: deadly signal
artifact_prefix='./'; Test unit written to ./crash-a9993e364706816aba3e25717850c26c9cd0d89d
0x61,0x62,0x63,
abc
Base64: YWJj

崩溃用例会保存到 ./crash-<hash>,其中的输入以十六进制、UTF-8 和 Base64 格式显示。

可复现性: 使用 -seed=<value> 重现模糊测试过程(仅适用于单核)。

模糊测试字典

字典通过提供输入格式提示,帮助 Fuzzer 更快地发现有趣的输入。

字典格式

创建一个文本文件,每行放置一个带引号的字符串:

# Lines starting with '#' are comments

# Magic bytes
magic="\x89PNG"
magic2="IEND"

# Keywords
"GET"
"POST"
"Content-Type"

# Hex sequences
delimiter="\xFF\xD8\xFF"
使用字典
./fuzz -dict=./format.dict corpus/
生成字典

从头文件生成:

grep -o '".*"' header.h > header.dict

从 man 手册页生成:

man curl | grep -oP '^\s*(--|-)\K\S+' | sed 's/[,.]$//' | sed 's/^/"&/; s/$/&"/' | sort -u > man.dict

从二进制字符串生成:

strings ./binary | sed 's/^/"&/; s/$/&"/' > strings.dict

使用大模型: 请 ChatGPT 或类似工具为你的格式生成字典(例如:“为 JSON 解析器生成一个 libFuzzer 字典”)。

另请参阅: 有关高级字典生成、特定格式的字典以及字典优化策略,请参阅 fuzzing-dictionaries 技术技能。

覆盖率分析

libFuzzer 会显示基本覆盖率统计信息(cov: N),但详细的覆盖率分析需要使用其他工具。

基于源代码的覆盖率

1. 使用覆盖率插桩重新编译:

clang++ -fsanitize=fuzzer -fprofile-instr-generate -fcoverage-mapping harness.cc target.cc -o fuzz

2. 运行模糊测试器以收集覆盖率数据:

LLVM_PROFILE_FILE="coverage-%p.profraw" ./fuzz -runs=10000 corpus/

3. 合并覆盖率数据:

llvm-profdata merge -sparse coverage-*.profraw -o coverage.profdata

4. 生成覆盖率报告:

llvm-cov show ./fuzz -instr-profile=coverage.profdata

5. 生成 HTML 报告:

llvm-cov show ./fuzz -instr-profile=coverage.profdata -format=html > coverage.html
提高覆盖率

提示:

  • 在语料库中提供更好的种子输入
  • 使用字典进行格式感知模糊测试
  • 检查测试框架是否正确测试了目标
  • 对复杂格式,可以考虑结构感知模糊测试
  • 开展更长时间的测试(数天或数周)

另请参阅: 有关详细的覆盖率分析技术、识别覆盖率缺口、系统性地提高覆盖率以及比较不同模糊测试器的覆盖率,请参阅 coverage-analysis 技术技能。

Sanitizer 集成

AddressSanitizer(ASan)

ASan 可检测缓冲区溢出和释放后使用等内存错误。强烈建议在模糊测试中使用。

启用 ASan:

clang++ -fsanitize=fuzzer,address -g -O2 -U_FORTIFY_SOURCE harness.cc target.cc -o fuzz

ASan 输出示例:

==1276163==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6020000c4ab1
WRITE of size 1 at 0x6020000c4ab1 thread T0
    #0 0x55555568631a in check_buf(char*, unsigned long) main.cc:13:25
    #1 0x5555556860bf in LLVMFuzzerTestOneInput harness.cc:7:3

使用环境变量配置 ASan:

ASAN_OPTIONS=verbosity=1:abort_on_error=1 ./fuzz corpus/

重要标志:

  • verbosity=1:显示 ASan 已启用
  • detect_leaks=0:禁用泄漏检测(泄漏会在结束时报告)
  • abort_on_error=1:发生错误时调用 abort(),而不是 _exit()

缺点:

  • 速度降低 2-4 倍
  • 需要约 20TB 虚拟内存(禁用内存限制:-rss_limit_mb=0)
  • 在 Linux 上支持最佳

另请参阅: 有关全面的 ASan 配置、常见问题、符号化以及与其他 Sanitizer 结合使用,请参阅 address-sanitizer 技术技能。

UndefinedBehaviorSanitizer(UBSan)

UBSan 可检测整数溢出、空指针解引用等未定义行为。

启用 UBSan:

clang++ -fsanitize=fuzzer,undefined -g -O2 harness.cc target.cc -o fuzz

与 ASan 结合使用:

clang++ -fsanitize=fuzzer,address,undefined -g -O2 harness.cc target.cc -o fuzz
MemorySanitizer(MSan)

MSan 可检测未初始化内存读取。使用起来更复杂(需要重新构建所有依赖项)。

clang++ -fsanitize=fuzzer,memory -g -O2 harness.cc target.cc -o fuzz
常见 Sanitizer 问题
问题解决方案
ASan 导致模糊测试速度过慢使用 -fsanitize-recover=address 处理非致命错误
内存不足设置 ASAN_OPTIONS=rss_limit_mb=0 或 -rss_limit_mb=0
栈耗尽增大栈大小:ASAN_OPTIONS=stack_size=8388608
_FORTIFY_SOURCE 导致误报使用 -U_FORTIFY_SOURCE 标志
MSan 在依赖项中报告问题使用 -fsanitize=memory 重新构建所有依赖项

实际示例

示例 1:对 libpng 进行模糊测试

libpng 是一个广泛使用的 PNG 图像读写库。漏洞可能导致安全问题。

1. 获取源代码:

curl -L -O https://downloads.sourceforge.net/project/libpng/libpng16/1.6.37/libpng-1.6.37.tar.xz
tar xf libpng-1.6.37.tar.xz
cd libpng-1.6.37/

2. 安装依赖项:

apt install zlib1g-dev

3. 使用模糊测试插桩进行编译:

export CC=clang CFLAGS="-fsanitize=fuzzer-no-link -fsanitize=address"
export CXX=clang++ CXXFLAGS="$CFLAGS"
./configure --enable-shared=no
make

4. 获取测试框架(或自行编写):

curl -O https://raw.githubusercontent.com/glennrp/libpng/f8e5fa92b0e37ab597616f554bee254157998227/contrib/oss-fuzz/libpng_read_fuzzer.cc

5. 准备语料库和字典:

mkdir corpus/
curl -o corpus/input.png https://raw.githubusercontent.com/glennrp/libpng/acfd50ae0ba3198ad734e5d4dec2b05341e50924/contrib/pngsuite/iftp1n3p08.png
curl -O https://raw.githubusercontent.com/glennrp/libpng/2fff013a6935967960a5ae626fc21432807933dd/contrib/oss-fuzz/png.dict

6. 链接并编译模糊测试器:

clang++ -fsanitize=fuzzer -fsanitize=address libpng_read_fuzzer.cc .libs/libpng16.a -lz -o fuzz

7. 运行模糊测试活动:

./fuzz -close_fd_mask=3 -dict=./png.dict corpus/
示例 2:简单的除法错误

可找到除零错误的测试框架:

#include <stdint.h>
#include <stddef.h>

double divide(uint32_t numerator, uint32_t denominator) {
    // Bug: No check if denominator is zero
    return numerator / denominator;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    if(size != 2 * sizeof(uint32_t)) {
        return 0;
    }

    uint32_t numerator = *(uint32_t*)(data);
    uint32_t denominator = *(uint32_t*)(data + sizeof(uint32_t));

    divide(numerator, denominator);

    return 0;
}

编译并进行模糊测试:

clang++ -fsanitize=fuzzer harness.cc -o fuzz
./fuzz

模糊测试器会很快找到导致崩溃的输入。

高级用法

提示与技巧
提示好处
从单核开始,需要多核时切换到 AFL++libFuzzer 测试桩可与 AFL++ 配合使用
对结构化格式使用字典发现漏洞的速度可提升 10-100 倍
使用 -close_fd_mask=3 关闭文件描述符如果 SUT 会写入输出,可提升速度
设置合理的 -max_len避免在超大输入上浪费时间
持续运行数天或数周,而不是几分钟突破覆盖率平台期需要时间
使用测试套件中的种子语料库从有效输入开始模糊测试
感知结构的模糊测试

对于高度结构化的输入(例如,复杂协议、文件格式),请使用 libprotobuf-mutator:

  • 使用 Protocol Buffers 定义输入结构
  • libFuzzer 会变异 protobuf 消息(保留结构的变异)
  • 测试桩会将 protobuf 转换为原生格式

详情请参阅感知结构的模糊测试文档。

自定义变异器

libFuzzer 支持用于专门模糊测试的自定义变异器:

extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
                                          size_t MaxSize, unsigned int Seed) {
    // Custom mutation logic
    return new_size;
}

extern "C" size_t LLVMFuzzerCustomCrossOver(const uint8_t *Data1, size_t Size1,
                                            const uint8_t *Data2, size_t Size2,
                                            uint8_t *Out, size_t MaxOutSize,
                                            unsigned int Seed) {
    // Custom crossover logic
    return new_size;
}
性能调优
设置影响
-close_fd_mask=3关闭 stdout/stderr,加快模糊测试
-max_len=<reasonable_size>避免在超大输入上浪费时间
-timeout=<seconds>检测挂起,防止执行卡住
禁用 ASan 以获取基准性能速度提升 2-4 倍(但会漏检内存错误)
使用 -jobs 和 -workers多核支持有限
在 Linux 上运行平台支持和性能最佳

故障排除

问题原因解决方案
运行数小时后仍未发现崩溃语料库质量差,覆盖率低添加种子输入,使用字典,检查测试桩
执行速度很慢(每秒 <100 次)目标过于复杂,日志过多优化目标,使用 -close_fd_mask=3,减少日志
内存不足ASan 使用 20TB 虚拟内存设置 -rss_limit_mb=0 以禁用 RSS 限制
模糊测试器在首次崩溃后停止默认行为使用 -fork=1 -ignore_crashes=1 继续运行
无法复现崩溃测试桩/目标存在非确定性移除随机数生成和全局状态
使用 -fsanitize=fuzzer 时出现链接错误缺少 libFuzzer 运行时确保使用 Clang,并检查 LLVM 安装
GCC 项目无法使用 Clang 编译代码特定于 GCC改用带有 gcc_plugin 的 AFL++
覆盖率没有提升语料库已进入平台期延长运行时间,添加字典,改进种子,检查覆盖率报告
出现崩溃,但 ASan 未触发未使用 ASan 时无法检测到内存错误使用 -fsanitize=address 重新编译

相关技能

技术技能
技能使用场景
fuzz-harness-writing编写有效测试桩、感知结构的模糊测试以及 FuzzedDataProvider 用法的详细指南
address-sanitizer内存错误检测配置、ASAN_OPTIONS 和故障排除
undefined-behavior-sanitizer在模糊测试期间检测未定义行为
coverage-analysis衡量模糊测试效果并识别未测试的代码路径
fuzzing-corpus构建和管理种子语料库、语料库最小化策略
fuzzing-dictionaries创建特定格式的字典,以加快漏洞发现
相关模糊测试器
技能适用时机
aflpp需要进行高强度多核模糊测试,或 libFuzzer 覆盖率进入平台期时
honggfuzz希望在 Linux 上使用基于硬件的覆盖率反馈时
libafl构建自定义模糊测试器或开展模糊测试研究时

资源

官方文档
高级主题
示例项目

评论

支持 Markdown