libFuzzer模糊测试Skill libfuzzer

libFuzzer 是一个用于C/C++项目的覆盖率引导模糊测试工具,帮助开发者发现软件漏洞和错误。它集成在LLVM工具链中,支持地址消毒剂等工具,适用于快速设置和单核模糊测试。关键词:模糊测试、覆盖率引导、C/C++、漏洞挖掘、软件测试、LLVM、代码安全。

测试 0 次安装 0 次浏览 更新于 3/14/2026

名称: libfuzzer 类型: 模糊测试器 描述: > 内置在LLVM中的覆盖率引导模糊测试器,用于C/C++项目。用于模糊测试 可以用Clang编译的C/C++代码。

libFuzzer

libFuzzer 是一个进程内、覆盖率引导的模糊测试器,是LLVM项目的一部分。由于其简单性和与LLVM工具链的集成,它是模糊测试C/C++项目的推荐起点。尽管libFuzzer自2022年底以来一直处于仅维护模式,但它比其替代品更容易安装和使用,有广泛的支持,并将在可预见的未来继续维护。

何时使用

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

选择 libFuzzer 当:

  • 您需要为C/C++代码进行简单、快速的设置
  • 项目使用Clang进行编译
  • 单核模糊测试最初足够
  • 后续过渡到AFL++是一个选项(harness兼容)

注意: 为libFuzzer编写的模糊测试harness与AFL++兼容,如果需要更高级的功能如更好的多核支持,可以轻松过渡。

快速开始

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

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // 如果需要,验证输入
    if (size < 1) return 0;

    // 使用模糊测试器提供的数据调用目标函数
    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版本:

# 从apt.llvm.org添加LLVM仓库
# 然后安装特定版本,例如:
apt install clang-18 llvm-18

macOS

# 使用Homebrew
brew install llvm

# 或使用Nix
nix-env -i clang

Windows

通过Visual Studio安装Clang。参考Microsoft的文档进行设置。

推荐: 如果可能,在本地x86_64虚拟机或租用DigitalOcean、AWS或Hetzner上的虚拟机上进行模糊测试。Linux为libFuzzer提供最佳支持。

验证

clang++ --version
# 应显示LLVM版本信息

编写Harness

Harness结构

Harness是模糊测试器的入口点。libFuzzer反复调用LLVMFuzzerTestOneInput函数,提供不同的输入。

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

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
    // 1. 可选:验证输入大小
    if (size < MIN_REQUIRED_SIZE) {
        return 0;  // 拒绝太小的输入
    }

    // 2. 可选:将原始字节转换为结构化数据
    // 示例:从字节数组中解析两个整数
    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. 调用目标函数
    target_function(data, size);

    // 4. 始终返回0(非零保留供将来使用)
    return 0;
}

Harness规则

不做
处理所有输入类型(空、巨大、畸形) 调用exit() - 停止模糊测试过程
在返回前加入所有线程 让线程运行
保持harness快速和简单 添加过多日志或复杂性
保持确定性 使用随机数生成器或读取/dev/random
每次运行之间重置全局状态 依赖先前执行的状态
使用狭窄、专注的目标 在一个harness中混合不相关的数据格式(PNG + TCP)

原理:

  • 速度重要: 目标每个核心每秒执行100-1000次
  • 可重现性: 崩溃必须在模糊测试完成后可重现
  • 隔离: 每次执行应独立

使用FuzzedDataProvider处理复杂输入

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

#include <stdint.h>
#include <stddef.h>
#include "FuzzedDataProvider.h"  // 来自LLVM项目

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

    // 提取结构化数据
    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);

    // 使用提取的数据调用目标
    char* result = concat(&str1[0], str1.size(), &str2[0], str2.size(), allocation_size);
    if (result != NULL) {
        free(result);
    }

    return 0;
}

LLVM仓库下载FuzzedDataProvider.h

交错模糊测试

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

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));

    // 根据第一个字节选择函数
    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;
}

另见: 关于详细harness编写技术、处理复杂输入的模式、 结构感知模糊测试和基于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函数,定义宏

使用消毒剂

地址消毒剂(推荐):

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

多个消毒剂:

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

另见: 关于详细消毒剂配置、常见问题、ASAN_OPTIONS标志、 和高级消毒剂使用,请参见address-sanitizerundefined-behavior-sanitizer技术技能。

构建标志

标志 目的
-fsanitize=fuzzer 启用libFuzzer运行时和插桩
-fsanitize=address 启用地址消毒剂(内存错误检测)
-fsanitize=undefined 启用未定义行为消毒剂
-fsanitize=fuzzer-no-link 插桩而不链接模糊测试器(用于库)
-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. 将静态库与harness链接:
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/

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

# 对于PNG解析器:
cp examples/*.png corpus/

# 对于协议解析器:
cp test_packets/*.bin corpus/

种子输入的好处:

  • 模糊测试器不从零开始
  • 更快到达有效代码路径
  • 显著提高效果

语料库结构

语料库目录包含:

  • 触发唯一代码路径的输入文件
  • 最小化版本(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: 作业和工作器(推荐):

./fuzz -jobs=4 -workers=4 -fork=1 -ignore_crashes=1 corpus/
  • -jobs=4: 运行4个顺序活动
  • -workers=4: 使用4个进程并行处理作业
  • 测试用例在作业之间共享

选项2: 分支模式:

./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>重现模糊测试活动(仅单核)。

模糊测试字典

字典通过提供关于输入格式的提示,帮助模糊测试器更快发现有趣的输入。

字典格式

创建文本文件,每行包含带引号的字符串:

# 以'#'开头的行是注释

# 魔数字节
magic="\x89PNG"
magic2="IEND"

# 关键字
"GET"
"POST"
"Content-Type"

# 十六进制序列
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

使用LLM: 请求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

提高覆盖率

提示:

  • 在语料库中提供更好的种子输入
  • 使用字典进行格式感知模糊测试
  • 检查harness是否正确测试目标
  • 对于复杂格式,考虑结构感知模糊测试
  • 运行更长的活动(天/周)

另见: 关于详细覆盖率分析技术、识别覆盖率差距、 系统覆盖率提高和跨模糊测试器比较覆盖率,请参见 coverage-analysis技术技能。

消毒剂集成

地址消毒剂(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配置、常见陷阱、符号化和 与其他消毒剂结合,请参见address-sanitizer技术技能。

未定义行为消毒剂(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

内存消毒剂(MSan)

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

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

常见消毒剂问题

问题 解决方案
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. 获取harness(或自己编写):

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: 简单除零错误

查找除零错误的harness:

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

double divide(uint32_t numerator, uint32_t denominator) {
    // 错误: 未检查分母是否为零
    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 harness与AFL++兼容
为结构化格式使用字典 10-100倍更快发现错误
使用-close_fd_mask=3关闭文件描述符 如果SUT写入输出,提速
设置合理的-max_len 防止在巨大输入上浪费时间
运行天/周,而不是分钟 覆盖率平台需要时间突破
使用测试套件中的种子语料库 从有效输入开始模糊测试

结构感知模糊测试

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

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

详情参见结构感知模糊测试文档

自定义突变器

libFuzzer允许自定义突变器用于专门模糊测试:

extern "C" size_t LLVMFuzzerCustomMutator(uint8_t *Data, size_t Size,
                                          size_t MaxSize, unsigned int Seed) {
    // 自定义突变逻辑
    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) {
    // 自定义交叉逻辑
    return new_size;
}

性能调优

设置 影响
-close_fd_mask=3 关闭stdout/stderr,加速模糊测试
-max_len=<合理大小> 避免在巨大输入上浪费时间
-timeout=<秒数> 检测挂起,防止卡住执行
禁用ASan进行基准测试 2-4倍速度提升(但错过内存错误)
使用-jobs-workers 有限的多核支持
在Linux上运行 最佳平台支持和性能

故障排除

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

相关技能

技术技能

技能 使用案例
fuzz-harness-writing 关于编写有效harness、结构感知模糊测试和FuzzedDataProvider使用的详细指导
address-sanitizer 内存错误检测配置、ASAN_OPTIONS和故障排除
undefined-behavior-sanitizer 在模糊测试期间检测未定义行为
coverage-analysis 测量模糊测试效果和识别未测试代码路径
fuzzing-corpus 构建和管理种子语料库、语料库最小化策略
fuzzing-dictionaries 创建格式特定字典以更快发现错误

相关模糊测试器

技能 何时考虑
aflpp 当您需要严肃的多核模糊测试,或libFuzzer覆盖率平台时
honggfuzz 当您想在Linux上使用基于硬件的覆盖率反馈时
libafl 当构建自定义模糊测试器或进行模糊测试研究时

资源

官方文档

高级主题

示例项目