捕捉怪异与全球安全漏洞在可靠性智能合同不变的检查
合同不变量程序程序状态的属性将永远是正确的。在我前一篇文章我讨论了可靠性的使用断言检查合同不变量。本文扩展了不变量的使用,提供了一些额外的例子。
不变的检查在字节码级别上的一个有趣的属性是它让你发现自己的低级问题,包括问题造成的编译器优化或编程语言的特性,从高级规则。例如,在最后一篇文章我们看到,不变的“合同应该总是保持解锁”可以利用可靠存储寻址打破。让我们看看一些例子。
示例1:整数舍入
整数除法在坚固四舍五入到最近的整数。不考虑这种舍入行为会导致错误和漏洞。考虑的例子σ'的博客文章也有一个伟大的那样:在这类错误
编译指示坚固^ 0.5.0;合同FunWithNumbers {
使用uint常数公共tokensPerEth = 10;
使用uint常数公共weiPerEth = 1 e18;
映射(地址= >使用uint)公共平衡;函数buyTokens公共支付(){
使用uint令牌= msg.value / weiPerEth * tokensPerEth;/ /转换魏乙,然后乘以标记率
平衡(味精。发送者]+ =令牌;
}公共{函数sellTokens(使用uint令牌)
需要(平衡[味精。发送方)> =令牌);
使用uint eth =令牌/ tokensPerEth;
平衡(味精。发送者]- =令牌;
msg.sender.transfer (eth * weiPerEth);
}
}
让我们把对审计师的帽子和想出一些有意义的属性来检查。在其他的事情我们可以断言,当比较合同账户状态在事务的开始和结束,下面的应该总是是正确的:
- 令牌味精的平衡。发送方增加当合同账户余额增加;
- 合同账户余额增加当味精的令牌的平衡。发送方增加;
- 令牌味精的平衡。发送者减少合同时账户余额减少;
- 合同账户余额减少当味精的令牌的平衡。发送者减少。
这可能是更普遍(如令牌的总和余额增加当且仅当合同余额增加)和补充额外的检查(平衡总是增加适量,…)捕捉更多可能的错误。
MythX支持标准和可靠性断言MythX AssertionFailed事件它允许您添加一个自定义的错误消息。检查合同的不变量的方法之一是通过创建一个修饰符将所有公共和外部功能。这确保了不变的持有的所有内部消息调用和事务。这种方法有一定的局限性,但让我们滚与它现在(支持内联规范即将MythX很快™)。
通常我们将创建一个修饰词按照以下思路:
修饰符checkInvariants {/ /保存旧的状态使用uint sender_balance_old =余额(msg.sender);/ /执行函数体
_;/ / MythX AssertionFailed事件如果(——这将永远不会发生——){
发出AssertionFailed(“一些错误消息”);
}/ /可靠性断言断言(——这应该持有——);
}
我们将添加检查通过创建一个包装器合同,覆盖的公共功能FunWithNumbers
:
合同VerifyFunWithNumbers FunWithNumbers {
使用uint contract_balance_old;
公共构造函数(){
(这).balance contract_balance_old =地址;
}
事件AssertionFailed(字符串消息);
修饰符checkInvariants {
使用uint sender_balance_old =余额(msg.sender);
_;
如果地址(这)。平衡> contract_balance_old & &平衡(味精。发送方)< = sender_balance_old) {
发出AssertionFailed(“不变的违反:发送方牌平衡时必须增加合同账户余额增加”);
}
如果余额(味精。发送者]> sender_balance_old & & contract_balance_old > =地址(这).balance) {
发出AssertionFailed(“不变的违反:合同账户余额必须增加当发送方牌平衡增长”);
}
如果地址(这)。平衡< contract_balance_old & &平衡(味精。发送者]> = sender_balance_old) {
发出AssertionFailed(“不变的违反:发送方牌平衡时必须减少合同账户余额减少”);
}
如果余额(味精。发送者]< sender_balance_old & &地址(这)。平衡> = contract_balance_old) {
发出AssertionFailed(“不变的违反:合同账户余额必须减少当发送方牌平衡减少”);
}
(这).balance contract_balance_old =地址;
}应付buyTokens()函数公共checkInvariants {
super.buyTokens ();
}
公共checkInvariants函数sellTokens(使用uint令牌){
super.sellTokens(令牌);
}
}
通过运行这个合同MythX混音插件检测两个不变的侵犯。让我们来仔细看看报道的问题。
第一个反例由MythX显示一个用户试图购买令牌很低叫6魏的价值。在这种情况下,6魏得到添加到合同平衡但用户不平衡增长。
采取另一个查看代码,我们可以看到,如果小于1醚当调用发送buyTokens ()
分工的结果msg.value / weiPerEth
将四舍五入0和随后的乘法tokensPerEth
同样将返回0。
第二个反例显示了用户第一次购买一个醚魏(1000000000000000000)的令牌(事务2),然后出售6令牌(交易3)。这个扣除6令牌从用户的平衡但不传输任何醚用户由于舍入误差sellTokens(使用uint令牌)
:使用uint eth =令牌/ tokensPerEth
将被四舍五入为0,如果令牌低于10或到最近的10的倍数。
示例2:任意通过大型数组存储写道
几年前我写了证明使用开源工具Mythril安全属性。通过检查合同不变量与MythX可以达到类似的结果。然而重要的是要指出这两种方法的区别:
- 的方法之前是基于非约束性象征性执行运行时字节码(使用一个旧版本的吗Mythril),存储变量初始化符号变量。该方法检测到一个错误的所有实例也返回假阳性。
- MythX结合象征性执行和输入部分初始化后起毛合同账户状态与具体的值。这样避免了假阳性但有剩余风险的假阴性。丢失的缺陷的风险下降工具允许运行的时间越长。
说完这些,让我们重温旧文章所示的例子是基于道格·霍伊特的经典USCC提交。这个聪明的合同包含一个隐藏方法的重写老板
状态变量:
编译指示坚固^ 0.5.0;合同Pwnable {
解决公共所有者;
使用uint[]私人bonusCodes;
公共构造函数(){
新使用uint bonusCodes = [] (0);
老板= msg.sender;
}
函数PushBonusCode(使用uint c)公共{
bonusCodes.push (c);
}
公共{PopBonusCode()函数
要求(0 < = bonusCodes.length);
bonusCodes.length——;
}
函数UpdateBonusCodeAt(使用uint idx,使用uint c)公共{
要求(idx < bonusCodes.length);
bonusCodes [idx] = c;
}
}
赶上不当老板变量的修改我们可以非正式定义属性:
- 业主不能改变的事务,除非事务发送方匹配现有的所有者。
这意味着以下可靠性断言:
断言(所有者= = old_owner | |味精。发送者= = old_owner);
使用上述方法创建一个包装器Pwnable
合同,我们得到:
合同VerifyPwnable Pwnable {修饰符checkInvariants {
地址old_owner =所有者;_;断言(味精。发送者= = old_owner | | = = old_owner所有者);
}
公共checkInvariants函数PushBonusCode(使用uint c) {
super.PushBonusCode (c);
}公共checkInvariants函数PopBonusCode () {
super.PopBonusCode ();
}函数UpdateBonusCodeAt(使用uint idx,使用uint c)公共checkInvariants {
超级。UpdateBonusCodeAt (idx c);
}}
这次MythX检测一个断言违反代码:
显然有一种方法可以改变合同所有者即使状态变量老板
没有显式地在代码的任何地方访问。这是由于动态数组大小的方式在存储:
- 数组的长度
使用uint [] bonusCodes
是存储在存储槽1; - 访问数组元素
n
指向存储地址keccak (1) + n
。
状态变量老板
住在存储槽0。访问它,必须先下溢的无符号整数变量bonusCodes.length
这发生在第一个函数调用:
PopBonusCode ()
在第二个叫我们写等于数组索引(MAX_UINT keccak (1))
。这将导致写入存储地址keccak (1) + (MAX_UINT keccak (1))
溢出到0:
UpdateBonusCodeAt (35707666377435648211887908874984608119992236509074197713628505308453184860938,0)
瞧!老板现在设置为零地址(设置为任何其他地址,将其转化为一个无符号整数并将其传递给第二个参数)。你可以试试这个混音的JavaScript VM或巧克力酱来验证它的工作原理。
值得指出的是,即使没有断言,MythX自动检测两个人的缺陷,使业主覆盖成为可能。第一个问题是整数溢出造成的减少bonusCodes.length
。注意,这是不可能的了,solc 6.0或更高版本使只读数组长度。
此外,MythX报告写到一个任意长度下溢后存储位置是可能的:
博士TL;
通过检查高层不变量使用符号执行和输入起毛您可以验证假设关于代码的行为。这可以发现意想不到的错误造成的稳固性编程语言的特性,如舍入误差,不安全的整数算法中的元素和处理存储。
顺便说一句,本文中所有的例子可以复制使用MythX的免费版本。你应该试一下!