【逆引き】スマートコントラクトの関数の再入場を禁止する方法
# 目次
今回はリエントランシー攻撃への対策として、Solidityの修飾子を使って再入場不可能な関数を作る方法を紹介します.
# 関数への再入場とは
前回はリエントランシー脆弱性に対する攻撃と対策を紹介しました.
ある関数の実行中に再度関数が先頭から再実行されることを再入場と呼びます.
再入場されても問題ない関数をリエントラントな関数と呼びます.
この記事では関数がリエントラントか否かに関わらず、そもそも再入場させない方法を紹介します.
# noReentrancy修飾子の実装
前回の脆弱性があるBankコントラクトを以下のように加筆します.
/**
* 攻撃される銀行
*/
contract Bank {
/**
* リエントランシ対策
*
* 関数実行中なら再度実行させない.
*/
modifier noReentrancy () {
require(!locked);
locked = true;
_;
locked = false;
}
bool locked = false;
/**
* 引き出し可能な残高
*/
uint256 public balance = 0;
/**
* 引き出し
*/
function withdraw (uint256 amount) public noReentrancy {
// 1. 実行条件チェック
require(amount <= balance);
// 2. 振込
Attacker(msg.sender).fallback(amount);
// 3. 残高更新
balance -= amount;
}
/**
* 預金
*/
function deposit(uint256 amount) public {
balance += amount;
}
}
関数が実行中であるか判定するために、bool locked
というフラグを追加しました.
true
なら実行中です.
Solidityでは修飾子を自分で定義して使用する事ができます.
修飾子を関数につけると、関数の実行前後に任意の処理を挟むことが出来ます.
noReentrancy
が今回再入場禁止のために追加した修飾子です.
_
は関数の実行を表します.
関数実行前にlocked
で無いことを確認してフラグを立てます.
実行後にフラグを折ります.
modifier noReentrancy () {
require(!locked);
locked = true;
_;
locked = false;
}
完成版のコードはこちらにあります.
https://gist.github.com/shunsukehondo/6a1050d0f559f0e1657cf6285435ed90
<div class="blogcard-snipet external-blogcard-snipet">
</div>
Remixでattack
を実行してみるとエラーが発生するはずです.
# まとめ
今回はリエントランシー攻撃への対策として、Solidityの修飾子を使って再入場不可能な関数を作る方法を紹介しました.
外部コントラクト/アドレスにアクションを起こすような関数には必ずつけるようにすると良いでしょう.