libFuzzer可直接跑通,但必须满足三硬条件:目标函数可独立调用、输入为字节流、编译链启用ASan;LLVMFuzzerTestOneInput须严格接收const uint8_t*和size_t、无全局状态、不抛异常、恒返0,且编译必须同时指定-fsanitize=address,fuzzer。

能直接跑通,但必须满足三个硬条件:目标函数可独立调用、输入是字节流、编译链带 ASan 支持。不满足任一条件,LLVMFuzzerTestOneInput 写得再漂亮也卡在编译或静默失败阶段。
LLVMFuzzerTestOneInput 函数怎么写才不崩溃也不漏 Bug
这个函数不是“随便包装一下业务逻辑”就行的。它本质是 libFuzzer 的唯一入口,所有变异输入都经它流入,所以必须满足:只接收 const uint8_t* 和 size_t,不做全局状态依赖,不抛未捕获异常,返回值恒为 0(非零会被视为异常终止)。
- 常见错误现象:
std::string构造时传入含\0的data导致截断;reinterpret_cast后直接当 C 字符串用,触发越界读 - 正确做法:用
std::string(data, size)显式指定长度,避免隐式 null 截断 - 关键细节:如果被测函数内部会解引用指针(比如解析结构体),需先检查
size是否足够,否则 libFuzzer 可能喂来 2 字节数据,而你的代码却试图读 8 字节 —— 这类越界在 ASan 下立刻报heap-buffer-overflow - 性能影响:在函数开头加
if (size == 0) return 0;可跳过大量无效空输入,提升单位时间路径探索效率
编译时必须启用 -fsanitize=address,fuzzer,缺一不可
libFuzzer 不是独立进程,它和你的测试代码链接进同一个可执行文件,靠 sanitizer 在运行时捕获内存错误。只开 -fsanitize=fuzzer 会漏掉绝大多数崩溃(如堆溢出、UAF),只开 -fsanitize=address 则无法驱动变异逻辑。
- 典型错误配置:
clang++ -O2 -fsanitize=address target.cpp -o fuzzer—— 缺fuzzer,运行时报undefined reference to LLVMFuzzerTestOneInput - 正确命令:
clang++ -g -O1 -fsanitize=address,fuzzer,fuzzer-no-link fuzzer.cpp target.cpp -o fuzzer - 注意:
-fno-omit-frame-pointer建议加上,否则 ASan 报错时堆栈可能不完整 - 兼容性坑:GCC 不支持
-fsanitize=fuzzer,必须用 Clang 11+;Ubuntu 22.04 自带的 clang-14 默认不装 libc++abi-dev,要手动sudo apt install libc++abi-dev
种子语料库(corpus)不是可选项,是效率分水岭
空目录启动 fuzzing,前几小时基本在撞“能进入函数”的输入;给一个合法 JSON 或 HTTP 请求样本,libFuzzer 立刻开始变异字段名、数值、换行位置 —— 覆盖率提升 5–10 倍很常见。
立即学习“C++免费学习笔记(深入)”;
- 最小可行种子:一个能通过你函数前置校验的输入文件,比如
echo '{"id":123}' > seeds/valid.json - 别用单个大文件:libFuzzer 按文件粒度加载,把 100 个不同协议头塞进一个文件,不如拆成 100 个独立小文件
- 字典(dictionary)强烈建议配:对协议字段、状态码、错误码这类固定字符串,用
-dict=protocol.dict能显著减少“瞎猜”时间 - 容易被忽略的点:首次运行后生成的
crash-xxx文件要手动归入种子目录,否则下次重启不会复现该路径 —— libFuzzer 不自动重学崩溃路径
真正卡住人的从来不是“怎么写第一个 fuzz target”,而是没意识到 LLVMFuzzerTestOneInput 必须像纯函数一样无副作用,以及 ASan + fuzzer 的编译标志必须同时存在且由 Clang 解析。这两点错一个,就只能看到 “fuzzer built but no crash found” 的假象。


















