id:BSN_2021 公众号:BSN 研习社 作者:红枣科技张雪良
背景:因为公链环境下所有的信息都是共享的,智能合约相当于是齐全透明化,任何人都能够调用,外加一些利益的驱动,导致引发了很多hacker的攻打。其中算术溢出攻打也是常见的攻击方式之一。
指标:带大家认识一下算术溢出破绽以及解决办法。
实用对象:实用于用Solidity语言开发的智能合约,例如BSN中的武汉链(基于ETH)和泰安链(基于 fisco bcos)上运行的智能合约。
前言
算术溢出(arithmetic overflow)或简称为溢出(overflow)是指在计算机领域里所产生的。运行单项数值计算时,当计算产生进去的后果 大于 寄存器或存储器所能存储或示意的能力限度的状况就称为算术上溢。反之,称为算术下溢。
上面,咱们演示一下solidity语言中的算术溢出景象。
代码如下:
pragma solidity ^0.7.0;// pragma solidity ^0.8.0;contract Arithmetic { // 最大值和最小值 function minAndMax() public pure returns (uint8 min,uint8 max){ return (type(uint8).min,type(uint8).max); } //上溢 function overflow() public pure returns (uint8){ return type(uint8).max + 1; } //下溢 function underflow() public pure returns (uint8){ return type(uint8).min - 1 ; }}
注:示例代码能够看出咱们应用的是uint8, 在solidity语言中,uint8示意的范畴是0 - 255。
另外,须要留神的是 溢出破绽 在 Solidity中 版本 < 0.8 时溢出时不会报错,当版本>= 0.8 时溢出会报错。
接下来,咱们别离应用版本0.7.0和0.8.0进行演示一下。
上图为执行0.7.0版本的运行示例代码,后果如下:
- 调用上溢办法
overflow
,执行代码type(uint8).max + 1
时没有报错,而是返回了计算结果0
。 - 调用下溢办法
underflow
,执行代码type(uint8).min - 1
时没有报错,而是返回了计算结果255
。
上图为执行0.8.0版本的运行示例代码,后果如下:
- 调用上溢办法
overflow
,执行代码type(uint8).max + 1
时vm有报错。 - 调用下溢办法
underflow
,执行代码type(uint8).min - 1
时vm有报错。
另外,0.8.0之后的版本也能够勾销默认的溢出校验,应用关键字unchecked
。
代码如下:
// 勾销上溢 function overflow_unchecked() public pure returns (uint8){ unchecked{ return type(uint8).max + 1; } } // 勾销下溢 function underflow_unchecked() public pure returns (uint8){ unchecked{ return type(uint8).min - 1 ; } }
在0.8.0版本的示例代码的中增加上述代码后,运行后果如下:
能够看出跟0.7.0版本运行成果统一,即
- 调用上溢办法
overflow_unchecked
,执行代码type(uint8).max + 1
时没有报错,而是返回了计算结果0
。 - 调用下溢办法
underflow_unchecked
,执行代码type(uint8).min - 1
时没有报错,而是返回了计算结果255
。
经典案例
上面是找了一段经典的存在算术溢出破绽的合约代码,示例如下:
// SPDX-License-Identifier: MITpragma solidity ^0.7.6;// This contract is designed to act as a time vault.// User can deposit into this contract but cannot withdraw for atleast a week.// User can also extend the wait time beyond the 1 week waiting period./*1. Deploy TimeLock2. Deploy Attack with address of TimeLock3. Call Attack.attack sending 1 ether. You will immediately be able to withdraw your ether.What happened?Attack caused the TimeLock.lockTime to overflow and was able to withdrawbefore the 1 week waiting period.*/contract TimeLock { mapping(address => uint) public balances; mapping(address => uint) public lockTime; function deposit() external payable { balances[msg.sender] += msg.value; lockTime[msg.sender] = block.timestamp + 1 weeks; } function increaseLockTime(uint _secondsToIncrease) public { lockTime[msg.sender] += _secondsToIncrease; } function withdraw() public { require(balances[msg.sender] > 0, "Insufficient funds"); require(block.timestamp > lockTime[msg.sender], "Lock time not expired"); uint amount = balances[msg.sender]; balances[msg.sender] = 0; (bool sent, ) = msg.sender.call{value: amount}(""); require(sent, "Failed to send Ether"); }}contract Attack { TimeLock timeLock; constructor(TimeLock _timeLock) { timeLock = TimeLock(_timeLock); } fallback() external payable {} function attack() public payable { timeLock.deposit{value: msg.value}(); /* if t = current lock time then we need to find x such that x + t = 2**256 = 0 so x = -t 2**256 = type(uint).max + 1 so x = type(uint).max + 1 - t */ timeLock.increaseLockTime( type(uint).max + 1 - timeLock.lockTime(address(this)) ); timeLock.withdraw(); }}
大家能够根据上述的正文信息,自行尝试一下,其关键点就是 “算术溢出” 。
解决方案
咱们能够看一下官网的形容:
Checked or Unchecked Arithmetic
An overflow or underflow is the situation where the resulting value of an arithmetic operation, when executed on an unrestricted integer, falls outside the range of the result type.
Prior to Solidity 0.8.0, arithmetic operations would always wrap in case of under- or overflow leading to widespread use of libraries that introduce additional checks.
Since Solidity 0.8.0, all arithmetic operations revert on over- and underflow by default, thus making the use of these libraries unnecessary.
大白话来讲就是:写solidity合约时尽量应用0.8.0版本及以上;如果应用的版本低于0.8.0时,须要是用相似safemath的类库去校验。
明天的解说到此结束,感激大家的浏览,如果你有其余的想法或者倡议,欢送一块交换。