Group of Software Security In Progress

GoSSIP @ LoCCS.Shanghai Jiao Tong University

Fuzzing JavaScript Engines With Aspect-preserving Mutation

作者:Soyeon Park, Wen Xu, Insu Yun, Daehee Jang, Taesoo Kim

单位:Georgia Institute of Technology

出处:S&P ‘20

原文:Fuzzing JavaScript Engines with Aspect-preserving Mutation

Abstract

Fuzzing是一种用于发现复杂程序中的bug或漏洞的技术,但现有fuzzing技术,无论是生成类(generative)还是突变类(mutational)方法,都难以充分利用高质量输入语料库(例如PoC或单元测试)。现有的fuzzer为了提高代码覆盖率,倾向于破坏输入语料库中编码的细微语义或条件,以生成新的测试用例。但是,对于类似于JavaScript引擎这种复杂程序,这种常规设计导致测试案例只能解决复杂代码库的浅层部分,由于巨大的输入空间和复杂的逻辑关系而无法有效地解决深层错误。

在本文中,作者提出一种称为切面保持突变(aspect-preserving mutation)的新技术,该技术可随机保留希望在突变间保持的特性,即“切面”(aspect)。作者设计了DIE,在该fuzzing框架中通过两种变异策略(即结构保留和类型保留的变异)进行了切面的保留。作者的评估表明,与最新的JavaScript fuzzer相比,DIE的切面保持不变的突变在发现新错误(多出5.7倍的独特崩溃)和生成有效的测试用例(减少2.4倍的运行时错误)方面更有效。 DIE在ChakraCore,JavaScriptCore和V8中新发现了48个高影响力的bug(目前已修复38个,分配12个CVE)。 DIE的源代码已经开源。

1 Introduction

针对JavaScript引擎的fuzzing技术,目前有两种类型,即生成型和突变型fuzzer。生成型fuzzer完全按照预定义的规则(例如JavaScript编程语言的无上下文语法或重新组装从输入语料库中分离出的可合成代码块)从头开始构建新的测试用例。突变型fuzzer从现有种子输入中合成了一个测试用例。 但是作者发现,这两种方法都不足以利用高质量的输入语料库,例如PoC或单元测试。这些输入语料库是经过精心设计的,旨在处理被测程序的特定属性,但是在fuzzing过程中不会保留这些属性。现有的fuzzer旨在根据简单的生成规则生成naive的测试用例,而无需利用输入语料库,或者在生成新的测试用例时无法保持这些输入语料库中编码的细微语义或条件,因为这些会阻碍fuzzing发现程序的更多代码路径。这种设计适用于小型程序,在较小的搜索空间很容易进行自动探索。但是,当测试类似JavaScript引擎这类复杂程序时,这种常规设计往往会产生使复杂代码库的浅部分受压的测试用例,例如解析器或解释器,而不是JIT优化算法。

在本文中,作者一种称为方面保持突变的新技术,该技术可在生成新的测试用例时随机保留原始种子输入的有益特性和条件,使用术语“切面”(aspect)来描述想要在整个变异中保留的输入语料库的此类首选属性。作者称保留切面的突变是一个随机过程,因为切面没有被明确标注为输入语料库的一部分,而是由轻量级突变策略隐含地推断和维护的。例如,在PoC攻击中,故意选择诸如循环之类的控制流结构来触发JIT编译,并仔细选择某些类型以滥用易受攻击的优化逻辑。在保持切面不变的突变下,理想情况下,作者希望在引入足够多的变体的同时,在新的测试用例中极高地维护这些切面,以便发现相似或新的错误。

作为评估,作者将两种新的变异策略(即结构和类型保留)结合到了成熟的JavaScript模糊测试程序DIE中。作者开发了一种轻型的类型分析,该分析可静态传播从动态分析中提取的观察到的类型信息。每个突变策略通过利用共享的类型化AST来保持切面特性。 与最新的JavaScript模糊测试相比,DIE的切面保持不变的突变在发现新的错误(更多的5.7倍独特的崩溃)和生成高质量的测试案例(减少2.4倍的运行时错误)方面更有效。 DIE在ChakraCore,JavaScriptCore和V8中新发现了48个高影响力的安全漏洞;目前,已经修复了其中38个漏洞,并分配了12个CVE(27 000美元的漏洞赏金)。

本文做出了三点贡献: * 提出了一种新的保留切面的突变方法,旨在保留跨突变的种子输入的理想特性和前提条件。 * 开发了成熟的JavaScript fuzzer,DIE。通过使用轻量级的静态和动态类型分析,实现了结构保留和类型保留这两种突变。 * 报告了48个新bug,其中38个已修复:ChakraCore中的28个bug,JavascriptCore中的16个bug以及V8中的4个bug。

2 Background

2.1 JavaScript引擎

JavaScript引擎是现代浏览器的复杂组件之一。尽管每个JavaScript引擎的设计和实现都大不相同,但是它们都有两个共同的属性: * 充当JavaScript的标准化运行时; * 提供JIT编译以提高性能。

JIT编译。 在运行时,引擎会分析执行情况(例如,类型,调用次数),以找到潜在的优化热点。然后,引擎将第一级IR(即字节码)转换为一系列低级IR(例如JavaScriptCore中的B3 IR),并最终转换为本地机器指令以加快执行速度。现代JavaScript引擎在编译过程中应用了高级优化技术,例如函数内联和冗余消除(请参见图4)。作为机器代码的一部分,JIT编译器插入各种检查(例如,类型)以验证优化期间所做的假设,如果优化的代码在验证中失败,则回退到未优化的代码,称为应急。尽管诸如解析器和解释器之类的面向用户的界面是ECMA262标准的直接实现,但JIT实现特定于每个JavaScript引擎,例如低级IR,优化技术等。换句话说,这是理想的选择实施的多样性和优化逻辑的复杂性,使安全审计更容易。

2.2 fuzzing JavaScript engine

JavaScript引擎fuzzer有两种流行的类型,即生成型(Generative)和变异型(Mutational)。

  • 生成型fuzzer基于预定义的语法或通过从大型语料库中分解而来的可合成代码块,从头开始构建新的测试用例。变异型fuzzer在种子输入上生成新的测试用例以进行测试。现代的fuzzer都利用代码覆盖来加速其探索。但是目前的fuzzer都无法有效地探索JavaScript引擎来深层错误,原因有两个:
    • 庞大的搜索空间:生成型fuzzer的一个主要优点是,它们可以完全控制测试程序中每个语句的生成过程。因此,构建有效输入非常简单。但是,生成fuzzer通过从代码单元开始构建全新的程序。同时,与JIT相关的错误需要具有特定属性的复杂输入才能触发。因此,搜索空间对于生成模糊测试器而言太大,无法在合理的时间内构建这样的测试用例。
    • 现有程序利用不足:近期,JavaScript模糊测试人员选择单元测试套件和已知错误的PoC作为种子输入。这类程序往往经过精心设计,会特别强调JavaScript引擎中一个或多个工作阶段的属性。因此,基于这些输入,fuzzer可以快速且更深入地研究JS引擎。然而,现有fuzzer无法充分利用此类程序的显著优势:
      • JIT相关的bug的PoC具有其独特的控制流和所用变量之间的数据依赖性,探索了优化器的特定代码路径。但是,一旦PoC分解为较小的标准化的代码段并与大型程序集中其他程序段混合后,则诸如CodeAlchemist这样的生成型fuzzer很少会在合理的时间内到达代码路径。
      • 当PoC基于语法规则的fuzzer(如Superion和Nautilus等)突变时,也同样会损失语义内容。
  • DIE以混合方式创建新的JS程序,将种子语料库与通过生成方法生成的单元进行合成。更重要的是,DIE完全尊重单元测试程序和PoC的属性:DIE通过用新的代码段替换与属性无关的代码段或仅插入新的语句来更改单个程序。同时,根据上下文从头开始生成用于突变的新片段。

2.3 Trend of Recent Vulnerabilities

作者在图1中总结了2016年至2019年在ChakraCore中发现的漏洞: * 所有漏洞都位于解析器、解释器或JIT编译器中。 * 解析器和解释器错误的数量一直在迅速减少,而JIT编译器错误逐渐占据主导地位。 * JIT编译器错误主要是由错误的推测或基于逻辑错误的错误优化引起的。 作者进一步将这些错误按类型进行划分,并注意大多数错误会导致越界(OOB)访问或类型混淆。用JavaScript(一种典型的高级语言)编写的普通程序需要精心制作的代码来触发这些情况。 DIE的目标是有效生成此类程序,从而更有效地触发这些深层问题来发现漏洞。

3 Overview

3.1 Motivation

由于代码库中很可能分散着开发人员引入的类似的错误,因此审核类似类型的漏洞有助于漏洞的发现。例如该例子CVE-2018-0840中:

  • 该CVE是一个典型的类型混淆漏洞,即NativeFloatArray和VarArray混淆之后产生的数据和对象无法区分的问题,具体来说就是2.3023e-320就会被当作float数据存放进入VarArray的元素中,而2.3023e-320并没有通过与0xfffc000000000000异或而变成一个可以被VarArray识别的float,所以VarArray对象在读取该元素时会将其当成一个对象来处理。
  • Chakra引擎在对JIT中的回调进行优化时会考虑一个叫做ImplicitCallFlags的标志位,通过这个标志位,就可以检测用户函数是否可能被调用,如果是的话就会启动bailout或进行相关检测。通过在调用回调的位置添加ExecuteImplicitCall验证,能够修补这种漏洞。但该函数本身实现存在问题:首先会执行implicitCall然后才会更新ImplicitCallFlags。如果回调函数触发异常,则会导致更新ImplicitCallFlags的操作被跳过,由于标志位没有被更新,所以优化过程中的相应排错机制也就没有被生成,最终导致漏洞的产生。
1
2
3
4
Js::ImplicitCallFlags saveImplicitCallFlags = this->GetImplicitCallFlags();
JS:Var result = implicitCall();
this->SetImplicitCallFlags((Js::ImplicitCallFlags)(saveImplicitCallFlags | flags))
return result;
  • 这两个CVE有着很多相似之处:
    • 都需要通过(1)处的重复调用opt()来满足JIT条件
    • 都需要通过(3)欺骗优化器以错误地假定arr类型。

Chakra 引擎中 JIT 编译优化过程中的数组类型混淆漏洞分析

这种直观的相似比较,在fuzzing过程中并没有能很好地利用,尽管使用已有的PoC作为种子输入,但为了探索搜索空间、提高代码覆盖率,fuzzing倾向于破坏输入种子中编码的条件。 但是在对JS引擎的测试中:

  • 穷举探索具有将近一百万行源代码的复杂程序的输入空间是不可行的
  • JS引擎中错误不是简单的内存损坏错误,难以通过简单的基于覆盖率的测试来找到,而是需要复杂条件才能实现的逻辑错误。

因此变异策略应该集中在产生高质量的测试用例上,而不是仅仅增加代码覆盖率。例如对上述的两个CVE的模糊测试的理想策略是保留某些先决条件,同时引入一个新的代码段(4),该代码段可以改变JavaScript优化算法的内部机制。如果将(a)用作输入语料库,则现有的基于覆盖率的fuzzer可能会丢弃条件1-3,因为它们对于发现新的代码路径不是必不可少的,被认为对现有语料库是多余的。

3.2 Challenges & Methods

理想的fuzzer希望能够重复利用高质量输入语料库中编码的条件,例如已知PoC或JS单元测试,从而生成满足触发新错误的必要前提的测试用例。有两种可能的方法 : * 在每个输入语料库中手动编码此类条件,但由于输入语料库的数量庞大且没有为fuzzer提高足够的自由空间搜索,因此不合适 * 通过程序分析从每个语料库中自动推断这样的先决条件。然而在额外的分析上花费10%的计算能力后,减少10%的勘探输入空间开销并不会使fuzzer受益。

作者的关键方法是随机地保存 “切面”(aspect),即在突变过程中保持的理想特性。这是一个随机过程,因为切面没有被明确标注为语料库的一部分,而是由轻量级突变策略(即所谓的保持切面的突变)隐式推断和维护的。 在本文中,作者通过两种变异策略来实现切面的保留,即结构和类型保留: 保留结构的突变: 作者观察到,维护输入语料库的某些结构(例如控制流)往往会在生成的测试用例中保留其切面。例如,PoC漏洞利用中的循环结构在调用JIT编译中起着重要作用,并且某些访问顺序对于触发优化阶段(例如,之前(3)中的冗余校验消除)是必须的。然而采用的盲目的突变和生成策略往往会破坏这些结构。根据作者的评估,保留结构是最有效的变异技术,可以跨变异保留输入语料库的各切面(例如,每个JIT优化阶段§VI-D),这比不使用该技术时造成的crash多2倍(表VII) 。 保留类型的突变: 作者观察到,输入语料库中的类型为fuzzer提供了足够的空间来生成高质量的测试用例,同时保留切面。例如,对象类型( 之前的(2) )应与JIT版本代码的假定参数类型(第9行中opt( )的{})相匹配,否则,代码应在JIT执行中bailed out。也就是说,如果未保留种子输入的类型,则派生的测试用例最终只会使JavaScript引擎的一小部分受压(例如,解析器或解释器)。此外,保留类型可以极大地帮助产生无错误的测试用例,包括语法编译错误和动态运行时错误,例如ReferenceError,TypeError和RangeError。这种错误不利于测试用例到达JavaScript引擎的深层更复杂的逻辑,因此它们被视为保留原始种子语料库的各种类型的方面的必要前提。在本文中,作者利用了轻量级的类型分析,该分析静态地传播了来自动态分析观察到的类型信息。根据评估,相较于最新的fuzzer,保留类型的变异将运行时错误减少了2倍。作者基于类型的突变也旨在感知语义,这意味着它通过尊重种子输入的某些语义来尝试避免方面的破坏,例如,避免阻碍JIT调用的try-catch逻辑。

4 Design

作者设计了DIE,面向变异的JavaScript模糊测试框架,该框架尊重种子输入的高级切面,例如PoC和单元测试,以生成有效的测试用例。为实现此目的,DIE通过以高概率保留影响总体控制流和所使用变量的原始类型的代码结构来更改JavaScript输入的AST。代码覆盖率指导和分布式设置还使DIE能够大规模地发现JavaScript引擎中的深层错误。

  1. DIE对原始种子文件进行预处理,即,结合动态和静态类型分析,以较低开销推断AST中的节点类型,生成类型化的AST。
  2. DIE从语料库中选择一个种子输入的类型化AST进行突变。
  3. 给定类型为AST的DIE通过替换每个节点(保留其类型)或插入新节点(保留整体结构)来生成新的测试用例。通过使用类型化的AST,DIE在突变过程通过类型保留突变来保留类型,通过结构保留突变来保留控制流结构。每个切面都倾向于保持某些特性,这些特性隐含在跨突变的原始语料库中。
  4. 突变后,DIE执行新生成的测试用例,并检查执行是否崩溃或终止而没有错误。DIE可以在执行测试用例后获得运行时覆盖率,并且在访问任何新代码路径时将其存储为新的输入语料库。DIE还通过使用多台机器同时查找错误来支持分布式模糊测试。

4.1 Custom JavaScript Type System

DIE改进了JavaScript的原始类型系统,为fuzzing定制了两个独特的属性:

  • 混合类型: 由于JavaScript是一种弱类型和动态类型的语言,因此每个变量的类型只能在运行时确定,甚至可以在其生命周期内更改。所有DIE引入混合类型来描述每个语法单元可能具有的所有类型,从而捕获运行时变化的类型。
  • 详细的复合类型: DIE检查JavaScript对象的子元素类型,以更精细的方式定义复合类型:
    • 数组:DIE记录数组中元素的通用类型,可以是数字或字符串。具有空插槽或各种类型的元素的数组被视为Any数组。
    • 对象:DIE存储对象实例的形状,该实例由其属性键和值的类型组成。
    • 函数:DIE考虑函数实例的参数和返回类型。

DIE的自定义类型系统是一项基本功能,可根据从给定测试用例中提取的语义信息更好地支持变异。例如,DIE知道一个现有Object的几个Number成员,因此与现有的Fuzzer相比,它具有更多用于创建有效Number表达式的构造块。此外,DIE的变异带来的语义错误更少。例如,对于Array类型,DIE倾向于使用Number数组的元素而不是任意数组来使数组索引发生突变。

DIE基于自定义类型系统,将每个JavaScript操作(包括用于内置对象的表达式和API)抽象为一个或多个类型缩减规则。使用特定类型的参数调用操作时,每个规则都声明返回值的类型。下表总结了DIE如何重新定义加法和数组索引操作。除非两个操作数均为数字,否则JavaScript中加法的默认返回类型为String。而且,索引数组的返回值完全取决于元素的类型。DIE依靠这些规则来推断AST节点类型以进行类型化AST构造,并为突变构建新的AST节点。

4.2 typed AST

基本上,DIE会更改语料库中已保存的JavaScript文件的语法单元(即AST节点),以生成用于测试的新输入。为了在保持生成的输入的有效性的同时构建新的AST节点进行突变,DIE在第一阶段会检索每个种子文件的类型和绑定信息。对于从种子文件中检索到的AST的每个节点,DIE对其进行扩展:在运行时由DIE提炼的潜在类型一组可用的声明变量。作者将这种包含每个节点的类型和绑定信息的扩展AST的命名为typed AST。 DIE会为语料库中的每个保存的输入维护AST,并对输入的AST进行变异以生成新的JavaScript文件。

DIE通过异构方法来推断AST节点在运行时可能具有的所有可能的类型:

  1. DIE动态地在种子文件中收集每个节点的类型,这些表示标识符(即对特定变量的引用)。
  2. 解析种子的AST之后,DIE通过在引用标识符节点的语句之前添加一个trampoline来对种子进行检测。
  3. trampoline跳到类型分析函数,此功能现在可以检索标识符的类型。该函数遍历Array并迭代Object的所有成员以获得DIE强制执行的精炼类型。
  4. DIE然后运行检测的种子文件,并对运行时输出的标识符的记录类型进行重复数据的删除,以得到最终的类型集。通过确定所有叶节点的类型(即标识符和文字),DIE可以自下而上静态地推断其他AST节点的类型。

4.3 Building Typed AST Nodes

DIE依靠其构建器来构建新节点,以使输入的键入的AST发生变异。DIE使用所需的类型和上下文(即发生节点突变的类型化AST节点的代码点)调用构建器。 然后,构建器利用上下文来构造其值类型与预期值兼容的新AST。

4.4 Mutating Typed ASTs

给定选定的输入后,DIE会以一种方面保持不变的方式更改其类型化的AST,以便利用其特性有针对性地测试JavaScript引擎底层组件中的特定代码路径。通常,测试用例的各个方面在很大程度上取决于其结构和类型信息。因此,在突变期间,DIE特别避免删除确定现有JavaScript程序结构的整个if语句,循环语句,自定义函数定义和调用等。同样,DIE避免重新定义具有不同类型的现有变量。然后将突变的AST转换为JavaScript代码以进行测试。处理完执行结果后,DIE会将对AST所做的更改还原为下一轮突变。如果生成的JavaScript文件发现目标JavaScript引擎的新代码路径,则其键入的AST与代码一起保存。

DIE采用以下方法来更改JavaScript文件的类型化AST(按优先级排序):

  • 突变类型化的子AST:DIE随机选择一个没有结构目标(即表达式或子表达式)的子AST(第4行)。然后,子AST被构建器构建的具有匹配类型的新AST替代(第5-6行)。
  • 插入语句: DIE定位一个语句块(例如,if语句的主体,一个函数或只是全局区域),并在该块内随机选择一个代码点进行插入(第10行)。接下来,DIE通过使用在该点声明的现有变量来生成新的表达式语句。在这里,表达式语句只是任何值类型的表达式,后跟分号,也可以由构建器构建。 DIE通过在末尾插入新语句来增加旧输入(第15行)。
  • 引入一个新变量:DIE除了插入语句外,还设法在类型化AST的随机代码点处插入新变量的声明。新变量始终由构建器构建的具有随机类型的表达式初始化(​​第14行)。

4.5 Feedback-driven Fuzzing in Distributed Environment

即使突变过程保留了切面,由于JavaScript的自由度很高导致搜索空间巨大,且重新执行成本开销过高,因此在JavaScript引擎中查找新的bug仍然具有挑战性。因此DIE在分布式环境中使用了反馈驱动的模糊测试。原始AFL的代码覆盖率以字节为单位表示每个边,以记录命中次数以查找溢出。但是,命中数在JavaScript中毫无意义,因为可以通过修改for语句中的范围来任意控制命中数。因此,DIE以bit为单位记录边,丢弃命中计数。因此,与原始AFL的设计相比,DIE可以在相同的内存大小内存储八倍的分支。

此外,为了使多台机器在分布式环境中进行协作,DIE开发了自己的代码覆盖同步机制。在维护其本地覆盖图的同时,如果DIE实例发现了本地感兴趣的测试用例,则其实例将与全局图同步。它会根据实例的本地map引入新的内容。然后,实例在同步后仍然有趣的情况下上传测试用例。这类似于EnFuzz的全局异步本地同步(GALS)机制,但在两个方面有所不同。首先,DIE同步代码覆盖率本身,而不是像EnFuzz这样同步测试用例。不同于EnFuzz由于fuzzer的异质性而需要重新评估测试用例,DIE可以避免重新执行,大大减小开销。其次,DIE可以支持多台机器,而不仅仅是一台机器上的多个进程。

5 Evaluation

作者评估DIE在使用方面保持不变的突变中发现最新JavaScript引擎中的新错误的能力方面的有效性。 并将DIE与基于各种指标的现有JavaScript引擎fuzzer进行比较,以展示DIE的各种优势。旨在回答五个问题:

  • DIE可以在真实的JavaScript引擎中找到新的错误吗?
  • 语料库中保留的方面在触发DIE发现的错误中起关键作用吗?
  • DIE是否完全保留了语料库中的切面?
  • DIE可以在语法上和语义上生成正确的JavaScript代码吗?
  • DIE在代码覆盖范围和针对最新模糊测试器的错误发现能力方面表现如何?

5.1 实验配置

目标引擎: 作者使用三种广泛使用的JavaScript引擎评估了错误发现能力:ChakraCore,JavaScriptCore和V8。 收集有效的种子输入: 当DIE根据现有测试套件和PoC进行变更时,种子语料库的质量和有效性在很大程度上影响DIE的性能。作者从两个公共资源中收集JavaScript文件:

  • 从四个JavaScript引擎(ChakraCore,JavaScriptCore,V8和SpiderMonkey)的源存储库中进行回归测试
  • js-vuln-db,这是一个公共存储库,用于收集JavaScript引擎CVE的PoC。

此外,为了充分利用种子语料库,作者进一步从收集的文件中删除了所有断言,以防止DIE生成的新输入提前终止。作者最终积累了14,708个唯一的JavaScript文件,其中包括158个来自js-vuln-db的JavaScript文件。

5.2 Identified Bugs Including Security Vulnerabilities

为了评估DIE查找新漏洞的能力,作者在分布式环境中运行DIE,包括一个主节点来存储和同步中间数据(例如Coverage Map)和多个从节点。

结果,DIE在ChakraCore中发现了28个错误,在JavaScriptCore中发现了16个错误,在V8中发现了4个错误,总共有48个错误。表VIII显示了发现的独特错误及其描述。作者使用以下标准对这些错误进行了计数:(1)在向供应商报告之前发现了但已解决的问题,(2)具有与规范和其他JavaScript引擎不同行为的语义错误,(3)除发行版本中的断言外的内存损坏错误,以及(4)供应商认可的安全性错误。

为了通过唯一的根本原因识别所有错误,作者手动分析了发现的每个崩溃,并识别了48个不同的错误。在不同的错误中,作者发现16个与安全性相关,这是由于它们与以前称为安全性错误的现有错误相似。在与安全相关的错误中,作者获得了12个供应商认可的CVE。此外,其中13个错误可能与安全性有关,包括内存损坏错误。作者可以在ChakraCore中识别出六个语义错误,因为在出​​现语义错误(包括语义错误)的情况下,ChakraCore提供的调试信息比其他消息更详细,因此DIE可以找到它们。

5.3 Effectiveness of Leveraging Aspect

为了评估保留方面的突变是否能够发现错误,作者手动研究了生成的崩溃输入与其对应的种子文件之间的关系。首先,作者将每个崩溃输入最小化为可以触发崩溃的最小PoC。然后,作者检查导致崩溃的PoC的结构或​​类型信息是否确实与种子文件的结构或类型信息相对应。作者检查了输入是否有DIE在ChakraCore中发现的84个不同的崩溃和28个合理的错误。下表列出了仅利用结构信息或原始种子文件的结构和类型信息的输入数量。

5.4 Evaluation of Aspect Preserving

为了证明保留结构和类型信息可以有效地维护有趣的切面,并与现有的fuzzer进行切面保留的性能进行比较,作者评估了DIE,不具有结构保留的DIE,Superion和CodeAlchemist,其种子语料库仅包含触发JIT编译的JavaScript程序。不保留结构的DIE方法(为方便起见,称为DIEt)会改变类型化AST中的任何节点,而与节点的结构含义无关。 DIEt在其突变期间仍会尊重类型信息。首先,作者测量调用JIT编译的生成的输入的速率。接下来,作者将生成的输入的发出的字节码中的唯一(规范化)语句的数量与种子语料库中的数量进行比较,以进一步证明在利用现有测试用例并覆盖JavaScript中的深层代码路径时,保持切面的变异的功能。在对字节码中的语句进行计数时,作者将操作数(例如字节码参数中的立即数和寄存器名称)进行规范化。最后,为了显示保留结构和类型信息以利用现有测试用例的各个方面的更细粒度的效果,作者评估了生成的输入集和起始语料库之间的JIT优化调用的比率差。请注意,作者选择Superion和CodeAlchemist进行比较,因为它们是最先进的JavaScript模糊测试程序之一,可发现公认的CVE的JavaScript错误并使用语料库进行输入突变和生成。

5.5 Validity of Generated Input

作者稍加修改了fuzzer,以检查使用JavaScript引擎执行生成的输入以将DIE与现有fuzzer进行比较的标准错误流。图5显示了DIE和现有的fuzzer生成的输入的错误率。 首先,作者测量了完整种子文件的错误率以进行比较。当作者执行试运行时,种子文件集最初产生了11.20%的错误率。有了种子集,DIE新创建的输入产生了覆盖率反馈为18.88%的错误率。此外,Superion和CodeAlchemist在相同的种子集下生成的错误率分别为43.54%和43.58%,而jsfunfuzz生成的错误率为45.62%,这意味着Superion和CodeAlchemist生成的输入均会产生2.31倍的运行时错误4并生成输入jsfunfuzz撰写的文章比DIE产生了2.42倍的运行时错误。该结果证明了DIE相对于现有JavaScript模糊测试器在生成有效输入方面的有效性,这是模糊测试结构化目标的重要因素。此外,作者测量了没有覆盖反馈的DIE,以显示DIE构造有效代码的能力。没有覆盖反馈,DIE产生的错误率为8.65%,低于原始种子集的错误率。结果表明,DIE正确分析了类型,基于作者的类型系统构建了一个有效的类型化AST节点,并将产生错误的AST节点替换为有效的AST节点。

5.6 Performance Comparison with Other Fuzzers

为了在探索输入空间和与最新的fuzzer进行崩溃之间的比较,作者首先在JavaScript引擎上运行了DIE,DIEt,Superion和CodeAlchemist来测量代码覆盖率。此外,作者针对三个主要JavaScript引擎的最新版本在相同的环境中运行它们,并计算了发现的崩溃次数。两项实验均持续24小时。

探索输入空间。 图6说明DIE探索的独特代码路径比Superion高出1.16倍,比CodeAlchemist高出1.29倍。有趣的是,DIEt访问的路径比原始DIE略多。这是因为DIEt有更多机会以更多样化的方式来改变多样化的节点,这与表V中的结果相匹配,即它产生了更多的唯一字节码。

到达崩溃。 表VII总结了每个模糊器发现的唯一崩溃的数量。请注意,DIE在JavaScript引擎上发现了最独特的崩溃5,而Superion和CodeAlchemist发现的崩溃更少。特别是,DIE在最新版本的ChakraCore中发现的比CodeAlchemist多5.7倍,而Superion在同一时期内没有发现任何崩溃。 从结果中,作者观察到大多数代码路径是在前两个小时内引入的,这表明利用种子中的各个方面有助于促进探索各种路径。更重要的是,作者得出的结论是,代码覆盖率倾向于显示输入空间搜索的能力,但它并不是判断JavaScript引擎模糊器发现错误的能力的绝对指标:(1)DIEt引入了比DIE更多的代码覆盖率,但发现的崩溃较少,(2)DIE在ChakraCore中发现的崩溃中有71.79%是在前两个小时之后生成的。

总结

个人认为,这篇文章立意新,针对JavaScript引擎的fuzzing问题,探讨了如何利用输入语料库以提升fuzzing效率。本文提出的aspect保留突变是一个不错的想法,与目前追求feedback的很多盲目做法产生了鲜明对比,且进行了细致的设计与实验,值得参考。