【入門編】 整数オーバーフロー・アンダーフローの静的解析とSolidity 0.8以降の挙動 – IoT・OT(制御システム) & ブロックチェーンセキュリティ防御ガイド

こんにちは!ブロックチェーンの世界へようこそ。
スマートコントラクトの開発に足を踏み入れたばかりの頃は、新しい用語やルールの多さに圧倒されてしまいますよね。「本当に自分に安全なコードが書けるんだろうか…」と不安になることもあると思います。でも、安心してください。一歩ずつ、身近な例から仕組みを紐解いていけば、必ず安全なコードを書けるようになりますよ。

今回は、スマートコントラクトの歴史において数々の巨額ハッキングを引き起こしてきた「整数オーバーフロー・アンダーフロー」という脆弱性と、Solidity 0.8以降のモダンな防衛策について、分かりやすく解説していきますね。

—

1. 家の鍵に例える「整数オーバーフロー」の正体

まずは、身近な防犯の仕組みからイメージしてみましょう。
あなたの家に、昔ながらの「ダイヤル式の自転車の鍵」や「車のオドメーター(走行距離計)」があると想像してください。

数字が「0から9」まで回る小さなダイヤルを思い浮かべてください。今、ダイヤルは「9」を指しています。ここに「+1」の力を加えると、次の数字はどうなるでしょうか?
そう、一周まわって「0」に戻ってしまいますよね。

これが、デジタル世界における「整数オーバーフロー(Overflow)」の仕組みです。

コンピュータの中では、数字を入れ物(データ型)の大きさに合わせて保管しています。例えば、Solidityの uint8 というデータ型は、0から255までの数字しか保存できません。もし、この uint8 の入れ物に「255」が入っている状態で、さらに「+1」を計算させるとどうなるでしょうか。

コンピュータはパンクする代わりに、くるっと一周して「0」に戻してしまうのです。

逆に「アンダーフロー」ってなに?

今度は、ダイヤルが「0」を指している状態を想像してください。ここに「-1」の操作(引き算)をするとどうなるでしょうか?
今度は逆回転して、一気に一番大きな数字である「9(今回の例なら255)」にワープしてしまいますよね。これが「整数アンダーフロー(Underflow)」です。

—

2. 攻撃者はこの「数字の巻き戻し」をどう悪用するのか?

「数字がグルグル回るだけで、何がそんなに怖いの?」と思われるかもしれません。しかし、これがブロックチェーンの世界、特にお金を扱うDeFi(分散型金融)だと、大惨事を引き起こします。

例えば、あるゲームのトークンコントラクトを想像してみてください。あなたの残高が「0枚」のとき、システム側で「-10枚」引き出すような処理があったとします。
もし、ここでアンダーフローが発生すると、あなたの残高は「0」になるどころか、一気に最大の数字(例:11579208923... という途方もない大金)に化けてしまうのです!

攻撃者は、この「コンピュータの計算の癖」を意図的に突いて、自分のお金や権限を無限に増やしてしまうわけですね。まるで、ボロい金庫のダイヤルを特定の角度で押すとガチャリと開いてしまうようなものです。

—

3. 昔の苦労と「SafeMath」という名の補助錠

こうした脅威を防ぐため、昔のSolidity(0.8未満)では、開発者たちは自衛のために「SafeMath(セーフマッス)」という特別なライブラリを使っていました。

これは、足し算や引き算をする前に「おい、今から足すけど、255を超えたりしないか?」と一つひとつ電卓側で安全確認(バリデーション)を行ってくれる、いわば「補助錠」のようなものです。

当時のコードは、こんな風に書いていました。

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

// OpenZeppelinのSafeMathライブラリをインポートして使っていました
import "@openzeppelin/contracts/math/SafeMath.sol";

contract OldStyleBank {
    using SafeMath for uint256;
    
    mapping(address => uint256) public balances;

    function deposit(uint256 _amount) public {
        // SafeMathの .add() を使って安全に足し算をする
        balances[msg.sender] = balances[msg.sender].add(_amount);
    }
}

「あれ、毎回 .add() とか書かないといけないの?面倒くさそう…」と思いましたよね?その通りです。人間はうっかりミスをする生き物なので、この書き忘れを狙ったハッキングが後を絶ちませんでした。

—

4. Solidity 0.8以降の革命:コンパイラが自動で守ってくれる!

「開発者が毎回チェックするのは大変すぎる!」ということで、Ethereumの開発チームは立ち上がりました。Solidity 0.8.0以降では、なんと言語の標準機能として、すべての算術演算にオーバーフロー・アンダーフローのチェックがデフォルトで組み込まれるようになったのです。

つまり、現在のモダンなSolidityであれば、特別なライブラリを使わなくても、安全にコードを書くことができます。

実際に見てみましょう。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20; // 0.8以降のバージョンを指定

contract ModernBank {
    mapping(address => uint256) public balances;

    function deposit(uint256 _amount) public {
        // Solidity 0.8以降では、通常の「+」演算子を使うだけでOK!
        // もしオーバーフローしそうになったら、自動的にトランザクション全体が
        // 「リバート(強制中止・ロールバック)」されます。
        balances[msg.sender] += _amount;
    }
}

これなら、うっかりミスでSafeMathを書き忘れる心配もありませんよね。言語そのものが「危ない計算は絶対にさせない!」とガードマンのように目を光らせてくれている状態になります。

—

5. 「あえてチェックしたくない」ときの例外:unchecked ブロック

「でも先生、セキュリティが厳しすぎて困ることはないの?」という鋭い質問が聞こえてきそうです。

実は、高度なガス代(手数料)の最適化を行うプロの開発現場では、「この計算は絶対にオーバーフローしないことが数学的に証明できているから、チェックの処理(ガス代の無駄)を省きたい!」というケースがあります。

そんなときは、unchecked という特別なキーワードを使うことで、古いバージョン(0.7以前)のような「チェックなしの高速な計算」をあえて選ぶこともできます。

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

contract OptimizedCounter {
    uint256 public count;

    function incrementLoop() public {
        // ループカウンターなど、オーバーフローしないことが確実な処理で使う
        unchecked {
            count += 1; // ここだけは自動チェックが無効になり、ガス代が安くなります
        }
    }
}

ただし、この unchecked を使うときは、まさに「諸刃の剣」を抜くようなものです。本当に安全かどうか、何重にもテストとコードレビューを行う必要があります。新人エンジニアのうちは、基本的に unchecked は使わないのが無難ですよ!

—

まとめ

今回は、スマートコントラクトの基礎である「整数オーバーフロー・アンダーフロー」について、防犯の例えを交えながら解説しました。

  • 昔(Solidity 0.7以前):開発者が SafeMath などの補助錠を自前で用意しなければならず、ミスが多かった。
  • 今(Solidity 0.8以降):コンパイラが標準で安全装置(自動リバート)をつけてくれるため、格段に安全になった。
  • 例外(unchecked):ガス代最適化のためにあえて安全装置を外すこともできるが、プロ向けの高度な手法である。

セキュリティの世界は一見難しそうに見えますが、根本にあるのは「予期せぬトラブルを防ぐための工夫」です。一歩ずつ、安全で信頼されるスマートコントラクトの作り手を一緒に目指していきましょう!

コメント

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