Abstract
数据库管理系统(DBMS)是现代数据密集型应用的关键组件。开发者采用了许多测试技术来检测DBMS的错误,如崩溃和断言失败。然而,大多数以前的工作无法检测导致DBMS返回错误结果的逻辑错误。最近的工作提出了几种oracle来识别错误结果,但它们依赖于基于规则的表达式生成来合成查询,没有任何指导。在本文中,作者提出了将基于覆盖率的指导、面向有效性的变异和oracle相结合来检测DBMS系统中的逻辑错误。具体来说,作者首先设计了一组通用的API来解耦fuzzer和oracle的逻辑,这样开发者就可以轻松地将fuzzing工具移植到测试DBMS,并为现有的fuzzer编写新的oracle。然后,作者提供了面向有效性的变异来生成高质量的查询语句,以便发现更多的逻辑错误。作者的原型系统SQLRight优于只依赖oracle或代码覆盖率的现有工具。总共,SQLRight从两个经过充分测试的DBMS,SQLite和MySQL中检测出了18个逻辑错误。所有错误都已经得到确认,其中14个已经被修复。
2、BackGround & Challenges
2.1 An Example Logical Bug
清单1展示了SQLite的一个逻辑错误,它与唯一部分索引有关。这个错误是由我们的工具SQLRight检测到的,并已经被SQLite开发者修复。1第一条语句创建了一个只有一个列pid(类型为INT)的表person。第二条语句向表中插入了三行数据,分别是1和两个10。第三条语句创建了一个唯一部分索引idx,它只维护pid值为1的行的记录。因此,只有第一行会被连接到idx。最后一条SELECT语句查询pid值为10的行,其中DISTINCT关键字要求去除重复结果。根据表内容,结果应该是一行10。但是,SQLite却产生了两行10(10\n10,违反了DISTINCT的要求。
问题是由于SELECT的错误优化导致的。当WHERE中的所有列都连接到某些唯一索引时,SQLite将DISTINCT关键字视为不必要的,并在查询处理过程中简单地忽略它。然而,它忘记了部分索引的情况,其中只连接了部分行。为了修复这个错误,SQLite检查索引类型,并仅将优化应用于完整索引的情况。
Security Impact.
这个逻辑错误将重复的数据行视为唯一的。根据具体的使用场景,它可能导致各种安全后果。例如,如果查询故意使用DISTINCT来隐藏匹配行的数量,以保护隐私,那么这个错误就会泄露数量信息。如果查询用于向用户分发随机密码,它可能会给多个不同的用户发送相同的密码。由于SQLite广泛应用于公共领域,在35亿部智能手机中占有一席之地[51],我们认为这个去重错误具有严重的功能和安全影响。
2.2 Oracles for Logical Bug Detection
逻辑错误比内存相关问题更难检测。一旦触发了内存错误,执行就可能崩溃,我们可以轻松地捕获它。大多数逻辑错误不会使程序崩溃,而只会产生错误的结果。我们需要一个oracle来提供预期的结果。例如,在清单1中,oracle应该指示预期的结果为10。然而,构建一个完整、无错误的oracle是具有挑战性的。高级分析师可以手动分析结果以识别错误,但无法处理大规模的测试用例。差分分析提供了一种自动化、可扩展的解决方案[16,29,49,52]。例如,RAGS向不同的DBMS发送一个查询并比较它们的结果以查找错误[49]。然而,由于方言和扩展的多样性,流行的DBMS只共享很小一部分功能,跨DBMS验证无法检测到DBMS特定的错误[49]。
小补充:差分分析(Differential analysis)是一种决策分析技术,它检查与两个选项相关的成本和收益,并比较两者的净结果。选择的替代方案是具有最有利(或最不利)财务影响的方案。
最近的工作构建了功能等效的查询,并检查DBMS是否为所有查询生成相同的结果[43-45]。例如,对于给定的查询,oracle NoREC将WHERE子句中的所有条件转移到SELECT表达式中,这有效地禁用了应用于原始查询的大多数优化[43]。修改后的查询的TURE行数应与原始查询的行数相同。Oracle TLP将WHERE子句中的条件x分成三个子查询:x IS TRUE、x IS FALSE和x IS NULL。它将三个子查询的结果组合起来,并检查与原始结果的等效性[44]。由于oracle是基于高级语义而不是低级实现构建的,因此我们可以使用它们来测试多个DBMS系统[30]。
我们可以定义一个简单的oracle,称为Index,来检测清单1中的错误:从数据库中删除索引不应影响查询结果。我们可以实现oracle Index来删除现有索引(或插入新索引)并检查DBMS是否产生与以前相同的结果。通过删除CREATE UNIQUE INDEX语句,在清单2中给出了清单1的一个功能等效形式。我们可以使用oracle Index在没有任何领域知识的情况下检测此错误。更重要的是,我们可以将测试扩展到大规模。
2.3 Coverage-guided Testing
基于覆盖率的测试,或者称为模糊测试[32],已经被广泛用于测试大量的程序,并成功地发现了数千个错误[18, 27, 48, 70]。现代的模糊测试工具,称为fuzzer,利用代码覆盖率来指导输入的选择和变异。具体来说,给定一个程序输入,fuzzer首先随机地更新它的内容,生成一组新的输入。它将新的输入提供给程序,并监控执行情况。如果程序崩溃或报告断言失败,fuzzer将把输入视为潜在错误的证明(PoC)。对于没有触发崩溃的输入,fuzzer将检查执行是否达到了新的代码路径,比如基本块或分支。如果是这样,它将把输入加入到一个队列中。否则,它将丢弃输入,并从队列中取出下一个输入进行另一轮模糊测试。基于覆盖率的测试已经被用于测试各种程序,包括但不限于操作系统[11, 22, 37, 63, 65]、编译器[10, 19, 38]、网络浏览器[17, 48, 66]、文档阅读器[12, 64],甚至智能合约[20, 35, 62]。
最近的工作也将基于覆盖率的模糊测试移植到了DBMS系统的测试上[21, 26, 60, 71]。图1展示了Squirrel [71]的概览,这是一个旨在检测DBMS系统中崩溃和断言失败的最新工作。Squirrel将一组输入,即SQL查询,作为模糊测试的种子。它首先将查询转换为中间表示(IR),其中包含了许多结构信息。然后,Squirrel采用三种变异方法来修改查询IR并创建新的查询,包括节点插入、删除和替换。对于每个新生成的IR,Squirrel构建了不同操作数(例如表和列)之间的数据依赖图,并用随机生成的字符串填充操作数。之后,它将新的IR转换回查询并提供给DBMS。最后,Squirrel报告崩溃并优先选择暴露代码的查询进行下一轮模糊测试。Squirrel成功地发现了一些常用DBMS系统(如SQLite)中的崩溃和断言失败。
中间的部分先跳过。。。
3 Design of SQLRight
作者提出了两种实用的解决方案,来应对前述的挑战,并采用基于覆盖率的指导方法来测试 DBMS 的逻辑错误。首先,我们提供了有效性导向的生成方法,它包含了一套策略,用于生成有效的、确定性的 SQL 查询(§3.1)。生成的查询不仅在语法和语义上具有高有效性,而且排除了可能导致误报的随机行为。其次,我们设计了一套通用的、全面的 API,来支持开发新的 DBMS 语义检查器(§3.2)。这些 API 将语义检查器和模糊器解耦,使用户更容易地测试 DBMS 系统。它们还帮助 DBMS 开发者采用基于覆盖率的指导方法来发现逻辑错误。
3.0 System overview
图2展示了我们的工具SQLRight的概览,它是第一个结合了基于覆盖率的指导、有效性导向的变异和语义检查器来寻找数据库管理系统(DBMS)逻辑错误的测试平台。它接受目标程序(即DBMS)和一组样本查询(即SQL语句)作为输入,并将生成逻辑错误的报告。首先,SQLRight将所有样本查询加入一个队列。对于每一轮模糊测试,它从队列中选取一个查询,并应用变异来生成新的查询。然后,它更新查询操作数,如表名和列名。之后,SQLRight将新的查询发送给DBMS,并检查执行结果以识别意外的行为。如果新的查询触发了新的代码,SQLRight将该查询加入队列以供未来测试。与传统的内存错误模糊器(如图1中的Squirrel)不同,SQLRight与DBMS语义检查器合作,产生高质量的查询来识别逻辑错误。具体来说,对于查询变异,它调用语义检查器API来更新特定于语义检查器的SQL语句,以准备结果检查。对于查询验证,它再次调用语义检查器API将查询转换为语义等价的变体。执行完毕后,它依赖于语义检查器来决定这些查询是否触发了逻辑错误。
这里的变异,指的是Mutation Engine部分
Mutation指的是在软件测试中,对程序的源代码或二进制代码进行微小的修改,以产生新的变异程序的过程。Mutation testing是一种评估测试用例质量的方法,它通过比较原始程序和变异程序的执行结果来检测测试用例是否能发现程序中的错误。Mutation testing的基本思想是,如果一个测试用例能够检测出一个变异程序和原始程序之间的差异,那么它也有可能检测出原始程序中的错误。Mutation testing可以应用于不同层次的软件测试,如单元测试、集成测试和系统测试。Mutation testing的优点是能够提供一个量化的指标来衡量测试用例的有效性和完备性;缺点是需要生成和执行大量的变异程序,这会导致高昂的计算成本和时间开销。¹²
参考的资料如下:
(1) Mutation Testing in Software Testing Means Examples 2023. https://www.softwaretestingo.com/mutation-testing-in-software-testing/.
(2) . https://bing.com/search?q=mutation+in+software+testing.
(3) Software Testing | Mutation Testing - GeeksforGeeks. https://www.geeksforgeeks.org/software-testing-mutation-testing/.
(4) Mutation Testing - javatpoint. https://www.javatpoint.com/mutation-testing.
(5) What is Mutation Testing? (Example) - Guru99. https://www.guru99.com/mutation-testing.html.
3.1 Validity-oriented Query Generation
=> 有效性导向的查询生成
这是文段的中文翻译:
SQLRight要求高质量的SQL查询,以测试不同DBMS的各个方面。任何导致语法或语义错误的查询都不利于发现逻辑错误。不幸的是,生成语义正确的查询已经被证明是NP难问题[29]。最近的模糊器利用基于类型的变异和语义引导的实例化来生成有效的查询[71]。然而,它们的有效率,大约30%,仍然不足以有效地测试DBMS系统。更糟糕的是,大多数生成的查询还没有准备好用于检测逻辑错误。因此,我们提出了几种实用的技术来提高查询有效性。
3.1.1 Cooperative Mutation 协同突变
Cooperative Mutation是指在基因突变中,不同途径功能的基因突变可能发生在同一癌症中,这种现象被称为共现性。这种共现性的突变被称为Cooperative Mutation => 用来类比这种情况
SQLRight采用了两种并行的突变策略,生成查询集的不同组成部分。如图3所示,它维护了两个队列:select队列只包含SELECT语句,用于生成能够产生输出的合适的SELECT查询;normal队列包含其他语句,用于为测试准备数据库,如创建表和插入值。在模糊测试初始化时,SQLRight扫描所有种子输入,将所有SELECT语句保存到select队列中,并将其他语句保留在normal队列中。对于每一轮模糊测试,SQLRight从normal队列中收集一组语句,并依赖normal突变引擎生成新的查询。然后,它调用oracle的协同突变来创建和附加几个SELECT语句。组合之后,我们利用实例化来构建一个具体的查询集。
在协同突变过程中,oracle保留了对正确性测试有用的查询元素。例如,oracle NoREC要求SELECT语句既有FROM子句又有WHERE子句。我们为oracle提供了接口,以通知突变引擎不要删除这些必要的节点,或者在原始语句没有这些节点时添加新的节点。图4说明了如何控制对语句SELECT COUNT(*) FROM v0 WHERE v1=0的突变。给定查询的IR,我们为三个节点,即SELECT_statment、FROM和WHERE,添加属性,将它们标记为不可变。因此,oracle NoREC的关键组件得到了保留。突变引擎仍然有完全的灵活性来更新from_clause节点和where_clause节点,以生成新的SELECT语句。基于IR的突变引擎支持from_clause和where_clause中的各种模式和不同条件,因此提供了与无约束突变器[71]相当的丰富能力。
mutation enging的作用好像还不是很理解
3.1.2 Dedicated Parsing
=> 专门的解析器
我们为每个数据库管理系统定制了我们的变异引擎,以提高语法的正确性。大多数流行的数据库管理系统都有自己定制的 SQL 方言,它们只共享有限的功能。因此,一个统一的 SQL 语法,只能覆盖数据库管理系统代码的一小部分,无法发现数据库管理系统独有功能的错误 。相反,一个旨在支持所有功能的通用语法可能会导致许多无效的查询。列表 3 显示了一个例子,一个带有单引号符号的字符串在不同的数据库管理系统中有不同的含义。SQLite 可以成功地运行这个查询,因为它默认将带有单引号的字符串视为常量,或者如果当前位置不允许字符串,则将其视为标识符。然而,PostgreSQL 会报告这个查询的错误,因为它从不接受带有单引号的字符串作为标识符。
为了解决这个限制,我们设计了一个工具,可以自动地将每个数据库管理系统的解析器移植到 SQLRight 中。我们观察到,大多数流行的数据库管理系统都使用 GNU Bison [14] 来编译它们的解析器前端。因此,我们遵循 Bison 定义的语法规则,将数据库管理系统的解析器前端转换为 SQLRight 的 IR。由于 Bison 格式有很好的文档,我们的工具可以轻松地为 SQLRight 移植不同的解析器。有了原始的数据库管理系统解析器前端,SQLRight 可以支持每个数据库管理系统的完整方言语法,并保证高语法正确性,以提高查询有效性。
小番外:PostgreSQL是一种开源的关系数据库管理系统,它可以存储和处理各种类型的数据,包括结构化的表格数据和非结构化的文本、图像、音频等。PostgreSQL支持多种高级功能,如事务、并发控制、触发器、存储过程、索引、全文搜索等。PostgreSQL还遵循开放的技术标准,可以与其他数据库或编程语言进行互操作。PostgreSQL是免费的,任何人都可以下载、使用和修改它。¹
这里就说明了=》SQLRight 可以支持每个数据库管理系统的完整方言语法,并保证高语法正确性,以提高查询有效性
3.1.3 Context-based IR Instantiation
=》基于上下文的IR 实例化
这是文段的中文翻译:
前人的工作Squirrel [71]通过识别所有SQL语句之间的数据依赖关系,来帮助实例化查询操作数,如表名和列名。然而,Squirrel构建的依赖图将多个SQL语句紧密地耦合成一个静态图。当处理复杂的查询时,它不能更新数据依赖关系来反映动态的SQL上下文。具体来说,它保持整个查询序列中的静态数据依赖关系不变,并不能调整自己来适应不断变化的SQL语句之间的关系。为了解决这个限制,我们设计了一个基于上下文的IR实例化算法。它根据SQL上下文动态地更新数据依赖关系,并将准确的具体值填充到查询骨架中。与其将多个SQL语句分组到一个依赖图中,SQLRight一次解决一个SQL语句。它只在解决多个SQL语句时将必要的依赖信息保存到库中。
这是文段的中文翻译:
图4展示了SQLRight如何利用上下文来实例化SQL IR。¹[1]为了生成新的查询,SQLRight忽略了所有已有的操作数,并给它们分配不同的名字。第一条语句创建了一个有三列的表。由于这是一个创建操作,SQLRight分配了v0作为表名,c1、c2和c3作为列名,并将这些名字保存到上下文中。当我们需要表和列时,SQLRight可以快速地从当前上下文中检索它们。第二条语句向一个表中插入一行。SQLRight在当前上下文中搜索,发现只有一个可用的表,即v0和它的三列。因此,它在这里使用v0,并准备一行三个值。下一条语句改变了一个列名。在当前上下文中搜索后,SQLRight发现一个表名v0和三个相关的列名c1、c2和c3。²[2]根据语义,它使用v0作为表名,并随机地选择c3作为旧的列名。它还分配了另一个列名c4作为新的名字。³[3]在这条语句之后,SQLRight更新上下文,从v0中删除c3并添加c4。⁴[4]当它处理SELECT语句时,它可以找到表名v0和三个相关的列,排除旧的c3。⁵[5]相比之下,Squirrel的原始方法将无法捕捉ALTER所引起的动态变化,并可能在SELECT中使用c3作为v0的列名。
小番外:
- alter的用法:
数据库中alter的用法是指在不删除表的情况下修改表的结构,如添加、删除或重命名列,更改列的数据类型,添加或删除约束等。alter语句的一般格式是:
ALTER TABLE 表名 [操作类型] [操作对象] [其他选项]
- 这里的随机指的是作者从c1,c2,c3中选择了c3
3.1.4 Non-determinism Mitigation
一些数据库管理系统(DBMS)的功能包含了非确定性的行为,这会导致即使没有逻辑错误触发,两次执行也会产生不同的结果。这样的查询会混淆DBMS的预言机(oracle),并导致误报。之前专注于内存相关错误的模糊测试工具(fuzzer)不关心查询结果,只是忽略了这个问题。因此,我们不能直接使用它们生成的查询来寻找逻辑错误。为了避免这一类的误报,我们识别并移除具有非确定性行为的语句或关键字。