三十一、以太坊安全之 可重入漏洞
如果一个函数在执行完成前被调用了数次,发生意料不到的行为时,可重入漏洞就可能出现。
我们看看下面这个函数,它可以用于从合约中提取调用者的总余额:
mapping (address => uint) private balances;
function payOut () {
require(msg.sender.call.value(balances[msg.sender])());
balances[msg.sender] = 0;
}
调用 call.value() 会导致合约外部代码的执行。即,若调用者是另一份合约,这就意味着合约回退措施的执行。这可能会在余额调整为 0 之前再次调用 payOut(),从而获得比可用资金更多的资金。
这种情况下的解决方法就是使用替代函数 send() 或 transfer(),后两者函数能为基础运作提供足够的 Gas,而想要再次调用 payOut() 时 Gas 就不足了。
注意,下面这句话也十分重要:
“若合约含有两个共享状态的函数,那么不需要重复调用函数也可能会发生相似的竞态条件(Race Conditions)。”
因此,最好的做法是在转账前更改状态,即转移资金前,在上述代码中把余额设为 0。
The DAO 攻击利用了该漏洞的一种变体。