semgrep-smart-contracts #04 (basic-arithmetic-underflow)

semgrep-smart-contracts というリポジトリがあります。 これは、スマートコントラクトの脆弱性を発見するためのsemgrepルールをまとめたものです。

今回は、basic-arithmetic-underflowというルールを読んでみます。

検出ルール

検出ルールは、以下の通りです。

このルールはまだBETAであり、まだsemgrepでは検出できません。

1
2
3
4
5
6
7
8
pattern-sinks:
  - pattern: $Y - $X
pattern-sources:
    - pattern-either:
        - pattern-inside: |
            function $F(..., $X, ...) external { ... }            
        - pattern-inside: |
            function $F(..., $X, ...) public { ... }            

underflowとは

solidityの特定のバージョンでは、型の下限値を超えてデクリメントをしてしまった場合、意図しない動作を誘発する恐れがあります。

例として、solidityでuint8の変数を使うことを考えます。

このuint8の変数が0のとき、デクリメントすると255になってしまいます。

1
2
uint8 balance = 0;
balance--; // balance == 255

この挙動は、solidity >= 0.8 からデフォルトでチェックされるように修正されたそうです。

このチェックを除外してコンパイルするためには、以下のようにuncheckedという構文を利用できるようです。

1
2
3
unchecked {
    balance--;
}

RemittanceTokenでの事例

この脆弱性が存在した、RemittanceTokenというコントラクトについて解説します。

RemittanceTokenは、ERC20のトークンで、このトークンのburn関数にバグがあります。

burn関数のソースコードは以下の通りです。

1
2
3
4
5
6
7
8
//Burn tokens from owner account
function burn(uint256 _count) public returns (bool success) {
    balanceOf[msg.sender] -=uint112( _count);
    deleteToken = _count.add(deleteToken).toUINT112();
    _totalSupply = _totalSupply.sub(_count).toUINT112();
    Burn(msg.sender, _count);
    return true;
}

2行目の計算にバグがあります。

1
balanceOf[msg.sender] -=uint112( _count);

このトークンを持っていない場合、0-_countの計算が発生するため、underflowが発生します。

その結果、攻撃者はこのトークンを無限に手に入れることが出来ます。