Fuzzilli:论文阅读
FUZILLI: Fuzzing for javaScript JIT Compiler Vulnerabilities
全文翻译
摘要
JavaScript 已经成为互联网基础设施不可或缺的一部分,并且如果没有这种编程语言,当今的交互式Web应用程序将是无法想象的。但不利的一面是,这种交互性意味着Web应用程序依赖于越来越多的计算密集型JavaScript代码,这给负责高效执行代码的JavaScript引擎带来了负担。为了满足这些日益增长的性能需求,现代JavaScript引擎都配备了复杂的即时编译器(JIT)。然而,JIT编译器是一项复杂的技术,因此为潜在的漏洞(甚至可能是致命的漏洞)提供了广泛的攻击面。在JavaScript引擎中发现软件漏洞的已有工作中,大部分是模糊测试。不幸的是,这些模糊测试策略并不是为生成实际触发JIT语义的源代码而设计的。因此,JIT漏洞不太可能被现有策略发现。在本文中,我们填补了这一空白,并提出了第一个Fuzzer,重点是JIT漏洞。更具体地说,我们提出了一个中间表示(IR)的设计和实现,重点是发现JIT编译器的漏洞。我们实现了所提出方法的完整原型,并在六个月的时间内对我们的Fuzzer进行了评估。我们总共发现了17个已确认的安全漏洞。我们的结果表明,有针对性的JIT Fuzzing是可能的,并且在JavaScript引擎的Fuzzing覆盖率中存在一个被忽视的危险问题。
1. 介绍
如果没有JavaScirpt(JS),现代Web是不可想象的。在AngularJS、React或JQuery等强大的JavaScript框架的推动下,现代Web内容通常完全在客户端创建,而不是以HTML的形式交付。这种演变给现有仅依赖解释JS代码的JS引擎带来了越来越多的性能问题。因此,为了实现动态的Web体验,现代Web浏览器积极转向JS代码的即时即使编译(JIT)和优化。虽然JIT引擎提供了令人满意的性能改进,但它们使JS代码的执行变得更加复杂,并且本质上暴露了一个很大的攻击面。基于JIT编译器缺陷的软件漏洞对攻击者很有吸引力,因为它们提供了强大的利用原语,并且通常允许基于单个漏洞的代码执行。攻击者可以将一次成功的攻击与浏览器**沙箱逃逸(sandbox)**链接起来,通过引诱受害者访问恶意网站来获得未经授权的权限。
JavaScript作为一种编程语言,其本质上是灵活和动态的。由于这种灵活性,JIT优化需要对引擎的全局状态、相关对象组,甚至参与优化代码段的单个对象做出假设。这些假设必须被证明为真,或者收到复杂的运行时机制的保护,这些机制在先前做出的假设被违反时通知引擎。任何被证明为假但在执行期间仍然未被检测到的假设都代表着一个重大漏洞,例如CVE-2018-4233,这是WebKit的JIT编译器中的一个错误。因此,JIT编译错误应该是软件测试的重点。一种在JavaScript引擎等复杂软件系统中查找错误的流行方法是模糊测试(简称Fuzzing)。模糊测试涉及使用许多不同的输入来测试软件,并评估软件如何响应这些输入。其根本目的是在软件中找到导致重要崩溃的边缘情况。然后,分析人员可以进一步调查这些崩溃,以创建一个概念验证,用于利用可能突破JavaScript 沙箱的漏洞。过去,模糊测试主要用于查找JavaScript引擎中的漏洞,并且已经发现了JavaScript引擎的一些关键问题。然而,以前的模糊测试方法针对JavaScript引擎,而没有侧重于特定组件,或者仅侧重于运行时API。这种方法可以发现范围广泛的漏洞,但很少发现需要多个前提条件同时满足的更复杂漏洞。特别是,JIT编译漏洞正是这种类型的漏洞。
为了使得JIT优化 出现,必须满足某些条件:引擎必须频繁执行有问题的代码,并且代码在观察期间必须表现出可预测的行为,因为只有这样JIT编译才会启动。这些条件意味着,不仅必须以一种特殊的方式构造JS代码以突出显示缺陷,而且还必须以类似的方式多次执行它,然后以不可预测的方式更改其行为,以便JS引擎遇到错误。对于通过突变或多或少随机生成代码片段的Fuzzer来说,这种行为很难重现。传统的JS fuzzer生成JS构造并将每个语句包装在try-catch块中,因为它们无法保证语义正确性。**遗憾的是,JIT 编译器对待包装在try-catch中的代码与未包装的代码不同,因此许多JIT 错误都无法通过这些方法检测到。**其他工作从现有的JS语料库生成测试用例。依赖于预先存在的语料库需要足够多样化的特定漏洞集合,才能推断出类似的漏洞。这种要求限制了发现与现有测试用例不同的漏洞的能力。**总之,开发一种有针对性的模糊测试方法来检测JIT编译中的新缺陷是一项尚未解决的挑战。**在本文中,我们弥补了模糊测试覆盖范围中的这一研究空白,并提出了第一个使用中间表示(IR)的模糊器,该中间表示专注于发现JavaScript引擎中的JIT漏洞。我们的IR允许我们生成没有初始输入语料库的新JavaScript程序,从而针对JIT编译器。此外,我们的IR允许实现语义上有意义的突变操作,例如拼接多个输入程序,同时重新连接指令操作数,这是基于AST的常见模糊测试方法中缺少的功能。
我们实施了所提出的方法,并对我们的原型在主要的JS引擎Apple JavaScriptCore、Google V8和Mozilla SpiderMonkey上进行了全面的评估。我们发现,在所有的引擎上,我们的Fuzzer都与最先进的开源模糊器Superion相比毫不逊色。此外,我们发现,当提供一个全面的输入语料库时,Superion无法实现显著的代码覆盖率提升。相比之下,我们的方法在不同的设置中表现良好,并且我们发现了17个严重(Critical)漏洞。
总之,我们的主要贡献是:
- 我们提出了基于IR的模糊测试方法的设计和实现,该方法对现代Web浏览器JS引擎中的JIT漏洞。
- 在一个全面的评估中,我们通过我们的原型实现发现了17个安全关键型漏洞。对已识别漏洞的更详细分析证实,大多数缺陷确实与JIT编译器有关。
- 我们对现代浏览器Fuzzer进行了全面的比较,发现我们的方法优于称为Superion的最先进方法。
2. 背景和相关工作
模糊测试是一个受欢迎的研究领域,在过去几年中收到了广泛关注。在下文中,我们将简要介绍该领域,并讨论与我们工作密切相关的工作。鉴于该领域的范围非常广泛,我们无法提供所有相关工作的全面概述,因此主要关注改进JavaScript模糊测试的相关工作。有关模糊测试领域的介绍和概述,我们请读者参考关于模糊测试和灰盒模糊测试的综述。有关最近模糊测试出版物的完整列表,我们请读者参考维护该领域已发表论文列表的在线存储库。
A. 模糊测试概述
模糊测试可以在高层次上划分为几种不同的方法,我们将在下面简要解释。这些通用方法提供了一个粗略的分类,在实践中,使用了许多混合方法,因此并不总是能够进行清晰的区分。
a)生成式模糊测试: 基于生成的方法使用生成器函数从头开始生成每个输入,这些生成器函数输出相关数据。生成方法的主要优点是,生成的输入在设计上是语法正确的,因为生成器函数尊重被测程序所期望的基础语法。
b)基于变异的模糊测试: 基于变异的方法使用种子文件,并根据某些规则对其进行操作,然后继续将略微修改的文件作为新的种子文件。突变可以是随机和任意的,例如位/字节翻转或消息部分的随机添加/删除,或者更有针对性,例如用已知过去存在问题的数据点(例如,MAX INT或MIN INIT等魔术值)替换整数或字符串。
c)引导式模糊测试: 引导式模糊测试扩展了基于变异的模糊测试中使用的方法,并根据某些指标(例如,覆盖率引导的模糊测试)基于相关性来修剪突变的文件。在langue fuzzing(用编程语言实现的程序)中,用于修剪的一个流行指标是分支覆盖率。使用分支覆盖率的fuzzer收集关于执行目标程序的分支的数据,并且如果它们在执行期间没有发现任何新的分支,则删除/忽略新的突变文件以供进一步考虑。这种方法确保了模糊测试过程保持一定的势头,并且不会陷入僵局。
B. JavaScript模糊测试
之前的几篇出版物涵盖了JavaScript浏览器引擎或独立的JavaScript引擎的一般模糊测试,但到目前为止,还没有任何出版物专注于JIT编译器中的漏洞。因此,在Javascript模糊器覆盖范围中存在一个空白,我们用我们的方法来填补。请注意,以前的JavaScript模糊测试工作涉及使用中间表示来进行JavaScript fuzzing或语义上正确的JavaScript模糊测试,这两种属性我们的fuzzer为了在fuzzing JIT编译器漏洞中取得成功而具备(并且需要)。因此,我们在下面讨论我们的方法如何与该领域以前的工作相关。
Holler 等人提出了该主题的早期工作之一,它们建议使用抽象语法树(AST)作为中间表示。在fuzzing期间,AST的子节点被获取并替换为来自其他程序的节点(甚至新生成的代码),然后转换回实际的fuzz语言。请注意,此过程生成符合fuzz语言语法的代码。然而,没有关注任何特定的错误类别或生成代码的语义有效性。在我们的工作中,我们不使用AST作为中间表示。相反,我们开发了自己的中间语言,该语言代表JavaScript的一个子集。最重要的是,它能够专注于JIT错误和生成代码的语义有效性。
最近,He等人提出了SoFi,一种语义感知的fuzzing策略。为了确保生成测试用例的有效性,作者建议使用细粒度的程序分析来识别变量,并推导出这些变量的类型以进行突变。此外,SoFi使用自动修复策略来修复无效测试用例中的语法和语义错误。遗憾的是,SoFi的完整源码没有公开提供,因此我们无法直接将我们的方法与Sofi进行比较。此外,我们保留对SoFi发现的错误是否确实具有critical级别的疑问。
Saxena等人开发了一种中间语言,用于将不同的JavaScript指令(例如,拆分字符串)规范化为单个动作,以改进模糊测试。通过这种方式,实现的细微细节被抽象成更高级别、更易于处理的表示。他们的重点是检测JavaScript应用程序中的客户端验证漏洞,而不是浏览器本身的JavaScript引擎。因此,他们的模糊测试是基于JavaScript程序的输入,而不是JavaScript引擎的输入(即,JavaScript代码)本身。
本着类似的精神,Hodov’an等人创建了JavaScript 引擎API的基于图的表示,并使用该图来生成用于模糊测试的输入数据。然而,他们专注于JavaScript引擎提供的API,并且不生成超出它的代码。因此,生成的代码侧重于语义正确性,但不太可能检测到JIT编译器错误。
Lee等人提出了Montage,一种基于神经网络语言模型(NNLM)的fuzzer。他们将AST转换为可以直接用于训练NNLM的AST子树。使用Montage,作者发现37个漏洞,包括3个CVE。尽管他们发现了一个与JIT相关的漏洞,但总体方法与我们的方法是正交的,因为他们在AST上使用机器学习。相比之下,我们在IR上使用预定义的突变。此外,他们不以JIT为目标,而是对JavaScript引擎进行广泛的模糊测试。
最近,Ta Dinh等人提出了Favocado,一种专门用于模糊测试JavaScript代码中绑定层的fuzzer。他们报告说,模糊测试此类绑定需要句法和语法上的正确性,才能达到预期的测试区域。这种方法与我们在fuzzing JIT相关代码部分时面临的挑战类似,这些代码部分也需要高度的语义和句法正确性。然而,Javascript引擎的目标方面与我们的方法不可比,因为我们专注于JIT编译器中的软件缺陷。
为了提高fuzz输入的语义正确性,Dewey等人研究了如何在该领域中使用约束逻辑编程(CLP)。作者使用CLP来生成语义上有效的代码,这与我们的重点类似,但他们并不关注JIT编译器漏洞,因为这类软件缺陷尤其难以处理。
Wang等人提出了Skyfire,一种用于模糊测试的种子生成工具,它需要一个输入语料库和一个语法。基于此输入,Skyfire学习概率上下文相关语法,并使用此语法生成种子输入。作者表明,他们的方法对于结构化程度很高的语言(例如XML)效果很好。然而,他们只提供了关于JavaScript模糊测试的初步结果,将未来的工作留给扩展他们的方法“以更好地支持更复杂的语言,例如JavaScript和SQL”。
Han等人提出了CodeAlchemist,一种用于JavaScript的生成式fuzzer。
Park等人提出了一种新颖的方法DIE,用于利用输入语料库中的隐藏信息,他们称之为aspects。这种方法使fuzzer能够生成更复杂,因此也更深刻的测试程序。他们分析给定的输入种子文件,不仅提取代码片段,还提取代码片段的aspects,例如结构和运行时类型。然后,所提出的fuzzing方法使用此信息来生成包含提取方面的新代码片段。尽管该工作具有类似于我们的类型系统,但类型信息应用于AST层而不是IR。
一般来说,AST能够表示任何有效的JavaScript程序,但这个抽象层面对于突变来说并不理想。类似于现代编译器使用的代码转换,我们将突变应用于IR层。这种设计决策使得能够实现语义上有意义的突变,从而产生高度多样化的生成的JavaScript程序,这是fuzzing的关键方面。
在一个正交的方法中,Aschermann等人提出了Nautilus,一种将输入语法于代码覆盖率相结合的多语言fuzzer。突变应用于AST层,因此收到上述限制。最近一项关于多语言fuzzing的工作称为Polyglot,它通过将种子语料库翻译成与语言无关的IR来改进 Nautilus。 遗憾的是,适用于 IR 的突变非常有限,例如,甚至像变量定义这样的基本语言特性也不容易进行突变。 相比之下,我们的专业化使我们能够包含高度专业化的突变器和生成器,这些突变器和生成器专门针对代码以触发 JIT 例程。因此,我们在主要Web浏览器中使用的真实JIT引擎中发现了明显更多的Critical软件漏洞。
关于没有特别关注模糊测试的Javascript相关安全研究,我们参考两篇最新的调查论文。
3. JIT 编译器漏洞
在本节中,我们首先简要概述现代浏览器中用于JavaScript的JIT编译,然后通过一个JIT漏洞的案例研究来说明技术挑战。
A. JIT
我们使用一个小的、直观的例子来简要介绍当前设计和实现高效JS JIT编译器的方法。更具体地说,我们解释了混合模式JIT 编译器架构的基本概念,即以解释器作为基线,然后是一系列连续更高优化的JIT编译器。有关不同JIT编译方法的更详细和全面的解释,包括基于模板和基于跟踪的JIT编译,我们请读者参考常见的编译器和解释器文献。
浏览器中使用的JavaScript引擎包含解析器、字节码编译器、解释器,通常还包含JIT编译器。首次遇到JavaScript代码时,引擎的解析器会构建相应的AST,该AST由字节码编译器编译为引擎特定的字节码。该字节码由解释器使用。然而,字节码解释速度很慢,这是由于调度开销以及每个字节码处理程序执行的众多(通常是冗余的)类型检查。如果代码被频繁执行,则最好通过将JavaScript代码编译为机器代码并在编译过程中对其进行优化来优化执行。
与经典的提前编译编译器(例如,clang)相比,JavaScript JIT 编译器面临的挑战的一个直观示例可以在图1中看到:给定的C代码可以直接编译成汇编代码,因为所有需要的信息都存在。与C相反,JavaScript是动态类型的,并非所有生成机器代码所需的信息都存在。因此,如果需要性能,JIT编译器不能简单地将JavaScript代码编译成机器代码。执行必须首先确认使用的类型,然后根据手头的类型进行处理。这些类型可以从原始整数到高度复杂的对象,当与相同功能一起使用时,所有这些类型都表现出不同的行为。

然而,在执行期间,使用模式变得明显。例如,让我们假设观察到add
操作只被用作整数调用。基于这种观察,可以执行推测性优化:编译器专门为推断出的类型配置文件编译JavaScript代码。最后,添加类型保护,表示优化类型假设。保护措施会检查给定的值是否属于假设的类型(在我们的示例中为整数)。只要保护措施有效,代码就会继续使用现在已优化的函数。如果保护失败,代码将”退出“,并且JavaScript代码的执行将返回到解释器,该解释器执行未优化的、速度较慢的函数。生成的抽象汇编代码将类似于编译后的C代码,但区别在于包含类型保护,并且,由于我们谈论的是整数加法,所以还包含溢出检查。我们可以将此类优化总结为以下步骤,从而生成正在审查的函数的已编译和优化版本:(1)收集使用模式数据,(2)推断类型模式,(3)针对这些类型优化代码,(4)在优化代码之前部署类型保护。
引擎不立即使用JIT编译网页的JavaScript代码的原因有两个:首先,分析器必须收集执行信息才能使JIT优化起作用。其次,JIT优化非常耗时,因为优化给定JavaScript程序的各个方面可能消耗的时间比通过执行速度增益节省的时间还要多。
为了在触发JIT编译之前收集所需的信息,分析器(引擎的一部分)收集已执行代码的执行信息。达到内部指定的阈值后,引擎会安排代码进行JIT编译。以后的执行直接调用优化后的代码,而不是通过字节码解释来执行函数。因此,典型的现代JIT编译器管道包括图2中可视化的步骤。

(a) 引擎将源代码转换为AST。
(b) 引擎将AST编译为自定义VM的字节码
(c) 引擎将字节码传递给JIT编译器,JIT编译器将其转换为编译器特定的IR。字节码旨在由解释器执行,而JIT IR旨在促进各种程序优化的实现。
(d) JIT编译器优化IR并添加类型保护,这实际上是将类型信息添加到IR。
(e) 最后,JIT编译器将IR降低为机器代码,该代码直接在主机CPU上执行。
B. JIT漏洞案例研究
CVE-2018-4233
是我们在初步探索JIT编译器漏洞期间发现的首批漏洞之一。JIT编译器尝试合并多个类型保护,但未能识别出被检查变量的类型可能会在两者之间发生变化。
保护冗余消除: JIT代码部署保护措施,以确保在编译期间做出的所有类型假设在运行时确实成立。遗漏任何违反的假设都可能产生严重的后果,从崩溃到可利用的漏洞。但是,根据代码的不同,保护措施可能是冗余的。JIT编译器可以删除冗余检查以进一步优化代码。为了确保保护措施可能是冗余的。JIT编译器会分析保护措施之间的代码是否存在潜在的副作用。这种分析可能是有缺陷的,正如
CVE-2018-4233
的情况一样。对被认为是无副作用的函数的调用可能会导致调用用户定义的JavaScript回调,而这反过来可能会更改变量的类型。具体的漏洞: 编译器假定
CreateThis
操作(负责在构造函数中创建一个新对象)不会导致任何副作用。但是,通过将构造函数包装在Proxy中,此假设被违反。通过能够更改参数对象的类型(在本例中,从浮点数数组更改为JavaScript值数组),可以在发出的机器代码中实现类型混淆。图3显示了一个触发此行为的概念验证。JIT编译器假定构造函数始终接收一个以双精度浮点数作为第一个参数的数组。它在发出的机器代码的开头使用类型检查来保护此假设。但是,CreateThis操作是在对参数对象进行类型检查之后执行的,并且当检索构造函数的
prototype
属性时,通过Proxy
回调调用JavaScript。在回调中更改参数数组的参数类型,然后在构造函数恢复并访问数组时会导致类型混淆。
由于执行了概念验证代码,因此存储为0x414141414141
的双精度浮点值3.54484805889626e-310
被错误地用作指针,由于在取消引用地址时发生访问冲突,导致一个攻击者可控制的崩溃。
4. 方法
JIT编译漏洞的模糊测试是一个尚未详细探索的领域,并且需要特别考虑语义正确性。在本节中,我们将介绍我们填补当前JIT编译器fuzzing漏洞空白的方法。我们首先定义一组我们认为成功fuzzing JIT编译器引擎所必须的要求,然后展示基于自定义IR突变的fuzzer如何满足这些要求。
A. 要求
语法正确性: 与代码库的其余部分相比,JavaScript引擎的解析器简单易懂,我们对此不感兴趣。此外,解析器不影响JIT编译器。因此,我们的fuzzing方法需要针对解析器后面的组件。以这些组件为目标需要发出的程序的语法正确性。由于解析阶段拒绝语法无效的示例,因此我们确保程序语法正确。
引导式模糊测试: JIT编译器深深嵌入在JavaScript引擎中。与代码接触的引擎的第一个元素是解析器,然后是解释器,只有在以正确的模式执行代码时,才会触发JIT编译器。为了深入到引擎中,我们需要反馈来生成越来越复杂的输入,强调不同的特性,最终达到JIT编译器。
语义正确性: 如第3节所示,只有在重复且可靠地执行代码的情况下才会触发JIT编译器优化。为了使此类执行发生,我们需要发送代码的语义正确性。发生异常会阻止后续代码的执行,并完全阻止JIT编译,因为引擎没有足够频繁地执行代码。通常,fuzzer通过使用try-catch块包装每个生成的语句来解决此问题。此方法确实可以确保在执行遇到异常后执行后续代码。遗憾的是,这会大大改变程序的语义,因为引入了额外的控制流。因此,JIT编译器以不同的方式处理生成的示例,而不是插入任何try-catch语句。实际上,当控制流图碎片化时(就像插入的try-catch块一样),JIT编译器无法执行许多优化。我们通过将try-catch构造添加到在fuzzing期间找到的程序中来证实了这一假设,之后在大多数情况下停止触发bugs。因此,很明显,成功fuzzing JIT编译器的核心要求是能够以很高的可能性生成语义上正确的代码。
语义代码突变: 我们确定我们想使用反馈方法,并且需要语义正确性才能获得成功的fuzzing框架。仍然缺少一个基本组件:代码的基础语义。JIT编译器只处理代码的语义属性,例如控制流和数据流。这是因为JIT编译器通常在其自己的字节码IR上运行,而对初始AST和语法没有任何了解。因此,希望在该级别执行突变并结合引导式fuzzing提供的反馈。使用反馈的最简单方法是使用基于突变的fuzzing。通过此过程,fuzzer可以动态地将样本添加到语料库中,从而产生新的覆盖范围,并在将来进一步对其进行突变。现有的基于突变的解释器fuzzer(例如LangFuzz)使用AST等表示形式来突变代码的语法元素。但是,句法元素与我们的方法所针对的组件(即JIT编译器)无关。此外,AST可能是模棱两可的。因此,仅语义突变更难实现,因为立即突变可能只会导致程序的句法更改,而不是语义更改。
图4显示了一个示例,其中具有不同AST的两个代码片段表达了相同的计算。基于AST的突变可能只是这两个代码片段之间的转换。为了解决这个问题,我们选择使用一个接近编译器使用的表示的中间表示。在这种IR上进行的突变避免了语义上无意义的突变,并提高了fuzzing的有效性。通过在IR上执行一组不同的突变我们可以更快地检测到不同的缺陷。我们注意到,可以限制AST突变以对抗无意义的突变,但实际上它会变成自己的IR。
B. 专为Fuzzing设计的中间表示(IR)
正如上一节所解释的,我们的模糊测试方法使用自己的IR。因此,我们将fuzzer的设计集中在自定义中间表示IR中对代码进行突变,然后将IR代码转换为JavaScript以供执行的想法上。我们根据第4节A中陈述的要求设计了我们的IR:
IR设计: 在我们的IR中,程序由指令列表组成,每个指令又由操作以及输入和输出变量列表组成。图5显示了一个示例程序,该程序计算从零到九的数字之和。请注意,IR操作可以是参数化的。参数包括操作中的常量、属性和方法名称、二元和一元操作的运算符以及比较。我们使用特殊的块指令实现了控制流,至少存在一个起始块和结束块。我们的IR使用静态单赋值(SSA)形式,这意味着任何变量都只有一个赋值。SSA形式有助于实现我们稍后使用的定义-使用分析。它还提高了类型推断的可靠性并简化了代码生成,例如,输出值将始终分配给新的SSA变量。可以通过Phi操作重新分配JavaScript变量,该操作产生一个可以通过Copy指令重新分配的输出。我们在附录C中给出了已实现的IR操作的完整列表,以及它们涵盖的JavaScript语言特性的描述。

所需不变式: 此外,我们要求以下五个不变式对于我们的IR中的每个程序都必须独立,因此对于从中生成的JavaScript程序也必须成立:
- 所有输入都是变量: 指令的所有输入值都必须是变量。没有立即数或嵌套表达式。这使得能够更直接地推理程序的数据流并方便对其进行突变。
- 变量在使用前定义: 为了减少可能的语义错误,所有变量在使用之前都必须定义,无论是在当前块还是封闭块中。
- 没有开放的语义块: 块的开头必须最终跟随相应的关闭指令或中间块指令,例如BeginElse,这也同样适用。这是保证语法正确性所必需的。
- 在外部定义的块的输入: 块指令的所有输入都必须在外部块中定义,反映了JavaScript的变量定义规则。
- Phi的使用: 为了保留SSA语义,Copy指令的第一个输入必须是Phi指令的输出。
将IR提升到JavaScript: 我们首先通过单独翻译每个指令,将程序从我们的IR提升到JavaScript。作为下一步,我们尽可能内联表达式以创建更易于阅读的代码。
C. 变异IR
我们设计的突变方式是,它们可以修改以我们的IR表示的程序的核心方面。特别是,我们通过突变实现了以下四个目标:
- 指令之间的数据流的突变(输入突变、生成突变)
- 指令执行的计算的突变(操作突变)
- 程序的控制流的突变(组合突变、生成突变)
- 来自两个不同程序的方面的组合(组合突变)
下面,我们将描述如何通过不同类型的突变来实现这些目标。
- 输入突变:输入突变是对程序的数据流的简单突变。我们将指令的一个SSA输入替换为另一个输入。这会导致指令在运行时对另一个值进行操作,从而可能产生不同的结果。
- 操作突变:操作突变包括选择一个随机参数化指令并更改其参数之一。例如,我们更改常量值,使得程序使用不同的方法或属性,或者替换二元或一元运算。
- 组合突变: 组合突变将不同程序的部分组合成一个新程序:在突变的简单版本中,我们将一个完整的程序插入到第二个程序中的随机位置。这需要重命名插入程序中的变量以避免变量名称冲突,但这很容易实现。突变的更复杂版本仅将现有程序的一部分插入到第二个程序中。突变选择一个随机指令,并递归地选择所有输出也用作输入的指令。然后,我们将结果切片复制到另一个程序中。图5显示了程序的示例切片。然后,可以将此切片简单地复制到不同的程序中,因为它本身是独立的。但是,此突变不会更改任何现有数据流,因为两个输入程序的SSA变量未混合。为了合并两个输入程序的数据流,之后需要进行输入突变。
- 生成突变:生成突变只是简单地将新生成的代码(这些代码使用现有值)在随机位置插入到现有程序中。为此,我们实现了几个代码生成器函数,这些函数发出简短的代码片段。总的来说,我们为IR的每个语言特性实现了一个简单的代码生成器,以及少量的特殊代码生成器,用于触发JIT编译或强调历史上容易出错的特性。
D. 实现高概率的语义正确性
我们对IR施加的不变式——每个突变都保留了这些不变式——避免了一些简单的语义错误,例如在使用变量之前定义变量。这些限制本身不足以确保生成的语料库的语义正确性。我们添加了三个额外的措施来提高语义正确性:
仅允许有效的语料库:我们通过确保仅将语义上有效的样本添加到运行时语料库中,来实现额外的语义正确性。为了实现这一点,不仅需要在每次执行期间记录覆盖率信息,还需要记录程序是否由于未捕获的运行时异常而异常终止。在所有受支持的引擎中,这可以通过退出代码来实现,如果未引发未捕获的异常,则退出代码通常为0,否则为非0。
仅执行小的更改:进一步提高语义正确性概率的一个关键见解是,每次突变只有很小的概率会将有效的(在语义上)程序变为无效程序。这是因为每次突变要么本质上是语义正确的(组合突变),要么仅以较小的方式影响程序(输入突变、操作突变、生成突变)。
轻量级类型系统:我们提高生成组件语义正确性的最后一步是自定义类型系统,因为类型错误是语义错误的重要来源。 为此,我们实现了一个轻量级的抽象类型推断引擎。 推断引擎可以静态地近似 SSA 变量的运行时类型。 然后,此信息用于避免生成明显无效的代码结构,例如对非对象的方法调用或对不可调用对象的功能调用。 为了不限制模糊器可以生成的代码的多样性,其他突变通常会忽略类型信息。
我们将类型系统设计得尽可能简单,但功能足够强大,可以推断出可以在运行时对值执行的可能操作。支持的基本类型为
Tinteger、Tfloat、Tstring、Tboolean、Tfunction(signature)、Tconstructors(signature)、Tobject([properties],[methods])、Tundefined、Tunknown
。这些类型可以使用联合运算符
t1 | t2
组合,表示一个值是多个类型之一。例如,JavaScript中加法运算符的结果通常是数字或字符串,因此将表示为Tinteger | Tfloat | Tstring
。此外,还可以使用
t1 + t2
组合两种类型。这种合并类型表示一个值同时是两种或多种类型。这种类型的一个例子是JavaScript
中的字符串,因为它们向用户公开属性和方法。因此,类型系统将它们表示为Tstring + Tobject
。最后,类型系统还可以对对象的属性和方法列表以及函数和构造函数的签名进行建模。
抽象类型推断引擎具有简单的规则,可以确定每个操作的输出类型,并且在运行时环境的静态模型上运行,该模型包含每个内置对象的类型信息。每当两个或多个代替控制流路径合并时,我们使用联合运算符组合变量状态。
JavaScript和我们的推断引擎之间的执行语义略有不同,例如,推断引擎没有原型(就像JavaScript中存在的那样)的概念。虽然简化了实现,但这会导致静态类型的近似中出现错误。但是,在实践中,这些问题被证明是没有问题的,因为代码生成器保守地使用类型近似。
由于静态类型推断系统仅仅是一种性能优化,因此可以完全禁用它,在这种情况下,所有变量的类型将变为Tunknown,并且代码生成器将生成真正的随机操作。在实践中,我们发现正确率在50%到75%之间变化。
E. IR的模糊测试
我们的模糊测试方法通常遵循基于突变的模糊器的标准进行设计。在每次迭代中,模糊器从现有语料库(以单行JavaScript程序为种子)中选择程序
P
,并随机对其进行突变以生成新程序Pm
。然后,模糊器将Pm
提升为JavaScript代码,该代码随后在目标引擎上执行,同时收集覆盖率统计信息,例如,通过Clang
的sanitizercoverage
功能。如果Pm
的执行增加了目标程序的覆盖率,则Pm
被认为是interesting
并保留以供将来进行突变。但是,由于我们的突变只能增加程序的大小而不能缩小程序的大小,因此必须在将
Pm
添加到语料库之前对其进行最小化。否则,语料库中程序的大小补将不断增加并减慢模糊测试的速度。通过定点迭代可以简单地进行最小化,该迭代连续删除指令,同时确保结果程序仍然显示相同的覆盖率增加。由于两个
interesting
的程序之间的距离通常大于单个突变可以桥接的举例,因此我们连续多次突变一个程序。但是,为了防止不必要的资源投入,如果最后一次突变产生了无效程序,则会还原最后一次突变。Algorithm 1
中给出了高级模糊测试算法的伪代码。
5. 实验
我们在Swift编程语言中实现了一个名为Fuzzilli的工具,其中包含前一节中概述的fuzzer设计。我们使用此原型实现进行评估,并针对三种最先进的JavaScript引擎的检测JavaScript引擎代码运行它:Google V8
、Apple JavaScript
和Mozilla SpiderMonkey
。
A. 模糊测试的时间
本节报告中的漏洞是连续六个月的模糊测试会话的结果。每个会话持续约一周,并使用约500个CPU核心。对于每个会话,使用当时的最新源代码版本,或者如果可用,则使用当前Beta版本的源代码。我们在Google Compute Engine(GCE)上运行模糊测试,并且主要使用多个N1standard-4机器类型(4个CPU,15GB RAM)。此外,我们选择使用抢占式示例来降低成本。
B. setup
我们将目标引擎编译为独立二进制文件,而不包含Web浏览器绑定。此外,我们修改了引擎以支持目标接口,该接口需要在我们fuzzer和JavaScript引擎之间通过一组通信管道进行通信。此外,我们降低了JIT编译阈值以更早地触发JIT编译,从而加快了模糊测试速度。通常,我们将阈值设置为大致100次函数执行会导致对其进行编译。此阈值允许引擎收集类型信息,同时加快了模糊测试速度。修改阈值是先前模糊测试解决方案部署的常用技术。
最后,我们出于性能原因,在启用了优化的自定义调试配置中编译了引擎。调试配置包含许多内部断言,这些断言在发行版本中出于性能原因被删除。我们启用断言,因为它们有助于检测不会立即体现为内存安全违规的可利用缺陷。例如,CVE-2019-8622
是通过断言失败发现的。JIT编译器假定特定操作永远不会导致垃圾回收(GC)发生。但是,在执行期间,降低的操作确实调用了在某些情况下可以触发GC的API。然后可以通过首先在专门选择的时间触发GC,然后故意制作JavaScript代码来利用这种情况,以便随后在JIT编译的代码中访问现在释放的JSObject。这两个步骤都是导致内存安全违规所必需的。由于这两个步骤都需要大量专门制作的代码,因此它们不太可能通过模糊测试直接找到,但此类违规的指标可以通过模糊测试来检测。
许多发现的漏洞首先体现为失败的断言或空指针解引用。之后,我们对Crash进行了手动分类,以确定它们是否对安全是至关重要且可利用。虽然鉴于失败的断言或崩溃情况,一些观察到的崩溃显然是可利用的,但其他崩溃首先需要进行大量分析才能确定可利用性。所有已识别的错误均以协调方式报告给开发人员。我们认为列出的所有缺陷都是可利用的,并且要么收到了CVE要么收到了Chrome内部问题编号。因此,这些问题随后由开发人员修复。
1)类型分类: Table 1显示了所有17个发现的漏洞的全面摘要,这些漏洞提交后分配给我们一个CVE或内部问题编号。所有已识别的漏洞都与JIT编译相关,并且跨越了诸如无效的边界检查删除、不正确的类型推断和寄存器错误分配问题等问题。
2)存在时长确定: 我们还通过编译旧版本的软件并验证找到测试用例是否触发崩溃来确定漏洞的存在时长。对于JavaScriptCore和V8,我们使用git的bisecting功能来查找最旧的版本。对于Firefox,我们使用旧的官方版本并在这些版本上进行了测试。由于测试用例可能由于无关原因而未在其他版本上触发,甚至可能触发另一个错误,因此Age确定的结果可能包含不准确之处,但通常是保守的。在我们无法如上所述动态确定问题版本的情况下,我们求助于手动源代码分析,以尝试确定何时引入了易受攻击的代码。由于这比编译和测试代码更容易出错,因此此处的结果不太确定。

6. 发现漏洞的分类
为了系统化发现的漏洞,我们首先描述导致每个漏洞的根本原因,然后介绍两个分类法。第一个根据影响对漏洞进行分类,第二个根据漏洞发生的时间对漏洞进行分类。Table 1中显示了已分类漏洞的表格概述。
A. 根本原因
我们确定了三个常见的根本原因:移动代码时的优化、运行时执行语义的错误建模和类型检查的错误删除。不符合此分类的根本原因被归类为杂项。
- 代码移动:一个常见的优化包括在程序中移动代码片段(例如,循环不变代码移动)。但是,如果这样做不正确,则以前安全的代码片段会变得不安全(3个漏洞)。
- 不正确的建模:可能因运行时执行语义的错误建模而出现问题,例如操作是否具有副作用或是否会触发垃圾回收(2个漏洞)。
- 不正确的类型推断:JIT编译器的一个核心优化是运行时类型信息的推断,这允许省略类型检查。只要将不兼容的值存储在具有关联类型信息的属性中,就必须更新该类型信息,因为JIT编译器依赖于它来省略运行时类型检查。(4个漏洞)。
- 杂项:并非所有发现的漏洞都具有共同的根本问题。这可能是因为它们是”一次性“错误,或者仅仅是因为没有发现可以形成类别的其他类似漏洞(9个漏洞)
此外,发现的漏洞可以根据其影响和影响时间来区分。接下来,我们简要解释这两个类别。
B. 按影响分类
我们确定了四种常见类型的效果集群(和一个杂项集群),大多数发现的漏洞都可能具备这些效果:
- 类型安全违规:所有导致某种类型混淆的漏洞(7个漏洞)
- 空间内存安全违规:所有导致空间内存损坏的漏洞,例如对堆分配的内存块的越界访问(2个漏洞)。
- 时间内存安全违规:所有导致时间内存安全违规的漏洞,例如,由于使用先前释放的内存(3个漏洞)。
- 使用未初始化的数据:所有使用未初始化数据的漏洞,例如从堆栈中未初始化的位置读取指针值(2个漏洞)。
- 杂项:所有不属于任何上述类别的漏洞(3个漏洞)。
C. 按影响时间分类
漏洞可能发生的时间有两种,一种是在运行时,另一种是在编译时。
- 运行时:此类别中的所有漏洞都是逻辑编译器缺陷,可能会导致内存损坏,从而在运行时发出的机器代码中触发(14个漏洞)。
- 编译时:此类别包括“经典”内存损坏错误以及编译器特定的错误,这些错误会导致编译期间发生内存损坏(3个漏洞)。
7. 我们方法的有效性
由于模糊测试的非确定性本质,不同模糊测试方法的目标属性难以比较。进行有意义的比较的另一个障碍是模糊器的不同目标和设计原则。 我们专门为查找 JIT 漏洞而设计了名为 Fuzzilli 的方法,并进行了相应的专业化。 此外,我们的方法不需要输入语料库,可以自行生成新的 JavaScript 代码。其他 JavaScript 模糊器,例如Superion
或SoFi
,具有更通用的设计目标,并且需要输入语料库。因此,在比较它们时,必须考虑设计的上下文。
为了评估我们方法的有效性,我们进行了描述性和经验性分析。我们首先通过分析我们方法的目标通用性和质量来开始我们的描述性评估。 然后,我们提出了一个实证研究,其中我们调查了我们不同生成器的影响,测量了我们方法的代码覆盖率,并将其与Superion
进行了比较。 为了允许将来的复制,我们使我们的代码和工程可以在线公开获得。
A. 可描述的有效性评估
在我们的实验中,我们发现并报告所有三个主要 JavaScript 引擎中的多个先前未知的漏洞,并被分配了相应的 CVE 或问题编号(参见表 I)。 这些发现表明,我们的方法在适用性方面实现了通用性,即我们没有针对特定引擎或基准进行优化,并且对不同的高度相关的 JavaScript 引擎产生了积极的安全影响。
此外,所有三个引擎都经过供应商特定的模糊测试基础设施和第三方模糊器的持续测试。 由于这些先前的努力,JavaScript 引擎通常被认为是经过良好测试的软件。 然而,我们发现了 17 个漏洞,这些漏洞逃过了竞争性模糊器的攻击,其中一些漏洞是三年多前引入的。 这表明我们的模糊器是 JavaScript JIT 模糊测试的重大质量改进。
B. 基于经验的有效性评估
我们的经验有效性评估是双重的。 首先,我们分析了不同的生成器中的每一个产生的影响,以及我们在三个主要的 JavaScript 引擎中随着时间的推移能够实现的突变数量。 其次,为了表明我们的模糊器在整个 JavaScript 模糊测试环境中具有竞争力,我们对 Superion进行了经验评估。 这个模糊器代表了 JavaScript 模糊测试的当前最新技术水平,并且作者已经证明它在几个方面优于其他方法。
生成器效果分析: 我们针对 SpiderMonkey、JavaScriptCore 和 V8 这三个 JavaScript 引擎运行了五次模糊器,持续 24 小时。 在这些运行过程中,我们记录了导致代码覆盖率增加的突变。 图 6 显示了 JavaScriptCore 和 SpiderMonkey 的结果,由于页面限制,V8 的图显示在附录 A 中。 我们可以观察到,在生成新样本时,生成和输入突变是最重要的贡献者。 组合和操作突变是接下来的两个有影响力的突变。 有趣的是,在我们的经验分析中,明确强调 JIT 的贡献最小。 这些结果表明,在模糊测试 JIT 漏洞时,无需完全关注与 JIT 相关的突变。 实际上,相对较小但持续的努力就足够了。 不同的突变策略的深入解释可以在第 IV-C 节中找到。
不同的引擎之间也没有明显的差异。 成功的突变器的分布保持在相同的比例内。 对于每分钟产生新样本的突变数量,JSC 和 SpiderMonkey 都趋于零,这一观察结果也成立。 但是,我们发现 JSC 的初始下降速度比 SpiderMonkey 慢。

- 与SoFi的比较: 我们本希望包含与最近发布的 SoFi 方法的直接比较。 遗憾的是,作者既没有发布完整的源代码,也没有在联系时提供代码。 我们分析了论文表 2 中报告的结果,发现三个相关的 JavaScript 引擎 SpiderMonkey、JavaScriptCore 和 V8 中发现的“错误”似乎并不代表实际的安全关键漏洞。 例如,为 SpiderMonkey 报告的前四个错误被开发人员标记为无效,根本不代表漏洞。 第五个报告是重复的。 同样,为 JavaScriptCore 报告的“错误”也被开发人员标记为无效。 我们对已报告结果的分析以及源代码的不可用性感到困惑。 遗憾的是,无法通过与作者的直接交流来解决这些问题,因此无法进行直接比较。
- 与Superion的比较: 比较模糊器的一个广泛使用的指标是代码覆盖率,因为它显示了引擎的到达程度以及因此的测试程度。 我们选择利用分支覆盖率作为我们的指标。 由于我们的模糊器专门从事 JIT 模糊测试,我们还比较了 JIT 特定的覆盖率。 如上所述,我们的目标引擎是 JavaScriptCore、V8 和 SpiderMonkey,因为它们用于现代 Web 浏览器中。 每个模糊器 JS 引擎的确切命令行标志显示在附录 B 中。
配置: 我们使用 Ubuntu 22.04 在配备 256GB RAM 的 Xeon Gold 5320 CPU 的 100 个内核上运行每个模糊器五次,持续 24 小时。 Fuzzilli 和 Superion 实例部署在虚拟机中,每个 100 个实例分配了 2GB 的 RAM。 对于这两个模糊器,都启用了语料库共享。
使用语料库: 我们的模糊器不使用输入语料库,而 Superion 确实需要语料库。 这是尝试进行公平和客观比较时需要注意的一个问题,因为语料库可能会决定模糊测试结果和进度的质量和最终覆盖率。 另一个阻碍是 Superion 没有发布其语料库。 我们选择使用公开可用的 DIE 语料库 2 作为 Superion 的输入。 为了衡量启动语料库对 Superion 成功的影响,我们还在随机选择的 DIE 子语料库上进行了评估。 我们通过连续添加随机样本直到达到 17% 的分支覆盖率来生成这些子语料库,这大约是整个 DIE 语料库产生的一半覆盖率。 每个子语料库都用于单独的评估。 我们承认不使用原始输入语料库可能会导致比先前报告的更糟糕的结果。 然而,模糊器在没有特定主体的情况下也能很好地工作这一事实,是模糊器必须具备的通用性特征。
评估: 为了评估代码覆盖率,我们将收集的样本文件分成每分钟模糊测试的集合。 针对相应的 llvm-cov 检测引擎评估每个集合。 我们合并了每个集合随时间推移产生的覆盖率数据,从 Fuzzilli 的按时间顺序排列的第一个集合以及 Superion 的输入语料库的覆盖率开始。 总覆盖率包括整个引擎的覆盖率,由 llvm-cov 报告文件表示为“TOTAL”。 为了获得 JIT 特定的覆盖率,我们提取并平均了每个可能参与 JIT 编译的文件的报告的覆盖率,方法是对各个文件路径使用正则表达式
结果: 我们评估了分支覆盖率,并在我们的五次运行中对其进行了平均。 Superion 将 JavaScriptCore、V8 和 SpiderMonkey 的完整 DIE 输入语料库的初始总覆盖率分别提高了 2.15%、0.98% 和 2.47%。 对于 17% 覆盖率的子语料库,Superion 将覆盖率提高了 11.14%、6.13% 和 6.74%。 我们的模糊器达到的最终覆盖率为 43.72%、30.64% 和 30.53%。
关于 JIT 特定覆盖率,Superion 提高了 0.89%、0.89% 和 1.22%。 对于 17% 覆盖率的子语料库,改进幅度为 14.37%、9.11% 和 8.87%。 我们的模糊器达到的最终覆盖率为 59.22%、53.47% 和 56.27%。
我们还专门针对 JavaScriptCore 评估了行覆盖率,因为这是我们和 Wang 等人评估的引擎的交集,以及他们报告的指标 43。 使用初始行覆盖率为 52.01% 的完整 DIE 语料库,Superion 提高了 1.44%。 对于部分语料库,Superion 提高了 11.49%。 我们的模糊器达到的行覆盖率为 49.51%。
对于 JIT 特定覆盖率,Superion 将初始覆盖率为 64.58% 的完整 DIE 语料库提高了 0.53%。 对于初始覆盖率为 44.10% 的部分语料库,Superion 提高了 14.13%。 我们的模糊器达到的 JIT 特定行覆盖率为 65.60%。
显示随时间推移的分支覆盖率的图在图 7 中给出。 表 II 中可以找到原始分支覆盖率结果的表格概述。 图 8 以可视化方式比较了行覆盖率。
讨论: JavaScriptCore 的行覆盖率和分支覆盖率相似,这导致假设这两个指标可以互换。 总体而言,当提供完整的 DIE 语料库时,Superion 的表现优于我们的模糊器。 Superion 的最终覆盖率比我们的最终覆盖率高 3%、6% 和 8%。 然而,除了启动语料库之外实现的覆盖率很小,并且 DIE 语料库本身已经达到了比我们的模糊器的最终覆盖率更高的覆盖率。 因此,更好的覆盖率不能归因于 Superion。
一个有趣的观察结果是,Wang 等人报告说,Superion 对 WebKit/JSC 的行覆盖率从 52.4% 增加到 78.0%,增加了 25.6% 的覆盖率。 然而,使用初始行覆盖率为 52.01% 的 DIE 语料库只会导致最终行覆盖率为 53.45%,仅增加了 1.44%(参考图 8)。 我们的假设是,这是由于输入语料库的差异造成的,并且应该被认为是不同输入语料库可能对最终覆盖率产生的影响的证明。
当仅向 Superion 提供缩减的语料库时,我们的表现明显优于 Superion。 我们的模糊器对 JavaScriptCore、V8 和 SpiderMonkey 的额外覆盖率分别达到了 15%、7% 和 7%。 与大型语料库相比,Superion 可以再次显着提高覆盖率,这表明初始语料库可以对结果产生影响。 然而,这些改进的益处值得怀疑,因为它们是已经存在的大型 DIE 语料库的子集,该语料库主要由浏览器供应商测试用例组成。 因此,JavaScript 引擎的测试没有总体改进。
关于 JIT 特定覆盖率,我们在完整 DIE 语料库上对 JSC 的表现优于 Superion 1.6%,并且对 V8 和 SpiderMonkey 的表现分别低了 0.8% 和 3.8%。 但同样,启动语料库本身的初始覆盖率已经高于我们的最终覆盖率,并且 Superion 增加的覆盖率很小。 然而,这表明即使向 Superion 提供全面的启动语料库,我们也能在 JIT 聚焦模糊测试方面优于 Superion 或与 Superion 竞争。 减少的启动语料库导致了与完整覆盖率相似的 JIT 覆盖率结果。
C. 经验教训
在 JIT 覆盖率方面,即使在向 Superion 提供完整的 DIE 语料库的情况下,我们也能与 Superion 竞争,甚至在 JSC 中胜过 Superion。 令人惊讶的是,在查看总体覆盖率时,Superion 几乎没有在完整的 DIE 语料库上进行改进。 关于减少的初始语料库,我们在 JIT 和一般覆盖率方面都严格优于 Superion。
最后,即使我们也从 52% 的初始行覆盖率开始,我们也无法重现报告的 JSC 行覆盖率提高 25.6%。 这让我们感到困惑,因为这表明 Wang 等人使用的语料库存在可以填补的显著且可达到的覆盖率缺口,但 DIE 语料库中不存在这些缺口。 由于原始的 Superion 语料库尚未发布,因此我们很遗憾无法尝试重现他们的原始结果。
因此,我们强调未来的模糊测试研究必须提供任何初始语料库以实现重现,并且应该更加强调使用不同的、可能新颖的语料库重现先前的模糊测试结果,以估计模糊器在不同的语料库中的泛化程度。
关于不同突变策略的影响,我们表明,为了能够专注于 JIT 漏洞,只需付出较小但持续的努力来强调 JIT 即可。
8. 结论
在本文的过程中,我们展示了 JIT 编译如何导致严重的漏洞,以及为什么当前的模糊测试方法不足以检测到此类漏洞。 我们建议通过我们生成语义上正确的代码的新方法来填补这个模糊测试空白,利用具有一定比例的 JIT 聚焦突变策略的 IR。 我们用 swift 编程语言实现了我们的方法,并在 500 个内核上针对 V8、SpiderMonkey 和 JavaScriptCore 进行了为期 6 个月的实验。 在此测试时间范围内,我们发现了 17 个先前未知的漏洞。 这些漏洞平均至少有 16 个月历史,因此也被各自供应商和研究人员的模糊器所忽视。 为了促进研究和加强 JS 引擎的安全性,我们将开源我们的代码。
我们还对我们的模糊器进行了描述性和经验性分析。 在我们的描述性分析中,我们展示了我们的模糊器如何在不同的引擎中很好地泛化,并且能够找到先前未知的漏洞,从而展示了其对最新技术的定性改进。 我们的经验分析表明,我们的模糊器部署了恒定但有限的突变焦点来关注 JIT。 此外,经验分析表明,即使向 Superion 提供全面的启动语料库,我们也能在 JIT 聚焦模糊测试方面优于或与最新的模糊器 Superion 竞争。 当减少启动语料库时,Superion 无法在三个引擎的通用覆盖率或 JIT 聚焦覆盖率方面超越我们。 这些结果强调了跨多个不同语料库测试利用输入语料库的模糊器的重要性,以判断其通用性。 我们还呼吁所有未来的模糊测试研究不仅需要发布源代码,还需要发布使用的评估语料库。
然而,我们的模糊测试方法并不完整,因为仍然有改进类型信息的空间,例如通过检测发出的代码使其更加精确,这将允许更具针对性的代码生成。 此外,我们的突变集是有限的。 增加突变集以包含对控制流的聚焦突变可能会产生更深层隐藏的漏洞。 此外,还可以增加特殊功能的集合,因为 JavaScript 是一种复杂且功能丰富的语言,不太受欢迎的功能可能包含尚未发现的问题。