ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

fmt 格式化库模糊测试(Fuzzing)实战指南:从本地构建、目标源码到崩溃复现

fmt 格式化库模糊测试(Fuzzing)实战指南:从本地构建、目标源码到崩溃复现 fmt 格式化库模糊测试Fuzzing实战指南从本地构建、目标源码到崩溃复现【免费下载链接】fmtA modern formatting library项目地址: https://gitcode.com/GitHub_Trending/fm/fmt本文聚焦开源格式化库 fmt 在 test/fuzzing 目录下内置的模糊测试fuzzing体系围绕 test/fuzzing/README.md 展开说明如何在本地用 clang 的 libFuzzer 构建并运行这组测试目标、理解六个 fuzz target 各自的输入数据布局与覆盖范围以及如何利用复现模式reproduce mode配合 gdb、valgrind 等工具定位崩溃。读完本文你将掌握一套可直接复制执行的 fmt 模糊测试构建与运行流程并看懂其底层实现原理。为什么格式化库需要模糊测试格式化库的核心输入是格式字符串 参数。攻击者或意外的调用方可能传入格式串错误、参数类型不匹配、浮点特殊值NaN、Inf、超长精度等组合。fmt 在 CMakeLists.txt 中通过FMT_FUZZ宏专门抑制模糊测试模式下的资源耗尽风险并声明该宏与FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION类似但使用不同名字以避免干扰其他使用 fmt 的项目的模糊测试。这说明模糊测试是 fmt 官方维护的质量保障环节仓库为它单独维护了一套测试工程而不是散落的临时脚本。test/fuzzing目录整体结构如下README.md构建与运行指南本文核心依据CMakeLists.txt定义 fuzz target 与相关 CMake 选项fuzzer-common.h共享基础设施数据解码、缓冲策略main.cc复现模式入口六个 fuzz 目标源文件chrono-duration.cc、chrono-timepoint.cc、float.cc、named-arg.cc、one-arg.cc、two-args.cc。构建前置条件test/fuzzing/README.md 说明辅助脚本 build.sh 目前仅在 Debian 和 Ubuntu Linux 上测试过在 Windows需 clang8或 Mac 上模糊测试本身没有已知问题但脚本可能无法开箱即用。注意当前仓库快照中并未包含 build.sh 文件因此本文直接给出与文档等效的纯命令行流程这也是文档明确推荐的方式——对所有 clang 支持的平台都应该能工作。核心依赖是 clang建议 8 或更高版本其中-fsanitizefuzzer需要 libFuzzer 支持。构建命令与 CMake 选项详解完整构建流程mkdir build cd build export CXXclang export CXXFLAGS-fsanitizefuzzer-no-link -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION -g cmake .. -DFMT_SAFE_DURATION_CASTOn -DFMT_FUZZOn -DFMT_FUZZ_LINKMAINOff -DFMT_FUZZ_LDFLAGS-fsanitizefuzzer cmake --build .逐条拆解环节作用CXXclang指定使用 clang 编译器libFuzzer 的-fsanitizefuzzer依赖 clangCXXFLAGS中的-fsanitizefuzzer-no-link对所有目标注入 sanitizer 运行时但不注入 fuzz 入口供库本身编译时使用CXXFLAGS中的-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION声明为模糊测试构建的生产不安全模式让被测库在此模式下简化/放宽部分检查以提升 fuzz 效率文档原文注释说明该标志面向所有平台通用CXXFLAGS中的-g保留调试信息便于崩溃回溯-DFMT_FUZZOn开启 fuzz 目标生成默认关闭详见下文-DFMT_SAFE_DURATION_CASTOn开启 chrono 相关安全时长转换配合chrono-duration-fuzzer使用-DFMT_FUZZ_LINKMAINOff关闭链接 main 的复现模式改用 libFuzzer 的main即真正执行模糊测试-DFMT_FUZZ_LDFLAGS-fsanitizefuzzer把 libFuzzer 运行时链接进 fuzz target提供LLVMFuzzerTestOneInput驱动循环注意FMT_SAFE_DURATION_CAST与FMT_FUZZ都是顶层 CMakeLists.txt 中的独立开关FMT_FUZZ默认值为OFF只有打开它add_subdirectory(test/fuzzing)才会被触发见 CMakeLists.txt并同时给 fmt 库本身追加FMT_FUZZ编译宏。fuzz 工程自身的三个 CMake 选项test/fuzzing/CMakeLists.txt 定义了三个开关FMT_FUZZ_LINKMAIN默认On启用复现模式。此时add_fuzzer会把 main.cc 链接进每个 fuzz target生成一个可读文件、逐个喂给LLVMFuzzerTestOneInput的普通可执行文件——对复现、kcov、gdb、afl、valgrind 很有用源码注释原话。关闭它则使用 libFuzzer 的入口也就是真正的模糊测试模式。FMT_FUZZ_LDFLAGS默认空字符串为 fuzz target 附加链接标志。源码注释说明这是为 oss-fuzz 准备的——oss-fuzz 通过环境变量$LIB_FUZZING_ENGINE注入 fuzz 引擎且只应注入到 fuzz target否则 CMake 配置阶段会失败。add_fuzzer函数的命名规则对每个源文件XXX.cc生成的可执行文件名是XXX-fuzzertest/fuzzing/CMakeLists.txt并强制cxx_std_14。因此六个目标实际名称为chrono-duration-fuzzer、chrono-timepoint-fuzzer、float-fuzzer、named-arg-fuzzer、one-arg-fuzzer、two-args-fuzzer。README 示例中的bin/fuzzer_chrono_duration可能是较旧命名以当前 CMake 规则为准运行时应使用bin/chrono-duration-fuzzer。运行一个 fuzzertest/fuzzing/README.md 给出的运行示例按上文命名规则校正cd build export UBSAN_OPTIONShalt_on_error1 mkdir out_chrono bin/chrono-duration-fuzzer out_chrono要点UBSAN_OPTIONShalt_on_error1让 UndefinedBehaviorSanitizer 遇到首个未定义行为立即终止而不是继续运行确保任何 UB 都会被捕获为一次崩溃。目录参数out_chrono是 libFuzzer 的 corpus语料库目录libFuzzer 会把产生的输入持久化到该目录后续运行会继续在此基础上变异。fuzzer 会持续运行直到发现崩溃此时把崩溃输入保存为文件或收到中断信号。可以用-max_total_time60、-jobs、-workers等标准 libFuzzer 参数控制。六个 fuzz target 的实现与数据布局所有 target 都导出extern C int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)由 libFuzzer 或 main.cc 调用。下面逐一说明其输入布局与覆盖范围。1. chrono-duration-fuzzerchrono-duration.cc测试std::chrono::duration的格式化fmt/chrono.h。输入布局data[0]表示类型representation映射 12 种类型char、signed char、unsigned char、short、unsigned short、int、unsigned int、long、unsigned long、float、double、long doubledata[1]周期period映射 15 种std::ratioatto、femto、pico、nano、micro、milli、centi、deci、deca、kilo、mega、giga、tera、peta、exa——源码注释明确yocto、zepto、zetta、yotta 未处理随后固定 16 字节作为Rep值的位模式assign_from_bufRep剩余字节格式字符串。它先static_assert(sizeof(Rep) fixed_size)保证 16 字节够用再用fmt::format_to写进fmt::memory_buffer并捕获所有std::exception。2. chrono-timepoint-fuzzerchrono-timepoint.cc测试时间点std::chrono::system_clock::time_point格式化。输入布局前sizeof(Rep)字节Rep system_clock::time_point::rep时间点内部计数值剩余字节格式字符串。构造方式是把 Rep 值当作system_clock::duration再构造time_point。这个 target 没有显式的 size 下限之外的复杂分派只捕获所有异常。3. float-fuzzerfloat.cc测试浮点格式化且内置往返round-trip校验前sizeof(double)字节解码为double用{}默认格式格式化后用std::strtod解析回浮点必须与原始值相等且输出必须恰好消费完ptr 1 buffer.end()再用{:.50g}超高精度格式化一次——源码注释说明这是为了触发 fallback formatter 路径对NaN特殊处理要求输出为nan或-nan依符号位仅在std::numeric_limitsdouble::is_iec559IEEE 754平台上运行。这是六个 target 中唯一自带断言式校验的任何一次往返失败都会抛出std::runtime_error并被 libFuzzer 判为崩溃。4. named-arg-fuzzernamed-arg.cc测试命名参数fmt::arg(name, value)。输入布局data[0]低 4 位参数类型13 种bool、char、signed char、unsigned char、short、unsigned short、int、unsigned、long、unsigned long、float、double、long doubledata[0]高 4 位参数名长度arg_name_size0~15且必须小于剩余长度否则直接返回随后固定 16 字节参数值再arg_name_size字节参数名字符串data_to_string(..., add_terminatortrue)追加\0以便作为 C 字符串使用剩余字节格式字符串。它通过模板 lambda decltype实现类型分派测试fmt::format(format_str, fmt::arg(name, value))。5. one-arg-fuzzerone-arg.cc单参数格式化覆盖 14 种情况bool、char、unsigned char、signed char、short、unsigned short、int、unsigned int、long、unsigned long、float、double、long double以及std::tm用std::time_t表示通过特化的from_reprstd::tm调用std::localtime(t)获得指针。布局1 字节类型选择 固定 16 字节参数值 格式字符串。6. two-args-fuzzertwo-args.cc双参数格式化类型组合是两个 4 位字段的笛卡尔积每种 14 种类型含void*共最多 196 种组合。布局data[0]低 4 位为参数 1 类型、高 4 位为参数 2 类型随后固定 16 字节 16 字节两个参数值剩余为格式字符串。共享基础设施fuzzer-common.h 的设计决策fuzzer-common.h 集中体现了 fmt fuzz 体系的工程化设计FMT_FUZZ_FORMAT_TO_STRING默认 0编译期选择格式化输出到std::string还是fmt::memory_buffer。注释说明buffer 更快但 string 可验证字符串语义为避免组合爆炸在编译期选择而非从 fuzz 数据动态决定。FMT_FUZZ_SEPARATE_ALLOCATION默认 1单独分配一块std::vectorchar缓冲存放输入而不是直接引用 fuzzer 的输入内存。注释说明如果给 fmt 的缓冲区是单独分配的AddressSanitizer 检测越界读的概率会高得多代价是拖慢模糊测试。fixed_size 16所有类型统一使用 16 字节的固定槽位。注释解释了动机为了提升 fuzzer 在不同类型之间交叉变异cross pollinate的效率使用固定大小的布局——同一段位模式被解释成另一种类型往往能产生有趣的新输入。assign_from_bufItem用std::memcpy从字节缓冲区构造一个平凡可构造类型的对象bool有专门特化首字节非零即 true。data_to_string把 fuzzer 字节流包装成fmt::string_view可选项式追加终止符。复现模式用 main.cc 调试崩溃当FMT_FUZZ_LINKMAINOn时每个 target 都链接 main.cc。其main遍历所有命令行参数视为文件路径逐个完整读入文件并把内容传给LLVMFuzzerTestOneInput。这意味着libFuzzer 崩溃时保存的 reproducer 文件形如crash-*、timeout-*可直接作为参数传入复现结合-g调试信息可无缝衔接 gdb、valgrind、kcov、afl 等工作流正如 test/fuzzing/CMakeLists.txt 注释所说明的。例如bin/chrono-duration-fuzzer crash-abcdef123456 # 复现 libFuzzer 保存的崩溃输入 gdb --args bin/chrono-duration-fuzzer crash-abcdef123456与 oss-fuzz 的衔接从源码注释可以确认这套工程是为 oss-fuzz 设计的FMT_FUZZ_LDFLAGS的说明For oss-fuzz - insert$LIB_FUZZING_ENGINEinto the link flags, but only for the fuzz targets, otherwise the CMake configuration step fails.——即 oss-fuzz 构建时会把$LIB_FUZZING_ENGINE传入该选项顶层 CMakeLists.txt 中FMT_FUZZ宏用于防止 fuzzing 模式下的资源耗尽并使模糊测试实际可行同时刻意避免与FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION同名防止干扰依赖 fmt 的第三方项目自己的模糊测试。因此本地运行只需把FMT_FUZZ_LDFLAGS设为-fsanitizefuzzer而 CI/oss-fuzz 场景则传入引擎提供的链接标志。小结与调试建议构建clang -fsanitizefuzzer-no-link 三个 fuzz 相关 CMake 开关FMT_FUZZOn、FMT_FUZZ_LINKMAIN、FMT_FUZZ_LDFLAGS流程见 test/fuzzing/README.md运行设置UBSAN_OPTIONShalt_on_error1为每个 target 建独立 corpus 目录崩溃处理libFuzzer 会把崩溃输入写到当前目录用FMT_FUZZ_LINKMAINOn的复现模式配合 gdb/valgrind 定位覆盖思路六个 target 分别覆盖 chrono 时长/时间点、浮点往返一致性、命名参数、单/双参数组合输入布局均以类型选择字节 固定 16 字节值 格式串为骨架理解布局有助于手工构造最小复现用例。如需更深入了解格式化本身的机制可继续阅读 include/fmt/format.h、include/fmt/chrono.h 与 include/fmt/base.h并与 test/fuzzing 中各 target 的调用方式相互印证。【免费下载链接】fmtA modern formatting library项目地址: https://gitcode.com/GitHub_Trending/fm/fmt创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表