MongoDB 服务器中的 LibFuzzer 覆盖率引导模糊测试:原理、编写与运行指南

发布时间:2026/9/12 11:05:39
MongoDB 服务器中的 LibFuzzer 覆盖率引导模糊测试:原理、编写与运行指南 MongoDB 服务器中的 LibFuzzer 覆盖率引导模糊测试原理、编写与运行指南【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongoLibFuzzer 是 LLVM 提供的一款面向 C/C 代码的覆盖率引导coverage-guided模糊测试工具。本文以 MongoDB 服务器仓库中的 docs/libfuzzer.md 为核心系统讲解 LibFuzzer 的工作机制、何时适合使用它、如何编写LLVMFuzzerTestOneInput入口函数、如何声明 Bazel 模糊测试目标以及如何结合 corpus 本地运行与调试模糊测试。读完本文你将能够为 MongoDB 中接受不可信字节流的 C 函数编写、构建并运行自己的 LibFuzzer 模糊测试。⚠️重要提示LibFuzzer 在 MongoDB 服务器中已被标记为deprecated弃用不建议用于新的模糊测试。新实现的模糊测试请参考 FuzzTest 指南对应mongo_cc_fuzztest规则。本文仅服务于理解、维护和复现仓库中既有的 LibFuzzer 模糊测试。LibFuzzer 是什么LibFuzzer 是一个对 C/C 代码执行覆盖率引导模糊测试的工具。它的核心工作方式如下它实现了一个int main内部负责生成精心构造的字节数组它会反复调用你提供的被测函数尝试触发UBSAN未定义行为消毒器检测到的故障每一次输入都会被赋予一个得分score——能够覆盖到新的或更多代码区域的字节数组得分更高得分高的输入会被保留LibFuzzer 会对这些高分输入进行合并与变异merge and mutate从而逐步覆盖越来越多的程序行为。简而言之LibFuzzer 维护一个种子 变异循环每次运行产生的输入会进入反馈回路覆盖率信息作为引导信号驱动下一轮变异最终在有限的执行次数内尽可能多地探索目标代码路径。什么时候应该使用 LibFuzzerLibFuzzer 最适合测试接受不透明、不可信用户数据块opaque blob of untrusted user-provided data的函数。在 MongoDB 仓库中这类典型场景包括BSON 文档解析如 fromjson_fuzzer.cpp直接以任意字节流作为fromjson的输入BSON 校验如 bson_validate_fuzzer.cpp对比新旧两套校验实现网络协议消息解析如 rpc/protocol_fuzzer.cpp将任意字节流当作 MongoDB wire protocol 消息来解析存储键格式解析如 key_string_to_bson_fuzzer.cpp、date_time_support_fuzzer.cpp、asn1_parser_fuzzer.cpp 等。这些函数有一个共同点它们接收的是原始字节而非结构化的类型化参数正好是 LibFuzzer 字节流输入模型的用武之地。需要特别说明的是如果你的被测函数接收的是结构化输入整数、字符串、自定义类型、BSON 对象等或者你想表达不仅仅是不崩溃的正确性属性API 不变式、差分等价、往返对称性那么应当使用 FuzzTest 而非 LibFuzzer。如何编写 LibFuzzer 模糊测试实现 LLVMFuzzerTestOneInputLibFuzzer 实现了int main并期望与你提供的一个目标文件object file链接该目标文件中实现了被测函数。你只需编写一个 C 文件实现如下签名extern C int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { // Your code here }LLVMFuzzerTestOneInput会被反复调用Data中是由 fuzzer 生成的字节Size总是如实告诉你的实现Data中有多少字节如果你的函数崩溃crash或触发了 UBSAN 故障LibFuzzer 会将其视为值得报告的问题finding。注意这里的签名与仓库中实际代码的细微差别仓库中不少模糊测试入口写作const char* Data如 fromjson_fuzzer.cpp 和 protocol_fuzzer.cpp这与const uint8_t*在二进制层面等价可放心使用。如何组织被测逻辑请记住你的函数往往只是把Data适配成我们内部 C 函数所需的格式。但具体怎么做你有很大的自由只要保证当有趣的事情发生时函数会崩溃或产生不变量违反invariant即可。文档给出了两个典型思路差分测试differential testing对同一个操作调用多个实现验证它们在相同输入下产生相同输出。仓库中 bson_validate_fuzzer.cpp 就是这种模式的典范它同时调用新版validateBSON与旧版fuzzerOnly::validateBSON并通过invariant(oldRet.isOK() ret.isOK() || ret.code() ErrorCodes::NonConformantBSON)让两套实现的返回值不一致时触发崩溃从而引导 fuzzer 找到两套实现的行为差异拆解字节从Data中逐字节挑出不同部分作为被测函数的不同参数传入。一个完整的示例fromjson_fuzzer仓库中的 fromjson_fuzzer.cpp 是一个简短而完整的实现结构如下void doFuzz(const char* data, size_t size) try { BSONObj obj; try { obj fromjson(std::string_view(data, size)); } catch (...) { return; // ignore fromjson exceptions } invariant(validateBSON(obj.objdata(), obj.objsize())); } catch (...) { LOGV2_FATAL(10041200, Exception, error_attr exceptionToStatus()); } extern C int LLVMFuzzerTestOneInput(const char* Data, size_t Size) { mongo::doFuzz(Data, Size); return 0; }它演示了几个要点解析类的异常被显式捕获并忽略因为解析失败本身不是 bug解析成功的对象必须通过validateBSON校验否则触发invariant即能解析出来的 BSON 必须是合法 BSON这一不变量任何未预期异常都通过LOGV2_FATAL上报。而 rpc/protocol_fuzzer.cpp 则演示了更复杂的场景它把字节流当作 MongoDB wire protocol 消息先做消息头长度校验再根据操作码dbMsg/dbInsert/dbQuery等分发到不同的解析路径并支持dbCompressed压缩消息的解压noop/snappy/zlib/zstd最后用catch (const DBException)忽略来自断言宏的所有预期错误。声明 Bazel 模糊测试目标最后你的 C 文件需要一个 Bazel 目标。仓库提供了一种定义 fuzzer 目标的方法用法与定义单元测试类似mongo_cc_fuzzer_test( name op_msg_fuzzer, srcs [ op_msg_fuzzer.cpp, ], hdrs [ op_msg_fuzzer.h, ] deps [ //src/mongo:base, :op_msg_fuzzer_fixture, ], )⚠️注意文档中的mongo_cc_fuzzer_test在当前仓库中已经更名为mongo_cc_fuzzer_test_deprecated其定义位于 bazel/mongo_src_rules.bzl。该规则是mongo_cc_test的包装器用于支持基于旧 libfuzzer 的测试源码注释明确写明不应再用于新测试请改用mongo_cc_fuzztest。仓库实际用法示例见 src/mongo/bson/BUILD.bazelmongo_cc_fuzzer_test_deprecated( name fromjson_fuzzer, srcs [ fromjson_fuzzer.cpp, ], deps [...], )从规则实现bazel/mongo_src_rules.bzl可以看到该包装器会为你自动做三件事追加mongo_fuzzer_test标签便于 Evergreen 筛选模糊测试任务追加编译选项-Wno-sign-compare与链接选项-fsanitize-coveragecontrol-flow通过target_compatible_with将该目标限定在启用了fsan构建配置//bazel/config:fsan_enabled时才可编译未启用时目标被标记为不兼容。如何运行 LibFuzzer编译要求sanitizer 组合你的测试目标文件及其所有依赖都必须使用 fuzzer sanitizer 编译同时还要开启一组可能产生有趣运行时错误的 sanitizer如UBSAN。在 Evergreen 中运行Evergreen 有一个构建变体build variant其名称包含字符串FUZZER该变体会编译并运行所有fuzzer 测试。如果你想在本地开发调试时构建 fuzzers请查看 Evergreen 配置中当前的 Bazel 参数对应仓库根目录的 etc/evergreen.yml 及 etc/evergreen_nightly.yml。本地运行与 corpus 的使用LibFuzzer 二进制文件接受一个指向corpus语料库目录的路径参数。corpus 是一组已知能产生有趣输出的示例的集合加速收敛如果 fuzzer 从一组可以开始变异的输入出发它能更快地产出有趣结果回写发现运行结束后fuzzer 会把它发现的新输入写回 corpus 目录跨运行复用跨多次执行复用同一个 corpus 是让 LibFuzzer 在更短时间内返回更多结果的好方法Evergreen 的语料传承Evergreen 任务会尝试从更早的提交中获取并复用 corpus如果可能的话。典型运行方式为./op_msg_fuzzer /path/to/corpuscorpus 目录既可以预先放置种子文件也可以从空目录开始此时运行结束后目录内会留下 fuzzer 发现的新输入。与 FuzzTest 的关系正如文档开头与结尾反复强调的LibFuzzer 已被弃用仓库中新的模糊测试统一走 FuzzTest 路线。两者对照如下维度LibFuzzer已弃用FuzzTest推荐输入模型不透明字节流const uint8_t* Data, size_t Size类型化 domain如fuzztest::ArbitraryT()、fuzztest::InRange(...)入口形式手动实现extern C int LLVMFuzzerTestOneInput属性函数 FUZZ_TEST/FUZZ_TEST_F宏Bazel 规则mongo_cc_fuzzer_test_deprecatedmongo_cc_fuzzer_testfuzztest引擎LibFuzzerFuzzTest Centipede正确性断言崩溃 / UBSAN / invariant支持往返、差分、API 不变式等属性断言两条路线在仓库中共存旧的 libfuzzer 风格测试如 bson_validate_fuzzer.cpp、fromjson_fuzzer.cpp仍由mongo_cc_fuzzer_test_deprecated维护运行而新测试如 ticket_semaphore_fuzz_test.cpp、sbe_value_fuzz_test.cpp则使用mongo_cc_fuzztest声明。在仓库中搜索mongo_cc_fuzzer_test_deprecated见 src/mongo/bson/BUILD.bazel可以列出全部仍在使用 LibFuzzer 的既有模糊测试。总结LibFuzzer 是 MongoDB 服务器中曾经主力的 C/C 覆盖率引导模糊测试工具它通过LLVMFuzzerTestOneInput入口反复投喂精心构造的字节流用覆盖率得分驱动变异以崩溃或 UBSAN 故障作为发现信号的闭环来寻找解析器与协议处理代码中的缺陷。理解它的工作机理、目标编写方式和 corpus 复用策略对于阅读仓库中大量既有 fuzzer 测试BSON 解析与校验、wire protocol、key_string、日期解析、ASN.1 解析等以及理解它们如何被编译、如何在 Evergreen 中运行仍然非常有价值。不过请注意新代码请直接使用 FuzzTest。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考