思考🤔
很扎实的一篇工作,作者提出要在 image 和 syscall 两个维度来测试文件系统,在变异 image 时只变异对系统影响较大的 metadata,提升 fuzz 效果的同时还减少了需要变异的数据大小;在变异 syscall 时采用了和 syzkaller 类似的策略,区别是作者利用了文件系统的状态信息(例如文件、路径、权限等)并且追踪了每个 syscall 对系统的影响,遗憾的是,由于未能在执行 syscall 时与系统进行交互,部分会导致文件系统状态变化的 syscall 不会进行变异(例如 unlink()
这类可能会删除文件的 syscall),理论来说还是会有部分状态空间是探索不到的。
同时作者还在 LKL 上实现了较新版本的 Linux 内核,从而可以以毫秒级的速度重置系统状态,这一点确实是其他模拟执行方式无法企及的,LKL 带来的加速对整体 fuzzing 速度和可复现性的提升非常明显,然而,LKL 自身已经不在支持新版本的 Linux 内核了,如果想用 LKL 测试 v6.0 以上版本的话,适配的工作量可能会非常巨大。
有关于文件系统 fuzz 的工作并不是很多,这篇论文及其后续的两篇论文(Finding Semantic Bugs in File Systems with an Extensible Fuzzing Framework)的成果都非常好,值得认真分析研究,同时 Related Work 中的一些工作也非常有意思,可以多看看。
# Abstract
文件系统是操作系统的基本构建模块,通常体积庞大且极为复杂,因此难以做到完全无错误。目前,文件系统主要依赖常规的压力测试工具和形式化检查工具来发现漏洞,但随着文件系统和操作系统复杂性的不断增加,这些工具的能力逐渐显得有限。因此,模糊测试作为一种不需要深入了解目标系统却行之有效的实用方法,逐渐成为首选。然而,文件系统模糊测试面临以下三个主要挑战:
- 对大型镜像文件进行变异操作会显著降低整体性能(可能需要变异几百 M 甚至数十 GB 数据)。
- 需要生成与镜像相关联的文件操作(syscall 及特定的参数)。
- 现有的操作系统模糊测试工具难以重现发现的漏洞。
为此,我们提出了 JANUS,这是首个反馈驱动的模糊测试工具,能够探索文件系统的二维输入空间。JANUS 通过变异大型镜像中的元数据,同时生成与镜像相关的文件操作来测试文件系统。此外,JANUS 使用库操作系统(Library OS)替代传统虚拟机进行模糊测试,使其可以加载操作系统的新副本,从而大幅提高漏洞重现的能力。
我们在八种文件系统上评估了 JANUS 的性能,发现了 Linux 内核中的 90 个漏洞,其中 62 个已被官方确认。共有 43 个漏洞已修复,并分配了 32 个 CVE(公共漏洞披露编号)。与目前最先进的文件系统模糊测试工具 Syzkaller 相比,JANUS 在运行 12 小时后,对所有文件系统都实现了更高的代码覆盖率。具体而言,在 Btrfs 和 ext4 文件系统中,JANUS 分别访问了 4.19 倍和 2.01 倍于 Syzkaller 的代码路径。此外,JANUS 能够成功重现 88% 至 100% 的崩溃,而 Syzkaller 对这些崩溃均无法重现。
通过以上改进,JANUS 不仅显著提升了文件系统模糊测试的效率,还为发现和修复文件系统漏洞提供了一种更可靠的工具。
# Introduction
文件系统是操作系统最基本的系统服务之一,在管理用户文件和在系统崩溃时保持数据一致性方面发挥着重要作用。目前,大多数传统文件系统(例如 ext4、XFS、Btrfs 和 F2FS)运行在操作系统内核中。因此,文件系统中的漏洞可能会导致严重的错误,例如系统重启、操作系统死锁以及整个文件系统镜像的不可恢复错误。此外,这些漏洞还可能带来严重的安全威胁。例如,攻击者可以通过挂载精心构造的磁盘镜像或调用存在漏洞的文件系统特定操作来在目标机器上实现代码执行或权限提升。然而,手动消除文件系统中所有的漏洞是一项挑战,即使对于专家也是如此。例如,Linux v4.18 中最新的 ext4 实现包含 50K 行代码,而 Btrfs 的实现接近 130K 行代码。同时,许多广泛使用的文件系统仍处于活跃开发中。文件系统开发人员不断优化性能并添加新功能,但这些更新也可能引入新的漏洞。
为了自动发现这些潜在的漏洞,大多数正在开发的文件系统依赖已知的压力测试框架(例如 xfstests、fsck、Linux Test Project 等),这些框架主要关注文件系统的回归测试和最低限度的完整性检查。例如,我们在 ext4 中发现的一个漏洞(即 CVE-2018-10880)可以通过移动关键扩展属性出 inode 结构来导致内核崩溃。我们通过挂载一个启用 inline_data 的正常 ext4 镜像触发了此漏洞,而该镜像能够绕过 xfstests 和 fsck 的完整性检查。此外,一些先前的研究工作应用了模型检查来发现文件系统漏洞,但这需要对文件系统和操作系统状态有深入的了解。由于现代操作系统日益复杂,这在实践中已经变得不切实际。另一方面,大多数经过验证的文件系统由于不够成熟,尚难以在实际中采用。
另一种方法 —— 模糊测试(fuzzing)—— 正受到越来越多的关注。模糊测试不仅需要对目标软件的最低了解,而且是一种高效、实用的方法,已经发现了数千个漏洞。因此,模糊测试成为自动发现各种文件系统漏洞(例如 Linux 内核中的 54 个漏洞)的可行方法。然而,与其他常见目标不同,模糊测试文件系统依赖两个输入:挂载的磁盘镜像和在挂载镜像上执行的一系列文件操作(即系统调用)。现有的模糊测试工具要么专注于将镜像作为普通二进制输入进行变异,要么生成一组随机的文件操作特定系统调用。遗憾的是,它们都未能高效且全面地测试文件系统,主要因为以下三个挑战:
首先,磁盘镜像是一个结构化但复杂的大型二进制块,其最小大小几乎是普通模糊测试工具首选最大大小的 100 倍。由于变异镜像涉及大量 I/O 操作,这大大降低了模糊测试的吞吐量。与二进制块变异相关的另一个问题是,现有的模糊测试工具仅变异镜像中非零的块。这种方法并不可靠,因为这些工具未能利用结构化数据的特性,例如文件系统布局,而变异元数据块比变异数据块更有效。此外,由于缺乏文件系统布局的知识,现有模糊测试工具在破坏元数据块后也无法修复任何元数据校验和。第二个挑战是文件操作是上下文相关的工作负载(workload),即文件系统镜像的实时状态决定了一组系统调用可以操作哪些文件对象,而特定系统调用的调用会改变正在操作的对象。不幸的是,现有的系统调用模糊测试工具独立生成随机系统调用,使用硬编码文件路径,未能生成有意义的文件操作序列,也未能覆盖文件系统的深层代码路径。第三个问题是现有模糊测试工具难以重现发现的漏洞。大多数针对操作系统或文件系统的模糊测试工具在测试生成的输入时未重新加载操作系统实例或文件系统镜像的干净副本,即它们 {未使用非老化的操作系统和文件系统 ++{.dot}。由于依赖 VM、QEMU 或用户模式 Linux (UML) 实例,这些工具无法快速重置系统状态,只能复用这些实例,导致操作系统状态污染,最终造成不稳定的执行和不可重现的漏洞。
“老化” 的操作系统或文件系统系统:以 syzkaller 为例,syzkaller 会在同一个操作系统(文件系统)中执行多个 ,例如,假设 触发了一个 bug,syzkaller 会尝试用 来复现 bug,然而该 bug 的触发路径可能是 ,由于忽略了前面的 的影响,导致无法复现 bug。这就是文中的 “老化” 的操作系统的含义,即在执行某个 时系统不是一个纯净的状态,而是叠加了多个 的影响的状态。
我们通过 JANUS 解决了上述挑战。JANUS 是一种进化型的反馈驱动模糊测试工具,可以高效地探索磁盘文件系统的二维输入空间。JANUS 通过利用元数据的结构化特性,仅对种子镜像的元数据块进行变异,从而显著减少输入搜索空间。其次,我们提出了基于镜像的系统调用模糊测试技术,以模糊测试文件操作。JANUS 不仅存储生成的系统调用,还推测这些调用完成后每个文件对象的运行时状态。JANUS 使用推测状态作为反馈生成新的一组系统调用,从而发出上下文感知的工作负载。在每次模糊测试迭代中,JANUS 优先进行镜像模糊测试,然后调用基于镜像的系统调用模糊测试,以全面探索目标文件系统。最后,JANUS 解决了重现性问题,通过使用库操作系统(例如 Linux Kernel Library)运行在用户空间中,而不是 VM,每次测试文件系统相关功能时都加载操作系统的干净副本,从而保证了结果的可重现性。
借助 JANUS,我们对 Linux 内核(v4.16–v4.18)中八个主流文件系统进行了四个月的模糊测试。我们的评估显示,与最先进的模糊测试工具 Syzkaller 相比,JANUS 在所有选定文件系统上最多实现 4.19 倍的代码覆盖率。此外,我们选择使用库操作系统,使得 JANUS 能够重现 88%-100% 的崩溃,而 Syzkaller 无法重现任何崩溃。迄今为止,我们已成功发现 90 个漏洞,其中 62 个已被开发者确认,43 个已修复并分配了 32 个 CVE。
本文的贡献包括以下几个方面:
- 问题:我们识别出现有文件系统模糊测试工具的三大主要问题:
- 对大体积的二进制镜像进行模糊测试效率低下;
- 模糊测试工具未能利用文件系统镜像与文件操作之间的依赖关系;
- 模糊测试工具使用老化的操作系统和文件系统,导致发现的漏洞难以复现。
- 方法:我们设计并实现了一种进化式文件系统模糊测试工具,名为 JANUS。它通过高效地变异种子镜像中的元数据块,并生成基于镜像的工作负载,深入探索目标文件系统。此外,JANUS 使用了一种库操作系统(Library OS,例如 LKL)来代替虚拟机,用以测试操作系统功能,从而能够在毫秒内提供全新状态的操作系统镜像。
- 影响:我们在 Linux 内核中的八种文件系统上对 JANUS 进行了评估,共发现了 90 个漏洞,其中 62 个已被开发者确认,43 个已修复并分配了 32 个 CVE 编号。此外,在代码覆盖率方面,JANUS 在所有选定文件系统上均优于 Syzkaller。例如,在对 Btrfs 和 ext4 进行 12 小时的模糊测试时,JANUS 分别覆盖了 4.19 倍和 2.01 倍于 Syzkaller 的代码路径。同时,JANUS 能复现 88%-100% 的崩溃,而 Syzkaller 无法复现任何一个。
# Threat Model
在本研究中,我们假设攻击者可以挂载一个经过精心构造的磁盘镜像到目标机器上,并操作镜像中存储的文件以利用内核文件系统中的安全漏洞。攻击者无需具备 root 权限即可实现这一点,常见的方式包括:
- 自动挂载:现代操作系统会自动挂载插入的未经信任的驱动器(如果其支持对应的文件系统)。这种机制已被一些臭名昭著的攻击利用,如 Stuxnet 和 “恶意女仆攻击”(Evil Maid Attack)等;
- 非特权挂载:macOS 允许非 root 用户挂载 Apple 磁盘镜像,并支持多种文件系统(如 HFS、HFS+ 和 APFS)。在这些文件系统中发现了多个漏洞,可能导致绕过内核的内存读取限制以及代码执行。Linux 也允许非特权用户通过
FS_USERNS_MOUNT
在用户命名空间中挂载任意文件系统。
此外,攻击者还可以通过云存储服务或可移动驱动器共享精心构造的磁盘镜像,从而诱导受害者挂载。一旦镜像被挂载,攻击者即可对镜像执行文件操作(如读取、写入、删除和重命名文件),以触发文件系统中的漏洞。
# Background and Motivation
商品操作系统通常将磁盘文件系统实现为内核模块。用户负责挂载大体积、已格式化的镜像,并通过文件操作来管理数据。本节内容如下:
- 一般模糊测试方法(§3.1):描述模糊测试的通用方法。
- 现有文件系统模糊测试工具(§3.2):总结当前文件系统模糊测试工具的现状。
- 现有工具的不足:解释为何现有方法无法高效地测试文件系统。
- 挑战与机遇(§3.3):总结文件系统模糊测试中的主要挑战,并探讨潜在的解决方案和机会。
# A Primer on Fuzzing
模糊测试是一种广泛应用的软件测试方法,通过反复生成新输入并将其注入目标程序以触发漏洞。它被认为是实际中发现现代软件安全漏洞最有效的方法之一。例如,先进模糊测试工具 AFL 及其变体已在开源软件中发现了无数漏洞。
为了高效地探索目标程序,近期的模糊测试工具利用过去的代码覆盖信息来指导后续输入的生成。此外,操作系统(OS)等软件属于最关键的程序,因为发现的漏洞可能允许在目标机器上实现权限升级。为了模糊测试操作系统,一些框架扩展了基于反馈的模糊测试方法,通过调用随机生成的系统调用来触发内核漏洞。
# File System Fuzzing
磁盘文件系统有两种输入空间:(1) 结构化的文件系统镜像格式;(2) 用户为访问挂载镜像上存储的文件而调用的文件操作。现有的一些文件系统模糊测试工具通常使用通用的模糊测试框架来分别针对镜像或文件操作。
# Disk Image Fuzzer
图 1:ext4 映像的磁盘布局。灰色块表示正在使用的元数据,仅占映像大小的 1%。其中一些元数据(包括范围树节点、目录条目和日志块)分散在映像中,而其他元数据(如超级块、组描述符等)则位于开头。
File system | ext4 | XFS | Btrfs | F2FS | GFS2 | ReiserFS | NTFS | AFL |
---|---|---|---|---|---|---|---|---|
Min. size(MB) | 2 | 16 | 100 | 38 | 16 | 33 | 1 | 1 |
表 1:不同文件系统在默认选项下(启用日志或记录功能)允许格式化的块设备的最小大小。大多数镜像大小超过了 AFL 建议的模糊输入大小 (1MB)
磁盘镜像是一个大型的结构化二进制块,包含 (1) 用户数据和 (2) 元数据,即文件系统为访问、加载、恢复和搜索数据或满足其他特定需求所需的管理结构。典型的 ext4 镜像的磁盘布局如图 1 所示。然而,元数据的大小仅占镜像大小的 1% ,而有效镜像的最小大小可达 100 MB(见表 1),并且启用某些功能后会进一步增大。将磁盘镜像作为模糊测试输入存在以下问题:
- 镜像的大尺寸导致输入空间呈指数增长,同时元数据的变异频率低。
- 模糊测试需要频繁对输入文件进行读写操作。例如,在模糊测试磁盘镜像时,需要反复在变异前后读取和写入镜像,必要时还需要保存镜像。这导致大镜像尺寸带来的文件操作性能开销。
- 为检测元数据损坏,某些文件系统(如 XFS v5、GFS2、F2FS 等)引入校验和保护磁盘上的元数据。在初始化期间,内核会拒绝带有无效校验和的变异元数据块的镜像。
磁盘镜像模糊测试器通过强制文件系统挂载变异的磁盘镜像并执行一系列文件操作来触发文件系统特定的漏洞。早期模糊测试器采用低效的方法,在有效镜像的随机偏移处变异字节生成新镜像,或仅变异元数据块中的字节。这些方法由于需要加载和保存整个镜像而导致巨大的磁盘 I/O 开销。此外,这些盲目模糊测试技术生成的镜像质量较差,未利用过往的代码覆盖信息。
为了解决上述问题,近期的模糊测试器开始采用代码覆盖驱动的方法,并从种子镜像中提取所有非零块进行变异。这种方法触及了大多数元数据块,并通过减少输入大小提高了模糊测试性能。然而,这种方法仍存在以下缺陷:
- 非零块不仅包含非零数据块,还丢弃了零初始化的元数据块,从而导致文件系统模糊测试效果不佳。
- 元数据块未被精确定位,因此校验和无法正确修复。
# File Operation Fuzzer
由于文件系统是操作系统(OS)的一部分,模糊测试的一种通用方法是调用一组系统调用。尽管将这些模糊测试器移植到文件系统操作的目标上较为简单,但它们在有效性上存在两大问题:
- 未考虑镜像与文件操作的动态依赖
文件操作仅修改镜像上存在的文件对象(如目录、符号链接等),每个完成的操作只影响特定对象。然而,现有的操作系统模糊测试器未考虑镜像与文件操作之间的动态依赖,盲目生成系统调用,导致文件系统探索不充分。例如,先进的 OS 模糊测试工具 Syzkaller 根据描述每个目标系统调用的参数和返回值数据类型的静态语法规则生成系统调用。尽管它能够生成单一语义正确的系统调用,但无法探索一组系统调用的集体行为及其对文件系统镜像的修改。例如,Syzkaller 可能对已被重命名(rename()
)或删除(unlink()
)的路径重复发出多个open()
调用。 - 使用老化的 OS 和文件系统
为了性能考虑,现有的操作系统模糊测试器大多使用虚拟化实例(如 KVM、QEMU 等)运行目标操作系统,而不是为每个测试输入重新加载一份全新的操作系统或文件系统。然而,这种方式有以下问题:- 老化的操作系统在处理大量系统调用后执行变得非确定性。例如,依赖于先前分配的
kmalloc()
跨运行行为不同。有时,内核组件(如日志系统)会在长时间模糊测试中静默失效并从操作系统中分离,而不会触发文件系统崩溃。 - 这些模糊测试器发现的漏洞往往累积了成千上万次系统调用的影响,这会阻碍开发人员生成稳定的漏洞复现用例并进行调试。
- 老化的操作系统在处理大量系统调用后执行变得非确定性。例如,依赖于先前分配的
# File System Fuzzer
如前所述,大多数模糊测试器要么模糊测试二进制输入,要么使用一系列系统调用模糊测试操作系统。然而,要模糊测试文件系统,我们需要同时变异两种输入:(1) 二进制镜像(即文件系统镜像);(2) 相应的工作负载(即一组特定于文件系统的系统调用)。
遗憾的是,将这两种现有的模糊测试技术结合起来并非易事。近期,Syzkaller 尝试通过变异镜像中的非零块,同时独立生成与上下文无关的工作负载来测试变异后的镜像。然而,这种方法仍然不够健全且效率低下。
# Challenges of Fuzzing a File System
我们总结了在 Linux 内核中对文件系统进行模糊测试的一系列挑战,并提出了设计 JANUS 来克服这些挑战的见解。值得注意的是,这些见解同样适用于其他操作系统。
处理大型磁盘镜像作为输入。一个优秀的镜像模糊测试工具需要通过以下方法有效处理复杂且巨大的磁盘镜像:
- 变异散布在镜像中的元数据,同时处理校验和问题;
- 减少因输入操作引发的频繁磁盘 I/O。
遗憾的是,当前的模糊测试工具无法同时解决这两个问题。理想的镜像模糊测试工具应专注于元数据部分,而非整个磁盘镜像,并且在变异任何元数据结构后必须修复对应的校验和。
缺乏上下文感知的工作负载。文件系统相关的工作负载会直接影响镜像内容。例如,某些有效的文件操作会在运行时修改镜像上的文件对象(例如 open()
创建新文件, unlink()
删除现有文件的链接)。然而,现有的模糊测试工具依赖预定义的镜像信息(如种子镜像中的有效文件和目录路径)来生成系统调用,因此无法在运行时全面测试目标文件系统中的所有可访问文件对象。因此,更好的方法是在执行文件操作后维护镜像上每个文件对象的运行时状态,以便生成新的操作。
探索二维输入空间。文件系统处理两种类型的输入:
- 磁盘镜像(二进制数据块)
- 文件操作(按顺序组织的操作)
这两种输入的格式完全不同,但存在隐式关联。为了充分探索文件系统,模糊测试工具需要同时对这两类输入进行变异。然而,现有工具并不支持这一点。因此,我们希望提出一种混合方法,通过同时模糊测试镜像字节和文件操作来探索这两个维度。
复现发现的崩溃。传统的操作系统模糊测试工具通常使用虚拟化实例来测试操作系统功能。然而,为了避免重启虚拟机或恢复快照的高昂开销,它们会在多次运行中重复使用相同的操作系统或文件系统实例,这导致内核执行不稳定以及无法复现的错误。这个问题可以通过利用库操作系统(library OS)解决。库操作系统能够提供精确的操作系统行为,并在毫秒级时间内重新初始化操作系统状态,从而实现高效且可复现的崩溃检测。
# Design
# Overview
JANUS 是一种反馈驱动的模糊测试工具,旨在通过变异种子镜像的元数据,并生成上下文感知的文件操作(即系统调用),来全面探索文件系统。为了应对文件系统模糊测试中的挑战,JANUS 采用了以下设计策略:
- 专注于元数据变异
JANUS 仅存储从种子镜像中提取的元数据作为变异目标。元数据对于文件系统管理用户数据至关重要。JANUS 在变异后会重新计算每个元数据结构的校验和。由于元数据仅占镜像空间的 1%,测试用例的大小远小于完整磁盘镜像,从而显著提高了模糊测试的吞吐量。 - 动态生成文件操作
JANUS 不依赖于手动指定种子镜像中存储的文件信息,因为这些信息会随着时间失效,导致测试用例生成效果不佳。相反,JANUS 根据完成旧工作负载后推测出的镜像状态动态生成新的文件操作。 - 探索二维输入空间
文件系统的输入包括磁盘镜像和文件操作。JANUS 通过合理调度镜像模糊测试和文件操作模糊测试,探索这两种输入的可能性。考虑到原始镜像决定了文件系统的初始状态,并影响初始文件操作的执行,JANUS 首先对镜像字节进行变异。 - 使用库操作系统复现崩溃
JANUS 使用库操作系统(library OS)在用户空间测试内核功能。库操作系统实例以用户应用程序的形式运行,可以快速重新启动,开销极低,从而提高了发现崩溃后复现错误的机会。
图 2: JANUS 的概览:在每次模糊测试迭代中,JANUS 从其工作集加载一个测试用例,该测试用例由以下三部分组成:种子镜像的元数据、包含文件操作列表的程序,以及程序在运行时执行后推测出的镜像状态(❶)。接着,JANUS 的模糊引擎以两种方式对测试用例进行变异:
- 镜像变异:镜像变异器随机变异元数据,模糊引擎输出变异后的元数据,同时保持程序不变,用于测试(➋)。
- 系统调用模糊:系统调用模糊器对程序中的现有系统调用进行变异或附加新的系统调用,并根据工作负载的变化更新镜像状态(②)。在这种情况下,模糊引擎输出不变的元数据和新生成的程序。
然后,JANUS 将输出的元数据释放到一个完整镜像中(❸),并将完整镜像与输出程序一起交给基于库操作系统的执行器(③)。执行器挂载镜像并执行程序,其执行轨迹被记录到一个共享给 JANUS 模糊引擎的位图中(➍)。
如果发现新的代码路径,输出的元数据、程序以及更新后的镜像状态将被打包成新的测试用例,并保存到工作集中,以供后续变异使用(➎)。
图 2 展示了 JANUS 的详细设计。JANUS 的二进制输入包含以下三部分:
- 元数据块:从种子镜像中提取的二进制数据。
- 序列化程序:描述一系列系统调用的文件系统工作负载。
- 推测的镜像状态:程序运行后镜像的状态。
在初始化阶段,JANUS 使用文件系统特定的解析器从种子镜像中提取元数据,同时检查镜像状态并生成初始程序。这些元数据、镜像状态和程序被打包成测试用例,存入 JANUS 的工作集。JANUS 通过从工作集中选择测试用例(❶),在无限循环中使用镜像变异器和系统调用模糊器探索二维输入空间:
- 镜像变异器:对元数据块进行多种方式的字节翻转,生成变异后的元数据(➋)。程序部分保持不变。
- 系统调用模糊器:变异现有程序中的系统调用参数,或向程序中添加新的系统调用,同时根据新生成的程序推测新的镜像状态(②)。此时,元数据部分保持不变。
生成的变异元数据与用户数据合并后,JANUS 重新计算校验和,生成完整镜像(❸)。序列化后的程序也被保存到磁盘中(③)。JANUS 依赖库操作系统的用户空间执行器,加载完整镜像并执行磁盘中存储的程序(➍)。执行器的运行时路径覆盖被记录到一个共享的位图中。模糊引擎检查位图,当发现新的路径时,JANUS 将精简后的镜像、序列化程序和推测的镜像状态存储为新的二进制输入,用于后续运行中的进一步变异(➎)。请注意,对于每个测试用例,JANUS 总是先运行镜像变异器进行若干轮变异,如果没有发现有趣的测试用例,再调用系统调用模糊器。
我们将在 §4.2 中首先描述 JANUS 如何通过解析种子镜像生成初始测试用例。接着,在 §4.3 和 §4.4 中分别介绍它如何对镜像字节进行模糊测试以及生成文件操作。更重要的是,我们将在 §4.5 中说明 JANUS 如何集成两个核心模糊器。最后,在 §4.6 中展示我们为文件系统模糊测试设计的全新基于库操作系统的环境。
# Building Corpus
图 3:JANUS 如何在给定种子图像的情况下生成初始语料库的伪代码。
JANUS 通过其镜像解析器和系统调用模糊器,基于种子镜像构建初始语料库(见图 3)。语料库中测试用例的第一部分是种子镜像的关键元数据块,这些块仅占镜像总大小的约 1%,从而克服了上一章中提到的模糊测试磁盘镜像的挑战。具体来说,JANUS 首先将整个镜像映射到内存缓冲区中,然后使用文件系统特定的镜像解析器扫描镜像,并根据所使用文件系统的规范定位所有的磁盘元数据。接着,JANUS 将这些元数据重新组装为一个紧缩的数据块,供后续变异使用,并记录其大小和在镜像中的偏移量。对于受校验和保护的元数据结构,JANUS 还记录了由镜像解析器识别的校验和字段在元数据中的偏移位置。
其次,初始测试用例还包括镜像上每个文件和目录的信息,使得 JANUS 可以利用这些信息生成上下文感知的工作负载。具体而言,系统调用模糊器会探测种子镜像,并提取每个文件对象的路径、类型(如普通文件、目录、符号链接、FIFO 文件等)以及扩展属性,这些信息被打包进每个初始测试用例中。此外,每个初始测试用例还包含一个由系统调用模糊器生成的、带有独特系统调用的起始程序,供后续变异使用。为了扩大语料库的总体覆盖范围,每个随机生成的系统调用都会操作一个独特的文件对象(有关程序格式和系统调用生成的详细信息,请参见 §4.4 )。种子镜像的元数据、文件状态,以及起始程序共同组成了一个输入测试用例,这些测试用例由 JANUS 打包并存储到磁盘上的语料库中,供未来的模糊测试使用。
# Fuzzing Images
图 4:JANUS 如何随机变异元数据块的伪代码
JANUS 依赖于其镜像变异器对镜像进行模糊测试。具体来说,镜像变异器会加载测试用例的元数据块,并应用多种常见的模糊测试策略(如提到的位翻转、随机字节上的算术操作等)来随机变异元数据的字节,如图 4 所示。与现有的模糊测试工具类似,JANUS 优先使用一组特定的整数(即图 4 中的 "有趣值",例如 -1
、 0
、 INT_MAX
等)而非纯随机值来变异元数据。在我们的评估中,这些特殊值使镜像变异器能够生成更多边界情况,这些情况往往无法被文件系统正确处理(例如 表 2 中由 JANUS 发现的 Bug #1、#6、#14、#28、#33 等),同时还能生成更多极端情况,从而通过在运行时触发特定 Bug 增加内核崩溃的可能性(例如,大部分由 JANUS 发现的越界访问 Bug)。
表 2 列出了由 JANUS 在广泛使用的文件系统中发现的先前未知的漏洞,这些漏洞已在 Linux 内核版本 v4.16**、**v4.17 和 v4.18 中修复。我们仍在等待为部分已确认的漏洞分配 CVE 编号。出于安全考虑,我们未列出开发者尚未修复的另外 19 个漏洞。表格最右列 Conditions 指示了 JANUS 的哪些组件对发现这些漏洞起到了贡献。I 表示仅需挂载变异的镜像即可触发该漏洞;I+S 表示需要挂载变异的镜像并调用特定系统调用才能触发漏洞。
图 5:JANUS 如何将变异元数据块释放回全尺寸图像进行测试的伪代码。
在变异完整个元数据块后,JANUS 将变异后的每个元数据块拷贝回其对应位置的内存缓冲区中(如图 5 所示),该缓冲区存储着原始的全尺寸镜像。为了保持镜像的完整性,镜像解析器会根据目标文件系统采用的特定算法重新计算每个元数据块的校验和值,并将该值填充到校验和字段的记录偏移位置中。
# Fuzzing File Operations
图 6:Janus 如何随机变异现有系统调用并生成新的程序的伪代码。
系统调用模糊测试工具使 JANUS 能够生成与镜像相关的工作负载,从而有效地探索文件系统如何处理用户请求的各种文件操作。首先,我们介绍由系统调用模糊测试工具操作的程序(Program)结构。一个程序包含一组有序的系统调用(system call)列表,这些系统调用用于修改被变异的镜像,同时维护一个变量库,用于存储系统调用中使用的变量。JANUS 将每个系统调用描述为一个包含系统调用号、参数值和返回值的元组。如果参数值或返回值不是简单的常量,而是一个变量,JANUS 会将其表示为指向变量库中相应变量的索引。此外,程序还包括一个活动文件描述符列表,其中记录了已打开但尚未被程序关闭的文件描述符。
类似于现有的模糊测试工具(如 Syzkaller),系统调用模糊测试工具通过两种方式从输入程序生成新程序:(1) 系统调用变异。系统调用模糊测试工具随机选择程序中的一个系统调用,并生成一个新值列表,以替换随机选择的参数的旧值;(2) 系统调用生成。系统调用模糊测试工具向程序追加一个新的系统调用,该系统调用的参数值是随机生成的。特别地,JANUS 采用了与 Syzkaller 相同的策略来为系统调用的简单参数生成值。这些参数的候选值与推测的运行时状态无关。对于具有明确可用值集合的参数,JANUS 从集合中随机选择值(例如, lseek()
的 int whence
参数)。此外,对于整数类型的参数(例如, write()
的 size_t count
参数),JANUS 在某个范围内生成随机数。
进一步来说,许多文件操作需要一个指针类型的参数。此类指针通常指向一个缓冲区,用于存储用户数据(例如, write()
的 void *buf
参数)或内核输出(例如, read()
的 void *buf
参数)。对于前者,系统调用模糊测试工具声明一个填充了随机值的数组作为参数。对于后者,由于 JANUS 不受运行时内核输出(除了代码覆盖率)驱动,因此始终使用一个固定数组。
然而,对于那些合理值依赖于文件系统运行上下文的复杂参数,JANUS 不仅基于参数的预期类型生成值,更重要的是基于维护的状态,并主要遵循以下三条规则:
- 如果需要文件描述符,系统调用模糊测试工具会随机选择一个打开的、且类型合适的文件描述符。例如,
write()
需要一个普通文件的描述符,而getdents()
需要一个目录的文件描述符; - 如果需要路径,系统调用模糊测试工具会随机选择一个现有文件或目录的路径,或者最近操作中已删除的文件或目录的路径。例如,对于
rename()
,JANUS 提供普通文件或目录的路径,但对于rmdir()
,则只提供有效目录的路径。如果路径用于创建新文件或目录,JANUS 还可能随机生成一个位于现有目录下的新路径; - 如果系统调用操作某文件的现有扩展属性(例如,
getxattr()
和setxattr()
),系统调用模糊测试工具会随机选择该文件的记录扩展属性名称。
这些生成策略使得 JANUS 能够生成面向上下文的工作负载,对新鲜文件对象进行操作,从而避免运行时错误并实现高代码覆盖率。
对于新生成的系统调用,JANUS 会将其追加到程序中,并特别总结该系统调用可能对文件系统造成的变化,并相应地更新镜像的推测状态。例如, open()
、 mkdir()
、 link()
或 symlink()
可能会创建一个新文件或目录,而 open()
还会引入一个活动文件描述符; rmdir()
或 unlink()
会从镜像中移除一个文件或目录; rename()
会更新文件路径, setxattr()
或 removexattr()
会更新特定的扩展属性。
需要注意的是,在当前设计中,JANUS 仅在完成程序执行后维护推测的镜像状态。因此,JANUS 避免对可能导致镜像状态变化的现有参数进行变异。例如,JANUS 可能对程序中 write()
的文件描述符 ( fd
) 进行变异,但绝不会修改 unlink()
的路径参数 ( path
),因为这样的变异可能会使变异后系统调用的后续调用无效(例如,将 unlink("A")
修改为 unlink("B")
会影响测试用例中所有后续针对文件 B 的文件操作)。
# Exploring Two-Dimensional Input Space
图 7:JANUS 一次 fuzz 迭代的伪代码
为了同时对元数据和系统调用进行模糊测试,JANUS 按顺序调度其两个核心模糊测试工具。图 7 描述了 JANUS 的一次模糊测试迭代。具体来说,对于一个包含缩减镜像和程序的输入测试用例,JANUS 首先启动镜像变异器,在缩减镜像上变异随机字节。如果在未改变程序的情况下没有发现新的代码路径,JANUS 会调用系统调用模糊测试工具,在程序中变异现有系统调用的参数值,并进行若干轮的变异。如果仍然没有探索到新的代码路径,JANUS 最终会尝试向程序中追加新的系统调用。需要注意的是,每个模糊测试阶段的轮数是由用户定义的。
按照这种顺序调度镜像模糊测试和文件操作模糊测试的方式是有效的,原因如下:
- 提取的元数据表示镜像的初始状态,元数据对文件操作执行的影响会随着镜像经过若干次系统调用操作而逐渐减小。因此,JANUS 总是首先尝试变异元数据。
- 引入新的文件操作会指数级地增加程序的变异空间,并且可能会抹去过去操作对镜像的更改。因此,JANUS 更倾向于变异现有的系统调用,而不是生成新的系统调用。
# Library OS based Kernel Execution Environment
为了避免使用老化的操作系统或文件系统导致不稳定的执行和不可复现的错误,JANUS 依赖于基于库操作系统的应用程序(即执行器)来进行操作系统功能的模糊测试。具体来说,JANUS 会为每个从模糊测试引擎生成的新镜像和工作负载分叉一个新的执行器实例(➍)。需要注意的是,与重置虚拟机实例相比,分叉用户应用程序所需的时间几乎可以忽略不计。因此,JANUS 能够以低开销保证每个测试用例都有一个干净的内核环境。此外,由于模糊测试引擎和执行器都在同一台机器的用户空间中运行,它们之间共享输入文件和覆盖位图非常简单,而这对于在虚拟机外部运行模糊测试引擎的基于虚拟机的模糊测试工具来说是一个挑战。此外,基于库操作系统的实例相比任何类型的虚拟机所需的计算资源要少得多。因此,我们可以在大规模部署 JANUS 实例,而不会引起严重的资源竞争。
# Implementation
表 3:JANUS 的实现复杂性,包括对文件系统模糊测试的 AFL 和 LKL 的更改。由于我们直接重用 AFL 中现有的二进制变异算法作为图像变异器,因此我们省略了它的代码大小。
我们将 JANUS 实现为 AFL(版本 2.52b)的一个变种。JANUS 采用了 AFL 的基础架构,包括分叉服务器(forkserver)、覆盖位图以及测试用例调度算法。在此基础上,我们扩展了 AFL,加入了镜像变异器和系统调用模糊测试工具。此外,我们实现了一个镜像检查器,用于从种子镜像构建初始语料库,以及一个程序序列化器,用于在内存和工作语料库之间传递生成的程序。此外,我们基于 Linux 内核库(LKL)实现了一个执行器,用于测试新生成的镜像和工作负载。值得注意的是,我们还修改了 LKL 以支持内核地址消毒器(KASAN)[17],这是一种被操作系统模糊测试工具广泛采用的技术,用于检测内存错误。为了便于在真实环境中复现错误,我们还实现了一个概念验证(Proof-of-Concept, PoC)生成器,该生成器能够从序列化的测试用例生成一个完整镜像以及一个可编译的 C 程序。表 3 显示了 JANUS 各组件的代码行数(LoC)。本节将详细描述几个主要组件的实现细节。
镜像解析器和镜像变异器:我们将镜像解析器实现为一个动态库,用于定位元数据并识别种子镜像上的校验和。目前,该解析器支持解析 Linux 上八种广泛使用的文件系统的磁盘镜像,包括 ext4、XFS、Btrfs、F2FS、GFS2、HFS+、ReiserFS 和 VFAT。我们的镜像解析器实现参考了这些文件系统的用户态工具(如
mkfs
和fsck
)。我们还实现了镜像变异器,它通过八种策略随机变异缩减镜像上的字节(见图 4)。这些变异策略直接从 AFL 中移植到 JANUS 中。镜像检查器:我们为 JANUS 实现了一个镜像检查器,用于遍历种子镜像上的文件和目录,并记录它们在镜像中的路径、类型以及扩展属性,以便构建初始测试用例(详见 §4.2)。
图 8:用协议缓冲语言 (protocol buffer language) 描述的序列化程序格式和推测的图像状态。
程序序列化器:我们将新生成的程序及其更新的状态描述为一种可序列化的格式(见图 8),并实现了相应的程序序列化器。序列化器可以从磁盘加载这些数据到内存中进行模糊测试和验证,同时也可以将它们从内存保存到磁盘以便记录管理。
系统调用模糊测试工具:系统调用模糊测试工具被实现为 AFL 的一个新扩展,当镜像变异无法取得进展时由 JANUS 调用。系统调用模糊测试工具接收反序列化的程序及其对应的状态,并通过系统调用变异或系统调用生成输出新的程序和更新的状态(详见 §4.4)。目前,JANUS 支持生成和变异 34 个用于基础文件操作的系统调用。一些与文件操作相关但主要在虚拟文件系统(VFS)层实现的系统调用(如
dup()
、splice()
和tee()
等)由于测试价值不大被 JANUS 排除在外。在我们的实现中,JANUS 支持生成和变异以下 34 个系统调用:
read()
、write()
、open()
、seek()
、mmap()
、getdents64()
、pread64()
、pwrite64()
、stat()
、lstat()
、rename()
、fsync()
、fdatasync()
、syncfs()
、sendfile()
、access()
、ftruncate()
、truncate()
、fstat()
、statfs()
、fstatfs()
、utimes()
、mkdir()
、rmdir()
、link()
、unlink()
、symlink()
、readlink()
、chmod()
、fchmod()
、setxattr()
、fallocate()
、listxattr()
、removexattr()
。在我们的实现中,JANUS 基本上会对测试用例中的元数据进行 256 轮变异,这与 AFL 的混乱阶段(havoc stage, 即非确定性变异)的默认设置一致。如果代码覆盖未能增加,JANUS 会尝试对现有系统调用进行 128 轮变异,并追加新的系统调用进行 64 轮测试。由于镜像变异在探索二维输入空间时的优先级更高(详见 §4.5),JANUS 在镜像变异上投入了更多的精力。
基于 LKL 的执行器:我们为 JANUS 构建的执行器基于 Linux Kernel Library (LKL),LKL 是一种典型的库操作系统(Library OS),用于将内核接口暴露给用户态程序。图 11 展示了一个使用 LKL 系统调用操作 ext4 镜像的代码示例。官方的 LKL 目前支持 Linux 内核 v4.16,我们将其移植以兼容更新版本,包括 v4.17 和 v4.18。为了在运行时实现 AFL 风格的路径覆盖,我们实现了一个 GCC 包装器,在构建 LKL 时选择性地对目标文件系统的源代码文件进行插装。此外,我们实现了一个链接到 LKL 的用户应用程序(即执行器),作为 JANUS 的模糊测试目标。对于生成的测试用例,执行器通过 forkserver 创建一个新实例,并调用 LKL 系统调用来挂载由镜像变异器修改的镜像,同时执行由系统调用模糊测试工具生成的一系列文件操作。
由于每次将完整镜像写入磁盘会消耗大量时间,我们引入了一个持久内存缓冲区(persistent memory buffer),由 JANUS 的模糊测试引擎与基于 LKL 的执行器共享,用于存储镜像(即图 3 中的 ctx.image_buffer
)。随后,我们修改了 LKL 文件系统下层的块设备驱动程序,使其在获取镜像数据时访问内存缓冲区,而不是磁盘上的镜像文件。
我们在运行时应用了写时复制(CoW,Copy-on-Write)技术,以确保在生成的工作负载操作镜像缓冲区时,除了变异的块之外,镜像缓冲区中的其他部分永远不会发生变化。具体来说,当设备驱动程序试图在运行时将任意字节刷新回镜像上的某个块时,该块会被复制以供修改,后续的访问将针对复制后的块。
我们还将 KASAN 移植到 LKL,以在运行时有效检测内存错误。KASAN 在运行时分配影子内存(shadow memory),用于记录原始内存中的每个字节是否可以安全访问。需要注意的是,KASAN 依赖 MMU(内存管理单元)将地址翻译为对应的影子地址,但 LKL 不支持这一功能。为了解决这一问题,我们在 LKL 启动时保留了影子内存空间,并建立了从 LKL 的内存空间到影子内存的映射。
# Evaluation
详见论文 EVALUATION 章节。
# Discussion
我们已经证明,JANUS 能够有效地探索代码路径,并在 Linux 内核中的磁盘文件系统中发现未知漏洞。接下来,我们将讨论 JANUS 的局限性以及未来的发展方向。
基于库操作系统的执行器:JANUS 依赖于 LKL(Linux Kernel Library)来测试内核文件系统。事实上,其他操作系统模糊测试工具也可以利用 LKL 测试内核的其他子系统,除了依赖 MMU 的组件。例如,在不修改 LKL 的情况下,JANUS 无法对文件系统的 DAX 模式进行模糊测试。我们也可以使用用户模式 Linux(UML),如 Oracle 的内核模糊测试工具所采用的方式。然而,UML 的多进程设计存在限制,这会使得在每次迭代中定位内核崩溃并终止其所有进程变得复杂。因此,UML 并不适合将内核作为用户应用程序进行模糊测试。
最小化的 PoC 生成器:一个理想的 PoC(概念验证)应该包含一个仅包含关键错误字节的镜像文件,以及一个最少文件操作的程序。为此,JANUS 当前通过暴力方式尝试恢复每个被修改的字节,并逐一删除每个被调用的文件操作,以检查内核是否仍然会在预期位置崩溃。尽管这种方法并非最优,但我们可以利用某些文件系统工具(如
fsck
和debugfs
)以及系统调用跟踪提炼技术来定位导致问题的字节和系统调用。另一种可能性是对内核应用污点跟踪。模糊测试 FUSE 驱动程序:目前,JANUS 不支持依赖 FUSE(Filesystem in Userspace,用户态文件系统)的文件系统(例如 NTFS、GVfs、SSHFS 等)。只要这些文件系统将用户数据存储在磁盘镜像中,并支持用户与数据交互的某些文件操作,就可以轻松扩展 JANUS 的模糊测试引擎以测试这些文件系统。
模糊测试文件系统工具:开发者在很大程度上依赖系统工具(例如
mkfs
、fsck
等)来管理文件系统。例如,Linux 会自动启动fsck
以从系统突然崩溃中恢复磁盘数据。此外,用户会使用fsck
来检查未受信任磁盘镜像的一致性,然后再挂载该磁盘。因此,开发者希望这些工具没有漏洞。我们认为,开发者可以轻松扩展 JANUS 的镜像变异器来生成损坏的镜像,从而对这些工具进行模糊测试并提高其健壮性。实际上,我们使用 JANUS 找到了fsck.ext4
中的两个未知漏洞,其中一个已经被修复。扩展以测试其他操作系统上的文件系统:如果存在对应的库操作系统解决方案,扩展 JANUS 以测试其他操作系统上的内核文件系统将非常简单。例如,Drawbridge 使得 Windows 可以高效地以进程方式运行。此外,我们还可以将 JANUS 的核心模糊测试引擎与其他通用内核模糊测试框架(例如基于 QEMU 和 KVM 构建的 kAFL)集成,用于测试 Windows 和 macOS 等主流操作系统使用的文件系统。
改进其他文件系统测试工具:JANUS 的目标是发现文件系统中的通用安全漏洞,这与其他工具(如崩溃一致性检查器和语义正确性检查器)的目标不同。然而,这些工具也需要文件操作序列。因此,JANUS 可以成为一个综合解决方案,为其他工具提供支持。
# Related Work
结构化输入模糊测试
许多方法已被提出,用于模糊测试高度结构化的输入,例如文件系统镜像。与 JANUS 不同,一些生成式模糊测试工具([1, 2, 3, 4, 5])基于手动描述的输入规范,从零构建语法正确的输入。此外,EXE [6] 依赖符号执行生成满足深层路径约束的有效输入。一些更先进的方法(例如 [7, 8, 9])通过一组样本学习输入结构。另一方面,基于变异的模糊测试工具([10, 11, 12, 13, 14, 15, 16])通过变异有效样本生成新的输入。生成的输入具有正确的结构但包含轻微错误,从而有可能触发漏洞。
- I. Fratric. DOM fuzzer. https://github.com/googleprojectzero/domato, 2018.
- R. Hodován, Á. Kiss, and T. Gyimóthy. Grammarinator: a grammar-based open source fuzzer. In Proceedings of the 9th ACM SIGSOFT International Workshop on Automating TEST Case Design, Selection, and Evaluation, pages 45–48. ACM, 2018.
- Mozilla Corporation. MozPeach. https://github.com/MozillaSecurity/peach, 2017.
- Mozilla Corporation. JavaScript engine fuzzers. https://github.com/MozillaSecurity/funfuzz, 2018.
- Peach Tech. Peach Fuzzer. https://sourceforge.net/projects/peachfuzz, 2016.
- C. Cadar, V. Ganesh, P. M. Pawlowski, D. L. Dill, and D. R. Engler. EXE: automatically generating inputs of death. ACM Transactions on Information and System Security (TISSEC), 12(2):10, 2008.
- O. Bastani, R. Sharma, A. Aiken, and P. Liang. Synthesizing program input grammars. In ACM SIGPLAN Notices, pages 95–110. ACM, 2017.
- P. Godefroid, H. Peleg, and R. Singh. Learn&fuzz: Machine learning for input fuzzing. In Proceedings of the 32nd IEEE/ACM International Conference on Automated Software Engineering (ASE), Champaign, IL, Oct. 2017.
- M. Höschele and A. Zeller. Mining input grammars from dynamic taints. In Proceedings of the 32nd IEEE/ACM International Conference on Automated Software Engineering (ASE), Champaign, IL, Oct. 2017.
- M. Böhme, V.-T. Pham, and A. Roychoudhury. Coverage-based greybox fuzzing as markov chain. In Proceedings of the 23rd ACM Conference on Computer and Communications Security (CCS), Vienna, Austria, Oct. 2016.
- M. Böhme, V.-T. Pham, M.-D. Nguyen, and A. Roychoudhury. Directed greybox fuzzing. In Proceedings of the 24th ACM Conference on Computer and Communications Security (CCS), Dallas, TX, Oct.–Nov. 2017.
- P. Chen and H. Chen. Angora: Efficient Fuzzing by Principled Search. In Proceedings of the 39th IEEE Symposium on Security and Privacy (Oakland), San Francisco, CA, May 2018.
- S. Gan, C. Zhang, X. Qin, X. Tu, K. Li, Z. Pei, and Z. Chen. CollAFL: Path Sensitive Fuzzing. In Proceedings of the 39th IEEE Symposium on Security and Privacy (Oakland), San Francisco, CA, May 2018.
- Google. OSS-Fuzz - Continuous Fuzzing for Open Source Software. https://github.com/google/oss-fuzz, 2018.
- LLVM Project. libFuzzer - a library for coverage-guided fuzz testing. https://llvm.org/docs/LibFuzzer.html, 2018.
- M. Zalewski. american fuzzy lop (2.52b). http://lcamtuf.coredump.cx/afl, 2018.
考虑到文件系统镜像的复杂性以及不同文件系统间镜像格式的多样性,JANUS 采用基于变异的策略对镜像进行模糊测试。类似文件系统镜像,许多文件格式通过校验和进行完整性检查。JANUS 利用专业知识修复元数据校验和。然而,一些支持校验和的模糊测试工具([17, 18])通过动态污点分析识别校验和字段,并在运行时绕过校验和检查。
X. Liu, Q. Wei, Q. Wang, Z. Zhao, and Z. Yin. CAFA: A Checksum-Aware Fuzzing Assistant Tool for Coverage Improvement. Security and Communication Networks, 2018, 2018.
T. Wang, T. Wei, G. Gu, and W. Zou. TaintScope: A checksum-aware directed fuzzing tool for automatic software vulnerability detection. In Proceedings of the 31th IEEE Symposium on Security and Privacy (Oakland), Oakland, CA, May 2010.
操作系统内核模糊测试工具
为发现操作系统中的安全漏洞,已有许多通用的内核模糊测试框架([19, 20, 21, 22])以及特定操作系统的内核模糊测试工具([23, 24, 25, 26, 27])。与 JANUS 不同,这些工具基于预定义的语法规则生成随机系统调用,但这种方法在文件系统模糊测试中效率较低。一些最新的操作系统模糊测试工具,如 IMF [23] 和 MoonShine [28],关注种子精简,这与本工作无直接冲突。然而,JANUS 可以利用这些方法生成高质量的种子程序作为起点。
- Google. syzkaller is an unsupervised, coverage-guided kernel fuzzer. https://github.com/google/syzkaller, 2018.
- MWR Labs. Cross Platform Kernel Fuzzer Framework. https://github.com/mwrlabs/KernelFuzzer, 2016.
- NCC Group. AFL/QEMU fuzzing with full-system emulation. https://github.com/nccgroup/TriforceAFL, 2017.
- S. Schumilo, C. Aschermann, R. Gawlik, S. Schinzel, and T. Holz. kafl: Hardware-assisted feedback fuzzing for OS kernels. In Proceedings of the 26th USENIX Security Symposium (Security), Vancouver, BC, Canada, Aug. 2017.
- H. Han and S. K. Cha. IMF: Inferred Model-based Fuzzer. In Proceedings of the 24th ACM Conference on Computer and Communications Security (CCS), Dallas, TX, Oct.–Nov. 2017.
- D. Jones. Linux system call fuzzer. https://github.com/kernelslacker/trinity, 2018.
- MWR Labs. macOS Kernel Fuzzer. https://github.com/mwrlabs/OSXFuzz, 2017.
- NCC Group. System call fuzzing of OpenBSD amd64 using TriforceAFL. https://github.com/nccgroup/TriforceOpenBSDFuzzer, 2016.
- NCC Group. A linux system call fuzzer using TriforceAFL. https://github.com/nccgroup/TriforceLinuxSyscallFuzzer, 2017.
- S. Pailoor, A. Aday, and S. Jana. MoonShine: Optimizing OS Fuzzer Seed Selection with Trace Distillation. In Proceedings of the 27th USENIX Security Symposium (Security), Baltimore, MD, Aug. 2018.
文件系统语义正确性检查工具
JUXTA [29] 和 SibylFS [30] 是另一类文件系统检查工具,旨在通过静态分析和高层次建模文件系统行为,验证文件系统的实现是否完全符合标准(例如 POSIX 标准、手册页等)。它们的目标和方法与 JANUS 不同。然而,JANUS 可以生成有意义的系统调用来发现崩溃一致性问题 [31, 32]。
- C. Min, S. Kashyap, B. Lee, C. Song, and T. Kim. Cross-checking semantic correctness: The case of finding file system bugs. In Proceedings of the 25th ACM Symposium on Operating Systems Principles (SOSP), Monterey, CA, Oct. 2015.
- T. Ridge, D. Sheets, T. Tuerk, A. Giugliano, A. Madhavapeddy, and P. Sewell. SibylFS: formal specification and oracle-based testing for POSIX and real-world file systems. In Proceedings of the 25th ACM Symposium on Operating Systems Principles (SOSP), Monterey, CA, Oct. 2015.
- J. Bornholt, A. Kaufmann, J. Li, A. Krishnamurthy, E. Torlak, and X. Wang. Specifying and checking file system crash-consistency models. In Proceedings of the 21st ACM International Conference on Architectural Support for Programming Languages and Operating Systems (ASPLOS), Atlanta, GA, Apr. 2016.
- J. Yang, C. Sar, and D. Engler. Explode: a lightweight, general system for finding serious storage system errors. In Proceedings of the 7th USENIX Symposium on Operating Systems Design and Implementation (OSDI), Seattle, WA, Nov. 2006.
文件系统抽象
一些研究([33, 34])提出了通用接口,使文件系统工具能够通过高级抽象访问和操作各种文件系统的磁盘元数据。通过利用这些接口,JANUS 可以以更通用的方式压缩磁盘镜像,而无需为每个目标文件系统实现特定的镜像解析器。
- K. Sun, D. Fryer, J. Chu, M. Lakier, A. D. Brown, and A. Goel. Spiffy: enabling file-system aware storage applications. In Proceedings of the 16th USENIX Conference on File and Storage Technologies (FAST), Oakland, CA, Feb. 2018.
- K. Sun, M. Lakier, A. D. Brown, and A. Goel. Breaking Apart the VFS for Managing File Systems. In Proceedings of the 10th USENIX Workshop on Hot Topics in Storage and File Systems, Boston, MA, July
2018.
# Conclusion
在这项工作中,我们提出了 JANUS,一种进化式文件系统模糊测试工具,它通过探索二维输入空间(即镜像和文件操作)来测试内核文件系统。与现有的文件系统模糊测试工具不同,JANUS 能高效变异输入镜像的元数据块,同时在镜像上生成上下文感知的工作负载。不同于传统的虚拟机,JANUS 依赖支持快速重加载的库操作系统来测试操作系统功能,从而避免了不稳定的执行和无法重现的漏洞。
我们向上游内核报告了 JANUS 发现的 90 个漏洞,其中 43 个已被修复,并分配了 32 个 CVE。在对流行文件系统进行 12 小时模糊测试时,JANUS 最多比 Syzkaller 探索了 **4.19 倍 ** 的代码路径,并成功复现了已发现崩溃的 88%-100%。我们计划开源 JANUS 的实现,由于我们的显著成果,已有多个文件系统开发社区提出了开源需求。我们相信,JANUS 将成为文件系统测试的 “一站式解决方案”,因为它可以作为基础设施,用于设计新的文件系统语义检查器和崩溃一致性检查工具。