【実務・中級編】 整数オーバーフロー・アンダーフローのSolidityバージョン別挙動 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

整数オーバーフローの「墓場」:Solidity 0.8.xの過信と現場の泥臭い教訓

現場の若手エンジニアから「Solidity 0.8以降を使っているからSafeMathは不要ですよね?」という質問を受けることがある。そのたびに私はこう答える。「技術は進化したが、お前の書くロジックの脆弱性は進化していない」と。

SCADAやIoTの現場で積み重ねてきた経験から言わせれば、言語側のガードレールを過信するのは、シートベルトをしたからといって時速200kmで壁に突っ込むようなものだ。今日は、整数演算という「最も基礎的だが、最も致命的な」穴について、実務的な視点で深掘りしていく。

1. なぜ「0.8.x以前」は地獄だったのか

Solidity 0.8.0以前の環境では、整数型(uint256など)の計算でビット幅を超えると、エラーを吐かずにラップアラウンド(0に戻る)を起こしていた。これがどれほど危険か、具体例を挙げよう。

かつて、とあるトークンコントラクトでこんなコードがあった。

// 0.7.x以前の脆弱な例
function sub(uint256 a, uint256 b) public pure returns (uint256) {
    return a - b; // bがaより大きい場合、アンダーフローして巨大な値になる
}

攻撃者はこれを利用し、残高以上のトークンを引き出したり、制限を突破したりする。当時の我々は OpenZeppelin の SafeMath を使い、すべての計算を関数経由にすることでこの地獄を回避していた。

2. Solidity 0.8.xの自動チェック:万能薬ではない

0.8.0から、算術演算でオーバーフロー/アンダーフローが発生すると、Solidityは自動的に revert(トランザクションの取り消し)を投げるようになった。これは素晴らしい進歩だ。しかし、ここには「暗黙の盲点」がある。

「チェックされない領域」の存在だ。

unchecked ブロック内では、この自動チェックが無効になる。ガス代を節約したいという安易な動機で unchecked を乱用すると、先祖返りした脆弱性が復活する。

// 0.8.xでもやってはいけないパターン
function withdraw(uint256 amount) public {
    unchecked {
        balanceOf[msg.sender] -= amount; // ここでアンダーフローが発生してもrevertしない!
    }
}

3. 実践:インシデントを防ぐためのセキュア実装

Web3のバックエンドを開発する際、JavaScript(Ethers.js等)側で数値を扱うときも同様の罠がある。Solidityの uint256 は最大 2^256 - 1 だが、JavaScriptの Number 型は 2^53 - 1 までしか安全に扱えない。

【重要】JavaScriptでの数値計算のセキュアな実装例

// BigNumberライブラリ(ethers.jsのBigNumberやbn.js)を必ず使うこと
const { ethers } = require("ethers");

function safeSubtract(balance, amount) {
    const b = ethers.BigNumber.from(balance);
    const a = ethers.BigNumber.from(amount);

    // 引き算の前に必ずチェックを入れる(ロジック上のガード)
    if (a.gt(b)) {
        throw new Error("残高不足:アンダーフローのリスクを検知");
    }
    
    return b.sub(a).toString();
}

4. 運用エンジニアが叩き込むべき「防御の要諦」

もし君がコントラクトをデプロイし、そのWebインターフェースを運用するなら、以下の「多層防御」を徹底してくれ。

1. Solidityのバージョン固定: 常に最新の安定版を使い、pragma solidity ^0.8.20; のように記述する。
2. unchecked は封印: 深刻なガス最適化が求められるシビアなケース(極めて限定的なループ内など)以外では、unchecked を使用するコードレビューを即座にリジェクトすること。
3. WAF/APIゲートウェイでのペイロード検証:
フロントエンドからの入力値が極端に大きな数値でないか、APIゲートウェイ(Nginx等)でサイズ制限を設ける。

Nginxでのリクエストサイズ制限設定例:

# 巨大な数値を送りつけて演算を混乱させる攻撃を弾く
client_max_body_size 16k; 

# さらに、特定のAPIエンドポイントで数値の妥当性をチェックするロジックをバックエンドに含める

最後に:セキュリティは「疑うこと」から始まる

整数オーバーフローを防ぐことは、単なるコードの書き方ではない。「この数値は本当に想定内の範囲に収まっているか?」と、あらゆる入力に対して疑いの目を向ける姿勢そのものだ。

現場でインシデントが起きたとき、犯人は往々にして「誰もが大丈夫だと思っていた当たり前の場所」に潜んでいる。0.8.xという盾に頼り切るのではなく、常にその裏側を想像するエンジニアであってほしい。

何かあれば、またいつでも相談に来い。泥臭い現場の知見を叩き込んでやる。

コメント

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