- 这是我们在ICSE’24的论文RPG: Rust Library Fuzzing with Pool-based Fuzz TargetGeneration and Generic Support [1] 的科普版本。
- 插播一条广告:西安电子科技大学广州研究院 ICTT(GZ) 实验室长期接收硕士、博士、博士后、教师岗位申请,欢迎勤奋、认真,有创新精神的有志青年加入我们研究组!详见:RPG:一种面向Rust库的模糊测试目标自动生成技术(ICSE'24)
一、背景介绍
Rust是一种新兴的系统编程语言,它具有内存安全和高效性的优点。Rust的设计理念是“不信任开发者”,也就是说,它不允许任何可能导致未定义行为的操作。这样,Rust可以避免很多常见的编程错误,比如空指针解引用、缓冲区溢出、内存泄漏等。由于这些优势,Rust不仅受到了很多系统开发者的青睐,也吸引了其他领域的科学家。
Rust库(crate)是Rust语言的基本组成部分,它们是一些可复用的代码模块,可以提供各种功能和特性。Rust库有两种类型:库(library)和二进制(binary)。库可以被其他库或二进制引用,而二进制则是可执行的程序。Rust库可以通过一个叫做Crates.io的在线仓库进行发布和下载,目前已经有超过6万个Rust库可供使用。然而,Rust的库也不是完美的,它们可能存在一些隐藏的错误和漏洞,这些错误和漏洞可能导致程序崩溃或者出现未定义行为。那么,如何有效地检测和发现Rust库中的错误呢?
一种常用的方法是模糊测试(fuzzing),它是一种自动化的软件测试技术,它通过向程序提供随机或者半随机的输入,观察程序的行为是否异常。模糊测试已经被证明是一种非常有效的发现软件错误的方法,它已经在许多领域和项目中得到了广泛的应用。然而,模糊测试也有一些挑战和限制,其中之一就是如何为目标程序编写合适的测试用例(也称为fuzz target)。对于Rust库来说,这个问题尤为突出,因为Rust库通常只提供了一些函数(也称为API),而没有给出具体的使用场景和示例。因此,如果想要对Rust库进行模糊测试,就需要手动编写fuzz target,这是一项非常耗时和繁琐的工作。
为了解决这个问题,一些研究人员提出了一种自动化的模糊测试目标(fuzz target)生成技术,它可以根据Rust库的API信息,自动地生成一些有效和有效的fuzz target,从而实现对Rust库的模糊测试,例如发表在ASE’24的RULF[2]。该文章指出,模糊测试目标是一个可以调用库中的函数(API)的Rust程序,它需要满足以下条件:
-
语法正确:模糊测试目标必须是一个合法的Rust程序,可以通过编译器的检查。
-
语义正确:模糊测试目标必须是一个有意义的Rust程序,可以正确地调用库中的函数,不会产生编译时或运行时的错误。
-
多样性:模糊测试目标应该能够调用库中的不同函数,以及函数之间的不同组合,以触发库中的不同分支和路径。
-
有效性:模糊测试目标应该能够调用库中的关键函数,特别是那些涉及到不安全(unsafe)代码的函数,以发现库中的潜在错误。
二、本文方法:RPG
RPG是本文提出的一种自动为Rust库生成模糊测试目标的新技术,它的全称是(R)ust (P)ool-based fuzz target (G)eneration。RPG的主要贡献是解决了两个挑战:
-
如何生成多样性和有效性高的函数序列,以优先考虑不安全代码和函数之间的交互,从而揭示Rust库中的错误。
-
如何提供对泛型(generic)函数的支持,并验证模糊测试目标的语法和语义正确性,以达到高覆盖率。
Overview
RPG的工作流程如下:
-
Rust库分析:RPG利用静态分析从Rust库中提取函数和数据类型的信息,基于此构建一个API依赖图和一个参数提供器。API依赖图记录了库中可以调用的函数(API)的信息,用于指导函数序列的生成;参数提供器包含了库中定义的数据类型以及一些常用的数据结构类型,用于为泛型函数提供类型候选。
-
函数序列生成:这一步是RPG的核心,因为一个函数序列直接反映了其对应的模糊测试目标的质量。RPG从一个序列集合开始,该集合考虑了不安全的API,并根据API依赖图和一个API池生成函数序列。API池由当前可用的API组成,其中可能有多个重复。特别地,当处理泛型函数时,RPG从参数提供器中取出数据类型,并保持泛型类型参数的一致性,以确保有效性。
-
函数序列优化:为了确保模糊测试目标的语法和语义正确性,RPG采用了移动-借用(move-borrow)循环检查和泛型声明检查来移除无效的函数序列。RPG还进行了序列过滤,以获得一个最小的序列集合,覆盖了最多的API及其依赖。最后,剩余的函数序列分别被合成,生成模糊测试目标。
RPG的优势:
-
优先考虑不安全代码和交互:文章的方案能够有效地检测Rust库中由不安全代码或复杂API序列交互导致的漏洞,而过去的方案主要关注单个API的覆盖,忽略了这些重要的因素。
-
提供泛型支持和有效性检查:文章的方案能够处理Rust库中广泛使用的泛型函数,通过类型推断和参数提供器为泛型参数提供合适的类型,并保持类型的一致性。文章的方案还能够对API序列进行移动-借用检查和泛型声明检查,以确保API序列的语法和语义有效性,而过去的方案缺乏对泛型的支持或只提供有限的支持,导致生成的目标不完善或无效。
-
使用基于池的序列生成和过滤:文章的方案能够使用一种基于池的方法,根据API的不安全性和依赖性,生成多样化和深度的API序列,以覆盖更多的API和依赖。文章的方案还能够对API序列进行过滤,以获得一个最小的序列集合,覆盖最多的API和依赖,而过去的方案使用的是基于图的序列生成,不能有效地探索API序列的可能性,也没有对API序列进行过滤,导致生成的目标冗余或低效。
三、技术细节:
3.1 API依赖图的构建
Rust 库函数包含结构、函数签名和所需参数等一系列信息。测试一个 Rust 库通常意味着测试其中所有的 API 函数,因此需要调用所有的 API 函数。然而,大部分 Rust库中的函数需要通过传递参数才能调用。对于需要基础数据类型或常见数据类型作为参数的函数,本文将提供由模糊测试生成的字符来作为传递参数,该字符将由工具生成。对于需要非基础数据类型作为参数的函数,这些参数通常只能通过其它函数的返回值来提供,这就形成了函数之间所谓的依赖关系。
因此,为了使函数调用更加简洁有序,本文通过静态分析方法获取 Rust 库中的 API信息,并构建了敏感 API 依赖关系图,这是受到 RULF 工作的启发而来的。虽然 RULF采用的 API 依赖关系图只包含简单的 API 依赖信息,不能满足本论文所需的序列生成方法。因此,需要通过更多静态分析方法来获取所需信息,以保证序列生成方法的正常运行。
本文使用rustdoc工具从Rust库中提取API的信息,包括名称,输入,输出,是否包含不安全代码,以及泛型类型的约束。文章根据API的输入和输出类型之间的兼容性,构建了一个带标签的API依赖图,表示API之间的依赖关系和依赖种类。
3.2 参数提供器
文章收集了Rust库中定义的数据结构类型,以及一些常用的数据结构类型,作为参数提供器,用于为泛型函数提供类型候选。
当序列生成过程中涉及到泛型类型时,主要问题在于如何选择数据类型作为参数调用目标方法。本文提出了通过参数供给的方法来实现泛型函数的调度。当序列生成过程时涉及到泛型时,若无确定的数据类型,则会通过参数供给器获取。由于泛型类型不仅能由库定义的数据类型提供,还可能由常见的数
据类型提供,而这些类型可能不会在库中特别说明。因此,为了解决此类情况,参数供给器不仅来源于库定义的数据类型,还包括一些预设的数据类型。在接下来的部分中,将作为泛型类型的参数称为泛参。
3.3 基于池的序列生成
目前可用的生成模糊测试目标方法中,RULF通过遍历图的方法,实现了工具自动生成模糊测试目标,方法主要分为两步:使用 Rust 库生成 API
序列,再将所生成的序列构建成适用于模糊测试的模糊测试目标。然而,该工作的方法存在以下问题:(1) API 序列的针对性差,不能有效覆盖存在不安全行为的函数或者只能通过特定执行序列才能触发的漏洞。(2) 因深度受限,无法有效探索要求较高深度的API。虽提出了回溯搜索方法生成长序列,但仍存在 API 缺失的情况。(3) 序列检查方法尚不完善,可能无法通过 Rust 编译器检查。
因此,为更好地生成高质量的模糊测试目标,本文提出了以下改进:1. 需要具有针对性的方法,以生成具有优先错误触发能力的模糊测试目标。2. 在不牺牲速度和质量的前提下,探索更深层次的 API。3. 随着功能的增加,需要全面而精确的语法和语义检查,来确保序列的合法性。
文章提出了一种基于池的方法,根据API依赖图和API池生成多样化的API序列。API池是一个包含多个API的集合,每个API可能有多次出现。文章根据API的不安全性和依赖性,确定每个API的初始数量。文章使用一种启发式算法,从API池中选择API,优先选择数量较多的API。文章还使用了一个探索阈值,当API池中消耗的API的比例超过阈值时,API池会重置,以便生成更长的API序列。
3.4 泛型支持和有效性检查
文章提出了一种调度方法,用于调用泛型函数。文章维护了一个泛型签名和具体类型的哈希映射,以确保API序列中泛型类型参数的一致性。文章使用了一个类型推断引擎,结合参数提供器,根据API依赖和泛型约束,推断泛型参数的具体类型,并将其传递给其他函数。文章还对API序列进行了移动-借用检查和泛型声明检查,以确保API序列的语法和语义有效性。
3.5 返回值回调
文章尝试使用API序列中的API的返回值,作为另一个API的输入,以利用内存相关的漏洞。文章对每个调用的API的返回值进行了反向移动-借用检查,以确定是否可以使用它,然后搜索一个可调用的函数。
四、实验效果
任务和性能:文章在50个流行的Rust库上评估了RPG,使用AFL++作为模糊测试工具。文章使用了以下指标来衡量RPG的性能:
-
API覆盖率:表示生成的模糊测试目标覆盖了多少比例的可达API。文章的结果显示,RPG的API覆盖率为71.8%,显著高于RULF的48.9%。
-
依赖覆盖率:表示生成的模糊测试目标覆盖了多少比例的API依赖。文章的结果显示,RPG的依赖覆盖率为11.1%,显著高于RULF的3.7%。
-
目标数量:表示生成的模糊测试目标的数量。文章的结果显示,RPG的目标数量为1,165,低于RULF的1,481,说明RPG生成的目标更精简和高效。
-
目标有效性:表示生成的模糊测试目标是否能够通过编译和运行。文章的结果显示,RPG的目标有效性为100%,高于RULF的94.9%,说明RPG生成的目标更符合Rust的语法和语义规则。
-
漏洞发现能力:表示生成的模糊测试目标能够发现多少个漏洞。文章的结果显示,RPG能够发现25个之前未知的漏洞,高于RULF的8个,说明RPG生成的目标更能够触发Rust库中的隐藏漏洞。
五、案例研究
添加图片注释,不超过 140 字(可选)
六、局限性讨论
参数提供器的局限性:文章的方案使用了一个参数提供器,包含了Rust库中定义的数据结构类型和一些常用的数据结构类型,作为泛型参数的候选。然而,这个参数提供器可能不能覆盖所有可能的类型,导致一些泛型函数无法被调用。文章的方案也没有考虑用户自定义的类型,可能导致一些泛型函数的调用不符合用户的期望。
类型推断的不完善性:文章的方案使用了一个轻量级的类型推断引擎,根据API依赖和泛型约束,推断泛型参数的具体类型。然而,这个类型推断引擎可能不能处理一些复杂的类型关系,例如多态性,继承,重载等,导致一些泛型函数的调用不正确或失败。
模糊测试的不确定性:文章的方案使用了AFL++作为模糊测试工具,对生成的目标进行模糊测试,以发现漏洞。然而,模糊测试本身是一个不确定的过程,可能受到很多因素的影响。
六、总结和展望
6.1 总结
-
文章提出了一种自动化的模糊测试目标生成技术,用于支持Rust库的模糊测试,能够有效地检测Rust库中由不安全代码或复杂API序列交互导致的漏洞。
-
文章提出了一种基于池的序列生成和过滤方法,能够生成多样化和深度的API序列,覆盖更多的API和依赖,同时保持目标的精简和高效。
-
文章提出了一种泛型支持和有效性检查方法,能够处理Rust库中广泛使用的泛型函数,通过类型推断和参数提供器为泛型参数提供合适的类型,并保持类型的一致性,同时对API序列进行移动-借用检查和泛型声明检查,以确保API序列的语法和语义有效性。
-
文章在50个流行的Rust库上评估了RPG的性能,使用AFL++作为模糊测试工具,发现了25个之前未知的漏洞,显著优于现有的方案。
6.2 未来工作
-
尽管本文提出的多种序列生成方法以及泛型调度方法已经能够有效生成模糊测试目标,但在实际代码库中,仍有一些无法调用的函数。主要是因为这些函数依赖于其它库的数据类型。因此,未来的模糊测试目标生成可以协同生成多个库的序列,并考虑Rust 库的交互。
-
本文从提高模糊测试目标的角度来改进 Rust 库的模糊测试结果,但目前仍然使用 AFL 等传统框架来实现模糊器。因此,在未来的 Rust 库模糊测试中,可以通过改进模糊器使之更适配于 Rust 库的特性,从而检测更多的错误
-
扩展参数提供器,以覆盖更多的类型,包括用户自定义的类型,以增加泛型函数的调用可能性和符合用户的期望。改进类型推断引擎,以处理更复杂的类型关系,例如多态性,继承,重载等,以增加泛型函数的调用正确性和成功率。
【参考文献】
[1] RPG: Rust Library Fuzzing with Pool-based Fuzz TargetGeneration and Generic Support. ICSE 2024.
[2] RULF: Rust library fuzzing via API dependency graph traversal. ASE 2021.
[3] Understanding memory and thread safety practices and issues in real-world Rust programs. PLDI 2020.
[4] Rust Library Fuzzing. IEEE Software 39, 5 (2022), 105–108.
[5] How do programmers use unsafe Rust? OOPSLA 2020.
[6] MirChecker: Detecting Bugs in Rust Programs via Static Analysis. CCS 2021