十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

SurrealDB 模糊测试实战指南:基于 cargo-fuzz 的 Harness 构建、编译与并行执行

SurrealDB 模糊测试实战指南:基于 cargo-fuzz 的 Harness 构建、编译与并行执行 SurrealDB 模糊测试实战指南基于 cargo-fuzz 的 Harness 构建、编译与并行执行【免费下载链接】surrealdbA scalable, distributed, collaborative, document-graph database, for the realtime web项目地址: https://gitcode.com/GitHub_Trending/su/surrealdb导读本文以 SurrealDB 仓库中 fuzz/README.md 为骨架系统讲解如何为 SurrealDB 搭建基于 libFuzzer 与 cargo-fuzz 的模糊测试环境从 nightly 编译器安装、cargo-fuzz 工具链部署到四个官方 fuzz harness 的构建与运行。读完本文你将掌握fuzz_executor、fuzz_sql_parser、fuzz_structured_executor、fuzz_format四个目标各自的设计意图与底层调用链并能通过字典文件与-fork并行参数最大化本地 CPU 利用率从而在自己复现崩溃、回归验证或扩展 Harness 时直接照搬这套工作流。为什么 SurrealDB 需要模糊测试SurrealDB 的核心是一个 SQL 方言SurrealQL解析器加上一套分布式文档-图数据库执行引擎。任何用户输入HTTP 查询、WebSocket 请求、导入文件最终都会进入两条关键路径语法解析surrealdb_core::syn::parse将字符串解析为 AST位于 surrealdb/core/src/syn/mod.rs执行引擎解析后的 AST 交给Datastore与Session执行如 surrealdb/core/src/dbs/iterator.rs 中的process。这两条路径一旦被畸形输入触发 panic、无限递归或栈溢出就会造成服务拒绝。为此仓库维护了一套由 cargo-fuzz 管理的模糊测试 Harness全部位于 fuzz 目录通过覆盖率反馈驱动的 libFuzzer 在运行时自动发现并复现崩溃输入。四个官方 Fuzz Harness 全景仓库在 fuzz/Cargo.toml 中声明了四个二进制目标分别覆盖解析与执行两个层面Harness 二进制源码目标对象核心动作fuzz_sql_parserfuzz/fuzz_targets/fuzz_sql_parser.rsstr原始字符串直接调用syn::parse验证不要崩溃fuzz_executorfuzz/fuzz_targets/fuzz_executor.rsstr原始字符串分号切分命令后在内存 Datastore 中逐条executefuzz_structured_executorfuzz/fuzz_targets/fuzz_structured_executor.rsAstarbitrary 结构化输入将任意生成的 AST 直接送入Datastore::processfuzz_formatfuzz/fuzz_targets/fuzz_format.rsAstarbitrary 结构化输入格式化 → 重新解析的往返一致性校验其中fuzz_executor与fuzz_sql_parser使用字节/字符串级输入能探测分词、解析、语法层面的问题而fuzz_structured_executor与fuzz_format依赖libfuzzer-sys的arbitrary-derive特性见 fuzz/Cargo.toml直接从任意字节流生成Ast结构体绕开字符串词法层专攻深层语义、类型推导与执行路径。环境准备nightly 编译器与 cargo-fuzz为什么必须用 nightly模糊测试的高效性依赖运行时代码覆盖率反馈coverage feedback来引导变异方向。在撰写本文所依据的 README 时当前 stable 版 rustc 尚无法对 harness 进行覆盖率插桩因此必须借助 nightly 中的前沿特性。仓库根目录提供了工具链锁定文件rust-toolchain.toml 与 rust-toolchain.nightly可用 rustup 按需安装对应 nightly 工具链rustup toolchain install nightly安装后所有 fuzz 相关命令都需要显式指定 nightly即cargo nightly ...。安装 cargo-fuzzcargo-fuzz 的完整安装选项可参考 cargo-fuzz 官方书fuzz/README.md 中给出的文档地址最简安装只需一条命令cargo nightly install cargo-fuzz该命令会把cargo fuzz子命令安装到本地 cargo bin 目录之后即可用cargo nightly fuzz驱动整个构建与运行流程。构建 Fuzzer优化与调试两种模式标准构建最大优化fuzz/README.md 给出的标准构建命令以fuzz_executor为例cargo nightly fuzz build --fuzz-dir ./ fuzz_executor其中--fuzz-dir ./指以 fuzz 目录即 README 所在目录作为 fuzz 工作区目标名fuzz_executor对应 fuzz/Cargo.toml 中的[[bin]]声明该命令默认携带调试信息并以-O3最大优化编译确保运行时吞吐最大化适合长时间挂机跑覆盖率。[profile.release] debug 1见 fuzz/Cargo.toml保证了在 release 优化下依然保留行级调试信息方便后续用符号化工具分析崩溃栈。无优化构建复现崩溃专用当你在排查一个已发现的崩溃时全量优化会显著拖慢编译。README 明确指出构建时追加-D可关闭优化虽然模糊测试速度会慢约 10 倍但对于复现某个固定崩溃输入而言依然绰绰有余cargo nightly fuzz build -D --fuzz-dir ./ fuzz_executor建议的实践是日常跑量用-O3构建拿到崩溃样本后切到-D构建复现并加日志调试。运行 Fuzzer单线程到全核并行列出可用 Harness构建完成后先用fuzz list确认仓库中所有可用的 fuzz 目标cargo nightly fuzz list --fuzz-dir ./该命令会输出fuzz_sql_parser、fuzz_executor、fuzz_structured_executor、fuzz_format四个名称与 fuzz/Cargo.toml 中的 bin 声明一一对应。默认单线程运行libFuzzer 的默认模式是单线程。以fuzz_executor为例cargo nightly fuzz run --fuzz-dir ./ fuzz_executor运行后会持续输出覆盖率、执行速度exec/s、新路径发现数量等统计一旦发现崩溃会把最小化后的输入落盘到 fuzz 工作区的artifacts目录。全核并行 字典文件要充分利用本机算力可以用 libFuzzer 的-forkN参数启动 N 个独立进程并行 fuzz并用-dict加载 SurrealQL 专用字典来提升变异效率README 中的#FUZZ_TARGET#需替换为实际目标名如fuzz_executor# -fork: 运行 N 个独立进程并行 fuzz这里用 nproc 匹配本机处理器数量 # -dict: 启用该 fuzzer 专属字典文件 cargo nightly fuzz run --fuzz-dir ./ \ fuzz_executor -- -fork$(nproc) \ -dictfuzz/fuzz_targets/fuzz_executor.dict注意--之后的参数会原样透传给 libFuzzer。$(nproc)在 Linux 上返回逻辑核数可自动做到一台机器开满核。深入源码四个 Harness 的实现原理fuzz_sql_parser最薄的防崩溃门卫fuzz/fuzz_targets/fuzz_sql_parser.rs 全量实现只有几行#![no_main] use libfuzzer_sys::fuzz_target; fuzz_target!(|data: str| { // Dont crash. _ surrealdb_core::syn::parse(data); });要点#![no_main]是 libFuzzer harness 的固定写法由libfuzzer_sys提供真正的入口输入是任意str直接喂给 surrealdb/core/src/syn/mod.rs 的parseparse内部会基于Capabilities::all()构建解析设置见 settings_from_capabilities并在 parse_with_settings 中对输入长度做u32::MAX上限校验、对对象与查询递归深度设限从解析器层面防止超长输入与深递归导致的栈溢出。它验证的契约是任何字符串经parse只应返回Ok或Err绝不 panic。fuzz_executor带会话的端到端执行fuzz/fuzz_targets/fuzz_executor.rs 是真正的执行级 harnessfuzz_target!(|commands: str| { let commands: Vecstr commands.split_inclusive(;).collect(); let blacklisted_command_strings [sleep, SLEEP]; use surrealdb_core::{dbs::Session, kvs::Datastore}; let max_commands 500; if commands.len() max_commands { return; } tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { let dbs Datastore::new(memory).await.unwrap(); let ses Session::owner().with_ns(test).with_db(test); for command in commands.iter() { for blacklisted_string in blacklisted_command_strings.iter() { if command.contains(blacklisted_string) { return; } } let _ignore_the_result dbs.execute(command, ses, None).await; // TODO: 为查询包一层 tokio 超时防止单个命令卡死整个 fuzz 进程 } }) });设计细节值得展开分号切分 上限保护split_inclusive(;)把一次模糊输入拆成多条语句模拟真实的多语句请求max_commands 500限制语句条数避免一个畸形输入构造出上万个查询拖垮 fuzz 进程黑名单机制sleep/SLEEP被直接拒绝。字典 fuzz/fuzz_targets/fuzz_executor.dict 中同样注释了# Sleep is just going to slow the fuzzer down二者配合防止 fuzz 输入调用sleep等函数让执行引擎挂起白白浪费 CPU内存 DatastoreDatastore::new(memory)依赖 core crate 的kv-mem特性见 fuzz/Cargo.toml每次模糊输入都启动一个全新内存库无磁盘污染、无跨输入状态干扰owner 会话Session::owner().with_ns(test).with_db(test)模拟拥有全部权限的超级用户会话确保测试聚焦执行引擎本身而非权限检查单线程 Tokionew_current_thread().enable_all()构建当前线程 runtimeblock_on包裹全部执行保证每次 fuzz 迭代串行且可预期忽略结果_ignore_the_result dbs.execute(...)表明目标是执行不 panic返回值本身不重要——panic 或超时才是要抓的 bug。fuzz_structured_executor直接生成 ASTfuzz/fuzz_targets/fuzz_structured_executor.rs 走的是结构化路线fuzz_target!(|query: Ast| { tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap().block_on(async { let dbs Datastore::new(memory).await.unwrap(); let ses Session::owner().with_ns(test).with_db(test); _ black_box(dbs.process(query, ses, None).await); }) });输入类型直接是surrealdb_core::sql::Ast由 libFuzzer 的arbitrary-derive从字节流生成合法的 AST 结构跳过syn::parse阶段把随机 AST 直接交给dbs.process从而探测**结构上合法但语义上致命**的查询例如类型不匹配、深层嵌套表达式、非法图遍历这比字符串变异更容易触达深层执行路径std::hint::black_box防止编译器把结果视为无用值而整段优化掉保证执行真实发生。fuzz_format格式化往返一致性fuzz/fuzz_targets/fuzz_format.rs 是一种**性质测试property test**式 harnessfuzz_target!(|query: Ast| { let format query.to_sql(); let res surrealdb_core::syn::parse_with_settings( format.as_bytes(), ParserSettings { object_recursion_limit: 1_000_000, query_recursion_limit: 1_000_000, files_enabled: true, surrealism_enabled: true, ..ParserSettings::default() }, async |parser, stk| parser.parse_query(stk).await, ); if let Err(e) res { panic!(Failed to parse format\n{e}\n\nSOURCE:\n{format}\nDEBUG:\n{:#?}, query); } });它的契约是任意 AST →to_sql()序列化 → 重新 parse 必须成功。如果格式化输出连自己的解析器都过不了说明存在 AST 序列化 bug如丢括号、运算符优先级丢失。这里把object_recursion_limit与query_recursion_limit放宽到 100 万、并启用files_enabled与surrealism_enabled是为了覆盖那些在生产默认配置settings_from_capabilities 中取自MAX_OBJECT_PARSING_DEPTH/MAX_QUERY_PARSING_DEPTH下可能被深度限制挡住的合法 AST确保 round-trip 测试不受解析深度阈值干扰。字典文件提升变异的语法先验libFuzzer 的字典.dict为变异器提供高价值 token 种子让随机字节更容易拼接出接近合法 SurrealQL 的片段。仓库维护了两个字典fuzz/fuzz_targets/fuzz_executor.dict覆盖 SurrealQL 全部关键字SELECT/DEFINE/RELATE/SCHEMAFULL等、运算符、!、??、::、、*~等、以及array::、crypto::、geo::、math::、parse::、rand::、search::、set::、string::、time::、type::、vector::等命名空间下的全部函数签名如string::distance::levenshtein(、vector::similarity::cosine(fuzz/fuzz_targets/fuzz_sql_parser.dict为纯解析器 harness 准备的同类 token 集。运行对应 harness 时用-dict指定如-dictfuzz/fuzz_targets/fuzz_executor.dict可以显著减少变异器在拼出一个合法关键字上浪费的迭代次数。注意字典中sleep(被注释掉与 executor harness 的黑名单逻辑保持一致避免 fuzz 进程被慢函数拖死。依赖与特性Cargo 配置详解fuzz/Cargo.toml 是理解这套 fuzz 环境的关键配置项值说明[package.metadata] cargo-fuzz true—标记该 crate 是 cargo-fuzz 项目cargo fuzz build才会识别libfuzzer-sys0.4.7arbitrary-derive提供 fuzz 运行时与Ast结构化输入的 derive 支持tokio1.44.2harness 内构建异步 runtime 执行查询surrealdb-corepath ../surrealdb/corefeatures[kv-mem, arbitrary]引入内存存储后端kv-mem与Ast/Value的 arbitrary 实现arbitrarysurrealdb-typespath ../surrealdb/typesfeatures[arbitrary]引入类型的 arbitrary 支持fuzz_format中的ToSql来自该 crate[workspace] members [.]—声明独立 workspace防止干扰仓库根目录的主 workspace[profile.release] debug 1—release 构建保留行级调试信息兼顾速度与崩溃定位两个default-features false意味着 fuzz crate 只拉取被显式声明的特性避免引入额外存储后端缩短编译时间并缩小 fuzz 二进制攻击面。工作流总结与排障建议综合 README 与源码一套完整的 SurrealDB fuzz 工作流如下初始化rustup toolchain install nightly再cargo nightly install cargo-fuzz构建日常跑量用cargo nightly fuzz build --fuzz-dir ./ fuzz_executor-O3复现崩溃时改用-D关闭优化确认目标cargo nightly fuzz list --fuzz-dir ./核对四个 harness 名称运行单线程cargo nightly fuzz run --fuzz-dir ./ fuzz_executor提效则追加-- -fork$(nproc) -dictfuzz/fuzz_targets/fuzz_executor.dict处置崩溃libFuzzer 会把触发 panic 的最小输入写入 artifacts 目录再用-D构建运行同一输入复现配合[profile.release] debug 1保留的调试信息定位栈帧。常见注意点所有命令都必须带nightly否则 stable 编译器无法插桩覆盖率--之后的参数是 libFuzzer 的不是 cargo-fuzz 的-fork、-dict、-max_len等均需放在--后若发现 fuzz 进程卡死可参照 fuzz/fuzz_targets/fuzz_executor.rs 中的 TODO为每条命令的执行 future 包一层tokio::time::Timeout或tokio::select!新增 harness 时在 fuzz/Cargo.toml 追加[[bin]]段并在 fuzz/fuzz_targets 下新建#![no_main]的fuzz_target!源文件即可被fuzz list自动发现。通过这套环境你可以系统性地对 SurrealQL 的解析与执行引擎做持续攻击面测试——无论是为上游提交崩溃报告还是在自己的分支上做变更前的回归验证fuzz 目录都是一份开箱即用的基础设施。【免费下载链接】surrealdbA scalable, distributed, collaborative, document-graph database, for the realtime web项目地址: https://gitcode.com/GitHub_Trending/su/surrealdb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表