实战指南:fuzz CLI 与 libFuzzer / Honggfuzz / AFL 测试目标全解析)
TiKV 模糊测试Fuzzing实战指南fuzz CLI 与 libFuzzer / Honggfuzz / AFL 测试目标全解析【免费下载链接】tikvDistributed transactional key-value database, originally created to complement TiDB项目地址: https://gitcode.com/GitHub_Trending/ti/tikv导读本文围绕 TiKV 仓库根目录下 fuzz/README.md 展开系统讲解 TiKV 内置的模糊测试体系一个名为fuzz的自定义 CLI 工具以及它如何驱动 libFuzzer、Honggfuzz、AFL 三种主流模糊器编译并运行针对 TiKV 编解码与 TiDB 查询数据类型的测试用例。读完本文你将掌握 fuzz 目录的整体结构、三种模糊器的环境准备与版本约束、list-targets/run子命令的完整用法、种子seeds与语料库corpus的组织规则并能从源码层面理解目标自动发现、模板代码生成和各类模糊器底层调用链的运作原理。fuzz 目录总体结构一套面向 TiKV 的模糊测试框架fuzz目录是 TiKV Cargo 工作区中的一个独立 package见 fuzz/Cargo.tomlname fuzzpublish false其职责正如 README 所概括既包含 TiKV 的模糊测试用例test cases也包含一个自定义 CLI 工具fuzz用它通过多种模糊器来构建和运行这些测试。目录布局如下fuzz/ ├── Cargo.toml # fuzz CLI 自身clap 3.1.6 解析参数 ├── cli.rs # fuzz 工具入口list-targets / run 子命令 ├── targets/ # fuzz-targets crate所有测试用例本体 │ ├── Cargo.toml # 依赖 tidb_query_datatype、tikv_util 等工作区 crate │ ├── mod.rs # 全部 pub fn fuzz_xxx 目标CLI 通过正则解析此文件 │ └── util.rs # ReadLiteralExt从字节流读取结构化字面量 ├── fuzzer-libfuzzer/ # 面向 libFuzzer 的包装 cratelibfuzzer-sys ├── fuzzer-honggfuzz/ # 面向 Honggfuzz 的包装 cratehonggfuzz 0.5.47 ├── fuzzer-afl/ # 面向 AFL 的包装 crateafl 0.14仅 x86_64 └── common/seeds/ # 可选种子输入default/ 与按目标命名的目录各fuzzer-*子 crate 是“同构”的它们都通过依赖本地fuzz-targetscratepath ../targets把测试目标链接进来区别只在于接入的模糊器库不同fuzzer-libfuzzer依赖libfuzzer-sys 0.3.1fuzzer-honggfuzz依赖honggfuzz 0.5.47cfg(not(target_os windows))fuzzer-afl依赖afl 0.14cfg(all(not(target_os windows), target_arch x86_64))即 AFL 仅支持 x86_64 非 Windows 平台。这种分层设计的核心价值在于测试用例只写一份在targets/mod.rs三个模糊器各自生成薄薄一层入口代码即可复用具体机制见后文“模板生成”一节。支持的 Fuzzer 与前置条件README 明确列出框架支持三种模糊器libFuzzer、Honggfuzz、AFL。它们的安装与系统依赖要求各不相同下面按 README 原文逐项说明并结合仓库源码给出版本约束的细节。Honggfuzz安装 cargo 插件版本必须与库一致cargo install honggfuzz --version 0.5.47README 特别强调cargo 插件的安装版本必须与fuzzer-honggfuzz项目模板所链接的库版本保持一致此处为 0.5.47。这一约束在仓库中有双重佐证fuzz/fuzzer-honggfuzz/Cargo.toml 中依赖声明为honggfuzz 0.5.47在 fuzz/cli.rs 的run_honggfuzz路径中执行cargo hfuzz version进行预检预检失败时会提示先安装插件注意cli.rs中这段预检提示文本写的是cargo install honggfuzz --version 0.5.45与 README/Cargo.toml 的 0.5.47 存在版本文字出入实际应以 Cargo.toml 链接的库版本为准。使用cargo run -p fuzz -- run Honggfuzz test构建 Honggfuzz 测试用例还需要额外的系统开发库且因系统而异。在较新的 Ubuntu 系统上可用如下命令安装sudo apt install binutils-dev libunwind-devAFLcargo install afl与 Honggfuzz 类似run_afl路径也会先执行cargo afl --version做预检。从 fuzz/fuzzer-afl/Cargo.toml 的 cfg 约束可以看到AFL 分支只会在 x86_64 且非 Windows 的目标平台编译。libFuzzerlibFuzzer 随 LLVM 提供不需要额外安装 cargo 插件。但 fuzz/cli.rs 的run_libfuzzer中显式panic!(libfuzzer-sys only supports Linux and macOS)即libFuzzer 分支仅支持 Linux 与 macOS源码中分别对应x86_64-unknown-linux-gnu与x86_64-apple-darwin两个目标平台。此外其构建依赖-Z sanitizer等一系列不稳定编译选项见下文“运行原理”因此需要能开启这些选项的 nightly 工具链cli.rs的注释也提及这是对 Rust nightly 某些问题的规避手段。Seeds可选良好的种子可以显著加速模糊测试的收敛。README 规定了种子的放置规则特定目标的种子放在fuzz/common/seeds/{target}/目录下其中target是 fuzz 目标名即fuzz_xxx去掉fuzz_前缀后的部分如果某目标没有提供种子文件则回退使用fuzz/common/seeds/default/作为种子。仓库当前已提供两类种子目录fuzz/common/seeds/default/testcase 与 fuzz/common/seeds/fuzz_codec_bytes/0——后者即为fuzz_codec_bytes目标准备的种子文件。种子目录的选择逻辑在 fuzz/cli.rs 的get_seed_dir中实现优先取seeds/{target}目录不存在时回退到seeds/default。快速上手list-targets 与 run列出可用的 Fuzz Target在 TiKV 仓库根目录执行cargo run --package fuzz -- list-targets该命令会打印框架发现的所有测试目标。发现机制见 fuzz/cli.rsCLI 启动时用正则pub fn fuzz_(\w)\(解析 fuzz/targets/mod.rs把所有匹配的fuzz_xxx函数名收集为FUZZ_TARGETS列表——因此新增一个 fuzz 目标只需在mod.rs里增加一个pub fn fuzz_xxx(data: [u8]) - Result()函数即可无需改动 CLI 或三个 fuzzer 包。用指定 Fuzzer 运行特定 Targetcargo run --package fuzz -- run [FUZZER] [TARGET][FUZZER]的合法取值是Libfuzzer、Honggfuzz和Afl注意首字母大写与Fuzzer枚举的显示名一致。CLI 在参数解析时开启了ignore_case true因此大小写不敏感fuzz/cli.rs 内置的单测accepts_documented_capitalized_fuzzer_names正是为了锁定文档中这三个“大写名称”的兼容性。语料库corpus目录的约定对于 Libfuzzer 和 Afl语料目录为fuzz/fuzzer-{FUZZER}/corpus-{TARGET}由create_corpus_dir在运行前自动创建。Honggfuzz 路径没有固定的 corpus 目录约定其输入通过环境变量HFUZZ_RUN_ARGS透传见下文。深入原理fuzz CLI 的源码级工作流程fuzz/cli.rs约 360 行是整个框架的枢纽它基于 clap 3.1.6 的 derive 模式定义了两个子命令list-targets与run并借助cargo_metadata定位工作区根目录。其核心流程可以拆解为三步。1. 目标自动发现零配置注册FUZZ_TARGETS在进程启动时通过lazy_static一次性构建读取fuzz/targets/mod.rs的源码文本用正则pub fn fuzz_(\w)\(捕获所有目标名。mod.rs顶部也特意留下了警告注释“DO NOT MOVE THIS FILE. IT WILL BE PARSED BYfuzz/cli.rs”说明该文件与 CLI 之间存在强耦合的约定。运行run时若传入未知目标CLI 会 panic 并提示“Runlist-targetscommand to see available fuzz targets”。2. 从模板生成目标入口代码三个 fuzzer 包各自维护一份template.rslibfuzzer、honggfuzz、afl。run的第一步write_fuzz_target_source_file会把模板读入做两处字符串替换后写入fuzzer-{FUZZER}/src/bin/{target}.rs__FUZZ_CLI_TARGET__→ 目标名如fuzz_codec_bytes__FUZZ_GENERATE_COMMENT__→NOTE: AUTO GENERATED FROM template.rs。三种模板的共同点是把fuzz_targetscrate 中的目标函数use进来并直接调用差异在于入口形态// libFuzzerno_main fuzz_target! 宏 #![no_main] fuzz_target!(|data: [u8]| { let _ fuzz_target(data); }); // Honggfuzzmain 无限循环 fuzz! 宏 fn main() { loop { fuzz!(|data: [u8]| { let _ fuzz_target(data); }); } } // AFLmain fuzz! 宏由 afl crate 提供 fn main() { fuzz!(|data: [u8]| { let _ fuzz_target(data); }); }每个 fuzz 目标函数的签名统一为pub fn fuzz_xxx(data: [u8]) - Result()由targets内部或上层调用方把Err视为正常返回、把 panic 留给模糊器捕获——这正是模糊测试“以崩溃为发现”的核心约定。3. 三种 Fuzzer 的差异化运行路径run校验目标合法后按 fuzzer 分派到三个独立实现维度AFLHonggfuzzlibFuzzer预检cargo afl --versioncargo hfuzz version无无独立插件构建cargo afl build --bin {target}插桩cargo hfuzz run {target}一体化cargo run --target {平台} --bin {target} -- {corpus} {seed}种子输入-i {seed_dir}HFUZZ_RUN_ARGS-f {seed_dir} --exit_upon_crash作为初始语料参数传入输出 corpus-o fuzz/fuzzer-afl/corpus-{target}由 honggfuzz 工具自身管理fuzz/fuzzer-libfuzzer/corpus-{target}AFL 路径先cargo afl build --bin {target}在fuzzer-afl目录内构建插桩二进制再执行cargo afl fuzz -i {seed_dir} -o {corpus_dir} {instrumented_bin}其中插桩二进制路径为工作区根下的target/debug/{target}。Honggfuzz 路径在现有RUSTFLAGS后追加-Z sanitizeraddress启用 AddressSanitizer构造HFUZZ_RUN_ARGS为-f {seed_dir} --exit_upon_crash {用户环境变量 HFUZZ_RUN_ARGS}即默认在崩溃时立即退出、并可透传自定义运行参数最后执行cargo hfuzz run {target}。libFuzzer 路径这是编译参数最重的一条路径cli.rs会拼出一整串RUSTFLAGS--cfg fuzzing \ -C codegen-units1 \ -C incrementalfuzz-incremental \ -C passessancov \ -C llvm-args-sanitizer-coverage-level4 \ -C llvm-args-sanitizer-coverage-trace-compares \ -C llvm-args-sanitizer-coverage-inline-8bit-counters \ -C llvm-args-sanitizer-coverage-trace-geps \ -C llvm-args-sanitizer-coverage-prune-blocks0 \ -C debug-assertionson \ -C debuginfo0 \ -C opt-level3 \ -Z sanitizeraddress这些选项组合的含义是开启 sancov 覆盖率插桩level 4、trace-compares、8-bit counters、trace-geps 等并配合 AddressSanitizercodegen-units1、incrementalfuzz-incremental则是源码注释中提到的对 Rust nightly 某些问题的 workaround。同时还会在ASAN_OPTIONS后追加detect_odr_violation0避免 ODR 检测干扰随后以cargo run --target {平台} --bin {target} -- {corpus_dir} {seed_dir}启动把 corpus 目录与种子目录作为 libFuzzer 的初始语料输入。现有 Fuzz Target 全景它们都在测什么当前 fuzz/targets/mod.rs 定义了 9 个测试目标聚焦于TiKV 与 TiDB 查询引擎的编解码与类型系统是高风险解析逻辑的典型代表Fuzz Target被测代码路径fuzz_codec_bytestikv_util 的字节串编码encode_bytes、encode_bytes_desc、encoded_bytes_lenfuzz_codec_numbertikv_util 的数字编解码u64/i64/f64/u32/i32/u16 的编码含 LE、desc、varint与对应解码函数fuzz_coprocessor_codec_decimaltidb_query_datatype 的 Decimalabs/ceil/floor/round/shift/类型转换与四则运算fuzz_hash_decimalDecimal 的 Hash 一致性验证“相等则哈希相等”否则 panicfuzz_coprocessor_codec_time_from_parseTime 的字符串解析Time::parse_datetime后执行大量时间运算fuzz_coprocessor_codec_time_from_u64Time 的from_packed_u64构造路径fuzz_coprocessor_codec_duration_from_nanosDuration 的from_nanos构造与round_frac等运算fuzz_coprocessor_codec_duration_from_parseDuration 的字符串解析Duration::parsefuzz_coprocessor_codec_row_v2_binary_searchRow V2 编码的二分查找RowSlice::search_in_non_null_ids/search_in_null_ids这些目标的实现展示了 TiKV fuzz 用例的两种典型写法直接喂字节如fuzz_codec_bytes/fuzz_codec_number把模糊器产生的原始[u8]直接送入encode_*/decode_*函数任何 panic 或内存错误都会被模糊器捕获。结构化反序列化借助 fuzz/targets/util.rs 的ReadLiteralExttrait从输入字节流中按需读取u8/i8/u16/i32/u32/u64/i64/f64/bool等字面量把随机字节组合成有意义的参数。例如fuzz_coprocessor_codec_decimal先用两个read_as_f64构造两个 Decimal再读取取整模式、小数位、位移量等参数执行全套 Decimal 运算fuzz_hash_decimal则构建一个“不变量检查”如果两个 Decimal 相等但哈希值不同就 paniceq but not hash eq这是典型的 property-based 模糊思路。常见问题与排错要点预检失败run各路径都会先执行预检命令如cargo afl --version、cargo hfuzz version。若失败CLI 会输出形如Pre-checking for fuzzing failed. Consider run cargo install xxx before fuzzing.的提示按提示安装对应 cargo 插件即可。Honggfuzz 版本不匹配cargo 插件版本必须与fuzzer-honggfuzz链接的库版本一致0.5.47否则可能出现链接期错误。此外在较新 Ubuntu 上还需安装binutils-dev、libunwind-dev等开发库。平台限制libFuzzer 分支在非 Linux/macOS 平台会直接 paniclibfuzzer-sys only supports Linux and macOSAFL 依赖的aflcrate 仅在 x86_64 非 Windows 平台编译Honggfuzz 依赖也排除了 Windows。nightly 工具链libFuzzer 路径依赖-Z sanitizer、-C passessancov等不稳定选项需要对应的 nightly Rust 工具链。语料目录自动创建LibFuzzer 与 AFL 的corpus-{target}目录由create_corpus_dircreate_dir_all自动创建无需手工初始化生成的src/bin/{target}.rs是模板自动生成的产物文件内注明AUTO GENERATED FROM template.rs无需手工编辑。总结TiKV 的fuzz框架把“用例编写”与“模糊器接入”解耦用例只需以pub fn fuzz_xxx(data: [u8]) - Result()的形式写在 fuzz/targets/mod.rsCLI 通过正则自动发现目标、按模板生成入口代码再分别驱动 libFuzzer、Honggfuzz、AFL 三种模糊器完成插桩构建与运行。借助种子目录 fuzz/common/seeds 与自动维护的 corpus 目录开发者可以快速对 TiKV 的编解码、Decimal/Time/Duration 等高风险解析逻辑开展持续的崩溃挖掘是 TiKV 质量保障体系中面向恶意与畸形输入的一线防线。【免费下载链接】tikvDistributed transactional key-value database, originally created to complement TiDB项目地址: https://gitcode.com/GitHub_Trending/ti/tikv创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考