本文へ移動

Delegatecall のストレージリスク

Delegatecall は、呼び出し元のストレージを使って別のコントラクトのコードを実行します。ストレージ衝突、アップグレード権限、信頼できないターゲットがプロキシやスマートウォレットを侵害する仕組みを解説します。

更新日

教育目的の参考情報であり、投資助言ではありません。投資により損失が生じる可能性があります。

直接の答え

delegatecall は、呼び出し元コントラクトのコンテキストでターゲットコントラクトのコードを実行します。呼び出し元は自身のストレージ、残高、address(this) を維持し、msg.sendermsg.value は元の呼び出しの値を引き継ぎます。

この動作により、プロキシ、ライブラリ、スマートウォレットのモジュールが実現できます。その一方で、委譲先のコードには呼び出し元と実質的に同じ権限が与えられます。ストレージへの書き込み、資産の移転、承認、外部呼び出しは、コードを提供したターゲットではなく呼び出し元として実行されます。

到達可能なすべての delegatecall ターゲットを特権コードとして扱う必要があります。安全性は、ターゲットの選択ルール、互換性のあるストレージレイアウト、初期化状態、アップグレード制御、そしてそのトランザクションで実際に使われる実装に左右されます。

仕組み

通常の外部呼び出しでは、呼び出し先が自身のストレージを読み書きします。delegatecall では、ターゲットのバイトコードが呼び出し元のストレージに対して実行されます。つまり、SSTORE 命令は呼び出し元に属するスロットを書き換えます。ターゲット側の変数名は実行時には意味を持たず、計算されたスロットの位置だけが意味を持ちます。

このため、次の四つの境界を監査する必要があります。

  • ターゲットの制御: 呼び出し先が固定されているのか、ユーザーが選択するのか、レジストリから解決されるのか、管理者が変更できるのかを確認します。
  • ストレージの互換性: すべての実装バージョンについて、変数の順序、型、継承、ストレージギャップ、名前空間付きまたは標準化されたスロットを比較します。
  • 初期化と認可: イニシャライザを再実行できないこと、アップグレード機能やモジュール管理機能が想定どおりの呼び出し元とガバナンス上の遅延を強制することを確認します。
  • 戻り値の処理: 失敗が呼び出し元まで伝播し、戻りデータが想定した型としてデコードされることを検証します。低レベル呼び出しでは、Solidity が通常行うコントラクト型の検査は提供されません。

ERC-1967 は、実装、ビーコン、管理者のアドレスを、コンパイラが通常割り当てる範囲外の標準化されたスロットに格納することで、プロキシのストレージ衝突を減らします。ただし、実装が安全であることや、正規の権限で行われたアップグレードが無害であることを証明するものではありません。

あるウォレットが ownerslot 0 に格納しているとします。プラグインはコンパイル時に counterslot 0 に配置しており、ウォレットが delegatecall でそのプラグインを呼び出すとカウンターを増加させます。

ストレージはウォレットに属するため、この書き込みによってウォレットの owner の値が変わります。書き込まれたワードが攻撃者の管理するアドレスを表していれば、プラグイン自体がウォレットの資産を保有したことがなくても、その後の認可チェックで攻撃者が所有者として認識される可能性があります。

成功したトランザクションレシートだけでは、意図した状態変更と有害な状態変更を区別できません。そのためトランザクションシミュレーションでは、実際のプロキシと実装のアドレスを対象に、ストレージ差分、資産と承認の変化、発行イベント、後続の呼び出しを確認する必要があります。

リスク

  • 任意ターゲットでの実行: ユーザーが制御できるターゲットや検証が不十分なターゲットは、呼び出し元の権限で悪意あるコードを実行できます。
  • ストレージ衝突: 実装は、所有権、残高、一時停止状態、さらには次の実装を選ぶスロットまで上書きできます。
  • 安全でないアップグレード: 管理者やガバナンスプロセスが侵害されると、ユーザーが資産を預けたり承認を与えたりした後で、以前にレビューされたコードが差し替えられる可能性があります。
  • 初期化の不備: 未初期化のプロキシや実装では、別のアカウントに特権ロールを奪われたり、危険な依存先を設定されたりする可能性があります。
  • 誤解を招く検証: プロキシのソース、現在の実装、またはインターフェースだけを検証すると、ビーコン、保留中のアップグレード、モジュールレジストリ、別の実行経路を見落とす可能性があります。

署名前には、直近のブロックで実装を特定し、誰がどの程度の遅延を経て変更できるのかを確認します。さらに、ターゲットの検証済みバイトコードとストレージレイアウトを調べ、完全な calldata をシミュレーションし、実行前後の機密性の高いストレージとトークン承認を比較します。スマートウォレットでは、モジュールを有効化および無効化する方法と、モジュールにターゲット選択を許可する仕組みも確認してください。

よくある誤解

  • 「ターゲットは呼び出し元の資産に触れられない。」 委譲先のコードは呼び出し元として動作するため、呼び出し元にその能力があれば、外部コントラクトの呼び出し、資産の移転、承認の作成が可能です。
  • 「変数名を一致させれば衝突を防げる。」 EVM が使うのはソースコード上の名前ではなくストレージスロットです。レイアウトの順序、継承、型には互換性が必要です。
  • 「プロキシコードが検証済みなら、システム全体も検証済みである。」 現在の実装、ビーコン、アップグレード管理者、初期化状態、モジュール権限は、それぞれ独立した信頼境界の一部です。

関連トピック

出典

ナビゲーション

Wiki を検索...