背景
介绍以太坊中的63/64 Rule。
Call Depth Attack
以太坊的合约可调用合约的,但调用的深度是有限制,最大为1024层。
在EVM.Call中有这样具体的处理逻辑:
// Fail if we're trying to execute above the call depth limit if evm.depth > int(params.CallCreateDepth) { return nil, gas, ErrDepth }
显然,如果大于1024层调用,就返回失败。
也就是说,在理论上,没有一个合约的调用是一定会成功的,都有出错的可能,
但是有些合约在编写时,没有考虑过这个问题,并没有处理可能的失败,比如这个合约例子:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.5; // DO NOT USE!!! contract Auction { address highestBidder; uint256 highestBid; function bid() external payable { if (msg.value < highestBid) revert(); if (highestBidder != address(0)) { //might be error payable(highestBidder).send(highestBid); // refund previous bidder } highestBidder = msg.sender; highestBid = msg.value; } }
这个合约中,payable(highestBidder).send(highestBid)这个调用,是可能失败的。
如果当前Auction合约的调用已经是1024层,再掉用send就会失败,由于这个合约没有处理send的返回值,导致合约会在send调用失败的情况下,继续执行后面的逻辑。
攻击者察觉到这个机会,于是就会故意创建足够深度的合约调用,来触发大于1024层的调用上限,进而达到自己的目的。上面这个合约例子中,send失败会导致不给前一个最高的bidder转账,实现最高bidder角色的转移。
这种利用1024上限的攻击,叫做call depth attack。而以太坊的应对方案是63/64 Rule,在EIP150中被提出。
63/64 Rule
63/64 Rule的思路很简单,就是调用下一层合约时,调用者会保留1/64的gas,只剩下最多63/64的Gas传递到被调用合约中。
比如A合约调用B合约的时候,A拥有1000Gas:
- 在63/64Rule出现之前,A最多可以把1000Gas全部传递给B,作为B合约调用的GasLimit。
- 在63/64Rule出现之后,A最多只能传递1000*(63/64)=984的Gas给B, 作为B合约调用的GasLimit。
这样随着调用深度的增加,越往后的合约,能够拥有的GasLimit越少,很快Gas就花光了,根本支撑不到1024层最大限制。
这个花费的效果如图:

也就是虽然1024层最大限制存在,call depth attack也存在,但是海量的Gas消耗让攻击者无法达到1024的上限。
Gas费不够引起的失败安全吗
回到合约例子:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.5; // DO NOT USE!!! contract Auction { address highestBidder; uint256 highestBid; function bid() external payable { if (msg.value < highestBid) revert(); if (highestBidder != address(0)) { //might be error payable(highestBidder).send(highestBid); // refund previous bidder } highestBidder = msg.sender; highestBid = msg.value; } }
payable(highestBidder).send(highestBid)这行代码没有处理返回错误,虽然63/64 rule的引用,让攻击者无法触发call depth attack。
但如果正常执行send代码时,因为Gas费不够失败了,这个合约中没有处理send的返回错误,安全吗?
答案是安全的,因为Gas费不够了,而send后面的代码会消费Gas,所以不会被执行,整个合约的调用就终止了。
这和call depth attack是有区别的。call depth attack触发时,Gas费完全可能是充足的,send后面的代码依然会被执行。
Ref
发表回复