libFuzzer模糊测试Skill libfuzzer

libFuzzer是LLVM项目中的覆盖率引导模糊测试工具,专为C/C++代码的自动化软件测试和漏洞挖掘设计。它通过生成随机输入并监控代码执行覆盖率来发现潜在缺陷,适用于软件开发中的安全测试和质量保障。关键词:模糊测试,C/C++测试,覆盖率引导,软件安全,漏洞发现,自动化测试工具。

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

name: libfuzzer type: fuzzer description: > 内置LLVM的C/C++项目的覆盖率引导模糊测试器。用于模糊测试可以用Clang编译的C/C++代码。

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) {
    // 如果需要,验证输入
    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。参考微软文档获取设置说明。

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

验证

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

编写测试线束

测试线束结构

测试线束是模糊测试器的入口点。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;
}

测试线束规则

不做
处理所有输入类型(空、巨大、畸形) 调用exit() - 停止模糊测试进程
返回前加入所有线程 让线程运行
保持测试线束快速简单 添加过多日志记录或复杂性
保持确定性 使用随机数生成器或读取/dev/random
在运行之间重置全局状态 依赖之前执行的状态
使用狭窄、聚焦的目标 在单个测试线束中混合不相关的数据格式(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

交错模糊测试

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

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

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

使用Sanitizers

AddressSanitizer(推荐):

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

多Sanitizers:

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

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

构建标志

标志 用途
-fsanitize=fuzzer 启用libFuzzer运行时和仪器
-fsanitize=address 启用AddressSanitizer(内存错误检测)
-fsanitize=undefined 启用UndefinedBehaviorSanitizer
-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. 将静态库与测试线束链接:
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-<哈希>,输入以十六进制、UTF-8和Base64显示。

可重现性: 使用-seed=<值>重现模糊测试活动(仅限单核)。

模糊测试字典

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

字典格式

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

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

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

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

# 十六进制序列
delimiter="\xFF\xD8\xFF"

使用字典

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

生成字典

从头文件:

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

从手册页:

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

改进覆盖率

提示:

  • 提供更好的种子输入到语料库
  • 使用字典进行格式感知模糊测试
  • 检查测试线束是否适当执行目标
  • 考虑复杂格式的结构感知模糊测试
  • 运行更长时间活动(天/周)

另见: 有关详细覆盖率分析技术、识别覆盖率差距、系统性覆盖率改进和比较不同模糊测试器覆盖率,请参阅coverage-analysis技能。

Sanitizer集成

AddressSanitizer (ASan)

ASan检测内存错误,如缓冲区溢出和use-after-free漏洞。强烈推荐用于模糊测试。

启用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配置、常见陷阱、符号化和与其他Sanitizers结合,请参阅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) {
    // 漏洞:未检查分母是否为零
    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) {
    // 自定义变异逻辑
    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上运行 最佳平台支持和性能

故障排除

问题 原因 解决方案
运行数小时后无崩溃 差语料库,低覆盖率 添加种子输入,使用字典,检查测试线束
非常慢的执行/秒(<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特定代码 改用AFL++与gcc_plugin
覆盖率无改进 语料库平台期 运行更长时间,添加字典,改进种子,检查覆盖率报告
崩溃但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 当构建自定义模糊测试器或进行模糊测试研究时

资源

官方文档

高级主题

示例项目