基于两个维度输入空间探索的文件系统Fuzzing

基于两个维度输入空间探索的文件系统Fuzzing

Fuzzing File Systems via Two-Dimensional Input Space Exploration

# Remarks

Conference: S&P 2019

Full Paper: gts3.org/assets/papers/

Artifact: github.com/sslab-gatech


# Summary

(+) Sigenifcance: The authors address an important problem of kernel file system security. The bugs in file systems are security-critical and difficult to detect due to the complexity of both file systems and operating systems. The author present JANUS, a dedicated fuzzing technique for detecting bugs in the file system

(+) Novelty: The authors present an approach that needs to pay attention to the input of two dimensions at the same time (i.e., mutating metadata on a large image, while emitting image-directed file operations.)

(+) Soundness: Experiments are conducted on 8 file systems and found 90 bugs in Linux kernel, compared against Syzkaller.

# Reading Notes


1. Introduction

Q: 测试内核中的文件系统(File system)有哪些挑战?

A: 由于文件系统自身结构的复杂性以及它在操作系统中扮演角色的重要程度,决定了模糊文件系统与模糊普通二进制程序之间的大不同。文件系统完整运行需要两个输入:一是加载好的磁盘映像,二是在磁盘映像上的执行的各种文件操作。想要有效的对文件系统进行模糊测试,必须解决以下几个问题:

  1. 文件系统以文件镜像(image),文件镜像的大小远远大于普通用户态程序
  2. 在一个文件系统镜像中,一般只有其中的元数据(metadata)会对文件系统的操作产生影响,普通的文件数据是无用的,而metadata只占整个文件系统镜像的1%左右,这导致模糊测试时对输入的突变操作可能大部分是无效的。
  3. 有效的文件操作应该是基于运行时的文件系统的实际状态的,而传统的生成workloads的方法只和文件系统的初始状态有关,和上下文操作无关,workloads的部分操作可能时无效的。因此在生成workloads的过程中,必须维持文件系统的状态,基于之前的状态来生成新的文件操作。
  4. 输入空间有两个维度,文件操作和文件系统镜像之间是存在联系。Fuzzer需要同步的对这两种输入进行fuzz,而不像一般的Fuzzer那样只考虑一种形式的输入。
  5. 目前针对操作系统的Fuzzer为了避免重置系统状态导致的巨大开销,在运行不同的workloads时,会复用之前的系统实例,这会使得在长时间运行后系统的行为变的不可靠,从而使得很多crash不可被复现。


Q: 文件系统测试用例的构成?

A: 测试用例有三部分构成:元数据(metadata),一系列文件操作(program or workloads),文件系统的状态(status)。

Q: 目前多研究现状?

A: 在文件操作测试用例这方面,当前流行的内核模糊测试工具Syzkaller,是盲目的生成系统调用,并不考虑文件操作与磁盘映像上的文件系统之间的动态依赖性(比如syzkaller对那些已被重命名rename()或已删除unlink()的旧路径文件重复发出read()调用)。

当前的模糊器都依赖VM,QEMU,UML的实例,在其上运行目标文件系统,由于每次加载实例时间较长,于是通过重用实例节约时间,这也导致运行一定时间后,系统状态变得不确定。甚至在一个崩溃出现后,由于经过了成千上万次的调用,导致开发人员难以重现BUGs。

由于文件操作和文件系统映像之间是存在联系。Fuzzer需要同步的对这两种输入进行模糊测试,而当前的Fuzzer只考虑一种形式的输入。


2. Approach


Q: 作者的方案?

A: 针对前面提出的挑战,作者提出了如下方案:

  • 因为metadata只占整个文件系统镜像的1%左右,JANUS通过从文件系统镜像中精确定位并提取metadata来解决第一个问题;
  • 通过基于文件系统的运行时状态来生成新的文件操作解决了第二问题;
  • 专门提出了文件操作的变异方式,从而解决了第三个问题
  • 同时探索两个维度的输入空间,从而解决第四个问题;
  • 第五个问题主要是执行环境。

Q: JANUS的整体设计架构?

A: Overview如下图所示

  • 首先JANUS会根据不同类型的文件系统,对输入的镜像进行解析,从中定位并提取出对应的metadata,并得到文件系统的初始状态status,然后生成一个打开文件的文件操作作为初始的program,并更新program运行后的文件系统的可能状态为新的status。对seed input的解析结果构成一个集合(Corpus)。
  • 之后从Corpus挑选出一个测例进行mutate。Metadata由Image mutator进行fuzz,生成新的metadata’;program交由Syscall fuzzer基于status进行fuzz,生成新的program’,以及对应的新状态status’。
  • 接着,JANUS会将metadata’,组装成一个完整的文件系统镜像,并重新计算相应的checksum。然后由一个用户态的操作系统执行器,挂载这个镜像,并执行program’,并把执行情况返回给引擎,如果这个测例是有价值的,比如其会使程序执行一条之前未被执行到的路径,则这个测例会被加入到Corpus,否则这个测例将被抛弃。
  • 最后,这个执行器每次运行新的program前都会重置,确保系统状态不受之前操作的影响。

首先,测试引擎从初始corpus中取出一个测试用例,然后调用image mutator,采取常规的变异策略(和syzkaller策略一样),比如位翻转等,变异测试用例的元数据,完成以后,再根据记录来重新计算好校验和。此时,测试用例中的program保持不变,image status也保持不变,然后根据记录的元数据偏移量,把这些元数据和未变异的用户数据重新组成完整的磁盘映像。最后把完整的映像和相应的文件操作加载到EXECUTOR(基于linux kernel library建立,可测试文件系统的功能,在用户空间中运行,这样可以保证每次执行后,在可以忽略的时间内刷新内核,方便开发人员重现BUGs)中去执行。在评估中,设定模糊一个测试用例的磁盘映像环节重复进行256轮次(由人员指定),当有出现新的路径覆盖时,会把这个测试用例(包括元数据,program,image status)打包保存到corpus中(类似AFL风格的代码覆盖反馈),如无新的路径发现,则开始对系统调用进行变异,取出相应的测试用例,元数据保持不变,调用syscall fuzzer对program进行变异,对于program里那些调用的普通参数,采用和syzkaller变异它们相同的策略,它们和image status无关。对于program里那些依赖文件系统运行上下文的敏感参数值,JANUS变异它们不仅考虑它们的预期值,更要根据image status来变异,例如,某个操作需要文件描述符为对象,,syscall fuzzer随机选择一个正确类型的已打开的文件描述符作为对象。此时,根据program里的调用和原status,生成新的status,成为新的测试用例。最后和前面一样,把未改变的元数据和用户数据组成完整的映像,在和program加载到executor中执行。128轮次以后,如有新的路径发现,把这个新的测试用例保存到corpus中,如没有,则开始增加新的文件操作到program中,元数据和原来的系统调用此时保持不变,在fuzzing 引擎结束后,依然需要更新image status,最后完整的映像加载到executor执行64轮次,如无新的覆盖出现,此测试用例使命结束。然后选择下一个测试用例,迭代进行。下图是二维输入的调度过程:

由于每次都在干净的内核上运行,当出现崩溃时,本文通过实现一个概念验证器根据序列化的测试用例来生成一个带有可编译C程序的完整的磁盘映像,以便重现BUGs。

综上看来,通过仅模糊元数据解决了输入测试用例尺寸太大的问题;引入image status,根据status变异生成的相应的文件操作,已解决文件操作依赖上下文的问题;在每次变异元数据,根据记录修正校验和;在基于library OS生成的executor测试,在用户空间里运行,这样解决了BUG重现问题。


Q: JANUS对两个维度的输入空间有什么不同的变异策略

A: 由于JANUS关注两个不同维度的输入空间,对其分别制定了变异策略

  • 对metadata的变异其他的fuzzer类似,JANUS会随机的改变metadata中的某些字节,而不会去考虑metadata的具体语义。
  • 对一系列文件操作的变异相对复杂。策略有两种,一种是改变一系列文件操作中已有的某个操作的参数;另一种是在一系列文件操作的最后新增一个文件操作。文件操作的类型是随机的,但是操作的文件对象和参数,是基于当前的status和一些系列人为设定的规则生成的。这确保一系列文件操作是上下文有关的,并依赖于当前的文件系统状态,能极大的减少无效操作。


3. Evaluation

Bug detection: 8个文件系统上进行了测试,最终发现了90个bug

Coverage: JANUS在代码覆盖率上也远远优于另一个系统内核Fuzzing工具Syzkaller。

Crash reproduce:实验显示根据JANUS生成的PoC,能复现其中90%左右的crash,而Syzkaller给出的PoC完全无法重现crash。

发布于 03-15

文章被以下专栏收录