スマートコントラクトの「算数の落とし穴」:整数オーバーフローとアンダーフローを理解しよう
こんにちは!IoTやブロックチェーンのセキュリティを専門にしているリサーチャーです。
今日は、スマートコントラクト開発の現場で、かつて多くのプロジェクトを破滅に追い込んだ「整数オーバーフロー・アンダーフロー」という現象について解説します。「算数でミスなんてするわけない」と思うかもしれませんが、コンピュータの世界では、この「数字の限界」が致命的なセキュリティホールになるんです。
難しい専門用語は使わず、身近な例えで紐解いていきましょう。一歩ずつ、安全なコードの書き方を学んでいきましょうね!
—
1. なぜ「数字の限界」が泥棒を招くのか?
まずは、「オーバーフロー」を身近な例で考えてみます。
想像してみてください。あなたの家の鍵が、0から9までしか回らない「ダイヤル式」だとします。
- オーバーフロー(溢れる): ダイヤルが「9」の状態で、さらにプラス1回回すとどうなりますか? そう、一周回って「0」に戻ってしまいますよね。
- アンダーフロー(沈む): 逆に「0」の状態でマイナス1回回すと、一周回って「9」になってしまいます。
コンピュータの世界でも同じです。例えば、「8ビット」という箱には0から255までの数字しか入りません。ここで255に1を足すと、コンピュータは「あ、溢れちゃったから0に戻そう!」と判断してしまい、256になるはずが0になってしまうのです。
これがスマートコントラクトで起きるとどうなるか?
「送金残高」を管理するプログラムでこの現象が起きると、「残高が0の人が、無理やりプラス1をすることで、逆に残高を最大値まで増やしてしまう」といった、魔法のような(しかし非常に危険な)不正操作が可能になってしまうのです。
—
2. 昔の常識:SafeMathライブラリの役割
Solidity 0.8.0以前のバージョンでは、算術演算のチェックが甘く、この「溢れ」を放置していました。そこで開発者が使っていたのが SafeMath という守護神のようなライブラリです。
これは、「足し算や引き算をする前に、本当に計算しても大丈夫か?」を毎回確認する仕組みでした。
// 昔の書き方:SafeMathを使わないと危険でした
uint256 public balance = 10;
balance = balance - 20; // これをすると、アンダーフローで balance がとんでもない巨大な数字に化けていた
もし SafeMath を使っていれば、引き算の結果がマイナスになりそうな瞬間にプログラムがエラーで止まり、不正なトランザクションを未然に防ぐことができました。いわば、「ダイヤルを0以下に回そうとしたら、ロックがかかって動かなくなる」仕組みですね。
—
3. 今の常識:Solidity 0.8.0からの「標準装備」
時代は変わり、現在はSolidity 0.8.0以降が主流です。ここでの大きな進歩は、「算術演算のチェックが言語の標準機能として組み込まれた」ことです。
つまり、特別なライブラリを使わなくても、計算結果が「溢れそう」になったら、プログラムが自動的に「危ない!」と察知して処理を中断(リバート)してくれるようになりました。
最新の安全なコード例
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract Bank {
uint256 public balance = 100;
function withdraw(uint256 amount) public {
// 0.8.0以降は、ここがマイナスになろうとすると自動でエラーを吐いて停止します
// これにより、ハッカーが残高を不正に増やすことを物理的に防げます
balance -= amount;
}
}
—
4. セキュリティリサーチャーからのアドバイス
「標準装備されているなら安心だよね!」と思うのは少し早計です。以下のポイントだけは、新人のIT担当者として必ず押さえておいてください。
- コンパイラのバージョンを確認する: 古いプロジェクトを引き継いだ場合、
pragma solidity ^0.5.0;のような古い記述が残っていることがあります。まずはここを最新の^0.8.xに書き換えることが、最初の防犯対策です。 - 例外処理(unchecked)の罠: プログラムの効率化のために、わざとチェックを外す
unchecked { ... }という記述ができる場合があります。これは「ガスの節約」にはなりますが、「本当に溢れないことが論理的に証明できる場所」以外では絶対に使わないでください。 - 「動けばいい」は禁句: セキュリティは「バグがないこと」を証明する作業です。コードを書くときは、「この計算でダイヤルが一周してしまう可能性はないか?」と常に自問自答する癖をつけましょう。
—
まとめ:防御の基本は「限界を知ること」
スマートコントラクトの脆弱性は、多くの場合「プログラマがコンピュータの限界を忘れたとき」に生まれます。
1. 古いコードを見つけたら最新版へアップグレードする
2. 計算の範囲(上限・下限)を意識する
3. わからないときは無理に unchecked を使わない
これだけで、あなたの書くコントラクトは、泥棒が侵入しようとしても「鍵が硬すぎて回せない」頑丈なものになります。
セキュリティは難しいものではなく、こうした「小さな工夫の積み重ね」です。まずは今日から、自分のコードのコンパイラバージョンを確認することから始めてみませんか?
また次回の記事でも、現場の泥臭い知見を共有していきますね。一緒に安全なWeb3の世界を作っていきましょう!
コメント