
Databend SQL AST 解析器 AFL 模糊测试实战指南从 cargo-afl 安装到 fuzz_parse_sql 持续运行【免费下载链接】databendData Agent Ready Warehouse : One for Analytics, Search, AI, Python Sandbox. — rebuilt from scratch. Unified architecture on your S3.项目地址: https://gitcode.com/GitHub_Trending/da/databend本文以 Databend 仓库中的 src/query/ast/fuzz/README.md 为核心指南系统讲解如何使用cargo-aflAmerican Fuzzy Lop 的 Rust 绑定对 Databend 的 SQL AST 解析器进行模糊测试Fuzzing。你将掌握从工具安装、fuzz target 构建、种子语料准备到持续运行的完整流程并深入理解 Databend 解析器的底层调用链tokenize → parse以及仓库内另外两套模糊测试方案之间的分工从而能直接在自己的环境下复现并扩展这套解析器安全测试方案。一、为什么需要对 SQL 解析器做模糊测试SQL 解析器是数据库前端最容易暴露安全隐患的组件之一它直接接收来自客户端的任意文本输入需要经过词法分析Tokenizer与语法分析Parser两个阶段任何未覆盖到的边界情况都可能引发 panic、死循环或非预期行为。Databend 的解析器由databend-common-astcrate 提供位于 src/query/ast其核心入口定义在 src/query/ast/src/parser/parser.rs是整个查询引擎的第一道关卡。模糊测试的思路很直接持续向解析器投喂随机变异或生成的 SQL 文本一旦触发崩溃crash或超时hang说明存在可被利用或需要修复的缺陷。cargo-afl正是这一思路在 Rust 生态中的标准实践Databend 官方将其作为解析器的模糊测试基础设施并沉淀在 src/query/ast/fuzz 目录中。二、环境准备安装 cargo-afl原文档给出的安装方式是一条命令cargo install cargo-afl说明如下cargo-afl是 AFL 模糊器原版由 Google 的 Michal Zalewski 编写针对 Rust 的工具链封装安装后会提供cargo afl build与cargo afl fuzz两个子命令该命令依赖 Rust 工具链可正常访问 crates.io仓库根目录的 rust-toolchain.toml 定义了 Databend 构建所用的工具链版本建议在安装前先确认本地 Rust 版本与之匹配AFL 依赖编译器插桩instrumentation来实现覆盖率引导安装完成后可用cargo afl --help验证是否成功。三、认识 fuzz targetfuzz_parse_sql3.1 目录结构与 Cargo 配置src/query/ast/fuzz 目录包含四个组成部分Cargo.toml独立的 fuzz crate 清单fuzz_targets/fuzz_parse_sql.rs本次模糊测试的目标程序in初始种子语料seed corpus目录README.md使用指南即本文依据。值得注意的一点是 Cargo.toml 中的这段注释# cargo cant build fuzz targets with afl # split fuzz into separate workspace can help resolve this. # add an empty [workspace] table to the packages manifest. [workspace]由于cargo-afl无法直接在 Databend 这样的大型工作区中构建 fuzz targetDatabend 将 fuzz 包拆分成独立的 workspace并在其清单中放置了一个空的[workspace]表来切断与父工作区的关联。Cargo.toml还通过[[bin]]声明了二进制目标[[bin]] name fuzz_parse_sql path fuzz_targets/fuzz_parse_sql.rs doctest false test true依赖上仅引入了databend-common-ast复用仓库工作区版本与afl 0.12两个 crate保持 fuzz 目标足够轻量。3.2 fuzz target 源码逐行解读fuzz_targets/fuzz_parse_sql.rs 的核心逻辑非常精简#[macro_use] extern crate afl; use databend_common_ast::parser::parse_expr; use databend_common_ast::parser::tokenize_sql; use databend_common_ast::parser::Dialect; fn main() { loop { fuzz!(|text: String| { let tokens tokenize_sql(text).unwrap(); let _ parse_expr(tokens, Dialect::PostgreSQL); }); } }这段代码体现了三个关键设计loop { fuzz!(...) }模式AFL 的 Rust 绑定通过fuzz!宏注入测试数据并反复回调闭包外层loop保证每次输入处理完成后立即进入下一轮迭代最大化吞吐测试管线为「词法 → 表达式解析」输入文本先经tokenize_sql切分成 token 序列再交给parse_expr以Dialect::PostgreSQL模式解析为表达式Expr。注意这里使用.unwrap()意味着一旦 tokenize 阶段遇到无法处理的输入会直接 panic——这正是 fuzz 期望捕获的崩溃信号聚焦表达式而非完整语句fuzz_parse_sql调用的是parse_expr而非parse_sql说明该 target 专门针对表达式expr解析路径做压力测试覆盖算术、比较、函数调用、字面量等高频子语法。3.3 底层解析调用链印证在 src/query/ast/src/parser/parser.rs 中可以找到被 fuzz 的两个函数的真实实现pub fn tokenize_sql(sql: str) - ResultVecToken_ { let mut tokens Vec::with_capacity((sql.len() / 4).clamp(4, 256)); for token in Tokenizer::new(sql) { tokens.push(token?); } Ok(tokens) } /// Parse udf function into Expr pub fn parse_expr(tokens: [Token], dialect: Dialect) - ResultExpr { run_parser(tokens, dialect, ParseMode::Default, false, expr) }可以看到tokenize_sql内部基于Tokenizer逐 token 推进任何词法错误都会通过?提前返回Errparse_expr最终落到run_parser(...)与expr解析器也就是 fuzz 闭包中真正接受考验的组合子。而 src/query/ast/src/parser/input.rs 定义了 fuzz 中使用的Dialect枚举pub enum Dialect { #[default] PostgreSQL, MySQL, Hive, PRQL, Experimental, }不同方言在标识符引号或、字符串引号、默认引号字符等细节上有差异见同一文件中的is_ident_quote、is_string_quote等实现。fuzz_parse_sql固定使用默认的PostgreSQL方言这也意味着 MySQL、Hive 等方言的差异路径暂未纳入本 target——后续若要扩大覆盖面可以让Dialect参数随 fuzz 输入轮换如按字节选择方言这是本 target 最容易扩展的方向。四、构建 fuzzer按原文档进入 fuzz 目录后执行cd src/query/ast/fuzz cargo afl build要点说明cargo afl build会以 AFL 插桩方式编译当前 crate即fuzz_parse_sql产物为target/debug/fuzz_parse_sql因为 fuzz 包已被声明为独立 workspace命令必须在 src/query/ast/fuzz 目录内执行而不是在仓库根目录否则会受父工作区影响而失败首次构建会同时编译databend-common-ast及其依赖链耗时取决于机器性能若机器缺少 gcc/clang 等 C 工具链AFL 插桩编译可能失败需要先安装基础编译工具。五、准备种子语料Seed Corpus模糊测试并非从零开始乱撞而是以少量合法、有代表性的输入作为种子再通过变异不断探索新路径。仓库已在 in 目录准备了 3 个种子文件种子文件内容覆盖点in/1.sqlSELECT count(*) FROM numbers(3);聚合函数、*、表函数numbersin/2.sqlSELECT uniq(number % 3, number) FROM numbers(1000);多参数函数、取模运算、复合表达式in/3.sqlCOPY INTO ontime200 from s1 FILES (...) FILE_FORMAT (...);COPY INTO、Stage 引用、结构化FILE_FORMAT参数从源码角度看这三个种子分别覆盖了「聚合/函数调用」「二元运算符 多参函数」「复杂 DDL/DML 语句中的表达式子项」为 AFL 提供了三个不同的探索起点。你也可以根据实际需求继续向in/目录添加种子文件例如带字符串转义、注释、嵌套括号的 SQL遵循「小而多样、尽量合法」的原则能显著提升变异效率。六、启动模糊测试使用fuzz_parse_sqltarget 正式开跑cargo afl fuzz -i in -o out target/debug/fuzz_parse_sql各参数含义-i in指定种子语料输入目录即上文的 in-o out指定输出目录AFL 会把发现的崩溃out/crashes、超时out/hangs以及队列队列状态写入这里首次运行时若out不存在会自动创建target/debug/fuzz_parse_sql第四步构建出的插桩版 fuzz 程序程序启动后会进入交互式 AFL 状态界面实时展示执行速度exec speed、路径总数total paths、崩溃数crashes等指标按Ctrl-C可随时停止。启动后的实践建议观察崩溃一旦界面出现非零的 crashes 计数AFL 会自动将复现输入保存到out/crashes/目录文件名为触发崩溃的输入内容复现与修复用保存的崩溃样本直接重放即可稳定复现例如target/debug/fuzz_parse_sql out/crashes/id:...也可配合cargo afl fuzz的-C崩溃模式修复解析器后再次运行同一样本验证长时运行模糊测试的价值随时间累积建议在 CI 之外的专用机器上长时间运行并定期归档out/中的发现超时与内存如遇复杂输入导致解析超时hangs可调整 AFL 的-t单次执行超时毫秒数与-m内存限制参数。七、仓库内的其他模糊测试分工与互补除了本文聚焦的 AFL 解析器 fuzzDatabend 仓库还维护着一套基于语法生成Grammar-based的模糊测试两者互补位置tests/fuzz/fuzz.py 使用 Python 的fuzzingbook.Grammars库以生成式文法构造 SQL 语句并通过 MySQL 协议默认127.0.0.1:3307可用QUERY_MYSQL_HANDLER_HOST、QUERY_MYSQL_HANDLER_PORT、MYSQL_USER等环境变量覆盖连接正在运行的查询实例执行语句观察执行错误定位差异fuzz_parse_sql是无外部依赖的解析器白盒 fuzz只在解析层打转速度快、直接定位解析缺陷fuzz.py是端到端黑盒 fuzz覆盖 binder、优化器、执行器等后续链路但需要起服务CI 集成两者的入口都能在 scripts/ci/ci-run-fuzz-tests.sh 找到该脚本cd到tests/fuzz后执行python3 fuzz.py说明语法生成式 fuzz 已纳入 CI 流水线而 AFL 解析器 fuzz 因耗时较长更适合作为独立的持续模糊任务离线运行。选择建议日常开发中快速回归解析器可用fuzz_parse_sql秒级启动、无依赖做发布前的深度安全验证可将 AFL 长跑与fuzz.py端到端 fuzz 结合使用。八、总结通过本指南你已经掌握了 Databend SQL 解析器模糊测试的完整闭环cargo install cargo-afl安装工具 → 进入 src/query/ast/fuzz 独立 workspace 执行cargo afl build→ 使用 in 中的种子语料执行cargo afl fuzz -i in -o out target/debug/fuzz_parse_sql。同时通过 fuzz_targets/fuzz_parse_sql.rs 的源码我们印证了其底层调用的是 parser.rs 中的tokenize_sql与parse_expr以及 input.rs 中的方言枚举。这套方案既可直接复现也可按需扩展——例如扩充in/种子、轮换Dialect、或参考 tests/fuzz/fuzz.py 将覆盖面推进到执行层是保障 Databend 解析器健壮性的第一道自动化防线。【免费下载链接】databendData Agent Ready Warehouse : One for Analytics, Search, AI, Python Sandbox. — rebuilt from scratch. Unified architecture on your S3.项目地址: https://gitcode.com/GitHub_Trending/da/databend创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考