【実務・中級編】 整数オーバーフロー・アンダーフローの脆弱性とSafeMathの役割 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

整数オーバーフローの「墓場」と、現代のスマートコントラクト防衛術

やあ。現場で泥をすすりながらバグと戦っているエンジニア諸君。今日はスマートコントラクト、特にSolidityの歴史を塗り替えた「整数オーバーフロー」という亡霊について話そう。

かつて、BeautyChain (BEC) というトークンが、オーバーフロー攻撃によって時価総額が一夜にして「ゼロ」になった事件を覚えているか? 攻撃者はたった数行のコードで、トークンの発行数を天文学的な数値に操作した。あれは、我々セキュリティエンジニアにとって忘れられない教訓だ。

1. なぜ「数値」が攻撃対象になるのか

Solidity 0.8.0以前、uint256型は256ビットの容量を持っていた。最大値は $2^{256} – 1$ だ。ここに1を足すと、計算機は0に戻ってしまう。これを「オーバーフロー」と呼ぶ。逆に、0から1を引けば最大値に飛ぶ。これが「アンダーフロー」だ。

攻撃者はこれを利用して、本来「残高が足りないはずの取引」を成立させたり、送金先を書き換えたりする。昔はこれを防ぐために SafeMath というライブラリが必須だった。

2. 歴史的遺物「SafeMath」と現代の最適解

かつて我々は、以下のように書いていた。

// 昔のコード:SafeMathをインポートして計算していた
using SafeMath for uint256;
balance = balance.sub(amount); // ここでアンダーフローがあればrevertされる

しかし、現在はSolidity 0.8.0以降、コンパイラが標準でこのチェックを行うようになった。 もしオーバーフローが発生すれば、プログラムは即座に停止(revert)する。今の時代、わざわざ古いバージョンのSolidityを使う理由は、既存のレガシーコードを改修する以外にはないはずだ。

3. 実務で遭遇する「罠」:オフチェーンとの乖離

スマートコントラクト側が安全になっても、油断してはならない。オフチェーン(Webアプリ側)との連携でミスをするパターンが後を絶たないからだ。

例えば、JavaScript (Web3.jsやethers.js) は数値の扱いに癖がある。Number型は安全に扱える範囲が狭く、BigIntを使わないと巨大な数値を正確に処理できない。

フロントエンドでの安全な計算実装例 (JavaScript/TypeScript)

フロントエンドでトークンの計算を行う際、JavaScriptの標準的な数値型で計算して誤差を生むのは致命的だ。必ず BigInt を使うこと。

// フロントエンドでの送金額計算の例
const balance = BigInt("100000000000000000000"); // 100トークン (18桁)
const transferAmount = BigInt("50000000000000000000"); // 50トークン

// 引き算の結果が負にならないか、フロント側でもバリデーションする
if (transferAmount > balance) {
    console.error("残高不足です");
} else {
    const newBalance = balance - transferAmount;
    console.log(`送金後の残高: ${newBalance.toString()}`);
}

4. インフラ・API層での防衛戦略

スマートコントラクトだけでなく、APIの入力値バリデーションも重要だ。Pythonなどでバックエンドを構築している場合、Pydanticを使って型の制約を厳密に定義しよう。

Python (Pydantic) による入力バリデーション例

Web APIのインターフェース層で、マイナスの数値や異常な桁数が飛んできた時点で弾くルールだ。

from pydantic import BaseModel, Field, validator

class TransferRequest(BaseModel):
    # 数値が0未満になることをAPIレベルで許さない
    amount: int = Field(..., gt=0, description="送金額は必ず正の整数であること")

    @validator('amount')
    def validate_max_limit(cls, v):
        # 許容される最大値を超えたリクエストは即座に拒否
        if v > 10**24: 
            raise ValueError("異常な送金額です")
        return v

5. 現場の教訓:なぜインシデントは起きるのか

多くのエンジニアは「ライブラリを使っているから大丈夫」「コンパイラがやってくれるから大丈夫」という慢心に陥る。しかし、脆弱性は常に「境界線」に宿る。

  • コントラクト上の最大値と、オフチェーンでの計算結果が一致しているか?
  • unchecked ブロック(ガス代節約のためにあえてチェックを外す機能)を不用意に使っていないか?
  • マルチシグや権限管理のロジックで、加算処理にミスはないか?

これらを徹底的に叩き込むこと。セキュリティとは、華麗なハックを防ぐことではなく、「当たり前のことを、当たり前に、しつこく守り抜く」ことの積み重ねだ。

もし君のプロジェクトで古いSolidityを使っているなら、今すぐ0.8.x系へ移行する計画を立てろ。それが、次の惨劇を防ぐための最初で最大のセキュリティ対策になる。

何か不明点があれば、コードを持ち込んで相談に来い。コードは嘘をつかないからな。

コメント

タイトルとURLをコピーしました