ARTICLE DETAIL

资讯详情

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

zstd 自定义 Sequence Producer 插件模糊测试实战:基于 seq_prod_fuzz_example 的完整指南

zstd 自定义 Sequence Producer 插件模糊测试实战:基于 seq_prod_fuzz_example 的完整指南 zstd 自定义 Sequence Producer 插件模糊测试实战基于 seq_prod_fuzz_example 的完整指南【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo本指南以 zstd 仓库中 tests/fuzz/seq_prod_fuzz_example 目录为核心讲解如何为自定义外部 Sequence Producer 插件接入 zstd 官方模糊测试fuzzing框架将插件编译进 libFuzzer 目标并通过 ASan/UBSan 进行健壮性验证。读完本文你将掌握插件接口的五个核心回调、fuzz.py的--custom-seq-prod构建流程、回退fallback机制的测试方法以及如何把这套流程迁移到自己的序列生成器上。背景为什么需要模糊测试外部 Sequence Producerzstd 的压缩核心依赖序列生成器sequence producer来发现输入中的重复模式并生成(offset, litLength, matchLength)序列。zstd 本身自带一套经过高度优化的内部实现同时也通过ZSTD_registerSequenceProducer()暴露了外部序列生成器 API允许第三方插件如硬件加速设备、专用匹配算法接管序列生成环节。这类插件往往运行在压缩热路径上任何越界读写、窗口越界、序列校验失败都可能导致崩溃或生成非法压缩帧。zstd 官方早在 PR #3437 就为外部 Sequence Producer API 引入了模糊测试基础设施但那一版只能覆盖 zstd 核心库中与插件交互的那部分代码插件作者自己的实现并不在被测范围内。为此zstd 在 fuzz_third_party_seq_prod.h 中定义了一套插件侧符号接口插件作者只需提供实现这些符号的目标文件.o并让该目标文件替换默认实现zstd 的 fuzzer 就会自动把插件代码纳入覆盖范围直接对插件的序列生成逻辑做压力测试。而 seq_prod_fuzz_example 正是这一机制的最小可运行示例。目录结构与示例内容seq_prod_fuzz_example目录只包含三个文件结构极其精简文件作用README.md使用说明四条命令完成拉取语料 → 编译插件 → 构建 fuzzer → 运行example_seq_prod.c插件示例实现实现全部五个回调符号Makefile用 clang 编译插件对象文件的构建脚本示例插件的定位是最小占位实现它并不真正做匹配而是返回ZSTD_SEQUENCE_PRODUCER_ERROR强制走回退路径专门用于验证插件被调用、出错、zstd 回退到默认序列生成器这条链路是否按预期工作。快速开始四条命令跑通整个流程README 给出的完整流程如下均在 tests/fuzz 目录下执行# 1. 下载/解压每个 fuzz target 的官方种子语料到 ./corpora/TARGET $ make corpora # 2. 用 clang 编译示例插件产出 example_seq_prod.o $ make -C seq_prod_fuzz_example/ # 3. 构建全部 fuzzer 目标并链接自定义插件对象文件 $ python3 ./fuzz.py build all --enable-fuzzer --enable-asan --enable-ubsan --cc clang --cxx clang --custom-seq-prodseq_prod_fuzz_example/example_seq_prod.o # 4. 以 libFuzzer 引擎运行 simple_round_trip 目标开始模糊测试 $ python3 ./fuzz.py libfuzzer simple_round_trip下面对每一步展开说明其底层机制。第 1 步make corpora 拉取种子语料fuzz/Makefile 中的corpora目标会为 fuzz.py 中注册的 19 个 targetsimple_round_trip、stream_round_trip、block_round_trip、simple_decompress等分别下载corpora/%_seed_corpus.zip并解压到./corpora/TARGET。这些种子样本是 libFuzzer 变异起点直接影响覆盖率爬升速度建议保留。第 2 步编译插件对象文件seq_prod_fuzz_example/Makefile 的完整内容如下CC clang CFLAGS -g -fno-omit-frame-pointer -fsanitizeundefined,address,fuzzer -I../ -I../../../lib/ .PHONY: default default: example_seq_prod.o example_seq_prod.o: example_seq_prod.c $(CC) -c $(CFLAGS) $^ -o $关键点必须使用 clangfuzz_third_party_seq_prod.h明确说明用 gcc 构建插件不受支持。原因在于-fsanitizefuzzer与-fsanitize-coverage是 clang 专属能力且 libFuzzer 引擎本身依赖 clang 的插桩。sanitizer 与 fuzz 插桩必须在编译插件时开启-fsanitizeundefined,address,fuzzer让插件的读写错误能被 ASan/UBSan 捕获-fsanitizefuzzer使插件代码也参与覆盖率反馈。包含路径-I../指向tests/fuzz为找到fuzz_third_party_seq_prod.h-I../../../lib/指向 zstd 库头文件为找到zstd.h。第 3 步构建 fuzzer 并链接插件fuzz.py build命令中的--custom-seq-prod参数是整个机制的开关。查看 fuzz.py 源码 可以看到它做了两件事if args.third_party_seq_prod_obj: cppflags [-DFUZZ_THIRD_PARTY_SEQ_PROD] mflags [THIRD_PARTY_SEQ_PROD_OBJ{}.format(args.third_party_seq_prod_obj)]定义预处理宏FUZZ_THIRD_PARTY_SEQ_PROD让 fuzzer 源码中插件模式分支生效通过THIRD_PARTY_SEQ_PROD_OBJ把插件对象文件传入 make最终被追加进每个 fuzz target 的链接输入见 fuzz/Makefile 中的FUZZ_D_OBJ10/FUZZ_RT_OBJ10变量。因此插件中的FUZZ_thirdPartySeqProd等符号会在链接期覆盖默认实现而默认的simpleSequenceProducer位于 contrib/externalSequenceProducer/sequence_producer.c则被取代。其余构建参数含义参数作用--enable-fuzzer启用 clang 内置 libFuzzer 引擎-fsanitizefuzzer此时LIB_FUZZING_ENGINE被忽略--enable-asan启用 AddressSanitizer-fsanitizeaddress--enable-ubsan启用 UndefinedBehaviorSanitizer-fsanitizeundefined并默认追加-fno-sanitizepointer-overflow等抑制项--cc clang --cxx clang显式指定编译器也可通过环境变量CCclang CXXclang等价传入注意fuzz.py会校验 sanitizer 组合MSAN 不能与其他 sanitizer 混用见build_parser中的--enable-msan检查逻辑。第 4 步运行 libFuzzerfuzz.py libfuzzer simple_round_trip会执行编译出的simple_round_trip二进制并自动构造三个目录corpora/simple_round_trip主语料目录不存在则创建corpora/simple_round_trip-crash崩溃产物目录通过-artifact_prefix传入 libFuzzercorpora/simple_round_trip-seed种子目录存在则作为额外输入。命令行中未被fuzz.py解析的参数会原样透传给 libFuzzer例如可以追加-jobs10 -workers10 -max_total_time1000进行多核长时间跑测。此外 fuzz/README.md 还提供了跨 target 的批量运行方式例如用for target in $(./fuzz.py list)循环逐个跑或用xargs -P并行跑。插件接口五个必须实现的符号插件需要实现的接口全部定义在 fuzz_third_party_seq_prod.h 中。该头文件要求先定义ZSTD_STATIC_LINKING_ONLY再包含zstd.h以获得ZSTD_Sequence等静态 API 声明。生命周期回调符号调用时机与约定size_t FUZZ_seqProdSetup(void)每个测试用例test-case开始前调用用于执行初始化如启动硬件设备。fuzzer 会assert()返回值必须为 0非零表示出错size_t FUZZ_seqProdTearDown(void)每个测试用例结束后调用释放Setup获取的资源防止跨用例泄漏同样要求返回 0void* FUZZ_createSeqProdState(void)每个测试用例开始时、且在Setup之后调用返回可传入ZSTD_registerSequenceProducer()的序列生成器状态对象。fuzzer 会assert()返回值非 NULLsize_t FUZZ_freeSeqProdState(void* sequenceProducerState)每个测试用例结束后释放状态对象要求返回 0一个值得注意的设计约束同一个测试用例内的所有压缩操作共享同一个状态对象。头文件注释明确解释了原因——当前 fuzzer 不涉及多线程场景因此共享状态是安全的未来若要支持多线程 fuzzing需要新的方案。核心被测函数size_t FUZZ_thirdPartySeqProd(void* sequenceProducerState, ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, const void* src, size_t srcSize, const void* dict, size_t dictSize, int compressionLevel, size_t windowSize);这个函数签名与ZSTD_registerSequenceProducer()的回调签名完全一致它就是插件作者真正想被 fuzz 的对象。每次调用都会收到FUZZ_createSeqProdState()返回的状态指针。windowSize参数尤其重要——插件生成序列时偏移必须落在窗口范围内见下文示例实现。内部宏头文件还定义了两个内部辅助宏fuzzer 源码在#ifdef FUZZ_THIRD_PARTY_SEQ_PROD分支下通过它们完成生命周期管理#define FUZZ_SEQ_PROD_SETUP() \ do { \ FUZZ_ASSERT(FUZZ_seqProdSetup() 0); \ FUZZ_seqProdState FUZZ_createSeqProdState(); \ FUZZ_ASSERT(FUZZ_seqProdState ! NULL); \ } while (0) #define FUZZ_SEQ_PROD_TEARDOWN() \ do { \ FUZZ_ASSERT(FUZZ_freeSeqProdState(FUZZ_seqProdState) 0); \ FUZZ_ASSERT(FUZZ_seqProdTearDown() 0); \ } while (0)当未定义FUZZ_THIRD_PARTY_SEQ_PROD时这两个宏会被展开为空操作fuzzer 行为与常规模式完全一致。示例实现逐行解读example_seq_prod.c 是一个刻意简化的契约自检插件_Thread_local size_t threadLocalState; size_t FUZZ_seqProdSetup(void) { threadLocalState 0; return 0; } size_t FUZZ_seqProdTearDown(void) { return 0; } void* FUZZ_createSeqProdState(void) { return calloc(1, sizeof(size_t)); } size_t FUZZ_freeSeqProdState(void* state) { free(state); return 0; } size_t FUZZ_thirdPartySeqProd( void* sequenceProducerState, ZSTD_Sequence* outSeqs, size_t outSeqsCapacity, const void* src, size_t srcSize, const void* dict, size_t dictSize, int compressionLevel, size_t windowSize ) { /* Try to catch unsafe use of the shared state */ size_t* const sharedStatePtr (size_t*)sequenceProducerState; assert(*sharedStatePtr threadLocalState); (*sharedStatePtr); threadLocalState; /* Check that fallback is enabled when FUZZ_THIRD_PARTY_SEQ_PROD is defined */ return ZSTD_SEQUENCE_PRODUCER_ERROR; }它演示了两个测试要点共享状态一致性自检状态对象每次calloc分配、free释放每次回调都把共享状态与线程局部副本比对并同步递增。若 fuzzer 中存在并发或生命周期管理缺陷例如状态被二次释放、跨用例复用assert会立刻暴露问题。强制回退路径ZSTD_SEQUENCE_PRODUCER_ERROR在 lib/zstd.h 中定义为((size_t)(-1))返回它表示序列生成失败请 zstd 内部回退。这样每次压缩都会走调用外部插件 → 失败 → 回退默认实现的完整链路专门验证插件模式下回退机制始终可用。真实插件不会永远返回错误——它会像 contrib/externalSequenceProducer/sequence_producer.c 中的simpleSequenceProducer那样用哈希表做匹配并把offset windowSize作为硬约束注释明确提示必须保持在窗口大小内然后返回生成的序列数量。fuzzer 如何接入插件从源码看调用链理解插件如何被调用需要看 fuzzer 公共辅助代码 zstd_helpers.cvoid* FUZZ_seqProdState NULL; // 全局状态由 FUZZ_SEQ_PROD_SETUP() 填充 static void setSequenceProducerParams(ZSTD_CCtx *cctx, FUZZ_dataProducer_t *producer) { #ifdef FUZZ_THIRD_PARTY_SEQ_PROD ZSTD_registerSequenceProducer( cctx, FUZZ_seqProdState, FUZZ_thirdPartySeqProd ); #else ZSTD_registerSequenceProducer( cctx, NULL, simpleSequenceProducer ); #endif #ifdef FUZZ_THIRD_PARTY_SEQ_PROD FUZZ_ZASSERT(ZSTD_CCtx_setParameter(cctx, ZSTD_c_enableSeqProducerFallback, 1)); #else setRand(cctx, ZSTD_c_enableSeqProducerFallback, 0, 1, producer); #endif ... }两条关键事实定义FUZZ_THIRD_PARTY_SEQ_PROD即传入了--custom-seq-prod时每个压缩上下文都会注册你的FUZZ_thirdPartySeqProd且强制把ZSTD_c_enableSeqProducerFallback设为 1——这意味着 fuzzer 会系统性测试外部插件失败 → 内部回退的路径未定义该宏时fuzzer 默认注册内置的simpleSequenceProducer仅在约 1/11 的概率下启用回退。两种模式在同一套 target 源码下共存插件模式不会影响常规 fuzz 目标的行为。同时fuzz/Makefile 默认把contrib/externalSequenceProducer/sequence_producer.c编译进所有 target$(DEFAULT_SEQ_PROD_SRC)因此未指定插件时 fuzzer 依然有合法的序列生成器可用。每个测试用例的开始/结束由FUZZ_SEQ_PROD_SETUP()/FUZZ_SEQ_PROD_TEARDOWN()宏在 fuzzer 入口处驱动保证状态对象与初始化动作与用例一一对应。迁移到自己的插件要把示例流程套用到自己的序列生成器上只需四步在插件源码中包含tests/fuzz/fuzz_third_party_seq_prod.h实现上面五个符号核心逻辑放在FUZZ_thirdPartySeqProd中。参照seq_prod_fuzz_example/Makefile用 clang 以-g -fno-omit-frame-pointer -fsanitizeundefined,address,fuzzer并根据需要调整 sanitizer 组合编译出.o文件。构建时把--custom-seq-prod指向你的.o文件其余构建参数保持一致--enable-fuzzer --enable-asan --enable-ubsan --cc clang --cxx clang。运行任意 round-trip 类 target如simple_round_trip、stream_round_trip、dictionary_round_trip这类 target 会校验压缩后再解压得到原始输入插件生成非法序列时必然暴露。建议先用./fuzz.py libfuzzer simple_round_trip -runs10000做冒烟验证再按需加-jobs/-max_total_time做大规模跑测崩溃样本会落在corpora/target-crash可用-artifact_prefix指定路径并通过哈希复现。常见问题与注意事项为什么必须用 clang插件编译与 fuzzer 构建都需要 clang 的-fsanitizefuzzer/ 覆盖率插桩fuzz_third_party_seq_prod.h明确说明不支持 gcc。FUZZ_THIRD_PARTY_SEQ_PROD是谁定义的它由fuzz.py在检测到--custom-seq-prod时自动注入CPPFLAGS无需手动定义只要构建命令带了该参数插件模式即生效。插件返回错误是否算 bug不算。ZSTD_SEQUENCE_PRODUCER_ERROR是合法的失败信号zstd 会启用回退被 fuzz 的正是错误处理 回退的鲁棒性。状态对象为什么可以跨用例内多次压缩共享这是当前 fuzzer 架构的既定假设单线程头文件注明未来多线程场景需要新方案你自己的插件不应假设每次回调拿到独立状态。延伸阅读tests/fuzz/README.mdzstd fuzzing 总览涵盖语料生成make -C ../tests decodecorpus./fuzz.py gen TARGET、AFL、MSAN 与回归测试用法。tests/fuzz/fuzz_third_party_seq_prod.h插件接口的权威定义即本指南引用的接口来源。tests/fuzz/fuzz.py构建/运行脚本build、libfuzzer、afl、regression、gen、minimize、zip、list八个子命令的完整实现。tests/fuzz/zstd_helpers.cfuzzer 与插件对接的核心注册逻辑。contrib/externalSequenceProducer/sequence_producer.c一个真实的simpleSequenceProducer参考实现哈希匹配 窗口约束可作为编写自己插件的起点。lib/zstd.hZSTD_registerSequenceProducer()、ZSTD_Sequence及ZSTD_SEQUENCE_PRODUCER_ERROR的 API 定义。【免费下载链接】mongoThe MongoDB Database项目地址: https://gitcode.com/GitHub_Trending/mo/mongo创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表