智能合约整数溢出漏洞详解 |猿创征文

StevenX5 2022-07-02 10:58:46

漏洞原理

以太坊虚拟机 (EVM) 为整数指定固定大小的数据类型。这意味着一个整数变量,只能表示一定范围的数字。例如uint8 只能存储 [0,255] 范围内的数字。尝试将 256 存储到 uint8 将导致 0。如果不小心,用户输入未被检查,并且执行的计算结果超出了存储它们的数据类型的范围,那么 Solidity 中的变量可能会被利用而导致漏洞。

整数溢出漏洞有上溢和下溢两种情形。

上溢

整数上溢是指数字的增量超过其能存储的最大值。如对于 uint256 类型的变量,Solidity 可以处理多达 256 个比特位的数值 (最大值是 2256  - 1),所以如果在最大数上增加 1 会导致 0。如下所示:

	0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
+ 	0x000000000000000000000000000000000001
------------------------------------------
= 	0x000000000000000000000000000000000000

下溢

同样,在相反的情况下,当数字是无符号的时,递减将会下溢该数字,从而得到可能的最大值。如下所示:

    0x000000000000000000000000000000000000
- 	0x000000000000000000000000000000000001
------------------------------------------
= 	0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF

安全隐患

当执行一个操作需要一个固定大小的变量来存储变量的数据类型范围之外的数字时,就会发生上溢或下溢。

例如,从一个存储值 为 0 的 uint8 (8 位无符号整数) 变量中减去 1 时,将得到数字 255,这是一个下溢。我们指定了一个低于 uint8 范围的数字,结果是给出了一个 uint8 可以存储的最大数字。同样的,从一个存储值 为 255 的 uint8 变量中增加 1 时,将得到数字 0,这是一个上溢。我们指定了一个高于 uint8 范围的数字,结果是给出了一个 uint8 可以存储的最小数字。虽然这两种情况都很危险,但在现实应用场景中下溢情况更有可能发生,也更具危害性。

让我们来看下面这个合约例子:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

// contract to test uint8 integer underflows and overflows
contract OverFlowUnderFlow {
    uint8 public a = 0;
    uint8 public b = 2**8-1;

    // a will end up at 2**8-1
    function underflow() public {
        a -= 1;
    }

    // b will end up at 0
    function overflow() public {
        b += 1;
    }
}

我们利用 remix 工具来编译和部署这个合约,然后看看发生了什么。部署好合约后,a 的默认值是 0,b 的默认值是 255。当我们执行 overflow 函数后,b 的值变成了 0,执行 underflow 函数后,a 的值变成了 255。如下图所示:

这个结果正如我们预料的那样。当执行 overflow 函数时发生了整数上溢,当执行 underflow 函数时发生了整数下溢。这个演示是在 Solidity 0.4.18 编译器版本上进行的。实际上,如果不指定编译器版本,Remix 将使用最新的 Solidity 编译器(也就是 0.8.7 版本)来编译合约。那么当我们执行 overflowunderflow 时编译器会报错且不会出现溢出的结果,也就是说,目前新的 Solidity 编译器版本已更新了这个漏洞。

预防措施

我们建议使用 OpenZeppelin 的 SafeMath 库来解决整数溢出问题。OppenZepplin 在构建和审计安全库方面做得很好,特别是他们的安全数学库是一个用来避免溢出漏洞的参考或库,且已称为一个标准。

下面是 SafeMath 合约安全库的源码:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

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

  function div(uint256 a, uint256 b) internal pure 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 pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

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

下面是经我们修复后的合约例子:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./SafeMath.sol";

// contract to test uint8 integer underflows and overflows
// fixed by using SafeMath
contract OverFlowUnderFlowFixed {
    using SafeMath for uint;
    uint public a = 0;
    uint public b = 2**256-1;

    // will throw
    function underflow() public {
        a = a.sub(1);
    }

    // will throw
    function overflow() public {
        b = b.add(1);
    }
}

同样的,我们还是利用 remix 工具来编译和部署这个合约,然后看看会发生什么。实际上,经过安全修复后的合约,当我们执行 overflow 函数和 underflow 函数时,Solidity 编译器会抛出错误但不会改变 ab 的值。

注:以太坊智能合约开发中,对于整数溢出漏洞,编译器在编译时是发现不了的,所以我们在编写合约时更应该谨慎处理。

...全文
161 1 打赏 收藏 举报
写回复
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
EargoChen 07-07

标配safeMath😄

  • 打赏
  • 举报
回复
相关推荐
发帖
Ethereum中文社区

183

社区成员

这里是由区块链、以太坊、Web3.0、元宇宙等相关技术组成的国内最大的开发者聚集地,帮助社区成员快速获得更全面、更深度的技术信息,链接更多资源,让开发者更好地入门学习、成长与应用实践。
以太坊区块链web3.py 开源
社区管理员
  • ETHPlanet
  • kkkuntor
  • 活动助手
加入社区
帖子事件
创建了帖子
2022-07-02 10:58
社区公告

Hi,欢迎加入我们!

 

在这里你可以:

  • 获取最官方最新最全的区块链、以太坊、Web3.0学习内容与资源
  • 获取行业内更有价值的研讨会、公开课资源,或参与社区发起的主题活动课程
  • 获得专业的以太坊、区块链、Web3.0相关技术资深专家/讲师的回复或指导,突破学习瓶颈
  • 进行学习打卡、提问或回答问题,提高个人或在社区的影响力,将有机会与各大技术官方深度合作
  • 参与丰富的社区活动,获得更多学习资源、行业资源等
  • 结识更多行业伙伴,参与线上/线下课程、交流会,拓展行业交流圈

 

【最新活动】:

1、6月1日21:00-21:30,DappLearning Infura AMA(线上)点击查看详情