無料プレビュー約10分で読めます
リエントランシー:いまも通用する最古の手口
自分の記録を更新する前に送金してしまうコントラクトは、送金の途中でもう一度そのお金を求められます。それがリエントランシーで、Ethereumの歴史で最初に有名になった大事故の原因であり、いまも被害を出し続けています。
仕組み
- コントラクトが預け入れた人の残高を保持し、出金の関数を用意しています。
- 出金は先に資金を送り、そのあとで預けた人の残高をゼロにします。
- 別のコントラクトに資金を送ることは、そのコントラクトのコードに制御を渡すことです。
- そのコードがもう一度出金を呼びます。残高はまだゼロになっていないので、確認は二度目も通ります。
- プールが空になるまで、これが繰り返されます。
直し方は、チェック、効果、やり取りという名前で知られている順序の規則です。まず検証し、次に自分の状態を更新し、最後にほかの相手と話します。リエントランシーガード、つまり最初の呼び出しが開いているあいだ二度目の進入を拒むロックは、その規則に重ねるもう一枚の備えです。
実際に起きたこと
- The DAO、2016年6月。リエントランシーの欠陥によって、Ethereum上に作られた投資ファンドから約360万ETHが抜き取られました。その対応はチェーンを分裂させました。多数派は盗難を巻き戻すためにフォークし、巻き戻しを拒んだチェーンは、今日もEthereum Classicとして続いています。
- Curve Finance、2023年7月。欠陥はCurve自身のロジックではなく、特定のバージョンのVyperコンパイラにありました。そこではリエントランシーガードが、書かれたとおりに機能していませんでした。複数のプールが抜き取られ、損失は約7000万ドルと報じられました。監査を受けたコードも、それをコンパイルするツールによって台無しにされることがあります。
なぜ生き残っているのか
- 外部への呼び出しはどれも制御を渡しえます。トークンの転送フック、コールバック、そして受け取り手に通知する新しいトークン規格です。
- 読み取り専用のリエントランシーは、もっと分かりにくいものです。攻撃者は状態が途中まで更新された時点でview関数に再進入し、別のプロトコルが誤った価格や残高を読んで、それに基づいて動きます。
- 関数をまたぐリエントランシーは、同じ状態を共有する別の関数を使うので、一つの関数だけにガードを付けても足りません。
実際に防げるもの
- チェック、効果、やり取りを、分かりやすい出金だけでなく、あらゆるところで適用すること。
- 外部への呼び出しを行う、状態を変えるすべての入口にリエントランシーガードを置くこと。
- すべてのトークンを敵のコードとして扱うこと。トークンのコントラクトは、転送のあいだ自分のロジックを動かすからです。
- 悪意のある受け取り手からコントラクトに呼び返すテスト。これが、公開前にこの問題を見つける方法です。
これは教育であり、金融アドバイスではありません。ここに、どのプロトコルの利用も、回避も勧める内容はありません。