首发于论文阅读

论文 SnapFuzz:An Efficient Fuzzing Framework for Network Applications

原文在这里:

近年来,受益于计算能力的提高和重要的算法进步,出现了在生产软件中发现了许多关键错误和漏洞的模糊测试系统。尽管取得了这些成功,但并非所有应用程序都可以有效地进行模糊测试。特别是,诸如网络协议实现之类的有状态应用程序受到低效模糊测试的限制,并且需要开发重置其状态并隔离其副作用的模糊工具(to develop fuzzing harnesses that reset their state and isolate their side effects?)。

在本文中,我们将介绍 SnapFuzz,一种用于网络应用程序的新型模糊测试框架。 SnapFuzz 提供了健壮的架构,可以将慢速异步网络通信转换为快速同步通信,在最近的安全点对测试目标进行快照,通过将所有文件操作重定向到自定义内存文件系统来加速所有文件操作,并删除需要进行许多脆弱的修改,例如配置时间延迟或编写清理脚本,以及其他一些改进。

使用 SnapFuzz,我们对五个流行的网络应用程序进行了模糊测试:LightFTP、TinyDTLS、Dnsmasq、LIVE555 和 Dcmqrscp。得到了性能加速分别为 62.8x、41.2x、30.6x、24.6x 和 8.4x,在所有情况下都具有明显更简单的模糊测试程序(harness)。通过其性能优势,SnapFuzz 在这些应用程序中比 AFLNet 还多发现了 12 次崩溃。

1 简介

Fuzzing 是一种测试软件系统的有效技术,AFL 和 LibFuzzer 等流行的 fuzzer 已经发现了开源和商业软件中的数千个错误。例如,谷歌在其产品中发现了超过 25,000 个错误,并在使用灰盒模糊测试 [1] 的开源代码中发现了超过 22,000 个错误。

不幸的是,并非所有软件都能从这样的模糊测试中受益。一类重要的软件,即网络协议实现,很难进行模糊测试。主要有两个困难:对此类应用程序进行深入测试需要了解它们实现的网络协议(例如 FTP、DICOM、SIP),以及它们具有副作用的事实,例如将数据写入文件系统或通过网络交换消息。

有两种主要方法可以较为有效地测试此类软件。 Google 的 OSS-Fuzz 采用的一种方法是编写单元级测试驱动程序,通过其 API [26] 与软件交互。虽然这种方法可能很有效,但它需要大量的人工工作,并且不会执行系统级测试,系统级测试中服务器实例需要与实际客户端实际交互。

AFLNet [31]是第二种方法,通过启动实际的服务器和客户端进程,并在它们之间生成随机消息交换来执行系统级测试,这些交换仍然遵循底层网络协议。此外,它不需要协议规范,而是通过使用服务器和客户端之间的真实消息交换的语料库来实现。AFLNet 的方法具有显着的优势,需要较少的手动工作并在协议级别执行端到端测试。

虽然 AFLNet 在模糊网络协议方面取得了重要进展,但它有两个主要限制。首先,它需要用户添加或配置各种时间延迟以确保遵循协议,并编写清理脚本以在模糊测试的迭代中重置状态。其次,由于异步网络通信、各种时间延迟和花费很多时间的文件系统操作等原因,它的模糊测试性能较差。

SnapFuzz 通过一个健壮的架构解决了这两个挑战,该架构将慢速的异步网络通信转换为快速同步通信,通过内存文件系统加快了文件操作并消除了清理脚本的需要,并改进其他方面,例如延迟和自动化forkserver 放置,正确处理信号传播并消除开发人员添加的延迟。

这些改进显着简化了网络应用程序的模糊测试工具的构建,在测试五个流行的服务器基准测试时,显着提高了 8.4 倍到 62.8 倍(平均值:24.6 倍)的模糊测试吞吐量。

2 从 AFL 到 AFLNET 到 SNAPFUZZ

在本节中,我们首先讨论 AFL 和 AFLNet 如何工作,重点关注它们的内部架构和性能影响,然后概述 SnapFuzz 的架构和主要贡献。

2.1 American Fuzzy Lop (AFL)

AFL [29] 是一种灰盒模糊器,它使用有效的覆盖引导遗传算法。 AFL 使用一种改进的边覆盖形式来有效地识别出能够改变目标应用程序控制流的有效输入。

简而言之,AFL 首先将用户提供的初始种子输入加载到队列中,选择一个输入,然后使用各种策略对其进行变异。如果变异的输入覆盖了新状态,则将其添加到队列中并重复循环。

在系统级别,AFL 最简单的模式(称为哑模式)是通过首先fork然后通过 execve 创建一个新进程从头开始重新启动目标应用程序。当这种情况发生时,启动进程的标准事件序列正在发生,操作系统加载程序首先将目标应用程序及其库初始化到内存中。 AFL 然后通过通常指向实际文件或标准输入的文件描述符,将模糊过的输入发送给新进程。最后,AFL 等待目标程序终止,但如果超过了预定义的超时,则终止它。对于 AFL 想要提供给目标应用程序的每个输入,重复这些步骤。

AFL 的哑模式相当慢,因为为每个生成的输入加载和初始化目标及其库(例如 libc)花费了太多时间。理想情况下,应用程序将在所有这些初始化步骤完成后重新启动,因为它们与 AFL 提供的输入无关。这正是 AFL 的 forkserver 模式所提供的,如图 1 所示。

在这种模式下,AFL 首先创建一个称为 forkserver 的子服务器(图 1 中的步骤 1),它通过 execve 加载目标应用程序,并在 main 函数即将启动之前将其冻结。

然后,在每次 fuzzing 迭代中,循环执行以下步骤:AFL 从 forkserver 请求一个新的目标实例(步骤 2),forkserver 创建一个新实例(步骤 3),AFL 将模糊输入发送到这个新实例(第 4 步),forkserver 检查目标实例是否崩溃(第 5 步)。

使用这种 forkserver 快照机制,AFL 用消耗更小的 fork 调用代替了加载开销,同时保证应用程序对于 AFL 新生成的每个输入都处于其初始状态。在最新版本的 AFL 中,这是作为 LLVM pass 实现的,但也可以使用其他不需要访问源代码的方法。

AFL 提供的另一项优化是延迟分叉服务器模式(defferred fork-server mode)。在这种模式下,用户可以在目标源代码中手动添加对 AFL 内部函数的特殊调用,以指示它在目标应用程序执行的后期创建 forkserver。在目标应用程序需要执行较长的初始化阶段才能使用 AFL 输入的常见情况下,这可以提供显着的性能优势。不幸的是,这种模式要求用户不仅可以访问目标应用程序的源代码,还需要了解目标应用程序的内部结构,以便将延迟调用置于正确的执行阶段。正如我们将在第 3.4 节中解释的那样,forkserver 放置有几个限制(例如,它不能在文件描述符创建后放置),如果违反这些限制,fuzzing 活动可能会浪费大量时间来探索无效执行。

2.2 AFLNet

AFL 主要针对经由文件接收输入的应用程序(stdin是一种特殊的文件类型)。这意味着它不能直接应用于网络应用程序,因为网络应用程序通过网络套接字接收输入,并且输入遵循底层网络协议。 AFLNet [31] 扩展了 AFL 以与网络应用程序一起工作。它最重要的贡献是它提出了一种新算法,关于如何生成遵循底层网络协议(例如 FTP、DNS 或 SIP 协议)的输入。更具体地说,AFLNet 通过客户端和服务器之间记录的消息交换示例来推断底层协议。

AFLNet 还通过构建所需的基础结构(infrastructure)来扩展 AFL,以通过网络套接字将生成的输入引导到目标应用程序,如图 2 所示。更准确地说,从系统的角度来看,AFLNet 充当客户端应用程序。在待进行模糊测试的服务器初始化的可配置延迟之后,它通过 TCP/IP 或 UDP/IP 套接字将输入发送到服务器,在这些交互之间具有可配置的延迟(我们在 §3.2 中描述了 AFLNet 所需的各种时间延迟)。 AFLNet 使用来自服务器的回复(否则服务器可能会阻塞)并在每次交换完成后向服务器发送 SIGTERM 信号,因为通常网络应用程序在无限循环中运行。

如图 2 所示,AFLNet 的架构类似于 AFL 的延迟分叉服务器模式,不同之处在于通信是通过网络而不是通过文件进行的。

像数据库或 FTP 服务器这样的网络应用程序通常是有状态的,通过将信息存储到各种文件来跟踪它们的状态。这可能会在 fuzzing 活动期间产生问题,因为当 AFLNet 重新启动应用程序时,其状态可能会被先前执行的信息污染。为了避免这个问题,AFLNet 要求用户编写自定义清理脚本,调用这些脚本来重置任何文件系统状态。

我们使用术语 fuzzing 工具来指代用户为了能够对应用程序进行模糊测试而需要编写的所有代码。在 AFLNet 中,这包括客户端代码、需要手动添加的各种时间延迟以及清理脚本。SnapFuzz 的一个重要目标是简化网络应用程序模糊harness的创建。

2.3 Snapfuzz

SnapFuzz 建立在 AFLNet 之上,通过改进其网络通信架构,如图 3 所示,无需对 AFLNet 的模糊算法进行任何修改。SnapFuzz 的主要目标是(1)提高模糊网络应用程序的性能(吞吐量),以及(2)通过简化模糊测试harness的构造来降低测试网络应用程序的障碍,特别是不用添加手动指定的时间延迟和编写清理脚本。同时,SnapFuzz 并没有以任何方式改进 AFL 和 AFLNet 的模糊算法或变异策略。在高层次上,SnapFuzz 通过以下方式实现了显着的性能提升: 通过消除同步延迟(SnapFuzz 协议)优化所有网络通信;自动将 AFL 的 forkserver 注入到应用程序中,而不需要用户干预(智能延迟 forkserver);执行启用二进制重写的优化,消除额外的延迟和低效率;自动重置任何文件系统状态;并通过将文件系统写入重定向到内存文件系统来优化文件系统写入。

SnapFuzz 还通过完全消除手动代码修改的需要,使模糊harness开发更容易,特定情况下会非常简单。这种手动更改通常需要:在每次模糊测试迭代后重置目标或其环境的状态;终止目标,因为服务器通常在无限循环中运行;为线程和进程固定 CPU;并向目标添加延迟 forkserver 支持。

图 3 显示了 SnapFuzz 的架构。虽然在高层次上它类似于 AFLNet,但有几个重要的变化。首先,SnapFuzz 使用二进制重写(第 3.1 节)拦截目标应用程序的外部操作。然后,它监控目标应用程序和 AFLNet 客户端的行为,以使用其 SnapFuzz 协议(第 3.2 节)消除同步延迟。其次,添加了一个自定义的内存文件系统,以提高性能并便于在每次 fuzzing 迭代后重置状态(第 3.3 节)。第三,forkserver 被智能延迟 forkserver 取代,它可以自动化和优化 forkserver 放置(第 3.4 节)。我们将在下一节中详细描述 SnapFuzz 的主要组件。

3 设计

SnapFuzz 有两个主要目标:显着提高模糊测试吞吐量,并简化模糊测试harness的构建。在高层次上,SnapFuzz 通过二进制重写(第 3.1 节)拦截目标应用程序与其环境之间的所有通信来实现这些目标。通过控制这种通信,SnapFuzz 可以:

(1) 实现一个高效的网络模糊测试协议,当目标应用程序准备好接受新请求或响应准备好被使用时通知模糊测试器(§3.2)。这提高了 fuzzing 吞吐量,并消除了 AFLNet 用户需要插入的所有自定义延迟,以便同步 fuzzer 和目标应用程序之间的通信。 SnapFuzz 还将互联网套接字替换为 UNIX 域套接字(UNIX domain socket),从而提高了性能,并实现了高效的服务器终止策略。

(2) 重定向所有文件操作以使用内存文件系统(第 3.3 节)。这提高了文件系统操作的性能,并消除了对用户提供的清理脚本的需要,因为 SnapFuzz 可以通过简单地丢弃内存中的状态在每次模糊迭代后自动清理。

(3) 自动防止和延迟fockserver(“智能延迟分叉服务器”)到最新的安全点(第 3.4 节)。这提高了性能并消除了手动注释的需要。

(4) 消除作为目标应用程序一部分的自定义延迟、不必要的系统调用和可能昂贵的清理例程,正确传播来自子进程的信号,并更好地控制 CPU 亲和性(§3.5)。

3.1 二进制重写

SnapFuzz 实现了一个加载时二进制重写子系统,该子系统动态拦截 OS 加载器和目标的功能,以监控和修改目标应用程序的所有外部行为。

应用程序通过系统调用与外部世界交互,例如 Linux 中的 read() 和 write()。作为优化,Linux 通过 vDSO(虚拟动态共享对象)调用提供了一些服务。 vDSO 本质上是一个由内核在每个应用程序中注入的小型共享库,以提供对某些服务的快速访问。例如,gettimeofday() 通常在 Linux 上使用 vDSO 调用。

SnapFuzz 的二进制重写组件的主要目标是拦截被模糊测试的应用程序发出的所有系统调用和 vDSO 调用,并将它们重定向到系统调用处理程序(system call handler)。§4.1 介绍了实现细节。

通过以这种粒度级别拦截目标应用程序与其外部环境的交互,SnapFuzz 可以显着提高模糊测试吞吐量并消除对自定义延迟和脚本的需求,正如我们在下一小节中讨论的那样。

3.2 SnapFuzz 网络模糊测试协议:消除通信延迟

网络应用程序通常实施多步协议,每个会话具有多个请求和回复。 AFLNet 的主要贡献之一是从一组记录的消息交换开始推断网络协议。然而,AFLNet 不能保证在某个模糊测试迭代期间,目标确实会遵守协议。例如,由于推断出部分不正确的协议、目标应用程序中的错误,或者最常见的原因是目标未准备好发送或接收特定消息,因此可能会出现偏差。

因此,AFLNet 执行多项检查并添加多个用户指定的延迟,以确保通信与协议同步。这些通信延迟会显着降低模糊测试的吞吐量,它们是:

(1) 在 AFLNet 尝试通信之前允许服务器初始化造成的延迟。

(2) 指定在断定没有响应即将到来之前等待多长时间,然后再尝试发送更多信息的延迟,

以及 (3) 指定在发送或接收每个数据包之后等待多长时间的延迟。

这些延迟是必要的,否则操作系统内核将拒绝在目标未准备好时过快的数据包,并且 AFLNet 将与其状态机不同步。但是会浪费很多时间,本质上是因为 AFLNet 不知道目标是否准备好发送或接收信息。

SnapFuzz 通过简单但有效的网络模糊测试协议克服了这一挑战。该协议跟踪目标的下一个动作,并通知 AFLNet。图 4 显示了 SnapFuzz 和 AFLNet 在每个 recv(用于接收数据)和 send(用于发送数据)系统调用上交换的消息。本质上,为了避免上述通信延迟的需要,SnapFuzz 会在目标即将发出接收或发送时通知 AFLNet。这是通过引入一个额外的控制套接字(通过高效的 UNIX 域套接字实现)来执行的,该套接字用作从 SnapFuzz 到 AFLNet 的仅发送通道。

SnapFuzz 网络模糊测试协议还实现了以下两个优化:

UNIX 域套接字。 AFLNet 用于与目标通信并向其发送模糊输入的标准 Internet 套接字(TPC/IP 和 UDP/IP)非常慢。正如之前所观察到的 [38],用 UNIX 域套接字替换它们可以显着提高性能。我们将在 §4.3 中讨论如何实现这一点。

高效的服务器端接。网络服务器通常循环运行。该循环通过特殊的协议特定关键字或 OS 信号终止。由于 AFLNet 不能保证每次 fuzzing 迭代都会通过终止关键字完成,如果目标没有终止,它会向它发送一个 SIGTERM 信号并等待它终止。信号传递很慢,而且服务器可能需要很长时间才能正确终止执行。在 fuzzing 的上下文中,正确的终止不是那么重要,而 fuzzing 吞吐量很重要。于是SnapFuzz 实现了一个简单的机制来终止服务器:当它接收到一个空字符串时,它会推断出 fuzzer 没有更多的输入可以提供,并且应用程序会立即被终止。这显然有一个缺点,它可能会错过终止例程中的错误,但可以单独测试这些错误。

总之,SnapFuzz 网络模糊测试协议提高了模糊测试性能(是显着提高,如评估所示)并不再手动指定三种不同通信延迟,因此简化了模糊测试harness的构造。

3.3 高效状态复位

AFLNet 用户通常必须编写清理脚本以在每次 fuzzing 迭代后重置应用程序状态。例如,AFLNet 测试 LightFTP 时,需要一个脚本来清理之前迭代中创建的所有目录或文件。在 SnapFuzz 下,不需要这样的清理脚本,它简化了测试工具的构建,并通过避免调用清理脚本来提高性能。

SnapFuzz 通过采用内存文件系统解决了这一挑战。众所周知,在 UNIX 下使用内存文件系统 tmpfs 可以极大地优化fuzzing 上下文。

SnapFuzz 使用内存文件系统既是为了提高效率,也是为了消除对涉及文件系统状态的清理脚本的需要。但是,我们没有使用 tmpfs,而是一个自定义的内存文件系统,它使用 memfd_create 系统调用来处理文件,使用 Libsqlfs 库来处理目录(有关详细信息,请参阅第 4.2 节)。这允许我们在分叉后快速复制状态,如下所述。

在最简单的情况下,AFL 在 main 之前检查目标应用程序,在放置 forkserver 的位置没有发生文件系统修改。因此,当模糊测试迭代完成时,目标应用程序进程将退出,操作系统会丢弃其内存,其中包括在模糊测试期间所做的任何内存文件系统修改。然后,当 forkserver 生成目标应用程序的新实例时,文件系统将恢复到实际文件系统的所有初始文件都未修改的状态。

当目标应用程序已经创建了一些文件之后放置延迟的forkserver,情况会更加复杂。在我们基于 memfd_create 的实现中,当 forkserver 创建一个要进行模糊测试的新实例时,Linux 内核在新实例和 forkserver 之间共享与新创建的内存文件相关联的内存页面。请注意,使用 tmpfs 并不能解决这个问题——据我们所知,没有办法以写时复制的方式复制 tmpfs 文件系统。新实例和 forkserver 之间的这种页面共享是有问题的,因为现在模糊应用程序实例对内存中文件的任何修改都将持续存在,即使在实例完成执行后也是如此。所以在下一次迭代中,当 forkserver 创建一个新实例时,这个新实例也会继承这些修改。

SnapFuzz 以如下方式解决了这个问题。首先,请注意 SnapFuzz 知道应用程序是在 forkserver 的检查点之前还是之后执行的,因为它会拦截所有系统调用,包括 fork。虽然目标应用程序在 forkserver 的检查点之前执行,但 SnapFuzz 允许正常处理所有文件交互。当从 forkserver 请求一个新实例时,SnapFuzz 在新实例中重新创建在内存文件系统中注册的所有内存文件,并通过使用高效的 sendfile 系统调用对每个内存文件进行一次复制其所有内容。

3.4 智能延迟分叉服务器

如第 2.1 节所述,延迟forkserver可以通过避免目标中的初始化开销来提供巨大的性能优势。这些开销包括加载目标使用的共享库、解析配置文件和加密初始化例程。不幸的是,要使用forkserver,用户需要手动修改目标的源代码。此外,在目标创建线程、子进程、临时文件、网络套接字、offset-sensitive文件描述符或共享状态资源之后,不能使用延迟的 forkserver,因此用户必须仔细决定放置它的位置:做太早会错过优化机会,太晚会影响正确性。

SnapFuzz 对延迟forkserver进行了两个重要的改进:首先,它可以比 AFL 的架构通常更进一步地延迟它,其次,它可以自动完成,无需手动修改源代码。

SnapFuzz 能够在通常会导致问题的许多系统调用之后放置 forkserver ,起作用的两个组件是:(1)它的自定义网络模糊测试协议,允许它跳过网络设置调用,例如socket和accept(§3.2)和(2 ) 它的内存文件系统,它将文件系统操作转换为内存更改(第 3.3 节)。

通过二进制重写,SnapFuzz 拦截每个系统调用,并将 forkserver 放置在它遇到产生新线程(clone、fork)的系统调用或用于从客户端接收输入的系统调用之前。 SnapFuzz 仍然必须在应用程序生成新线程之前停止的原因是,forkserver 依赖 fork 来生成要进行模糊测试的新实例,而 fork 无法重建现有线程——在 Linux 中,fork 多线程应用程序会创建一个具有单个线程的进程线程 [4]。作为一种可能的缓解措施,我们尝试将 SnapFuzz 和 pthsem / GNU pth 库 [12] 结合起来——一个绿色线程库,提供基于非抢占优先级的调度,绿色线程在事件驱动框架内执行——但是性能开销太高。

特别是,我们将 pthsem 与 LightFTP 一起使用,因为该应用程序在接受输入之前必须执行两个clone系统调用。借助 pthsem 支持,SnapFuzz 的 forkserver 可以跳过这两个clone调用以及 37 个额外的系统调用,因为现在 SnapFuzz 可以在 LightFTP 准备好接受输入之前放置 forkserver。

然而,尽管有这个增益,但由于这个库的开销,整体性能比没有 pthsem 的 SnapFuzz 版本低 10%。理想情况下,SnapFuzz 应该实现一个轻量级的线程重建机制来重新创建所有死线程,但这留作未来的工作。

3.5 额外的二进制重写优化

在本节中,我们将讨论 SnapFuzz 执行的几项额外优化,这些优化是通过其基于二进制重写的架构实现的。它们涉及开发人员添加的延迟、对 stdout/stderr 的写入、信号传播和 CPU 亲和性,并强调了 SnapFuzz 方法在解决模糊网络应用程序时的各种挑战和低效率方面的多功能性。

3.5.1 消除开发人员添加的延迟。有时,网络应用程序会在轮询新连接或数据时添加休眠或超时,以避免 CPU 使用率过高。 SnapFuzz 通过二进制重写消除了这些延迟,使这些调用使用更积极的轮询模型。

我们还注意到,在某些情况下,应用程序开发人员故意选择添加睡眠以等待各种事件。例如,LightFTP 添加了一秒钟的睡眠,以等待其所有线程终止。这在生产环境中可能没问题,但在模糊测试活动中,这样的延迟是不必要且昂贵的。 SnapFuzz 通过拦截然后根本不发出这一系列系统调用来完全跳过此类睡眠。

3.5.2 避免 stdout/stderr 写入。默认情况下,AFL 将 stdout 和 stderr 重定向到 /dev/null。这比实际写入文件或任何其他介质的性能要高得多,因为内核会积极优化这些操作。 SnapFuzz 更进一步,通过完全跳过任何针对 stdout 或 stderr 的系统调用来节省额外的时间。

3.5.3 信号传播。一些应用程序使用多进程而不是多线程并发模型。在这种情况下,如果子进程因段错误而崩溃,则信号可能无法正确传播到 forkserver,从而导致崩溃丢失。我们在 Dcmqrscp 服务器(第 5.5 节)中偶然发现了这种情况,其中出现了一个有效的新错误,但 AFLNet 无法检测到该问题,因为 Dcmqrscp 的主进程从未检查其子进程的退出状态。

由于 SnapFuzz 完全控制了目标的系统调用,因此每当一个进程即将退出时,它也会检查其子进程的退出状态。如果检测到错误,则会将其提升到 forkserver。

3.5.4 智能亲和力。 AFL 假设它的目标是单线程的,因此试图将 fuzzer 和目标固定到两个空闲的 CPU。不幸的是,除了关闭 AFL 的 pinning 机制之外,没有任何机制可以处理多线程应用程序。SnapFuzz 可以检测到何时将生成新线程或进程,因为fork和clone系统调用都被拦截。这为 SnapFuzz 提供了通过将线程和进程固定到可用 CPU 来控制线程调度的机会。 SnapFuzz 实现了一个非常简单的算法,将每个新创建的线程或进程固定到下一个可用的 CPU。

4 实现

SnapFuzz 是在 AFLNet 之上实现的,目标是 Linux 平台。然而,SnapFuzz 中的想法可以使用其他模糊测试工具和操作系统来实现。下面,我们提供与二进制重写(第 3.1 节)、内存文件系统(第 4.2 节)和 UNIX 域套接字的使用(第 4.3 节)相关的实现细节。

4.1 二进制重写

SnapFuzz 中的二进制重写使用两个主要组件:

1) 重写器模块,它扫描特定函数、vDSO 和系统调用程序集操作码的代码,并将它们重定向到插件模块,以及 2) SnapFuzz 所在的插件模块。

重写器。 SnapFuzz 是一个普通的动态链接可执行文件,它提供了目标应用程序的路径以及调用它的参数。当 SnapFuzz 启动时,标准 Linux 操作系统的预期事件序列正在发生,第一步是动态加载程序,将 SnapFuzz 及其依赖项加载到内存中。

当 SnapFuzz 开始执行时,它会检查目标的 ELF 二进制文件以获取有关其解释器的信息,在我们的实现中,它始终是标准的 Linux ld 加载器。 SnapFuzz 然后扫描加载程序代码以查找系统调用程序集操作码和一些特殊function,以指示加载程序加载 SnapFuzz 插件。特别是,重写器:(1) 拦截加载程序的动态扫描,以便将 SnapFuzz 插件共享对象作为依赖项附加,以及 (2) 拦截共享库的初始化顺序,以便预先添加 SnapFuzz 插件初始化代码(在 .preinit_array 中)。

在 SnapFuzz 重写器完成对加载器的重写后,将执行传递给重写的加载器,以加载目标应用程序及其库依赖项。随着加载程序的正常执行,SnapFuzz 会拦截其用于将库加载到内存中的 mmap 系统调用,并扫描这些库以递归地重写它们的系统调用并将它们重定向到 SnapFuzz 插件。 SnapFuzz 重写器基于开源加载时二进制重写器 Saber [14]。

Plugin。加载程序完成后,将执行传递给目标应用程序,该应用程序将通过执行 SnapFuzz 的初始化函数开始。根据 ELF 规范,执行从 .preinit_array 的函数指针开始。这是 LLVM 清理程序用来及早初始化各种内部数据结构的常见 ELF 功能,例如影子内存 [32, 33]。SnapFuzz 使用相同的机制在执行开始之前初始化其子系统,例如其内存文件系统。

在插件的初始化阶段之后,控制权被传递回目标并恢复正常执行。在这个阶段,SnapFuzz 插件仅在目标即将发出系统调用或 vDSO 调用时执行。发生这种情况时,插件会检查调用是否应该被拦截,如果是,它会将其重定向到适当的处理程序,然后将控制权返回给目标。

4.2 内存文件系统

如第 3.3 节所述,SnapFuzz 重定向所有文件操作以使用自定义内存文件系统。这减少了从存储介质读取和写入的开销,并消除了手动编写清理脚本的需要。

SnapFuzz 实现了一个轻量级的内存文件系统,它使用两种不同的机制,一种用于文件,另一种用于目录。对于文件,SnapFuzz 的内存文件系统使用最近的 memfd_create() 系统调用,该调用于 2015 年在 Linux [8] 中引入。此系统调用创建一个匿名文件并返回一个引用它的文件描述符。该文件的行为类似于普通文件,但存在于内存中。在这种方案下,SnapFuzz 只需要专门处理通过路径名启动与文件交互的系统调用(如 open 和 mmap 系统调用)。默认情况下,处理文件描述符的所有其他系统调用与 memfd_create 返回的文件描述符兼容。

当目标应用程序打开文件时,SnapFuzz 的默认行为是检查该文件是否为常规文件(例如,忽略设备文件),如果是,则创建内存中的文件描述符并复制文件的全部内容在目标的内存地址空间中。SnapFuzz 会跟踪路径名以避免重新加载同一个文件两次。这不仅是性能优化,也是正确性要求,因为应用程序可能已更改内存中文件的内容。

对于目录,SnapFuzz 使用 Libsqlfs 库 [5],它在 SQLite 数据库之上实现了 POSIX 样式的文件系统,并允许应用程序访问具有自己的文件和目录层次结构的完整读/写文件系统。 Libsqlfs 简化了具有目录和权限的真实文件系统的模拟。SnapFuzz 仅将 Libsqlfs 用于目录,因为我们通过 memfd_create 观察到文件的性能更好。

4.3 UNIX 域套接字

AFLNet 使用标准 Internet 套接字(TPC/IP 和 UDP/IP)与目标通信并向其发送模糊输入。 Internet 套接字堆栈包括计算数据包校验和、插入标头、路由等功能,这在单台机器上对应用程序进行模糊测试时是不必要的。

为了消除这种开销,与之前的工作 [38] 类似,SnapFuzz 将 Internet 套接字替换为 UNIX 域套接字。更具体地说,SnapFuzz 使用 Sequenced Packets 套接字 (SOCK_SEQPACKET)。

此配置提供了性能优势并简化了实施。 Sequenced Packets 与 TCP 非常相似,为数据报提供了一个有序的、可靠的、基于双向连接的数据传输路径。不同之处在于,Sequenced Packets 要求消费者(在我们的例子中是在目标应用程序中运行的 SnapFuzz 插件)在每次输入系统调用时读取整个数据包。这种网络通信的原子性简化了目标应用程序可能由于调度或其他延迟而仅读取部分模糊器输入的极端情况。相比之下,AFLNet 通过公开手动定义的旋钮(manually defined knobs)来处理这个问题,以在网络通信之间引入延迟。

我们修改后的 AFLNet 版本创建了一组具有 Sequenced Packets 类型的 UNIX 域套接字,并将一端传递给 forkserver,后者随后将其传递给 SnapFuzz 插件。 SnapFuzz 插件启动与修改后的 AFLNet 的握手,之后 AFLNet 准备好将生成的输入提交给目标或使用响应。

将网络通信从 Internet 套接字转换为 UNIX 域套接字并非易事,因为 SnapFuzz 需要支持 TCP 和 UDP 这两个主要 IP 系列,它们在建立网络通信的方法上略有不同。此外,SnapFuzz 还需要支持 (e)poll 和 select 等不同类型的同步和异步通信。

对于 TCP 系列,套接字系统调用创建一个 TCP/IP 套接字并返回一个文件描述符,然后在系统准备好发送或接收任何数据之前将其传递给绑定、侦听并最终接受。 SnapFuzz 监视目标上的这一系列事件,当检测到接受系统调用时,它会从 forkserver 返回 UNIX 域套接字文件描述符。SnapFuzz 不会干扰套接字系统调用,并有意允许其正常执行,以避免与在基本套接字上执行高级配置的目标应用程序发生并发症。此策略类似于内存文件系统通过 memfd_create 系统调用(第 4.2 节)使用的策略,以便在默认情况下提供兼容性。

UDP 系列以类似的方式处理,唯一的区别是,SnapFuzz 不是监视accept系统调用以返回 forkserver 的 UNIX 域套接字,而是监视bind系统调用。

5 评估

我们使用以前用于评估 AFLNet [31] 的五种流行服务器程序展示了 SnapFuzz 的好处:LightFTP(§5.4)、Dcmqrscp(§5.5)、LIVE555(§5.7)和 TinyDTLS(§5.8)。我们的实验表明,SnapFuzz 显着提高了模糊测试吞吐量,同时减少了创建模糊测试工具所需的工作量。由于其显着的性能优势,与 AFLNet 相比,SnapFuzz 在这些应用程序中还发现了 12 次另外的崩溃。

5.1 方法论

由于 SnapFuzz 的贡献在于提高了模糊测试的吞吐量,我们的主要比较指标是每秒模糊测试迭代的次数。请注意,每次模糊测试迭代可能包括模糊器和目标之间的多次消息交换。模糊测试活动由给定数量的模糊测试迭代组成。在 fuzzing 活动中,由于目标执行的代码不同,fuzzer 的速度可能会因迭代而异,有时会显着变化。为了确保 SnapFuzz 和 AFLNet 之间进行有意义的比较,我们没有固定时间预算并计算每个执行的迭代次数,而是固定迭代次数并测量每个系统的执行时间。我们监控标准模糊测试指标,包括错误计数、覆盖率、稳定性、路径和完成的周期,以确保 SnapFuzz 和 AFLNet 活动具有相同(或非常相似)的行为。

我们选择将每个目标运行一百万次迭代,以模拟真实的 AFLNet 模糊测试活动(大约 16 到 36 小时)。我们重复执行每个活动 10 次。

对于错误发现,我们让 SnapFuzz 运行 24 小时,每个基准测试 3 次。然后,我们将所有发现的崩溃累积在一个存储库中。为了对发现的崩溃进行唯一分类,我们重新编译了 ASan 和 UBSan 下的所有基准测试,然后根据来自 sanitizer 的报告对崩溃输入进行分组。

5.2 实验装置

我们所有的实验都是在 3.0 GHz AMD EPYC 7302P 16 核 CPU 和 128 GB RAM 上进行的,运行 64 位 Ubuntu 18.04 LTS(内核版本 4.15.0-162)和 SSD 磁盘。请注意,使用较慢的 HDD 而不是 SDD 磁盘可能会为 SnapFuzz 的内存文件系统组件带来更大的收益。

SnapFuzz 建立在 2021 年 1 月的 AFLNet 修订版 0f51f9e 和 Sabre 修订版 7a94f83 之上。测试的服务器及其工作负载取自上述修订版的 AFLNet 论文和存储库。

我们将 AFLNet 提出的默认配置用于所有基准测试,但有几个例外。对于 Dcmqrscp 服务器,需要进行两项更改:1)我们必须包含一个 Bash 清理脚本来重置服务器数据目录的状态,以及 2)我们必须在请求之间添加 5 毫秒的等待时间,因为我们观察到 AFLNet 与其目标不同步。这些变化进一步强调了这样一个事实,即用户在构建模糊测试工具时需要指定的清理脚本和延迟是脆弱的,并且在使用不同的机器时可能需要调整,因此 SnapFuzz 消除他们需求的能力很重要。

在 TinyDTLS 中,我们决定将请求间等待时间从 30 毫秒减少到 2 毫秒,因为我们注意到 AFLNet 的性能由于这种大延迟而受到严重影响。同样,这表明为这些时间延迟选择正确的值是困难的。

5.3 结果总结

表 1 显示了结果的总结。特别是,它比较了 AFLNet 和 SnapFuzz 完成一百万次迭代所需的平均时间。可以看出,AFLNet 需要 15 小时 17 分钟到 35 小时 35 分钟来完成这些迭代,而 SnapFuzz 只用了一小部分时间,在 34 分钟到 2 小时 7 分钟之间。每种情况下的加速都令人印象深刻,在 Dcmqrscp 的 8.4 倍和 LightFTP 的 62.8 倍之间变化。在所有情况下,我们都观察到相同的覆盖率统计数据、错误数和稳定性数。

5.4 LightFTP

LightFTP [6] 是一个小型的文件传输服务器,它实现了 FTP 协议。 fuzzing 工具指示 LightFTP 登录特定用户,列出 FTP 服务器上主目录的内容,创建目录,并执行各种其他命令以获取系统信息。

LightFTP 使用了大量 SnapFuzz 的子系统。首先,它大量使用文件系统,因为每次迭代创建目录的可能性都很高。其次,它具有详细的日志记录和写入标准输出。第三,它的初始化阶段很长,因为它解析配置文件,然后经历初始化 x509 证书的重量级过程。最后,LightFTP 是一个多线程应用程序,并且具有硬编码的睡眠,以确保其所有线程都已正常终止。

SnapFuzz 优化了上述所有功能。所有目录交互都被转换为内存中的操作,从而避免了上下文切换和设备(硬盘)开销。 SnapFuzz 取消 stdout 和 stderr 写入。 SnapFuzz 的智能延迟分叉服务器在 LightFTP 服务器初始化阶段后对其进行快照,因此在 SnapFuzz 下进行模糊测试只需支付一次初始化开销。最后,SnapFuzz 取消任何对睡眠和类似系统调用的调用。

请注意,SnapFuzz 可以将 forkserver 放置得比手动放置要晚。要使延迟的 forkserver 正常工作,请记住在 forkserver 对目标进行快照之前必须打开任何文件描述符。这是因为文件描述符的底层资源在分叉发生后被保留。这限制了可以手动放置延迟分叉服务器的区域。 SnapFuzz 以其内存文件系统克服了这一挑战,如 §4.2 所述,因此它能够在整个初始化过程完成后放置 forkserver。

在 AFLNet 下,LightFTP 运行 100 万次迭代平均需要 35 小时 35 分钟,而在 SnapFuzz 下只需 34 分钟,提供了 62.8 倍的加速。

5.5 Dcmqrscp

Dcmqrscp [2] 是一个 DICOM 图像存档服务器,它管理多个存储区域并允许存储和查询图像。

模糊测试工具指示 DICOM 服务器将连接信息回显给客户端,并在其数据库中存储、查找和检索特定图像。

Dcmqrscp 大量使用 SnapFuzz 的内存文件系统,因为在每次迭代中读取或创建文件的概率很高。

Dcmqrscp 还受益于智能延迟分叉服务器,因为它有一个漫长的初始化阶段,在该阶段服务器动态加载 libnss 库并解析多个配置文件,这些配置文件决定了 DICOM 语言的语法和功能。

我们的信号传播子系统(第 3.5.3 节)能够暴露 Dcmqrscp 中的一个错误,该错误也由 AFLNet 触发,但由于信号没有正确传播而被遗漏。

在 AFLNet 下执行一百万次 Dcmqrscp 迭代平均需要 17 小时 35 分钟,而在 SnapFuzz 下只需 2 小时 7 分钟,提供 8.4 倍的加速。

5.6 dnsmasq

Dnsmasq [3] 是一个单线程 DNS 代理和 DHCP 服务器,设计占用空间小,适用于资源受限的路由器和防火墙。 fuzzing 工具指示 Dnsmasq 从其配置文件中查询各种虚假域名,然后将结果报告回其客户端。

Dnsmasq 是一个内存数据库,与文件系统的交互很少。因此,它主要受益于 SnapFuzz 协议及其 §3.5 的附加优化。此外,它还从智能延迟 forkserver 中受益匪浅,因为它具有使用 dlopen() 并执行各种与网络相关的配置的漫长初始化过程。在进程准备好接受输入之前,Dnsmasq 需要大约 1,200 次系统调用。

至于其他基准测试,AFLNet 下手动放置的 forkserver 无法以与 SnapFuzz 的智能延迟 forkserver 相同的深度对应用程序进行快照。这是因为 Dnsmasq 需要执行一系列系统调用来建立与 AFLNet 的网络连接。这个序列包括创建一个套接字,绑定它的文件描述符,调用listen,执行一个选择来检查传入的连接,最后接受连接。因此,在 AFLNet 下,forkserver 的最新可能位置将在此序列之前。在 SnapFuzz 下,网络通信被转换为不需要上述任何内容的 UNIX 域套接字通信,因此智能延迟 forkserver 可以在从 fuzzer 读取输入之前对目标进行快照,从而节省大量初始化时间。

在 AFLNet 下,一百万次 Dnsmasq 迭代平均需要 15 小时 17 分钟,而在 SnapFuzz 下只需 30 分钟,提供了 30.6 倍的加速。

5.7 直播555

LIVE555 [7] 是一个单线程多媒体流服务器,它使用 RTP/RTCP、RTSP 和 SIP 等开放标准协议。 fuzzing 工具指示 LIVE555 服务器接受请求,以流方式提供特定文件的内容,服务器用信息和实际流数据回复这些请求。

LIVE555 只读取文件,因此不需要状态重置脚本。它有一个相对较小的初始化阶段,主要开销来自对标准输出的许多写入以及向用户发送欢迎消息。 LIVE555 主要受益于 SnapFuzz 协议和标准输出写入的消除。

LIVE555 仅在 forkserver 执行其快照后读取其文件。因此,这些文件不会保存在 SnapFuzz 的内存文件系统中,而是在每次迭代中从实际文件系统中读取。当智能延迟 forkserver 启动时,我们将预定义一组要加载到内存文件系统中的文件作为未来的工作进行优化,因此目标可以从内存而不是实际文件系统中读取这些文件。

在 AFLNet 下,100 万次 LIVE555 迭代平均需要 25 小时 47 分钟,而在 SnapFuzz 下只需 63 分钟,提供了 24.6 倍的加速。

5.8 微型 DTLS

TinyDTLS [13] 是一个针对物联网设备的 DTLS 1.2 单线程 UDP 服务器。在模糊测试工具中,TinyDTLS 接受新连接,然后启动 DTLS 握手以建立通信。

AFLNet 遵循的协议有几个步骤,通过成功的网络操作或在超时到期后完成下一步。 TinyDTLS 支持两种密码套件,一种基于椭圆曲线 (EC),另一种基于预共享密钥 (PSK)。基于 EC 的加密速度很慢,需要在请求之间使用较大的超时时间,这大大减慢了 AFLNet 模糊测试的速度。此外,AFLNet 在网络交互之间包含一些硬编码延迟,因此它不会压倒目标——如果没有这些延迟,网络数据包可能会被丢弃,并且 AFLNet 的状态机可能会失步。由于 TinyDTLS 的处理延迟,如果 AFLNet 在短时间内发送太多数据,网络缓冲区可能会填满。为了解决这个问题,AFLNet 会检查每次发送和接收是否所有字节都已发送,如果没有则重试。

SnapFuzz 通过其网络模糊协议处理所有这些问题。 (我们还注意到 TinyDTLS 使用了 SnapFuzz 的 UDP 转换功能,这与其他使用 TCP 的服务器不同。)

最终结果是消除了所有这些延迟:AFLNet 不再需要猜测目标的状态,因为 SnapFuzz 明确告知 AFLNet 目标的下一步动作。同样,丢弃数据包的问题也消失了,因为 AFLNet 总是被告知何时是发送更多数据的正确时间。最后,SnapFuzz 的 UNIX 域套接字消除了发送和接收重试的需要,因为域套接字协议保证了与目标之间的完整缓冲区传递。 TinyDTLS 将大量数据写入标准输出,因此它还受益于 SnapFuzz 跳过这些系统调用的能力。

在 AFLNet 下,100 万次 TinyDTLS 迭代平均需要 23 小时 21 分钟,而在 SnapFuzz 下只需 34 分钟,提供了 41.2 倍的加速。

我们提醒读者,在 TinyDTLS 中,我们决定将手动添加的请求之间的时间延迟从 30ms 减少到 2ms,因为我们注意到 AFLNet 的性能受到它的严重影响。如果没有这种变化,AFLNet 将花费更长的时间来完成一百万次迭代,而 SnapFuzz 实现的加速会明显更高。

5.9 性能细分

在 §5.4–§5.8 中,我们讨论了 SnapFuzz 的哪些组件可能使每个应用程序受益最多。这些结论是通过调查应用程序发出的系统调用得出的,使用 strace 提供的关于每个系统调用在内核中占用多少的估计值。为了更好地定量理解每个组件的贡献,我们进行了一项消融研究,其中我们运行了不同版本的 SnapFuzz,进行了短时间的 10k 次迭代。我们选择了少得多的迭代次数,因为以 1M 迭代运行如此多的实验对我们的计算基础设施来说是令人望而却步的。这意味着我们的加速有时与 1M 迭代所获得的加速有很大不同。然而,这些实验的主要目标是进一步了解不同组件的影响及其相互作用。

由于组件之间存在各种依赖关系,我们从仅包含网络模糊测试协议的 SnapFuzz 版本开始,并不断地逐个添加组件。但是,必须了解我们添加组件的顺序很重要,因为它们的效果通常是相乘的。特别是,这意味着与稍后添加相同组件的情况相比,早期添加的组件的额外影响可以显着减少。我们举两个例子:

(1) SnapFuzz 协议和智能亲和。 SnapFuzz 协议是一种高性能的非阻塞协议,它轮询模糊器和应用程序进行通信。在 AFLNet 的默认受限 CPU 亲和力下,该协议性能不佳,因为轮询模型需要独立的 CPU 内核才能获得预期的性能优势。同时,智能 CPU 亲和性组件取决于是否启用了 SnapFuzz 协议,因为该协议会改变它在 CPU 上执行的内容。

(2) 内存文件系统和智能延迟分叉服务器。当启用内存文件系统时,智能延迟 forkserver 性能更好,因为使用内存文件系统它可以延迟 forkserver 过去的文件系统操作。另一方面,启用智能延迟分叉服务器时,内存文件系统的性能也更好。这是因为内存文件系统在加载和存储目标在其执行开始时正在读取的文件时具有固定的开销。

这种初始开销可能会降低性能,尤其是对于短时间的执行。启用延迟分叉服务器时,会绕过此开销,因为这些文件仅在内存中加载一次,并且连续操作将仅在内存中。

一种选择是尝试所有可能的排序。但是,完整的数字很大(6!= 720),并且由于工程限制(例如 SnapFuzz 协议已深深嵌入 SnapFuzz 中并且禁用它需要进行重大的工程检修),因此某些订单难以运行。尽管如此,我们相信我们在这里展示的排序对于深入了解每个 SnapFuzz 组件的影响仍然有用。

表 2 显示了我们的结果。我们观察到所有组件对至少一个基准有重大影响。此外,SnapFuzz 协议、智能亲和性和智能延迟 forkserver 总是会带来收益,同时消除开发人员添加的延迟(无睡眠)、避免 stdout/stderr 写入(无 stdout)和内存文件系统没有区别在一些基准测试中。删除对 stdout/stderr 的写入是影响最小的组件,仅使 LIVE555 受益。

报告的数字与我们对§5.4–§5.8 的定性观察基本一致。例如,LightFTP 的主要优点来自 SnapFuzz 协议 (1.90x),它消除了同步和服务器终止延迟;来自 smart affinity (1.79x),因为 LightFTP 是一个多线程应用程序;消除 LightFTP (2.76x) 中存在的开发人员添加的延迟;来自智能延迟分叉服务器(2.39x),因为它的初始化阶段很长;并且来自内存文件系统(2.23x),因为它大量使用了文件系统。虽然 LightFTP 已写入标准输出,但删除它们并不会产生明显的差异。

其他基准的性能数字也与我们的预期基本一致。例如,内存文件系统对 Dnsmasq 没有任何好处,它是一个几乎没有文件系统交互的内存数据库;但它从智能延迟服务器(4.79x)中受益匪浅,因为它有一个很长的初始化过程,在它能够接受输入之前发出了超过 1,200 个系统调用。

5.10 发现独特的崩溃

正如预期的那样,SnapFuzz 能够复制所有 AFLNet 发现的崩溃。通过其性能优势,它还在 5 个基准测试中的 3 个中发现了额外的崩溃。在 24 小时的模糊测试活动中,SnapFuzz 在 Dcmqrscp 基准测试中发现了 4 个错误,而 AFLNet 却找不到任何错误。对于 Dnsmasq,SnapFuzz 能够发现 7 次崩溃,而 AFLNet 仅发现 1 次;对于 LIVE555 基准测试,SnapFuzz 能够发现 4 次崩溃,而 AFLNet 发现 2 次。这两个工具都在 TinyDTLS 中发现了 3 个错误。总体而言,SnapFuzz 发现了 18 次独特的崩溃,比 AFLNet 多 12 次。这些错误是各种堆溢出、堆栈溢出、useafter-free 错误和其他类型的未定义行为。幸运的是,它们似乎已在这些应用程序的最新版本中得到修复。我们计划在最新版本上重新运行 SnapFuzz。

6 相关工作

SnapFuzz 专注于为网络应用程序创建一个高效的模糊测试平台,并帮助算法研究建立在坚实的基础之上。我们设想这种关注点分离将通过减轻为网络和其他有状态应用程序构建高性能模糊器的艰巨任务来帮助未来的研究更快地取得进展。

SnapFuzz 建立在 AFLNet [31] 之上,并重用其推断网络协议的能力。然而,AFLNet 存在各种低效率问题,并且在其 fuzzing 工具中需要脆弱的手动延迟和清理脚本。我们对 AFLNet 的综合评估显示了 SnapFuzz 如何解决这两个问题,从而在 8.4x-62.8x 范围内实现令人印象深刻的加速。

除了 AFLNet,一种流行的模糊网络应用程序的方法是通过 Preeny [11] 的去套接字功能。 Preeny 拦截连接和接受等网络功能,并让它们返回与标准输入和标准输出同步的套接字,本质上允许 AFL 继续模糊文件并通过网络套接字重定向它们的内容,正如正在测试的网络应用程序所期望的那样。同步是通过一种骇人听闻的方式完成的:Preeny 实现了一个小型服务器线程,该线程不断地轮询 AFL 生成的输入文件,然后通过 UNIX 域套接字将读取的数据转发到适当的网络调用到目标 [10]。

虽然与 AFLNet 和 SnapFuzz 进行直接比较并不容易,因为有意义的模糊测试活动需要 AFLNet 推断的网络协议,但我们预计在 Preeny 之上重写 AFLNet 会比普通 AFLNet 慢,因为文件施加的额外开销- 基于模糊测试和 Preeny 使用的附加线程服务器。

Multifuzz [38] 提出了一个更高级的去套接字库,称为 Desockmulti,它类似于 Preeny,但以各种方式进行了优化,例如通过删除线程的使用并添加启动到目标的多个连接的能力。 MultiFuzz 专为发布/订阅协议而设计,评估不包括 AFLNet 和我们使用的基准。对于使用的两个基准,libcoap 和 Mosquitto,论文报告在 AFLNet 之上的吞吐量分别增加了 62.6 倍和 147.6 倍。我们希望 SnapFuzz 性能更好,特别是由于其专门的网络模糊测试协议和内存文件系统(MultiFuzz 使用 tmpfs,参见 §3.3),但不幸的是,MultiFuzz 不能作为开源使用(只有它的 Desockmulti 库可用),所以直接比较是不可能的。

徐等人。 [36] 提出了用于模糊测试的新操作系统原语。这包括,例如,一个新的快照系统调用,它旨在解决与 SnapFuzz 相同的目标,即有效地对目标进行快照。这种方法的主要缺点是它需要内核支持;相比之下,SnapFuzz 在用户模式下运行,使用未经修改的操作系统。

大多数测试网络协议实现的工作都集中在算法而不是平台级别的改进上,特别关注推断网络协议实现[20,22,31,37]。这项工作与 SnapFuzz 正交,可以与它结合,就像我们对 AFLNet 的协议推理算法所做的那样。更广泛地说,灰盒模糊测试是一个活跃的研究领域 [17],最近的工作是通过将探索引向有趣的程序部分 [18、19]、将其与符号执行结合起来 [21、30、34]、推断输入语法来提高其有效性[15, 35] 或将其专门用于各种应用领域 [16, 25, 27, 39]。

除了灰盒模糊测试,其他形式的模糊测试已被用于测试网络应用程序,特别是黑盒模糊测试 [23, 24] 和故障注入 [9, 28]。

7 结论

Fuzzing 无状态应用程序已被证明非常成功,发现了数百个错误和安全漏洞。最近,由于算法的进步使得生成遵循应用程序网络协议的输入成为可能,对网络服务器等有状态应用程序进行深度模糊测试已经变得可行。不幸的是,对此类应用程序进行模糊测试需要清理脚本和手动配置的时间延迟,这些延迟很容易出错,并且模糊测试吞吐量较低。 SnapFuzz 通过强大的架构解决了这两个挑战,该架构将同步通信协议与内存文件系统相结合,能够将 forkserver 延迟到最新的安全点,以及其他优化。因此,SnapFuzz 简化了模糊测试工具的构建,并显着提高了模糊测试吞吐量,在一组流行的网络应用程序上提高了 8.4 倍至 62.8 倍,使其能够发现额外的崩溃。

SnapFuzz 将在发布后不久提交工件评估并作为开源提供给社区,希望它有助于提高网络应用程序的安全性和可靠性,并促进该领域的进一步研究。

发布于 2022-05-11 17:13