标题和作者
结构脉络大概如下:
- 引言:介绍了数据库管理系统(DBMS)的重要性和逻辑错误的危害,回顾了现有的检测逻辑错误的技术,如Oracle和覆盖率引导的模糊测试,提出了将两者结合的动机和挑战,概述了本文的主要贡献和创新点。
- 背景与挑战:举例说明了一个SQLite的逻辑错误,介绍了Oracle的原理和种类,分析了覆盖率引导的模糊测试的优势和局限,指出了生成有效SQL查询和支持多种Oracle的难题。
设计:提出了两个解决方案,一是有效性导向的查询生成,包括协作突变、专用解析、基于上下文的IR实例化和非确定性消除等策略,以提高查询的语法和语义正确性;二是通用接口的Oracle开发,包括预处理、添加输出、转换和比较等API,以简化Oracle的实现和集成。- 实现:介绍了SQLRight的原型系统,基于Squirrel和SQLancer,支持四种Oracle,分别是NoREC, TLP, Index和Rowid,并针对不同DBMS定制解析器。还介绍了一些实现细节,如内存高效突变、错误定位和查询最小化等。
- 评估:在SQLite, MySQL和PostgreSQL三个DBMS上评估了SQLRight的效果,回答了四个问题:SQLRight能否检测到真实世界的逻辑错误?SQLRight能否比现有工具找到更多错误?覆盖率引导如何指导测试?查询有效性如何帮助检测错误?结果显示SQLRight在60天内发现了18个逻辑错误,超过了SQLancer和Squirrel+oracle,并且覆盖率引导和查询有效性都对发现逻辑错误有积极作用。
- 相关工作:总结了相关领域的研究进展,包括DBMS测试、覆盖率引导的模糊测试、Oracle设计等,并分析了本文与它们的区别和联系。
- 结论与未来工作:总结了本文的主要贡献和创新点,展望了未来可能的改进方向,如支持更多DBMS、开发更多Oracle、提高查询多样性等。
Abstract
这篇论文的作者提出了一种新的方法来检测数据库管理系统(DBMS)的逻辑错误。他们结合了基于覆盖率的引导、面向有效性的突变和预言机来检测DBMS系统中的逻辑错误。他们首先设计了一套通用的API,以便开发人员可以轻松地将模糊测试工具移植到DBMS测试中,并为现有的模糊测试工具编写新的预言机。然后,他们提供了面向有效性的突变,以生成高质量的查询语句,以便发现更多的逻辑错误。他们的原型系统SQLRight在性能上超越了那些只依赖预言机或代码覆盖率的现有工具。总的来说,SQLRight在两个经过充分测试的DBMS(SQLite和MySQL)中检测到了18个逻辑错误。所有的错误都已经得到确认,其中14个已经被修复。
- 模糊测试,是一种自动化软件测试方法,通过向系统注入非法、畸形或非预期的输入,以揭示软件缺陷和漏洞。
- 覆盖引导(coverage-guided),“Coverage-guided” 是一种软件测试方法,它是为了帮助发现程序中的错误或漏洞而设计的。通俗来说,就好像你在地图上规划一条路线,然后用GPS 导航来引导你。在软件测试中,“Coverage-guided” 就像是一种导航系统,它帮助测试人员或工具找到程序中尚未测试到的部分,以确保尽可能多地覆盖代码。这个方法通过追踪程序执行时经过的代码路径来工作。如果测试覆盖率不够,就会有可能错过一些潜在的问题。“Coverage-guided” 测试会不断地尝试各种输入,以尽可能多地覆盖程序中的不同代码路径。当测试发现新的代码路径或分支时,它会将这些信息反馈给测试人员或工具,以便进一步测试这些路径,从而提高软件的质量和稳定性。
- 面向有效性的突变(Efficiency-oriented Mutation)这是一种方法,用于生成高质量的查询语句,以便更有效地测试DBMS系统。在面向有效性的突变中,测试人员或测试工具会对现有的测试输入进行变异,以生成新的测试用例。这些变异的目标是确保测试输入仍然具有有效性,即输入仍然是有效的输入。有效性在这里通常指的是输入是否符合程序的预期输入格式和约束条件。如果生成的测试输入无效,那么它们不太可能用于有效测试。
- 预言(Oracles),是一种用于检测数据库管理系统(DBMS)逻辑错误的技术。逻辑错误是指DBMS返回了不正确的结果,而不是崩溃或断言失败。**Oracles的核心思想是构造一些功能等价的查询,也就是说,对于同一个数据库,这些查询应该返回相同的结果。**如果DBMS对不同的查询返回了不一致的结果,那么就说明存在一个逻辑错误。文章中提到了四种Oracles,分别是NoREC, TLP, Index和Rowid,它们都有各自的转换和比较方法。Oracles可以与覆盖率引导的模糊测试相结合,提高检测逻辑错误的效率和效果。具体包括:
- NoREC:这种Oracle通过将WHERE子句中的条件移动到SELECT表达式中,来生成语义等价的查询。这样可以避免一些优化 对原始查询的影响,比如索引和视图。如果两个查询的结果行数不同,就说明可能有逻辑错误。
- TLP:这种Oracle通过将WHERE子句中的一个条件x分解为三个子查询:x IS TRUE, x IS FALSE, 和 x IS NULL。然后将三个子查询的结果合并起来,与原始查询的结果进行比较。如果不一致,就说明可能有逻辑错误。
- Index:这种Oracle通过在数据库中插入或删除不同的索引来生成语义等价的查询。索引可以加速查询的执行,但不应该影响查询的结果。如果有索引和无索引的查询返回了不同的结果,就说明可能有逻辑错误。
- Rowid:建表的时候without rowid
擦,实在是晦涩难懂
Introduction
数据库管理系统(dbms)广泛用于数据密集型程序,帮助数十亿设备托管数万亿数据库[23,51,54 - 57]。dbms中的任何错误都会影响大量用户。尽管在测试DBMS系统上花费了很多努力[10,58,70,71],但大多数都集中在寻找最终导致执行意外终止的崩溃和断言失败上。它们无法检测导致DBMS返回意外结果的逻辑错误,比如泄漏额外的行。由于逻辑错误通常不会使程序崩溃,因此我们需要一个oracle来确定每次执行是否产生正确的结果。然而,由于各种语言方言和特点,构建oracle非常耗时且容易出错[49]。
DBMS oracle的最新进展揭示了逻辑错误检测[43-45]。例如,Rigger等人通过将SQL查询转换为语义等价的形式来构造几个通用的oracle。一个DBMS可以用不同的代码路径处理这些查询,但最终结果应该是相同的。任何不一致都表明存在潜在的逻辑错误。SQLancer是实现这些oracle的工具,它已经成功地发现了许多逻辑错误[30,42]。然而,SQLancer依赖于基于规则的生成器来合成原始查询,这可能会限制其探索程序状态的能力。具体来说,**它基于特定DBMS的语法和底层数据库的模式为WHERE和JOIN子句创建表达式。考虑到巨大的查询空间,它可能会在类似的查询上投入大量的时间和精力,而这些查询无法检查不同的程序代码。**覆盖率引导的程序测试或模糊测试已被用于测试广泛的程序,并成功地发现了数千个与内存相关的错误[18,27,32,48,70]。**模糊测试的核心思想是利用代码覆盖率来指导输入生成。**模糊测试工具(fuzzer)从队列中获取输入,并随机更新它以生成新的测试用例。然后,它运行带有新测试用例的程序,同时收集正在执行的代码(如基本块或分支)。如果执行触发了新的代码,则fuzzer将把新的测试用例添加到队列中,并将其用于将来的突变;否则,fuzzer将放弃新的测试用例,并继续更改队列中的另一个输入。以前的工作应用覆盖引导的模糊测试来测试dbms,并展示了检测内存相关错误的好处[10,60,71]。然而,没有任何研究尝试将基于覆盖率的指导应用于逻辑错误检测。
- 大多数的测试都在寻找最终导致执行意外终止的崩溃和断言失败上,对于逻辑错误关注较少
- 将SQL查询转换为语义等价的形式来构造几个通用的oracle,能够成功的发现逻辑错误,但是普适性、鲁棒性很差
- 覆盖率引导的程序测试或模糊测试已被用于测试广泛的程序,但并没有应用于逻辑错误检测
本文旨在了解基于覆盖率的指导在逻辑缺陷检测方面的好处,特别是在DBMS系统中。该研究揭示了采用当前基于突变的模糊测试器来发现逻辑错误面临的若干挑战。主要的问题来自生成有效的SQL查询。因为我们需要比较DBMS执行的结果,所以生成的查询应该通过语法和语义检查,并成功地产生有意义的输出。现有的模糊器可以合成触发断言失败和崩溃的查询,但其中许多都不是完全有效的,因此不能用于查找逻辑错误。
为了解决这个问题,我们提出了面向有效性的变异,定义了一组策略来提高生成的查询的有效性。首先,我们设计了一种自动转换各个DBMS的SQL解析器以实现模糊测试的方法。目前,大多数模糊测试器使用一个解析器来处理不同的DBMS系统[10,71]。由于许多dbms使用自己的SQL语言来支持独特的[49]特性,统一的解析器可能会产生许多不兼容的查询。更糟糕的是,错误的查询可能会触发错误处理代码,并根据覆盖率指导进行优先级排序。因此,我们为每个DBMS提供一个查询解析器,以减少无效查询。我们的第二个努力是构建一个基于上下文的实例化算法,该算法强制执行SQL元素之间的准确依赖关系。例如,DROP TABLE X从当前数据库中删除表X。我们的方法将遵循语义,并从上下文中删除相应的表,以便它不会用于以下语句。我们还考虑了oracle产生更有用查询的需求。具体来说,我们允许oracle将必要的元素标记为不可变的。我们的协作式变异引擎将避免改变这些元素,同时随机更新其他元素。最后,消除查询中的不确定性行为,以避免不必要的误报。通过这些方法,有效地提高了查询的有效性。
另一个阻碍fuzzing检测逻辑错误的问题是fuzzer与DBMS oracle之间缺乏统一的接口。大多数模糊测试器只是简单地依赖于操作系统或各种清理程序来检测bug[28, 46, 47],而检测逻辑错误需要生成适当的SQL语句并检查执行结果。为了弥补这一不足,我们设计了一套富有表现力的api来简化DBMS oracle的实现。我们的api解耦了模糊测试逻辑和oracle逻辑。开发人员可以专注于一个领域,无论是模糊测试还是oracle,从而轻松地应用现有的方法来检测逻辑缺陷。我们实现了一个SQLRight系统,它结合了基于覆盖率的指导、面向有效性的突变和oracle来检测DBMS系统的逻辑错误。SQLRight首先以协作方式修改现有查询。它插入一组oracle必需的语句,并应用面向有效性的变更来提高有效性。然后,它将查询发送给oracle,以创建功能等效的查询对应项。SQLRight将所有生成的查询提供给DBMS,并收集执行结果和覆盖率信息。然后,SQLRight调用oracle来比较不同查询的结果,以识别逻辑错误。最后,将提高覆盖率的查询插入到队列中,以备将来发生变化时使用。我们实现了四个oracle,包括之前工作[43,44]中提出的两个和本文提出的两个。
在3个流行的数据库管理系统SQLite[51]、PostgreSQL[39]和MySQL[34]上对SQLRight进行了测试。SQLRight在60天内成功检测到18个逻辑错误。我们已经向他们的开发人员报告了所有的发现:所有的bug都得到了确认,14个bug已经修复。为了理解代码覆盖率和查询有效性的贡献,我们通过逐个禁用它们来进行单元测试。我们的评估表明覆盖率和有效性都有助于发现更多的逻辑错误,但后者的贡献比前者更大。我们还将所提出的系统与最先进的工具进行了比较,包括SQLancer(使用oracle来检测逻辑性错误)和Squirrel(使用代码覆盖率来检测崩溃和断言失败)。我们将oracle api移植到Squirrel来帮助它检测逻辑错误。测试72小时,SQLRight报告了12个独特的逻辑错误,Squirrel检测到一个bug, SQLancer没有发现任何bug。结果表明,将基于覆盖率的引导与oracle可以触发DBMS系统中更多的逻辑错误。
总结本文的主要贡献如下
- 研究了基于覆盖指导的模糊测试在DBMS系统中检测逻辑错误的有效性,确定了两个有用的因素:查询有效性和代码覆盖率。
- 实现了SQLRight,这是一个基于覆盖率的模糊测试器,用于检测dbms的逻辑错误。SQLRight提供通用api以简化oracle开发,并嵌入面向有效性的变更以提高查询质量。
- 在实际的DBMS系统上评估SQLRight,发现18个逻辑错误。单元测试展示了工具不同组件的贡献。我们在https: //github.com/psu-security-universe/sqlright上发布了SQLRight的源代码,以帮助增强DBMS系统的安全性和健壮性。
2 Background & Challenges
2.1 An Example Logical Bug
2.2 Oracles for Logical Bug Detection
removing indexes from the database should not affect the query result.
2.3 Coverage-guided Testing
覆盖引导测试(Coverage-guided testing,简称[32])已被广泛用于测试大量程序,并成功发现了数千个错误[18,27,48,70]。现代模糊测试工具fuzzers利用代码覆盖率来指导输入选择和变异。具体来说,给定一个程序输入,模糊测试器首先随机更新其内容以生成一组新输入。它向程序提供新的输入,并监视程序的执行。如果程序崩溃或报告断言失败,模糊器会将输入视为底层bug的概念验证(proof-of-concept, PoC)。对于没有触发崩溃的输入,模糊器将检查执行是否到达新的代码路径,如基本块或分支。如果是,它会将输入添加到队列中。否则,它将丢弃输入,并从队列中获取下一个输入进行下一轮模糊测试。覆盖指导测试已被用于测试各种程序,包括但不限于操作系统[11,22,37,63,65],编译器[10,19,38],web浏览器[17,48,66],文档阅读器[12,64],甚至智能合约[20,35,62]。最近的工作也移植了覆盖引导的模糊测试来测试DBMS系统[21,26,60,71]。图1展示了Squirrel[71]的概述,这是一项最近的工作,旨在从DBMS系统中检测崩溃和断言失败。Squirrel以一组输入(即SQL查询)作为模糊测试的种子。首先将查询转化为包含大量结构信息的中间表示(intermediate representation, IR);然后,Squirrel采用3种变异方法修改查询IR并创建新IR,包括插入、删除和替换节点;对于每个新生成的IR, Squirrel构建不同操作数(例如表和列)之间的数据依赖关系图,并用随机生成的字符串填充操作数。之后,它将新的IR转换回查询并将其提供给DBMS。最后,Squirrel会报告崩溃,并为下一轮模糊测试设置揭示代码的查询的优先级。Squirrel成功地从SQLite等常用DBMS系统中发现了一组崩溃和断言失败。
2.4 Challenges of Fuzzing Logical Bugs
据我们所知,还没有人尝试用覆盖指导的模糊测试来测试dbms的逻辑缺陷。目前主要有两个挑战阻碍了fuzzing和DBMS oracle的结合。首先,目前的模糊测试器仍然不能生成高质量的SQL查询。例如,在最近的工作中,Squirrel嵌入了两种新技术,特别是保持语法的变异和语义引导的实例化,旨在提高自动生成查询的有效性。然而,即使采用了这些先进的技术,Squirrel对SQLite的有效性也只能达到30%左右,对其他DBMS系统的有效性甚至更低。在测试dbms崩溃和断言错误时,我们可以容忍如此低的有效性,因为无效查询仍然可能触发一些bug。但是,一个查找逻辑错误的工具不能利用任何无效的查询,因为DBMS不会产生任何有意义的结果,oracle也无法工作。其次,目前的模糊测试器主要依赖操作系统和清理程序来检测bug(如断言失败)[13,28,46,47]。它们不能与DBMS oracle协作检测逻辑错误。为了支持不同的DBMS oracle,我们需要重新设计模糊器的体系结构。
3 Design of SQLRight
本文提出了两种实用的解决方案来应对上述挑战,并采用了基于覆盖率的DBMS逻辑错误测试指南。首先,提供了面向有效性的生成,其中包含一组策略来生成有效的、确定的SQL查询(§3.1)。生成的查询不仅在语法和语义上达到了较高的有效性,而且能够排除可能导致误报的随机行为。其次,设计了一套通用、全面的api来支持开发新的DBMS oracle(§3.2)。这些api将oracle和模糊器解耦,使用户更容易测试DBMS系统。它们还帮助DBMS开发人员采用基于覆盖率的指导来查找逻辑错误。系统概述。图2展示了我们的工具SQLRight的概述,这是第一个结合了基于覆盖率的指导、面向有效性的突变和oracle来为DBMS系统查找逻辑错误的测试平台。它接受目标程序(即DBMS)和一组示例查询(即SQL语句)作为输入,并将生成逻辑错误报告。首先,SQLRight将所有示例查询添加到队列中。对于每一轮的模糊测试,它从队列中提取一个查询,并应用变异来生成新查询。然后,更新查询操作数,如表名和列名。之后,SQLRight将新查询发送给DBMS,并检查执行结果以识别意外行为。如果新查询触发新代码,SQLRight将查询添加到队列中以供将来测试。与传统的memorybug模糊器(如图1中的Squirrel)不同,SQLRight与DBMS oracle合作产生高质量的查询来识别逻辑bug。具体来说,对于mutation查询,它调用oracle api来更新oracle特定的SQL语句,为结果检查做准备。为了验证查询,它再次调用oracle api将查询转换为语义等价的变体。执行之后,它依赖于oracle来决定查询是否会触发逻辑错误。
3.1 Validity-oriented Query Generation
SQLRight需要高质量的SQL查询来强调不同dbms的各个方面。任何导致语法或语义错误的查询对于查找逻辑错误都没有用处。不幸的是,生成语义正确的查询已被证明是np难的[29]。最近的模糊器利用基于类型的变异和语义引导的实例化来产生有效的查询[71]。然而,它们的测试正确率约为30%,还不足以有效地测试DBMS系统。更糟糕的是,大多数生成的查询还没有准备好检测逻辑错误。因此,提出了一些实用的技术来提高查询的有效性。
3.1.1 Cooperative Mutation
SQLRight并行采用两种独立的变异策略来生成查询集的不同组件。如图3所示,它维护两个队列:select队列只包含select语句,用于生成产生输出的适当的select查询;普通队列承载其他语句,用于为测试准备数据库,如创建表和插入值。在模糊测试初始化期间,SQLRight扫描所有种子输入,将所有SELECT语句保存到SELECT队列,并将其他语句保存在普通队列中。对于每一轮模糊测试,SQLRight从普通队列中收集一组语句,并依赖普通变异引擎生成新查询。
然后,调用oracle的协作mutation来创建和添加多条SELECT语句。在组合之后,我们利用实例化来构建一个具体的查询集。在协同变异过程中,oracle会保留对正确性测试有用的查询元素。例如,oracle NoREC要求SELECT语句同时包含FROM子句和WHERE子句。我们为oracle提供了接口,通知mutation引擎不要删除这些必要的节点,如果原始语句没有节点,则不要添加新节点。图4说明了如何控制语句SELECT COUNT(*) FROM v0的变化,其中v1=0。给定查询的IR,我们为三个节点添加属性,具体来说是select_statement、FROM和WHERE,将它们标记为不可变的。因此,oracle NoREC的关键组件得以保留。变异引擎仍然可以完全灵活地更新from_clause节点和where_clause节点以生成新的SELECT语句。基于ir的变异引擎支持from_clause子句和where_clause子句中的各种模式和不同条件,因此提供了与无约束的mutator相当的丰富功能[71]
相当于做了约定,限制部分(阴影部分)不变,只改变其他部分。
3.1.2 Dedicated Parsing (专用的解析)
我们为每个DBMS定制变异引擎,以提高语法的正确性。大多数流行的DBMS系统都有自己定制的SQL方言,这些方言共享的功能有限。因此,针对常用功能的统一SQL语法只涵盖了DBMS代码的一小部分,无法发现DBMS特有功能[49]的缺陷。相比之下,一个旨在支持所有功能的万能语法可能会导致许多无效查询。清单3显示了一个带有单引号符号的字符串在不同dbms中具有不同含义的示例。SQLite可以成功运行此查询,因为它将单引号字符串默认视为常量,或者将当前位置不允许的字符串视为标识符。然而,PostgreSQL报告了一个错误,因为它从不接受单引号字符串作为标识符。为了解决这个问题,我们设计了一个工具来自动地将各个DBMS的解析器移植到SQLRight中。我们观察到,大多数流行的DBMS系统都使用GNU Bison[14]来编译它们的前端解析器。因此,我们遵循Bison定义的语法规则,将DBMS解析器前端转换为SQLRight的IR。得益于文档完善的Bison format,我们的工具可以轻松地为SQLRight移植不同的解析器。SQLRight前端采用原有的DBMS解析器,能够支持各个DBMS的全部方言语法,保证语法的正确性,从而提高查询的有效性。
- 作者发现 most popular DBMS systems use GNU Bison [14] to compile their parser front-ends.
- Therefore, we follow the grammar rules defined by Bison and translate the DBMS parser front-ends to SQLRight’s IR.即作者根据完善的Bison文档,遵循不同的语法规则,将DBMS的前端解析成SQLRight的IR。
3.1.3 Context-based IR Instantiation (基于上下文的IR实例化)
之前的工作Squirrel[71]确定了所有SQL语句之间的数据依赖关系,以帮助实例化查询操作数,如表名和列名。然而,Squirrel构建的依赖图将多条SQL语句紧密耦合为一个静态图。在处理复杂查询时,无法通过更新数据依赖关系来反映SQL上下文的动态变化。具体来说,它在整个查询序列中保持静态数据依赖关系不变,无法进行自我调整以适应不同SQL语句之间不断变化的关系。为了解决这一限制,设计了一种基于上下文的IR实例化算法。它根据SQL上下文动态更新数据依赖关系,并将准确的具体值填充到查询骨架中。SQLRight不是将多个SQL语句组合到一个依赖关系图中,而是一次解决一条SQL语句。当解决多条SQL语句时,它只将必要的依赖信息保存到库中。
清单4显示了SQLRight如何利用上下文实例化SQL IR。要生成新查询,SQLRight会忽略所有现有的操作数,并为它们分配不同的名称。第一条语句创建一个包含三列的表。因为这是一个创建操作,所以SQLRight分配v0作为表名,c1、c2和c3作为列名,并将这些名称保存到上下文中。当我们需要表和列时,SQLRight可以从当前上下文中快速检索它们。第二条语句向表中插入一行。SQLRight在当前上下文中搜索,只找到一个可用的表,即有三列的v0。因此,它在这里使用v0并准备一行包含三个值。下一条语句修改一个列名。在当前上下文中搜索之后,SQLRight会找到一个表名v0和三个关联的列名c1、c2和c3。根据语义,它使用v0作为表名,并随机选择c3作为旧列名。它还分配另一个列名c4作为新名称。在这条语句之后,SQLRight更新上下文,从v0中删除c3并添加c4。对于SELECT语句,它可以找到表名v0和三个相关的列,不包括旧的c3。相比之下,Squirrel的原始方法将无法捕获ALTER的动态更改,并可能使用c3作为SELECT的v0列名。
即,在SQLRight中,对于有上下文关系的sql语句,SQLRight会根据其执行的顺序,来动态的更新上下文,从而进一步判断逻辑错误。如Squirrel这样的原始方法,无法捕获像alrer这样的动态更新,在最后一个sql中仍然会判断c3列存在。这样有利于找出更多的逻辑错误,且更准确,且符合sql在实际环境中运用的效果。
3.1.4 Non-determinism Mitigation(非确定性缓解)
一些DBMS功能包含不确定行为,这使得两次执行即使没有触发逻辑错误也会产生不同的结果。这样的查询会混淆DBMS oracle并导致误报。以往专注于内存相关bug的模糊测试器并不关心查询结果,直接忽略了这个问题。因此,我们不能直接使用他们生成的查询来查找逻辑错误。为了避免该类别中的误报,识别并删除具有不确定性行为的语句或关键字。目前,SQLRight将不确定性行为分为三类。一类设计了返回随机结果的函数。例如,SQLite中的random()可以生成一个伪随机整数。另一类查询的结果依赖于不断变化的环境变量,如日期和时间(例如SQLite中的julianday()和PostgreSQL中的current_timestamp)。清单5显示了一个使oracle报告一个虚假警报的示例。这个例子创建了两个表,有ROWID的v0表和没有ROWID的v2表。根据SQLite文档,对这两张表进行相同的查询应该返回相同的结果。然而,由于random()的随机行为,两次执行产生了不同的结果,oracle将报告一个潜在的错误(假阳性)。用常量值替换random()或删除INSERT语句可以解决这个问题。不确定性的第三个来源是未定义的行为。具体来说,DBMS标准中没有指定结果,它完全依赖于每次动态执行来(随机地)决定如何生成结果。例如,LIMIT子句将SELECT的结果最多分成N行。它旨在防止大量输出。然而,LIMIT子句导致语义相同的查询之间的结果不一致,因为它取决于DBMS决定返回哪N行。我们从所有生成的SQL查询中删除了LIMIT子句。
对于某些多次执行,没有逻辑错误,也会产生不同结果。为了避免混淆,采取措施——识别并删除具有不确定性行为的语句或关键字
- 一类设计了返回随机结果的函数。例如,SQLite中的random()可以生成一个伪随机整数。另一类查询的结果依赖于不断变化的环境变量,如日期和时间。
对策:将随机值替换为常量- DBMS标准中没有指定结果,它完全依赖于每次动态执行来(随机地)决定如何生成结果。例如,LIMIT子句将SELECT的结果最多分成N行。它取决于DBMS决定返回哪N行。
对策:删除limit子句
3.2 General Interfaces for DBMS Oracles (用于DBMS oracle的通用接口)
oracle对于检测逻辑缺陷至关重要,但是没有oracle能够检测所有DBMS系统的所有缺陷。考虑到各种不同的SQL方言和扩展[49],我们需要多种不同的oracle来覆盖不同的逻辑缺陷。使我们的模糊测试平台支持许多不同的oracle是很重要的。在这项工作中,我们提出了一组通用api,允许开发人员采用现有的oracle并开发新的oracle。
SQLRight为fuzzer提供了四个通用api:
- preprocess():preprocess()将查询集作为输入,并执行必要的操作,为以后的步骤做好准备。这里的一个常见任务是检查oracle在给定查询集上的适用性。如果需要,oracle可以修改查询集以生成兼容的查询。例如,oracleRowid试图从查询中查找CREATETABLE语句。如果没有创建表,它将通知模糊器跳过当前查询,继续下一个查询。Oracle索引标识并从查询集中删除CREATE UNIQUE Index语句。根据设计,具有唯一索引的列将不允许插入重复的值,因此具有或不具有该索引将影响最终结果,并可能导致误报。
- append_output():由于我们需要检查DBMS执行的结果来识别错误,SQLRight为每个oracle提供了append_output()API来插入正确的输出生成语句。SELECT语句查询底层数据库并返回结果,因此此API的默认行为是将几个SELECT语句附加到给定查询集的末尾。在查询验证期间,这些语句将填充正确的表名和列名,以便它们能够产生有意义的结果。Oracle可以有其特殊的输出语句,也可以根据其功能应用其他策略。例如,因为oracleNoREC将条件从WHERE转移到SELECT,所以它只附加具有from子句和WHERE子句的SELECT语句(这两个子句对于SELECT都是可选的)。
- transform():这个API用于将一个查询转换为一个或多个功能等价的查询,例如通过改变WHERE条件或者添加或删除索引等方式。SQLRight会执行所有转换后的查询,并收集它们的结果。oracle的核心任务是将一个查询转换为不同的等效变体。transform()接受查询集作为输入,并返回一个或多个等效的查询集。例如,oracleNoREC只返回一个变量,其中原始查询中的where条件被移动到SELECT表达式。Oracle索引可能会在给定的查询集中插入或删除各种CREATE Index语句,因此可能导致多种变体。SQLRight执行transform()返回的所有变量,并比较它们的结果。
- compare():这个API用于定义Oracle自己的比较方法,来判断不同查询的结果是否一致。如果不一致,说明可能存在一个逻辑错误。比较方法可以是简单地要求所有结果完全相同,也可以是根据具体的Oracle功能进行一些松弛的比较,例如只比较结果中的行数等SQLRight允许oracle定义自己的比较方法来识别意外结果。一个简单的比较算法可能要求所有结果完全相同。然而,这种方法可能过于严格,并可能引入错误警报。例如,在Index oracle的结果中,由于额外的索引,行的顺序可能会有所不同。因此,我们提供了一个松散的比较函数,它只检查结果中的行数。但是,如果在SELECT中使用聚合函数(如MIN和SUM),则比较应该期望相同的输出。
我们采取特殊操作来处理oracleNoREC,因为新的查询表单总是为每个记录生成一行(根据条件为TRUE或FALSE)。在这种情况下,compare()API应该比较原始结果中的行数和转换结果中的TRUE行数。。
4 Implementation
我们实现了SQLRight作为第一个用于查找逻辑错误的基于覆盖率指导的DBMS模糊器的原型。我们的实现基于Squirrel[71]和SQLancer[30]。具体地,采用Squirrel的query-mutation模块,实现了通用oracle api和面向有效性的mutation。目前,SQLRight支持4种oracle,包括从SQLancer[43,44]移植而来的NoREC和TLP,以及本文提出的Index和Rowid。我们将SQLite、MySQL和PostgreSQL的解析器移植到SQLRight中,以有效地测试这些dbms。接下来,我们将介绍几个具体的实现细节,对今后开发其他DBMS模糊器有所帮助。
节约内存突变。我们重新设计了存储SQL查询的方式,以减少内存使用。Squirrel使用一个库将每个节点类型(如select子句)映射到一组IR节点。在插入或替换时,它会访问库以检索一组与类型匹配的候选项。尽管将IR节点保存在内存中会加速突变,但每个IR节点都会占用大量内存,测试几天后库将占用数百gb的内存。例如,Squirrel分配了超过16KB来存储清单1中的简单查询。为了减少内存的使用,我们将查询字符串而不是IR存储在内存中,并将字符串指针保存在库中。由于字符串要小得多(如清单1中的117B),因此可以在低资源平台上运行模糊测试,并轻松启动大量实例。需要权衡的是,每次我们改变查询时,SQLRight需要将字符串再次解析为IR,这将减慢mutation的速度。§5.2中的评估表明,在同一时间段内,SQLRight比Squirrel生成的查询更少。然而,由于面向有效性的设计,SQLRight在生成有效查询时实现了更快的速度,这有助于它在覆盖率方面优于Squirrel。
错误二分查找法。该文采用bug二分法识别重复bug报告[21]。对于每个bug报告,我们使用二分查找算法在所有代码提交中定位最初引入bug的提交。乐观地假设一个提交会引入一个bug,我们使用第一个有bug的提交来标记潜在的bug。如果两个查询共享相同的第一个错误提交,我们只报告其中一个,并将另一个视为重复。我们使用Python实现了bug二分器,它以触发bug的查询作为输入,并自动定位第一个有bug的提交。当遇到新的提交时,bisector会编译新版本并使用bug触发查询进行测试。为了加快二分速度,我们将每个DBMS可执行文件保存到一个缓存系统中,并在缓存中快速查找已构建的二进制文件。如果缓存了所有必需的版本,我们的分割线可以在一秒钟内完成任务。请注意,fossil [41], SQLite的版本控制系统,提供了bisect命令来协助对bug进行二分。然而,它只计算中间的提交并相应地更新代码。我们的bisector将编译代码并自动验证测试结果。
查询最小化程序。bug触发查询可能包含许多复杂语句,开发人员难以对其进行诊断。因此,我们开发了一个实用程序,可以自动最小化bug触发查询。我们的最小化器使用SQLRight的IR delete操作,它从查询IR表示中删除一个节点。在删除一个节点后,如果剩余的查询仍然有效并且可以触发bug,我们将从当前语句中删除更多节点;否则,我们将重新添加该节点,并继续删除下一个节点。最小化算法保持删除过程,直到删除任何节点都会导致剩余部分无效或遗漏bug。在这种情况下,我们将报告当前查询为最小版本
5 Evaluation
我们在现实世界中流行的DBMS系统上评估我们的工具SQLRight,以回答以下问题:
Q1. Can SQLRight detect real-world logical bugs? (§5.1)
表2:检测到的Bug。SQLRight检测到27个错误,包括18个逻辑错误、5个崩溃和4个断言失败。我们已经向他们的开发人员报告了这些错误,并得到了27个确认和23个修复。D(深度)是指触发错误的突变数量。
Q2.SQLRight能找到比现有工具更多的错误吗?(§5.2)
Q3。代码覆盖率如何指导测试?(§5.3)
Q4。查询有效性如何帮助检测错误?(§5.4)
实验设置。为了回答Q1,我们使用SQLRight测试了三个流行的DBMS系统,SQLite、MySQL和PostgreSQL,它们在以前的工作中通常用于评估DBMS的错误查找工具[21,43–45,71]。对于Q2,我们将SQLRight与SQLancer和Squirrel进行比较,后者是最先进的DBMS错误查找工具。由于Squirrel无法检测逻辑错误,我们将预言机移植到Squirrel+预言机。为了回答Q3,我们将覆盖率反馈与三种处理新查询的方法进行了比较:全部丢弃、全部保存和随机保存一些。为了回答Q4,我们分析了SQLRight的每个查询有效性组件,以了解其对代码覆盖率、查询有效性和错误检测的影响。
我们在Ubuntu 20.04系统上进行了SQLite实验,该系统具有两个28核Intel(R)Xeon(R)Gold 6258R CPU和791GB内存。我们在三台使用Ubuntu 20.04系统、8核Intel(R)Core(TM)i7-10700 CPU和64GB内存的计算机上进行了PostgreSQL和MySQL的实验。由于SQLancer需要特定的SQLite版本3.34.0,因此我们采用此版本进行评估。对于其他数据库管理系统,我们选择最新版本,特别是PostgreSQL 14.0和MySQL 8.0.27。我们使用AFL[70]编译SQLite和PostgreSQL,但将覆盖图大小扩大到256K,以减少冲突问题[15]。由于MySQL是多线程的,为了避免更新覆盖图时的数据竞争,我们用区块覆盖和禁用命中计数来实现它。我们从每个DBMS的官方单元测试中收集种子语料库,特别是SQLite TCL测试脚本、PostgreSQL官方测试基础设施和MySQL单元测试样本。我们对SQLRight和Squirrel使用相同的种子语料库。SQLancer是一个基于生成的工具,不需要任何种子输入。
5.1 DBMS Logical Bugs
即举了两个简单的例子,来说明覆盖范围、生成有效性的必要性。因为这两个bug是分别通过这两种方法测出来的
由于资源的可用性有限,我们对每个具有不同持续时间的DBMS进行错误查找实验。
具体来说,我们花了60天测试SQLite,14天测试PostgreSQL,7天测试MySQL。SQLRight总共检测到27个错误,包括18个逻辑错误、5个崩溃和4个断言失败。除了MySQL的3个逻辑错误外,其他所有错误都来自SQLite;我们没有从PostgreSQL中发现任何错误。尽管测试持续时间不同,但我们可以从以前的工作中找到类似的错误数量模式[43–45,71],其中大多数错误来自SQLite,很少来自PostgreSQL。我们已经向他们的开发人员报告了所有的发现。所有错误都已确认,其中23个已修复。SQLRight检测到的逻辑错误比崩溃和断言失败更多。由于我们设计了一组提高查询有效性的解决方案,SQLRight生成的异常语句会减少导致DBMS崩溃的次数。
表2提供了更多错误详细信息。“说明”栏表明逻辑错误来自不同的优化,许多错误是由于多个优化的相互作用造成的。例如,当表在dex中具有唯一分部时,第一个错误是由不正确的DISTINCT优化引起的:DISTINCT和unique partial INDEX单独工作很好,但当查询同时满足这两个条件时,就会出现错误。
“Oracle”列显示检测每个错误的Oracle。
大多数错误都是由NoREC检测到的,而其他oracles检测到一两个错误。事实上,多个oracles可以通过不同的查询检测到几个bug,但我们只记录第一次检测,并将其他bug视为重复。
我们介绍了两个逻辑错误的细节,以帮助演示反馈的必要性和错误发现的有效性。
附录A提供了另外三个逻辑错误的详细信息。
基于覆盖范围的反馈的必要性。清单6演示了由于不正确的索引查找导致的SQLite逻辑错误。
第一条语句创建了一个包含两列的WITHOROWID表。v1是表的PRIMARY KEY,按DESC顺序排列。第二条语句向v0插入一行,第三条语句在列v2上创建索引v3。SELECT语句搜索满足条件v2=10和v1<11的行。行(10,10)满足条件,应重新转向。但是,由于不正确的索引搜索混淆了DESC PRIMARY KEY和索引v3,SQLite不会重新转换任何内容。在清单6的末尾,我们展示了相关的种子输入,它被变异以触发错误。正如我们所看到的,原始输入缺少触发bug所需的大部分组件,如WITHOROWID、DESC、INDEX和SELECT。
即,使用了without rowid之后,在建立索引时,索引将成为主键,即v2将成为表v0的主键。但是,在建表时,已经指定了v1是主键,所以有所冲突,导致SQLlite返回了错误的结果。
从种子输入中,SQLRight必须累积七个连续的突变才能生成此错误触发查询。每个突变都会触发新的代码覆盖,并保存到队列中。如果没有代码覆盖率的指导,SQLRight很难维护如此深入的突变链,也无法检测到这个bug。
SQL有效性的必要性。通过有效性改进技术,SQLRight可以检测Squirrel+oracle和SQLancer将遗漏的错误。清单7显示了一个逻辑错误,该错误只能通过每个DBMS解析器和基于上下文的实例化来检测。第一条语句创建具有两列的表v0:v1和v2。第二行创建一个附加到两个v2的索引。第三条语句在表中插入一行。两个SELECT语句在v0中搜索v1=v2和v1='x’的行。这些条件不满足,因为v1和v2具有不同的值。但是,如果我们使用别名引用表v0,SQLite将错误地优化查询以忽略第一个条件并返回行。SQLancer无法检测到此错误,因为它在生成SELECT语句时不使用别名。尽管Squirrel+oracle在解析过程中允许使用别名,但它不将其用于实例化。如果表有别名,Squirrel+oracle会将条件解析为v1=v2,从而导致“列名不明确”的错误。
SQLRight采用了基于上下文的实例化,避免了冲突问题,成功地触发了这个bug。
即,在这个示例中,先看case1。SQLright测试中,出现了实际输出与期望输出不一致的bug,出现bug的原因是因为使用别名之后,SQLlite会自动进行sql优化,会忽略条件,并返回行。
那么是怎么找出这个bug的呢?因为SQLright的有效性生成中的上下文关联的特性,因为有了“给表设置别名”的这一限制,所以在生成where时,为属性前面增加了别名。如果换成Squirrel+oracle,表有别名,就会将sql优化成case2这样,这样就会直接报错,而不是检验出这个bug。如果换成SQLancer更不行,因为他不允许使用别名。
5.2 Comparison with Existing Tools
SQLancer包含三个oracles,NoREC、TLP和PQS。NoREC和TLP都只是修改查询语句来检测错误,SQLRight可以使用我们的通用API轻松地支持它们。
然而,PQS依赖于数据库内容来构建状态,我们考虑在未来支持它。因此,我们使用NoREC和TLP来比较SQLRight、SQLancer和Squirrel+oracle。我们在每个设置中启动五个实例,并运行它们72小时。由于SQLancer不支持NoREC for MySQL,因此我们跳过此设置。附录中的图5(NoREC)和图8(TLP)显示了评估结果。
独一逻辑错误。根据图5a b和图8a b,SQLRight报告了所有设置中最多的12个错误,包括使用NoREC的6个SQLite错误和3个MySQL错误,以及使用TLP的2个SQLite缺陷和1个MySQL错误。Squirrel+oracle只发现了1个bug,来自使用NoREC的SQLite;
SQLancer没有发现任何逻辑错误。SQLancer的空结果可能是由于它在测试这些DBMS系统中的广泛使用[43-45]。基于生成的方法已经达到了在其支持的突变中发现更多错误的极限。这表明了引入更多多样的机制来检测逻辑错误的必要性。SQLRight结合了代码覆盖率、oracles和查询有效性,可以检测到比SQLite和MySQL多得多的逻辑错误。
所有工具都没有在PostgreSQL中发现任何错误,这表明它在以前的工作中观察到了良好的代码质量[43–45,71]。
代码覆盖率。根据图5c d e和图8c d e,在三个DBMS系统上,SQLRight触发的代码覆盖率明显高于其他系统。考虑到SQLancer是一个基于生成的测试程序,并且具有很高的有效率,SQLRight对SQLancer的外部覆盖主要来自于基于覆盖的指导和功能齐全的每个DBMS解析器。尽管Squirrel+oracle也利用代码覆盖期来指导测试,但准确的解析器和基于上下文的实例化帮助SQLRight胜过这种最先进的基于覆盖率的DBMS测试仪。例如,给定相同的种子语料库,SQLRight可以正确地解析更多的查询,甚至在一开始就实现更高的代码覆盖率。随着覆盖率的提高,SQLRight能够发现更多的错误。
查询有效性。从图5f g h和图8f g h中,我们可以发现SQLancer实现了最高的查询有效性,其中超过80%的查询对SQLite有效,99%的查询对MySQL有效,超过28%的查询对PostgreSQL有效。我们的工具SQLRight对三个经过测试的DBMS实现了大约30%、25%、10%的有效性。这个结果是合理的,因为SQLancer遵循定义良好的规则来生成SQL语句,从而产生更有效的查询。
尽管如此,SQLRight实现了比Squirrel+oracle高得多的有效性,后者几乎不能为oracle生成任何有用的查询。这一结果证实了提高检测逻辑错误的有效性的必要性。另一个观察结果是,SQLancer可以在72小时内保持一致的高有效率。基于变异的模糊器,无论其配置如何,都会随着时间的推移而获得较低的有效率。原因是在模糊处理过程中,一些无效查询会触发新的代码覆盖率(例如错误处理代码)并添加到队列中。无效查询的突变可能会产生更多的无效查询,从而降低总体有效率。我们还测量了生成有效查询的速度,并获得了相同的模式,如图5i j k和图8i j k所示。
SQLancer可以生成比其他查询更多的有效查询,但其效率会随着时间的推移而变化。
总的来说,SQLRight可以发现比SQLancer和Squirrel+oracle更多的逻辑错误。它在触发更多程序代码方面也优于现有工具。尽管SQLancer可以生成高质量的查询,但由于缺乏查询多样性,它在发现新的逻辑错误方面效率较低。
5.3覆盖率反馈的贡献
我们将SQLRight与三种不同的反馈方法进行了比较:
SQLRightdrop删除所有生成的查询,并且只更改种子输入;SQLRightsave将所有生成的查询保存到队列中(无论它们是否触发新的覆盖率),并轮流对每个查询进行变异;SQLRightrand将所有生成的查询的10%随机保存到队列中。我们使用带有NoREC和TLP的SQLite进行单元测试。对于每个设置,我们在24小时内并行运行五个实例。图6显示了代码覆盖率和逻辑错误的平均结果。我们还测量了不同深度的查询的贡献。具体来说,如果查询是通过N个突变从种子输入生成的,我们将其深度定义为N。表3显示了关于查询深度的代码覆盖率的细分。
独特的逻辑错误。图6a和b显示了检测到的错误数量。对于NoREC和TLP,SQLRight报告的错误最多,包括4个使用NoREC的错误和2个使用TLP的错误。使用NoREC,SQLRightdrop和SQLRightsave报告了2个错误,而SQLRightrand只检测到1个。在没有代码覆盖期的情况下,SQLRight使用TLP没有发现任何逻辑错误。
代码覆盖率。根据图6c和d,SQLRight实现了最高的代码覆盖率:使用NoREC的40.1K分支和使用TLP的39.2K分支。SQLRightdrop检测到使用NoREC的35.2K个分支和使用TLP的35.0K个分支,分别比SQLRight少13.7%和11.9%。
SQLRightrand和SQLRightsave触发的分支比SQLRightdrop更少。结果显示了基于覆盖范围的指导在生成更多样的查询方面的好处。
突变深度。表3显示,SQLRight保持着更深的突变链,其中SQLRight可以积累21个突变(“最大深度”列),而其他突变最多可积累7个突变。高深度查询有助于SQLRight触发更多的代码覆盖率。在NoREC中,除了种子触发代码(深度0)外,14.9%的新覆盖率由4-7深度的查询触发,17.0%由8个或更多突变的查询触发;在TLP中,高深度查询的贡献率分别为18.4%和11.4%。SQLRightdrop只对种子输入进行μ语句处理,以生成1深度查询。尽管它在深度1上得到了更多的覆盖,但由于高深度查询,SQLRight最终获胜。SQLRightrand和SQLRightsave可以积累一些突变。然而,如果没有基于覆盖率的指导,积累会很慢,并且落后于SQLRight。
积累的突变也有助于检测更多的错误。
表2的“D”列显示了触发查询的bug的深度。
在18个逻辑错误中,只有3个可以用1μtation触发,而其他错误则需要多达7个突变。崩溃和断言失败也需要多个突变,证明了基于覆盖的反馈的一致性。
5.4 Contribution of Validity(有效性贡献)
我们评估了§3.1中引入的有效性改进技术,包括协作变异、特定于DBMS的解析器、基于上下文的实例化和非确定性限制。
SQLRight Detect禁用了非确定性限制技术。SQLRight ctx valid禁用基于上下文的实例化,并使用Squirrel中基于依赖图的验证。在SQLRight ctx-valid的基础上,SQLRight db par&ctx-valid进一步重用了Squirrel的SQL解析器。Squirrel+oracle没有采用任何有效性改进技术。我们用两个oracles对三个DBMS进行了单元测试。我们用五个实例评估每个设置,并运行它们24小时。附录中的图7(NoREC)和图9(TLP)显示了结果。
独特的逻辑错误。SQLRight触发的bug最多,如图7a b和图9a b所示,包括使用NoREC的SQLite中的4个bug,使用TLP的SQLite的2个bug,在使用NoREC的MySQL中的2个bug,以及使用TLP在MySQL中的1个bug。
SQLRight数据库par&ctx有效,Squirrel+oracle使用NoREC只发现1个错误。其他设置没有发现错误。
代码覆盖率。根据图7c d e和图9c d e,SQLRight在所有单元测试的代码覆盖率中占主导地位。结果表明,有效性技术有助于生成更多样的查询和探索更多的代码。在所有有效性改进技术中,每DBMS解析器对代码覆盖率的贡献最大。因为解析器理解不同的SQL方言,所以它可以探索每个DBMS特有的自定义特性。这些功能无法通过Squirrel访问,导致SQLRight ctx valid和SQLRight db par&ctx valid之间存在显著的覆盖差距。
查询有效性。每DBMS解析器在查询有效性方面再次发挥着最重要的作用。在测试PostgreSQL和MySQL时,Squirrel的解析器无法生成任何有用的查询。首先,Squirrel解析器不支持SELECT中的自定义函数,包括生成NoREC兼容查询所必需的函数COUNT。其次,这些解析器不支持UNIONALL,UNIONALL是实现TLP兼容查询的关键组件。在三个数据库管理系统中,SQLRight对PostgreSQL的有效性最低。
由于PostgreSQL强制执行最严格的语法和语义规则,因此很难生成有效的SQL查询[43–45,71]。
非决定论导致的假阳性。表4显示了平分后的假正数,图7和图9显示了SQLRight Detect的代码覆盖率和有效性。
SQLRight Detere与SQLRight共享相似的代码覆盖率和有效性,但它在SQLite中产生95个误报,在MySQL中产生82个误报。大多数误报是由于运行了dom函数,如randomblob、julianday等。通过禁用所有非确定性行为,SQLRight仍然会引入少量误报。它们主要是由于每个DBMS中的特殊语义,我们将在§6中提供更多详细信息。我们没有从PostgreSQL中观察到假阳性,即使使用SQLRight威慑。原因是种子语料库不包含随机行为。
SQLRight中面向有效性的优化可以帮助生成更高的有效性查询,减少误报,并最终帮助发现更多错误。
6 Discussion
在本节中,我们首先讨论SQLRight的误报(FP)和无效查询的常见原因。然后,我们介绍了种子输入对于查找逻辑错误的重要性。
FP1:在SQLite中查看相关性。我们在View of SQLite中发现了一些与数据相关性问题有关的误报。清单8给出了一个触发错误警报的示例。我们预计第5行返回的行数与第6行的结果相同(可能为4)。但是,第一个SELECT返回四行,而第二个返回2行。SQLite开发人员明确表示,这种不一致性是由于未定义的亲和性造成的。
具体来说,SQLite为每列分配一个关联(类似于数据类型),并为每个关联使用不同的比较算法。在本例中,v2.v3的亲和性是独立的,因为来自两个来源的亲和性不同:SELECT v1 from v0返回一个具有亲和性TEXT的列,而SELECT v1=10 from v1返回一个带有亲和性NONE的列。因此,在最后两个SELECT中,SQLite可以自由选择任何亲和性来比较v3。在第5行中,SQLite使用亲和性TEXT并重新转换四行,而在第6行中,SQLite选择亲和性BLOB并只找到两行匹配的行。我们发现,亲和性问题通常被第三方工具报告为“错误”,包括SQLancer[30]。尽管SQLite文档已经清楚地解释了这个问题[50],但我们怀疑DBMS管理员可能仍然会错过它并创建不明确的查询。
FP2:SQLite子查询中的排序。清单9显示了一个由于子查询中未定义排序而导致的误报。
该查询首先创建具有列c1和c2的表v0,并插入行(NULL,10)和(NULL,NULL)。然后,它创建视图v3以访问v0中的特定行。如果v0中的一行满足条件c1为NULL,则最小c1将分配给c4,c2将分配给c5。两个SELECT语句的计数都是c5,但它们返回的结果不同。这个问题是由于SQLite子查询中未定义的顺序造成的。VIEW创建语句在子查询中使用聚合函数MIN。为了保持行号的一致性,SQLite将返回一行c2并将其分配给c5。然而,c2的哪一行没有定义,也就是说,它可以是10或NULL,因为v0中的两行都满足条件c1为NULL。如果返回NULL,则计数结果为0;如果使用10,则结果为1。
FP3:MySQL中的GROUP BY NULL。清单10显示了MySQL中常见的误报。当我们从sql_mode关闭ONLY_FULL_GROUP_BY时,这个假阳性是可重复的。示例查询首先创建表v0并插入行(1)、(1)和(3)。然后,它使用oracleTLP来检查v0的行。两个SELECT返回的结果不同,触发TLP报告潜在错误。区别在于GROUP BY NULL,它强制SELECT返回一行,而不管真正匹配的行是什么。第一个SELECT有一个SELECT子句,因此返回一行,而第二个SELECT有三个SELECT子句并返回两行。
如果GROUP BY子句中的SQL表达式返回NULL,也可能触发此问题。因此,除非我们手动从GROUP BY中排除表达式,否则很容易触发这种误报。幸运的是,由于误报数量较少,我们可以很容易地识别和忽略此类误报。
无效查询的示例。尽管SQLRight使用了几种技术来提高有效性,但它不能保证100%的正确性。清单11显示了SQLRight为PostgreSQL生成的一组无效查询。查询首先创建一个具有自定义类型circle的表,插入一个circle值,然后在对其行进行重新排序后尝试打印出该表。由于PostgreSQL没有圆的比较方法,因此重新排序操作将失败。要修复此程序,SQLRight必须了解PostgreSQL几何类型及其相关函数。它应该生成查询来比较圆的半径、直径、面积或其他几何特征。最后一个SELECT显示一个正确的查询。由于大量的自定义数据类型及其相关功能,我们考虑在未来支持它们。
种子语料库的重要性。在评估过程中,我们没有注意到种子输入对于SQLRight查找逻辑错误很重要。SQLRight依赖于随机突变来生成新的查询,而种子输入提供了突变的所有元素。如果种子输入涵盖了广泛的功能,SQLRight可以生成更多样的测试查询,从而可以测试DBMS的更多方面。为了保证种子输入的质量,我们从每个测试的DBMS。例如,我们从TCL测试脚本中为SQLite收集了160多个种子输入,涵盖了各种SQLite函数,如WHERE、ROWID和IN。TCL测试是SQLite的内置测试工具。它的代码可在SQLite GitHub存储库中获得,它包含1272个测试文件和46000多个独特的测试用例。这些TCL测试脚本不仅包含以前发现的错误,还涵盖了许多SQLite特定的功能,如ROWID。我们计划在未来的工作中向SQLRight引入更多高质量和多样化的种子输入.
7 Related Work
在本节中,我们将讨论与SQLRight相关的最新测试方法,重点是DBMS测试。
正在检测DBMS中的逻辑错误。差分测试[29]通常用于检测DBMS系统中的逻辑错误[43-45,49,61,68]。一个方向是使用不同的DBMS系统[16,49,52]运行一个查询,并检查结果的一致性。另一个方向是使用一个DBMS的不同设置运行一个查询,如版本或优化级别[67,68]。SQLancer提出了第三个方向,即构建功能等效的查询来测试一个DBMS[43-45]。这种方法需要对SQL和DBMS有深入的了解[30]。SQLRight采用第三种方法生成等价查询。它还使用随机变异来使查询多样化,并依靠基于覆盖范围的指导来帮助探索程序状态。我们的评估展示了代码覆盖率在查找逻辑错误方面的好处。
检测DBMS中的性能错误。性能是DBMS系统的一个重要指标,许多测试工作旨在找出性能瓶颈[25]。BmPad[40]估计一组查询的执行时间,并检查是否需要实际执行时间。AMOEBA[26]构造函数等效查询,并检查DBMS是否能够在类似的时间内处理它们。Apollo检测到性能回归错误,新版本的运行速度比旧版本慢[21]。一个常见的挑战是确定性能降级是一个错误,还是仅仅是一个设计选择。
由于每个DBMS都支持许多功能,开发人员倾向于针对常用查询优化系统。这种优化可能会减缓很少使用的语句。因此,DBMS开发人员需要付出额外的努力来检查和确认性能错误。SQLRight关注逻辑错误其通常对结果一致性具有严格的策略。大多数报告的错误在我们报告后立即得到修复。
正在检测DBMS中的崩溃错误。大多数测试工作都花在了通过两种方法检测程序崩溃上,一种是基于生成的方法,另一种是突变的方法。基于生成的方法采用定义良好的规则来合成大小有效的DBMS查询[33,58,59]。由于生成完全有效的DBMS查询是NP完全的[29],因此大多数生成器都关注语法有效性,并采取切实可行的方法来提高语义正确性。SQLsmith[58]针对PostgreSQL[71]进行了高度定制和有效。它基于对未降级数据库模式的理解生成约束查询。一些工作将查询生成问题简化为满足约束[1,31],并使用SAT解算器来提供有效的查询[2]。不同的是,Chandra等人[7]提出生成一个合适的数据库来促进DBMS测试。SQLRight在两个方面与这些赋值函数不同。首先,它不需要任何预定义的数据库,而是为每个查询创建一个数据库。其次,它利用代码覆盖率来突出显示有希望进行测试的查询。
一些模糊器已经显示了它们测试DBMS系统的能力[3,4,8,9,24,27,36,53,69,70]。Grimoire利用类似语法的组合来合成高度结构化的输入[6]。巴蒂等人[5]提出了一种从现有SQL状态中插入或删除语法组件的引擎。然而,由于严格的语法和语义要求,大多数工具无法探索DBMS的深层逻辑,如查询规划和执行。Squirrel最近的工作将SQL字符串转换为基于IR的表示,并依赖于IR类型来提高查询生成的有效性[71]。SQLRight采用了Squirrel的类型引导变异,并进一步与DBMS oracle合作,生成高质量、兼容oracle的SQL语句。更重要的是,SQLRight专注于检测除崩溃和断言失败之外的逻辑错误。
8 Conclusion
我们设计了第一个工具SQLRight,它结合了代码coverage、面向有效性的突变和oracles来查找DBMS系统的逻辑错误。SQLRight包含一组通用API,因此用户可以轻松地使用现有的模糊器来测试DBMS和开发新的oracles。我们还提高了查询的有效性,以提高模糊效果。评估证实,基于覆盖率的指导和查询有效性有助于SQLRight发现比现有工具更多的错误。总体而言,SQLRight从SQLite、PostgreSQL和MySQL中检测到18个逻辑错误。Devel运营商已经确认了所有报告的错误,并修复了其中的14个。
Acknowledgment
略
疑问
- 在流行DBMS中检测的sql是内部系统自带的,还是用户输入,还是某个公开数据集?
- 每个DBMS的官方单元测试语料库
- 第5章中说,SQLancer是一个基于生成的工具,不需要任何种子输入,那么,如何检查其对逻辑错误检查的有效性?