# 智能合约安全之整型溢出

• BeilunYang
• 更新于 2024-06-23 10:16
• 阅读 419

## 概述

1. 将一个uint8类型，值为0的变量进行减1操作时，计算结果会等于255，称为减法溢出
2. 将一个uint8类型，值为255的变量进行加1操作时，计算结果会等于0，称为加法溢出
3. 将一个uint8类型，值为128的变量进行乘2操作时，计算结果会等于0，称为乘法溢出

## 重大事件

• 2018年4月22日，黑客利用整型溢出漏洞，对美链(BEC)发起攻击，无中生有，凭空取出57,896,044,618,658,100,000,000,000,000,000,000,000,000,000,000,000,000,000,000.792003956564819968个BEC代币，导致BEC价格近乎归零
• 2018年4月25日，SMT项目方发现其交易存在异常，黑客利用其整型溢出漏洞创造了65,133,050,195,990,400,000,000,000,000,000,000,000,000,000,000,000,000,000,000.891004451135422463个SMT代币

## 代码演示

``````library SafeMath {
function mul(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}

function div(uint256 a, uint256 b) internal constant returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}

function sub(uint256 a, uint256 b) internal constant returns (uint256) {
assert(b &lt;= a);
return a - b;
}

function add(uint256 a, uint256 b) internal constant returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}

/**
* @title Pausable token
*
* @dev StandardToken modified with pausable transfers.
**/

contract PausableToken is StandardToken, Pausable {

function transfer(address _to, uint256 _value) public whenNotPaused returns (bool) {
return super.transfer(_to, _value);
}

function transferFrom(address _from, address _to, uint256 _value) public whenNotPaused returns (bool) {
return super.transferFrom(_from, _to, _value);
}

function approve(address _spender, uint256 _value) public whenNotPaused returns (bool) {
return super.approve(_spender, _value);
}
// batchTransfer函数存在漏洞
// 这一行没有考虑整型溢出
uint256 amount = uint256(cnt) * _value;
require(cnt > 0 && cnt &lt;= 20);
require(_value > 0 && balances[msg.sender] >= amount);

balances[msg.sender] = balances[msg.sender].sub(amount);
for (uint i = 0; i &lt; cnt; i++) {
}
return true;
}
}``````

``````import { ethers } from "hardhat";

async function main() {
// deploy
const [owner, attacker1, attacker2, attacker3] = await ethers.getSigners();
const BecToken = await ethers.getContractFactory("BecToken");
const becToken = await BecToken.connect(owner).deploy();
await becToken.deployed();
console.log('BecToken totalSupply:', await becToken.totalSupply());
// attack
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});``````
2. 然而忽略了`batchTransfer``uint256 amount = uint256(cnt) * _value;` 这一行代码。_value值为用户可控制的参数，却没有进行任何值的判断。攻击者可以轻松构造恶意参数请求，让`uint256(cnt) * _value` 溢出。
3. 攻击者直接调用`batchTransfer` 合约函数，传入两个账户地址`attacker2.address attacker3.address` , 以及`_value 2**255`
4. 当使用构造的恶意参数时，`uint256 amount = uint256(cnt) * _value;` 发生乘法溢出，`amount`的值为0，成功绕过了`require(_value > 0 && balances[msg.sender] >= amount);` 检查，向`_receivers`地址转入`2 ** 255`个token, 哪怕`msg.sender(attacker1)`账户下只有0个token，没有`2 ** 255`个token

## 如何修复和预防

1. Solidity < 0.8.0，使用openzeppelin提供的SafeMath库函数进行数值的加减乘除操作
2. Solidity >= 0.8.0, Solidity编译器自动集成SafeMath, 直接使用 `+ - * /` 即可，发生溢出时会自动回退交易

## 完整代码

https://github.com/demo-box/blockchain-demo/tree/main/integerOverflow