尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Gleam 编译器模糊测试:随机程序能否发现漏洞?

Gleam 编译器模糊测试:随机程序能否发现漏洞? 对 Gleam 编译器进行模糊测试能否通过生成随机程序来发现编译器中的漏洞呢发布时间为 2026 年 8 月 25 日周二。引言有人经常查看 Gleam 的更新日志和问题跟踪器非常喜欢这个项目以及为其做出贡献的人们。但每次看到与代码生成或 Erlang 和 JavaScript 之间不同输出相关的问题时就会纠结因为根本没办法“穷举所有 Gleam 程序”运行它们并查看是否存在问题。此人把这想象成一个棋盘棋盘上有近乎无限的可能局面希望这个棋盘上摆满 Gleam 程序并且有一个无限大的程序数据库以此来发现那些未经测试的边界情况。最初此人尝试让一个大语言模型LLM来帮忙让它阅读大量过去的 Gleam 问题并“深入思考”以找出更多边界情况。它提出了各种各样的位数组组合、嵌套匿名函数和嵌套 use 模式。不出所料这种方法收效甚微。花费了 20 美元的令牌费用后它只发现了一个问题该问题被立即报告并修复。虽然一个总比没有好但“LLM 模糊测试”存在很多问题成本高、结果不确定有点像在玩老虎机。不过此人一直回避尝试另一个想法因为它听起来工作量太大了那就是结构感知模糊测试。结构感知模糊测试编写软件并非易事人类在这方面也并非总能做得很好。为了提供帮助开发了其他软件能够部分自动化地搜索漏洞。其中一种程序就是模糊测试器fuzzer它们生成随机输入供程序使用。其原理是从大规模来看这些随机输入的分布方式会使未曾考虑到的边界情况浮出水面。模糊测试器的输入范围很广从完全随机的乱码字节到高度结构化的、了解语法的抽象语法树AST都有。向程序输入完全随机的字节通常用于处理图像、文件、网络请求、协议等的用例。在许多案例中模糊测试都发现了开源软件中的实际安全漏洞和缺陷。此外模糊测试器还通过缓冲区溢出发现了一些实际可利用的安全漏洞。谷歌有一个名为“OSS Fuzz”的项目会持续对许多重要的开源项目进行模糊测试。在这个案例中要处理的不是浏览器或网络协议而是一个编译器这就为结构感知模糊测试提供了可能。这意味着不是生成随机字节流而是生成源代码或 AST 形式的代码流。走进 GleamGleam 有几个特点使其成为模糊测试的理想候选对象双目标代码生成它能为 JavaScript 和 Erlang 两个目标平台生成代码可以比较同一程序在两个目标平台上的输出并标记出任何差异。简约的语法至少与大多数其他流行编程语言相比是这样可以用相对较少的代码生成有效的程序覆盖该语言提供的几乎所有概念。静态类型能确保程序在运行时不会崩溃但这并不意味着类型系统不会有漏洞。过去就曾出现过与类型推断相关的问题语言的每个方面都需要有自己的测试方法。函数式特性一切皆为表达式的特点让程序的组合和结构变得非常方便。基于 Rust 编写Gleam 编译器本身是用 Rust 编写的这使得集成现有的模糊测试工具变得非常容易可以测试编译器的部分功能而无需运行单个 .gleam 文件。参考资源将深入探讨模糊测试器的更多技术细节但不会涉及太多代码或详细内容。模糊测试器将基于生成式而非变异式。在一篇文章中作者得出结论至少对于 WebAssemblyWasm来说变异式方法比生成式方法发现的问题要多得多所以在未来的项目中实现变异式方法可能是值得的若想更深入地探讨这个话题可以查看相关资源。可以在分叉的 Gleam 仓库的特定分支中找到 Gleam 模糊测试器的完整代码。阶段一解析器模糊测试器的一个重要设计选择是使用公共编译器 API。尽管编译器 API 可能没有稳定性保证但这能让其轻松与未来版本的 Gleam 保持兼容。同时它还避免了处理实现细节有助于确保不会产生误报或漏报。以下是解析器如何捕获和分类输出的一些示例展示了不同代码输入对应的编译结果如编译成功、解析错误、分析拒绝等。使用 fuzz crate 和一些包装代码可以迅速向 Gleam 编译器发送随机生成的输入目前还未结构化看看是否能让编译器崩溃而非只是给出带有更多上下文的错误信息。只运行 1 秒因为输出量很大展示了运行过程中的一些信息和结果。这样做的好处是无需运行 gleam 二进制文件就能测试所有内容它在内存中通过 Rust 编译器管道运行。当让这个模糊测试器运行一段时间后它真的在 nightly 版本中发现了一个回归问题而在 v1.18.1撰写本文时 Gleam 的最新版本中并未出现该问题。关键输入是 const 表达式中的管道操作符 |例如const b 1 | 2。一旦发现问题还会使用 git bisect 来处理该问题以区分是 nightly 版本的回归问题还是当前 Gleam 最新版本存在的问题。当然理想情况下不会只让它运行 1 秒而是运行数小时。公平地说在这一步发现的漏洞很容易捕获但可能无法发现代码生成方面的漏洞。在发布本文时为了专注于类型安全的程序libFuzzer 包已从分支中移除计划在项目的后期阶段进行更有针对性、更高效的编译器崩溃测试即 [tree - splicer。阶段二类型安全的程序为了生成类型安全的 Gleam 程序将构建一个“生成器smith”。创建自己简化版的 Gleam AST然后以概率方式生成程序。这样当生成器决定“我们需要一个解析为 Int 的表达式”时它可以提供一个字面量值如 3或者一个匿名函数 fn() { 3 }()或者一个变量等等。通过这种方式可以可靠地在程序之间创造出大量的多样性最终发现新的边界情况。展示了其中一个程序的样子看起来像是一堆乱码但这正是目的生成的程序由随机选择的有效表达式组合而成希望能发现那些在一个或两个目标平台上导致错误行为的组合。但是如果一个 Gleam 程序成功编译如何知道它是否产生了错误行为呢这就是两个编译目标发挥作用的地方。例如如果一个包含 case 表达式的程序存在漏洞而该逻辑在 Erlang 中正确实现那么当在每个分支中提供不同的值时JavaScript 目标平台上的输出就会不同这样就能发现问题。这种方法并非万无一失但它是一个很好的起点。编译器中可能存在在两个目标平台上都会出现的漏洞因此这种方法可能会产生误报。正如常言所说没有万能的解决方案。在介绍 Gleam 生成器之前要稍微岔开一下话题。echo 问题JavaScript 和 Erlang 对运行时的值表示方式有不同的理解。例如JavaScript 中没有专门的整数和浮点数类型只有 Number。当使用 Gleam 的内置 echo 关键字将某些值转换为字符串时它们的表示方式也会有所不同。展示了一个程序在 Erlang 和 JavaScript 上的输出在 JavaScript 中1.0 打印为 1而在 Erlang 中位数组 1, 2, 3 打印为 \u{0001}\u{0002}\u{0003}。在 Erlang 中记录 Wibble 不包含任何标签。如果想在模糊测试器中直接比较输出这就不太理想。能想到几种解决这个问题的方法有意不测试那些知道 echo 输出会有差异的值在 Gleam 中构建一个自定义的 echo 函数并将其注入到生成的模块中在 Rust 中构建一个自定义的“解析 Gleam 输出”的功能因为可以从生成的 Module AST 中知道哪些值会在 Gleam 中被 echo 输出。在这个版本中选择了第三种方法。由于这是一个约束性很强的问题生成了大量的测试用例并编写了相应的代码来解析任何来自 Gleam 程序的 echo 输出并将其解析为 Rust 枚举类型以便可预测地比较输出。虽然这远非完美但目前看来效果不错。现在一切就绪是时候启动模糊测试器让它生成 10 万个程序了不过这里又要岔开一下话题。重复发现和阻塞问题一旦启动模糊测试器它会生成大量相似结构的程序。因此如果发现了一个漏洞后续还会不断生成能复现该漏洞的程序。有几种方法来处理这个问题修改 Gleam 生成器的代码在问题修复之前不生成某些表达式的组合如果有可用的修复方案无论是自己的还是作为 Pull Request为运行模糊测试器的 Gleam 分叉版本提供并应用补丁这样可以继续运行模糊测试器直到修复该问题的 Pull Request 在 nightly 版本中可用并合并回分叉版本如果问题是一个错误信息而不是值不匹配通常可以找到一个 string.contains 语句在分析时跳过这个问题。在撰写本文时选择了第三种方法。确实尝试过打补丁的方法但这假设 Pull Request 或修复一定是正确的且不会引入任何新的漏洞。最好还是依赖官方仓库中 Gleam 的准确状态。例如发现了两个 JavaScript 代码生成方面的问题所以目前模糊测试器会完全忽略任何与官方仓库中报告的问题具有相似特征的问题。这确实意味着可能会跳过其他具有相似错误特征的代码生成问题。如果程序 AST 中包含导致这些问题的精确表达式可以引入更多的启发式方法和分析。从更大的规模来看这绝对是个好主意因为目前是以 100 个程序为一批运行模糊测试器并手动验证它跳过或标记为“新漏洞”的任何发现。在 100 个程序中通常只有少数几个所以一个人处理起来还是可以应付的。还展示了判断是否为特定问题的代码示例。使用模糊测试器如果想在仓库中亲自尝试一下可以按照示例的命令进行操作展示了不同种子运行模糊测试的结果包括匹配情况、值的对比等。还可以将正在运行的程序打印到标准输出也能批量对程序进行模糊测试展示了批量测试的过程和相关信息。
返回列表