使用Diffusc对可升级智能合约进行差异模糊测试
可升级智能合约
虽然设计可升级智能合约还有其他方法,但迄今为止最常见的USC模式是基于delegatecall的代理模式。在这种模式中,代理合约存储一个实现合约的地址,该地址可由合约所有者或管理员更改。有许多子模式,但关键特征是在代理的回退函数中使用delegatecall,该函数捕获所有对代理本身未定义函数的调用。
关键的是,delegatecall与典型的call操作码不同,因为它从目标合约获取函数代码,但在代理的上下文中执行,因此代理的存储用于所有业务逻辑。这允许交换实现,而无需将状态迁移到新合约。
差异模糊测试
模糊测试是一种安全分析技术,其中随机生成的输入被馈送到被测软件中,同时模糊器监控其执行是否存在错误。有多种风格,其中一种是差异模糊测试,其中两个类似的实现接收相同的输入,模糊器寻找两者之间执行的任何差异。
有几种专门用于测试智能合约的模糊器,其中Echidna是最成熟和功能最丰富的。虽然智能合约领域之外的模糊器通常会监控被测软件的崩溃,但智能合约模糊测试通常会寻找不变性违规。
智能合约的差异模糊测试使用外部测试,测试函数接受一些随机输入并将其馈送到两个实现中的匹配函数,然后比较两次调用的结果,断言它们应该相等。
Diffusc实现
Diffusc是一个人工辅助工具,旨在简化智能合约升级的验证:
- 利用Slither的静态分析来识别受升级影响的所有函数
- 生成用于部署合约和与合约交互的包装器。包装器合约有两种模式:标准模式和分叉模式
- 用户应审查包装器是否存在错误,添加Diffusc无法自动推断的信息,并在适当的地方添加其他不变性和前提条件
- 最后,Diffusc利用Echidna执行差异模糊测试并尝试发现升级的问题。一些失败的测试可能需要额外的手动审查
使用Slither差异升级版本
Diffusc的第一个组件是Slither中的一对实用程序扩展,它们将包含在静态分析工具的即将发布的版本中。可升级性实用程序主要做两件事:
- 比较两个USC实现以生成差异,通过污点分析增强以识别可能受其他地方更改影响的未修改代码
- 识别代理存储其实现地址的存储槽
困难的部分在于实现比较和查找受更改影响的代码。
查找新的和修改的函数
要查找新函数和变量,我们比较两个USC的函数签名和变量列表。可以以相同的方式找到缺失或修改的变量。为了找到修改的函数,我们依赖函数的中间表示(通过SlithIR),并遍历控制流图以查看函数是否匹配。这使我们能够寻找语义变化,而不受添加内联代码注释或代码格式化等更改的影响。
差异的污点分析
由于我们对这些更改如何影响代码的其他部分感兴趣,我们还执行污点分析以找到其他可能有希望进行模糊测试的入口点。如果未修改的函数读取或写入也由新函数或修改函数写入的存储变量,或者该函数对修改函数进行内部调用,我们认为该函数被污染。如果变量由任何新的、修改的或受污染的函数写入,我们认为该变量被污染。
有时仅模糊测试被测合约本身的新函数、修改函数和受污染函数可能不够。因此,我们在比较期间还通过查找新函数、修改函数和受污染函数中的任何外部调用来执行跨合约污点分析。
为Echidna生成差异模糊测试不变性测试
Diffusc对提供的两个USC实现以及通过命令行参数指定的任何其他目标执行差异静态分析,使用新的Slither实用程序。利用在此过程中收集的信息,该工具现在可以开始以Solidity测试合约的形式自动生成差异模糊测试不变性。
选择不变性
通常,在为智能合约编写不变性测试时,我们必须仔细识别关键不变性,这需要深入了解合约的业务逻辑和可能的状态空间。这非常重要,并且仍然适用于测试可升级智能合约,但无法轻松自动化。
由于创建Diffusc的目标始终是尽可能自动化,我们采用不同的方法来选择不变性:对于我们感兴趣的每个要进行模糊测试的函数,我们创建一个包装器方法,该方法使用相同的输入调用两个实现上的函数,并断言两次调用的结果应该相同。
标准模式和分叉模式
您可以以两种模式运行Diffusc,这会影响测试代码的生成方式:
- 标准模式:所有合约都部署在本地测试网上,没有任何预先存在的状态。这是使用Echidna的标准方式
- 分叉模式:合约从链上地址获取,Echidna使用链的两个分叉
拥有这两种模式的原因是为了简化工具使用;每种模式在不同场景下都会更容易使用。
使用Diffusc触发现实世界的错误:Compound
为了演示如何在现实世界中使用Diffusc,让我们考虑Compound示例。我们已经看到了Compound的Comptroller合约在现在臭名昭著的升级中的大部分相关更改。
在模糊测试活动期间升级
一个关键细节是,触发此错误要求用户在升级之前已经与至少一个cToken市场进行过交互。这意味着我们的模糊测试合约必须能够在Echidna生成的模糊测试交易序列中间执行升级。
手动配置
开发人员/用户通常需要增强自动生成的测试合约,因为并非所有内容都可以自动化。例如,可能有一些Diffusc无法推断的协议设置要求,例如链接合约并将代币铸造到适当的地址。Diffusc无法确定每个包装器方法的适当前提条件,也无法推断如何执行升级。
将Diffusc添加到您的安全工具箱
正如我刚刚通过上面的两个示例所演示的,给定两个USC实现版本,Diffusc会自动生成差异模糊测试合约,这些合约可与Echidna一起使用以检测现实世界的错误。它可以通过两种方式做到这一点:标准模式和分叉模式,每种模式都有优缺点,如Compound示例所示。无论哪种情况,并非所有内容都可以自动化,并且需要用户进行一些手动工作以确保测试包装器函数的正确性。但我们期望Diffusc的用户将是完全有能力完成这项工作的智能合约开发人员。
可升级智能合约将继续存在。虽然开发人员可以在发现错误时修补他们的合约,但我们可能会继续看到升级中引入的新错误。由于有数十亿美元的加密货币锁定在USC中,风险很高,这使得开发人员每次更改时彻底分析其合约的安全性变得更加重要。Diffusc不能替代其他智能合约安全实践,但它是开发人员安全工具箱中的另一个工具,应在最终确定任何升级之前使用。