如果余额到达uint的最大值(2^256),便又会变为0。应当检查这里。溢出是否与之相关取决于具体的实施方式。想想uint值是否有机会变得这么大或和谁会改变它的值。如果任何用户都有权利更改uint的值,那么它将更容易受到攻击。如果只有管理员能够改变它的值,那么它可能是安全的,因为没有别的办法可以跨越这个限制。
对于下溢同样的道理。如果一个uint别改变后小于0,那么将会导致它下溢并且被设置成为最大值(2^256)。
对于较小数字的类型比如uint8、uint16、uint24等也要小心:他们更加容易达到最大值。
通过(Unexpected) Throw发动DoS
考虑如下简单的智能合约:
// INSECURE contract Auction { address currentLeader; uint highestBid;
function bid() payable {
if (msg.value <= highestBid) { throw; }
if (!currentLeader.send(highestBid)) { throw; } // Refund the old leader, and throw if it fails
currentLeader = msg.sender; highestBid = msg.value; } }
当有更高竞价时,它将试图退款给曾经最高竞价人,如果退款失败则会抛出异常。这意味着,恶意投标人可以成为当前最高竞价人,同时确保对其地址的任何退款始终失败。这样就可以阻止任何人调用“bid()”函数,使自己永远保持领先。建议向之前所说的那样建立**基于pull的支付系统 **。
另一个例子是合约可能通过数组迭代来向用户支付(例如,众筹合约中的支持者)时。 通常要确保每次付款都成功。 如果没有,应该抛出异常。 问题是,如果其中一个支付失败,您将恢复整个支付系统,这意味着该循环将永远不会完成。 因为一个地址没有转账成功导致其他人都没得到报酬。
address[] private refundAddresses;
mapping (address => uint) public refunds;
// bad
function refundAll() public { for(uint x; x < refundAddresses.length; x++) { // arbitrary length iteration based on how many addresses participated if(refundAddresses[x].send(refunds[refundAddresses[x]])) { throw; // doubly bad, now a single failure on send will hold up all funds } } }
再一次强调,同样的解决办法: 优先使用pull 而不是push支付系统。
通过区块Gas Limit发动DoS
在先前的例子中你可能已经注意到另一个问题:一次性向所有人转账,很可能会导致达到以太坊区块gas limit的上限。以太坊规定了每一个区块所能花费的gas limit,如果超过你的交易便会失败。
即使没有故意的攻击,这也可能导致问题。然而,最为糟糕的是如果gas的花费被攻击者操控。在先前的例子中,如果攻击者增加一部分收款名单,并设置每一个收款地址都接收少量的退款。这样一来,更多的gas将会被花费从而导致达到区块gas limit的上限,整个转账的操作也会以失败告终。
又一次证明了 优先使用pull 而不是push支付系统。
如果你实在必须通过遍历一个变长数组来进行转账,***估计完成它们大概需要多少个区块以及多少笔交易。然后你还必须能够追踪得到当前进行到哪以便当操作失败时从那里开始恢复,举个例子:
struct Payee { address addr; uint256 value;
} Payee payees[];
uint256 nextPayeeIndex;
function payOut() { uint256 i = nextPayeeIndex; while (i < payees.length && msg.gas > 200000) { payees[i].addr.send(payees[i].value); i++; } nextPayeeIndex = i;
}
如上所示,你必须确保在下一次执行payOut()之前另一些正在执行的交易不会发生任何错误。如果必须,请使用上面这种方式来处理。
Call Depth攻击
由于EIP 150 进行的硬分叉,Call Depth攻击已经无法实施* (由于以太坊限制了Call Depth最大为1024,确保了在达到最大深度之前gas都能被正确使用)
5 软件工程开发技巧正如我们先前在基本理念章节所讨论的那样,避免自己遭受已知的攻击是不够的。由于在链上遭受攻击损失是巨大的,因此你还必须改变你编写软件的方式来抵御各种攻击。