skip to content

Jit-Picking: Differential Fuzzing of JavaScript Engines 阅读笔记

JIT picking 是一个使用差分测试对 JS 引擎进行测试的 fuzzing 工具,目前很多对 JS 引擎 JIT 部分的测试都是基于差分测试了

本文问题

缺乏有效的错误检测机制

  • 现有的模糊测试方法主要依赖于诸如崩溃信号、内存安全检查器或断言等通用的错误检测机制。
  • 然而,这些机制对于识别逻辑错误和误计算并不敏感,因为它们通常需要严重的错误才能触发警报。

JIT 编译器的复杂性

  • JIT 编译器会执行各种复杂的优化,例如类型推断、范围分析、循环不变式代码移动等。
  • 这些优化可能会导致代码的行为与解释器执行的行为不同,从而引入逻辑错误和误计算。

本文方案

为了克服这些挑战,论文提出了使用差分模糊测试来检测 JavaScript 引擎中的 JIT 逻辑错误和误计算。

差分模糊测试的核心思想是

  • 对同一输入执行两次 JavaScript 代码:
    • 一次启用 JIT 编译器。
    • 另一次仅使用解释器。
  • 比较两次执行的行为,如果存在差异,则表明可能存在逻辑错误或误计算。
  • 论文提出了一种名为 Jit-Picker 的工具,该工具实现了差分模糊测试,并通过注入状态探针来检测执行过程中的不一致性。

Jit-Picker 的工作原理

  • 代码注入: 在生成的 JavaScript 代码中注入状态探针,以收集执行过程中的计算结果。
  • 执行比较: 分别使用解释器和 JIT 编译器执行代码,并比较探针收集到的结果。
  • 错误检测: 如果两次执行的结果存在差异,则表明可能存在逻辑错误或误计算。

下面是本文方案的执行流程:

HoLj5Z

更细粒度的检测机制

可以看出来本文最关键的机制就是更细粒度的检测机制,本文采用在代码中插桩的形式来构建一个更细粒度的检测机制, 实时收集运行信息,这样可以方便后续对结果进行对比

一开始作者构想使用在生成的测试用例中插入探测函数进行计算结果获取

Orfl1P

但是这样会产生两个问题:

遗漏循环内的错误计算:

所有探测调用均位于代码执行末尾,可能导致循环内部发生的错误计算被遗漏。在右图中,result2 仅在特定迭代中出现错误计算,除非该错误值传播到循环外,否则 Jit-Picker将无法捕获。

变量生命周期延长抑制优化:

若需在 main() 函数末尾探测变量,这些变量必须声明在最外层作用域。然而,延长变量生命周期会阻碍某些编译器优化(如寄存器分配或死代码消除)。由于激进的代码优化往往是错误计算的根源,我们应避免因变量生命周期延长而减少优化的可能性。

本文的贡献

于是本文的对浏览器引擎进行了修改,将探测函数构建为 JS 引擎的一条 IR 指令,同时将这个指令的属性设置为可内联原生函数, 在 SpiderMonkey 编译的时候,引擎在优化阶段将其视为原子操作,而非不透明的外部调用。

实验评估

下图是本文方案测试后的代码覆盖率图像

ZDj1q5

可以看到代码覆盖率上大多数情况下和 Fuzzilli 持平,只有在 V8 的测试上不如 Fuzzilli

但是可以看到用于测试的种子数量要少于 Fuzzilli 因此我们做到了在更少的测试用例下可以得到不错的代码覆盖率

GUNUDQ