新年前,我们最后来谈一谈以太坊安全性的特点。
不可能修改的bug
当合约公开在区块链上之后,它就不能去修改了。相应的,合约中出现的任何 bug 也没有机会改正。如果希望能够修改bug,合约编写者就需要在编写合约的时候预留一些用来修改或终止合约的代码。但预留修改后门这一方式具有争议,因为在以太坊的愿景中,智能合约一旦部署,其设定就应当是不可更改的。
正式因为 bug 不可修改,可能会导致一些很严重的攻击事件没有弥补的方法。DAO 攻击是唯一的例外。以太坊使用了一个硬分叉解决了这一问题。但这种做法没有得到整个社区的赞同,因为它违背了“代码即法则”这一准则。
调用栈大小限制
每次合约调用另一个合约的时候,调用栈就会增加一个 frame. 上限是 1024 个。当这一上限达到的时候,下一次调用会触发一个异常。如果攻击者先将自己的调用栈离填满只差一个 frame,然后去调用受害者合约的函数,那么受害者函数执行中任何调用将会导致执行失败。如果受害者函数没有正确的处理执行成功与否,将可能导致意料之外的结果。在文章后续部分,我们将以一个例子说明这一点。
为了解决这一问题,在一次以太坊升级中,规定了每次通过 call 或 delegatecall 调用合约函数时,只能为被调用函数分配最多 63/64 的剩余 gas. 而以太坊中每个区块最多只能包含约 470 万的 gas。也就是说,如果调用者最初投入了数量为 a 的 gas, 在 10 层递归调用后,最内层的函数最多只有 (63/64)^10*a 的 gas. 因此,调用深度不可能超过 1024. 后面的攻击案例中,假设还没有这一修补方案。
时间约束
大量的应用使用时间约束来决定某些行为什么时候被允许。比如在一个 ERC 20 合约中,从某个时刻开始,允许用户使用 ether 购买一个 token.
在智能合约的执行过程中,当前时间取自给定交易所在区块的区块时间戳。所以,一个区块中的所有交易在执行时使用的是相同的时间戳。这一设定保证了智能合约在每个矿工那里的执行结果是一致的。
但这个设定也可能导致一些攻击,因为矿工在选择时间戳的时候有一定的自主权,这可能为一些攻击埋下了后门。
GovernMental 合约的攻击
GovernMental 是一个“庞氏骗局游戏”型合约。严格来说,其实不能叫骗啦,因为庞氏骗局的规则被公开地写在合约里。在这个合约中,每个人通过向合约中转一笔钱来加入这个庞氏骗局游戏。合约通过两个数组记录参与者的地址和每个参与者转入的钱的数量。如果最后一个人加入后 12 小时后都没有下一个人加入,那么最后一个人就可以获得全部的收益。(这个设定是不是有点像著名的 Fomo3D,值得注意的是,Fomo3D 的出现晚于这篇论文呦。)
下面是一个简化版的 GovernMental 合约
在这个合约中,参与者可以通过 invest() 函数来投入 ether 并加入这个游戏。合约维护一个名为 jackpot的变量,表示如果后续没有人加入,赢家将拿走多少 ether. 新来的参与者必须投入多于 jackpot 一半的 ether, 同时,新来的参与者投入的 ether 中,会有一半被加入 jackpot. 如果连续一分钟没有人新来的参与者,最后一个人获得 jackpot 中的 ether,同时合约拥有者拿走剩下的钱(留下 1 个 ether 作为下一轮的启动资金)。需要注意的是,这里并没有检查最后分钱的时候,通过 send() 函数进行的转账是否成功。
这个简化版的合约有几个点可供被攻击。
攻击一
这个攻击来自合约拥有者自身,目的是让本来的游戏赢家无法拿到钱。合约拥有者利用 send() 函数的异常处理和调用栈大小限制进行攻击。
攻击的方式是,通过预先进行大量的递归调用,导致执行 resetInvestment() 时,调用栈达到了大小上限,无法再执行给游戏赢家和合约拥有者的 send() 函数。合约拥有者和游戏赢家都拿不到钱,钱依然留在合约中,但之后重置合约状态的代码会照常执行。只要再进行一轮正常的游戏,合约拥有者就可以将本该属于上一轮游戏赢家的钱收入囊中。
攻击二
这一攻击方式来自矿工。矿工可以通过拒绝打包其他人与 GovernMental 合约的交易,来使自己希望的人成为合约游戏的赢家。更多地,进行攻击的矿工在打包区块时可以任意决定交易顺序,来影响这个区块过后谁是 lastInvestor.
之前也提到过,每一个新参与者需要投入的 ether 数量不能低于合约中 jackpot 中 ether 数量的一半。而一个人在发起交易的时候看到的 jackpot 数值,与它的交易被执行时 jackpot 的数值可能是不一样的。这就导致这个人可能发起交易时以为自己投入的 ether 数量是符合要求的。但是执行时,由于 jackpot 数值的改变,变成了一笔无效交易。这就是上篇文章中提过的不可预测状态问题。
另外,矿工拥有决定区块时间戳的权利。而合约在执行时,判定 resetInvestment 是否可以执行,就是读取矿工决定的区块时间戳。通过影响区块时间戳,也可以影响游戏结果。
结语
通过这几期对参考文献 [1] 的学习,我们看到了一些以太坊 Solidity 合约中设计的弱点。虽然这些弱点称不上是漏洞,但是如果在编写合约时,对这些点不了解,没有充分考虑,就可能写出有安全问题的合约出来。
参考文献:
[1] Atzei, Nicola, Massimo Bartoletti, and Tiziana Cimoli. "A survey of attacks on ethereum smart contracts (sok)." Principles of Security and Trust. Springer, Berlin, Heidelberg, 2017. 164-186.
--
Conflux 是致力于打造下一代高性能的 DAPP 公链平台
欢迎关注我们的微信公众号:Conflux中文社区(Conflux-Chain)
添加微信群管理员 Confluxgroup 回复“加群”加入 Conflux官方交流群