【Ethereum】ERC20トークンで発生した脆弱性「batchOverflow」について
つい最近話題になった「batchOverflow」についてホットなうちにまとめる。
参考
イーサリアム基盤ERC20トークン“重大バグ”発見|ポロニエックスほか複数の大手取引所取引停止 | 仮想通貨まとめ
まぁ重大バグではないが。。
要約
原因となったメソッド「batchTransfer」
早速原因となったメソッドを見ていきます。
function batchTransfer(address[] _receivers, uint256 _value) public whenNotPaused returns (bool) { uint cnt = _receivers.length; uint256 amount = uint256(cnt) * _value; require(cnt > 0 && cnt <= 20); require(_value > 0 && balances[msg.sender] >= amount); balances[msg.sender] = balances[msg.sender].sub(amount); for (uint i = 0; i < cnt; i++) { balances[_receivers[i]] = balances[_receivers[i]].add(_value); Transfer(msg.sender, _receivers[i], _value); } return true; }
とてもシンプルなメソッドで、複数のアドレスに同時に支払いを行うもので、払い戻し等に使うことが予想される。
具体的に言えば、1つ目の引数に指定したアドレスのリスト_receivers
に対して、2つ目の引数である_value
を送金する関数。
最初のところから。
uint cnt = _receivers.length; //送金先のアドレスがいくつあるかを取得 uint256 amount = uint256(cnt) * _value; //送金量の合計を計算
はい、いきなり原因が存在する。2行目です。
悪意のあるユーザが、この関数を呼び出すときに、_value
を巨大な値にしたとすると、
cnt*_value
は非常に大きな値になり、amount
が桁あふれ(オーバーフロー)を起こしてしまう。
桁あふれ(オーバーフロー)については、こちら など調べたらいろいろ出てくるはず。
要するに3桁しか管理できないところに1000を入れても、桁が溢れて0となってしまう。
悪意あるものによってオーバーフローされると、amount
はほぼ0になってしまう。
その前提で次の2行。
require(cnt > 0 && cnt <= 20); //cntが0より大きく20以下であること require(_value > 0 && balances[msg.sender] >= amount); //_valueが0より大きく、送金元にamount以上が存在すること
1行目で送金先のアドレスの数を制限している。
2行目では、送金が0より大きく、送金元に十分資金があるように制限している。
ここで本来は、_value
が大きいとamount
も大きくなり、送金元の保持する資金を超えてしまったらエラーになるはず。
しかし、amount = uint256(cnt) * _value
によるオーバーフローにより、_value
が大きくてもamount
が小さくなってしまう。
結果、送金元に十分資金があることのチェックが出来ず、悪意あるものの攻撃に対して、条件チェックで防げなくなってしまった。
あとは残り。
balances[msg.sender] = balances[msg.sender].sub(amount); //送金元の資金からamountを引く for (uint i = 0; i < cnt; i++) { //送金先アドレスの数だけループ balances[_receivers[i]] = balances[_receivers[i]].add(_value); //送金先のアドレスの資金に_valueを足す Transfer(msg.sender, _receivers[i], _value); //実際に送金先アドレスに、_valueを送る }
前の通り、悪意あるものが攻撃した前提で、_value
は大きく、amount
は0に近い状況を考える。
1行目で送金元資金から、amount
を引いていますが、amount
はオーバーフローにより小さいため、送金元資金はほとんど減らない。
(ここはあまり関係ないかも?)
そして、for文に入り、送金先アドレスに、大きい値である_value
を送金。。といった感じ。
プログラムを見ると、かなりシンプルな脆弱性であり、わかりやすいです。
そしてオーバーフローによる攻撃自体は、元々Solidityとして注意すべきものとして挙げられているので、
恐らくERCトークンに標準搭載しているメソッドであれば、誰か気づいたと思う。
また、今回の攻撃自体もスマートコントラクトのセキュリティをしっかり勉強しておけば防げると思う。
なので、セキュリティ対策はしっかり勉強していきたいですね!
【Ethereum】スマートコントラクトのベストプラクティスについてまとめる①
Ethereumでのスマートコントラクト開発、すなわちSolidityでのプログラミングでは、かなりしっかりとしたコーディングをすることが求められる。
なぜなら、
- 一度ブロックにデプロイしたコードは変更できない
- 悪意のある人の攻撃により資産を盗まれる可能性がある
- 下手に書くと、手数料のGASが必要以上にかかる
といった理由があるから。
そこで、SolidityでのベストプラクティスをConsenSysが出しているので、それをまとめる。
英語が時々更新されるので、最新情報を確認したい時は英語版を確認することをお勧めします。
割と長いのでここではざっくりのまとめです。
※自分の言葉に直してる部分があるので、元々の言い方や内容と違う部分もあります。
心構え
失敗は起きる前提で考える
しっかりテストして慎重にデプロイする
コントラクトコードはシンプルなものにする
- 複雑だとバグが起きやすい
- オンチェーンにすべきもののみコントラクトとする
- 共通部分をモジュール化する
最新のセキュリティ対策できるようコードをこまめにアップデートしていく
スマートコントラクトの特性を把握する
- 実行にGASがかかること
- 悪意のあるコントラクトへのコールがいること
- コードは公開されていること
シンプルさと複雑さのトレードオフを意識する
- 基本はアップグレードできて、モジュール化して、コードの再利用をすべき
- しかし、ベストプラクティスが最適でない複雑にすべきケースもあるので、意識しておく
推奨する実装
信用できない外部コントラクトと関わるものにはマークをする
変数、メソッド、コントラクト、インターフェースを対象に、
信用できない外部コントラクトを呼び出すものに関わる場合
- 信用できない場合:untrustedと名前に入れる
- 信用できる場合:trustedと名前に入れる
外部コントラクトを呼び出した後にStateの変更をすることを避ける
外部コントラクトを実行すると悪質なコードが実行されうる。
そうすると、コントラクト実行後にStateを変えようとしても、期待通りに実行されず、
制御フローを悪意のあるものに操作されることがある。
(Race Conditionが実例)
send(), transfer(), call.value()()を使い分ける
全て送信先がコントラクトだった場合、送信先のコントラクトでのコードが実行されうるのだが、
someAddress.call.value()()
の場合、送信先コントラクトが利用できるGAS量が制限されていない。
結果、GASを使い切るような攻撃をされるリスクが生まれる。
一方someAddress.send()
とsomeAddress.transfer()
は、2300GASまでと決まっているため、そういったリスクはない。
ただし、制限ゆえに使えないケースもあるので使い分けが大事。
※このあたりはややこしいので別途まとめる予定
external callのエラーハンドリング
external call時に、呼び出し先がコントラクトだと、呼び出し先のコードが実行され、
Exceptionがthrowされたり、falseがreturnされる。
その中でも、raw-levelなメソッド(someAddress.send()など)の場合には、呼び出し先でエラーになった時の処理を記述する。
if (!someAddress.send(100)){ //エラーが発生した場合の処理を書く(返金など) }
external call部分のトランザクションを分ける
連続する処理の中に、外部コントラクトの呼び出しをする場合、悪意あるユーザのコントラクトだった場合、そこで処理が止まってしまうことがある。
そこで、各外部呼び出しを、受信者が開始できる独自のトランザクションに分離する方がよい場合がある。
特に複数ユーザへの支払い時には、ユーザーに自動的に資金を送金するのではなく、各ユーザから資金を引き出してもらうほうがよい。
新たにデプロイされたコントラクトの残高が0と決めつけない
デプロイ前にコントラクトにweiを送金できるため、残高0と決めつけた設計をしないこと
オンチェーンのデータは公開されていることを忘れない
基本的にオンチェーンのデータは全て公開されている。
ゲームやオークションなどで非公開な情報の処理を行う場合は、それに応じた設計が必要。
例えばじゃんけんの場合、何も考えずにやると同時に手を出すのは難しく後出し側がオンチェーンデータを見れば勝ってしまう。
そこで互いに事前に出すものをハッシュ化したものを登録する。
実際に出したあと、出したもののハッシュ値が事前のものと同じかを検証する。
複数名の契約において、一部の参加者が途中でオフラインになることを考慮する
たとえばゲームにおいて途中で一人のプレイヤーが次のアクションを意図的に行なわなかったり、偶然できない場合を想定しておく。
(資金を預ける場合は、一定時間たったら資金の払い戻しを行うなどの対処を考える等)
assert()とrequire()を使う
require()
はユーザの入力へのチェック等に用いて、assert()
は不変プロパティのチェックや内部エラーのチェックに用いる
除算の丸めに注意する
除算では最も近い整数に切り下げられる。
uint a = 5 / 2
は2となる。
こういう時は、乗算を使う。
Etherを強制的にアカウントに送金できる
selfdestruct(victimAddress)
を使うことで、victimAddressに強制的に元のコントラクト内のEtherを送金できる。
victimAddress側ではフォールバック関数含め、コードは一切実行されないため防ぎようがない。
抽象コントラクトとインタフェースを使い分ける
インタフェースは、実装済みのfuctionを持つことはできない。
また、インタフェースには、ストレージにアクセスできない、または他のインタフェースから継承できないなどの制限もある。
しかし、インタフェースは実装前にコントラクトを設計する上では有用。
fallback関数をシンプルにする
Fallback関数は、引数なしのメッセージが送信されたとき、もしくは関数が一致しないときに呼び出される。
send()
または transfer()
から呼び出されたときは2300GAS分までの処理が可能。
これらのメソッドからEtherを受信したければ、fallback関数でできるのはeventでの記録をすることくらい。
それい以上のガスが必要な場合は、他の関数で呼び出されるような設計が必要
メソッド、変数のアクセス制限を明示しておく
メソッドや変数には、external
、public
、 internal
、private
を指定しておくこと
pragmaのバージョンを固定する
プラグマを固定すると、未知のバグのリスクが高い最新のコンパイラなどを使用して、誤った処理がされないようになる。
pragma solidity ^0.4.4;
はよろしくなく、
pragma solidity 0.4.4;
とすべきらしい
※世の中的に前者が割と多く、どこまで後者にすべきなのかはわからなかった。
関数とイベントの命名規則
関数とイベントの混乱の危険を避けるため、イベント名は大文字で開始し、大文字の前に接頭辞を付ける(Logを推奨)。
関数の場合はコンストラクタを除き、常に小文字で始める。
できるだけ新しい構造を使う
suicide
よりもselfdestruct
を使うなど
組み込み関数をシャドーイング出来ることに注意
msg
やrevert()
などの組み込み関数に対して、シャドーイング、すなわち同一名のメソッドを宣言することで
元の組み込み関数にアクセスできなくなる。
tx.originを使わない
msg.sender
を使うようにする。
tx.origin
は将来的に削除の予定らしい。
詳細はこちら
timestampの30秒ルール
minerは、ブロック生成時に、30秒間の範囲で好きなタイムスタンプを設定する権限を持つ。
もし、ゲームの乱数でtimestampを使っていた場合、minerにより乱数調整ができてしまう。
ただし、30秒の間で状態が変わらないのであれば、問題はない。
複数の継承を扱う場合の注意
例えば、BとCというコントラクトがあるときに
contract A is B, C { function A() public B(3) C(5) { setFee(); } }
としたときどうなるか?などの挙動を理解しておく。
(基本コードの左から順に継承していくらしい。C -> B -> A)
詳しくは、こちら
まだまだあるので続きは別記事にします。
【深層強化学習】Chainerrlのa3cのモデル定義で入力チャネル数でNoneは使わないこと
急にテーマが代わり、強化学習の話。
しかも、かなりニッチな話。
深層強化学習のライブラリとして「Chainrrl」を使っている。
「Chainrrl」はPreferred Networksが提供しているChainerを使った深層強化学習ライブラリ。
新しい手法とかも実装されているので便利。
「a3c」という強化学習手法を実装しているとき下記のようなエラーが発生した。
エラー内容
File "/Library/Python/2.7/site-packages/chainerrl/agents/a3c.py", line 122, in __init__ async.assert_params_not_shared(self.shared_model, self.model) File "/Library/Python/2.7/site-packages/chainerrl/misc/async.py", line 63, in assert_params_not_shared assert a_param.data.ctypes.data != b_param.data.ctypes.data AttributeError: 'NoneType' object has no attribute 'ctypes'
エラーの内容の通りだが、指定したモデルを同期しようしたときにパラメータ型でエラーが発生している。
原因と対策
モデルの中で下記の通り、モデルの定義部分で入力チャネル数(Convolution2Dの第1引数)をNoneにするとダメらしい。
def __init__(self, ndim_obs,n_channels=4): super(CommonFunction, self).__init__( conv1=L.Convolution2D(None,16,ksize=(4,1),pad=1,stride=1), conv2=L.Convolution2D(None,32,ksize=4,pad=1,stride=1), conv3=L.Convolution2D(None,64,ksize=4,pad=1,stride=1), c_fc1=L.Linear(256, 200), c_fc2=L.Linear(200, 200), c_fc3=L.Linear(200, 34))
入力チャネル数を指定するとうまく動く
def __init__(self, ndim_obs,n_channels=4): super(CommonFunction, self).__init__( conv1=L.Convolution2D(3,16,ksize=(4,1),pad=1,stride=1), conv2=L.Convolution2D(16,32,ksize=4,pad=1,stride=1), conv3=L.Convolution2D(32,64,ksize=4,pad=1,stride=1), c_fc1=L.Linear(256, 200), c_fc2=L.Linear(200, 200), c_fc3=L.Linear(200, 34))
おそらく、a3cのaの一つである「Asynchronous(非同期)」の実装に、ctypesを利用しているが、
L.Convolution2D(None,....)
とすると、ctypesを持たないインスタンスになってしまうので、エラーになってしまう。
(chainerrlのasync.pyの56~63行目あたり)
(あまりちゃんとchainerrlのソース読んでないので憶測です。)
ちなみにDeepQNetwork(DQN)ではL.Convolution2D(None,....)
で問題なかった。
(Asynchronousではないからかな?)
ちなみに上の例では、L.Convolition2D
でNoneを指定しているが、L.Linear(None,...)
でも同じ話。
まぁ深く考えず入力チャネル数を指定すればいいだけ。
【Ethereum】スマートコントラクト開発の基本用語をまとめる
今まで勉強してきた基本的な内容をまとめておきます。
かなり省略気味です。
PlasmaやCasper, RaidenNetworkなど、今後のEthereum内の技術は一旦置いときます。
(あくまでスマートコントラクト開発ということで)
Ethereum
- ブロックチェーン 技術を活用したスマートコントラクトのアプリケーション(Dapps)のプラットフォーム
- プラットフォーム内で使う通貨は「Ether」
- 仮想通貨としてビットコインに継ぐ時価総額2位
- スマートコントラクトのプログラムの実行に必要な燃料「GAS」として消費される
スマートコントラクト
- 様々な契約(コントラクト)を自動で執行する仕組み。
- Ethereumなどブロックチェーン で有名になったが、ブロックチェーン じゃなくてもスマートコントラクトといえる
- ブロックチェーン の場合、契約情報をP2Pネットワーク上のブロックに記述。非中央集権的なスマートコントラクトが可能になる
Dapps
- Decentralized Applicationsの略
- ブロックチェーン・スマートコントラクト により非中央集権で分散化されたアプリケーション
- 現時点での使われ方としては、ゲームが多く、後はDEX(分散型取引所)や予測市場(Augur, Gnosis)など
- CryptoKittiesと呼ばれる仮想猫を育てたり交換するゲームが一番有名
ここから技術的なところ
Geth
- EthereumのP2Pネットワークにアクセスするためのクライアント
- 他のクライアントとしては「Parity」が有名だが、Gethが現状一番メジャー
- Go Ethereumの略で、Go言語ベース
- マイニングやブロック上の情報確認(特定アドレスの資産など)、デプロイ済みのコントラクトプログラムの実行が可能
Solidity
- Ethereumスマートコントラクトを記述するための言語
- 完全チューリングな言語
- javascriptに近い
Web3.js
Truffle
OpenZeppelin
- セキュアなスマートコントラクト開発をするためのフレームワーク
- トークンとして、ERC20、ERC721、ERC827の機能が実装されていたり、ICOの機能も実装されている
- ユーザのトークンを扱うことになるスマートコントラクト/Dappsにおいて、セキュアなコーディングは必須なので重要
- Truffleの中で使うことが可能
Remix
Ganache
- Truffleのチームが開発した開発環境
- Gethなどのクライアントがなくても、Ethereumネットワークにアクセスできる
- Remixの場合、Gethの起動が多分必要(RPCによる接続のため)
Mist
- Ethereumのウォレット兼ブラウザ
- 公式のウォレット
- マイニングやDAppの操作が可能
- GUIのため、わかりやすい
MetaMask
- GoogleChromeやFireFoxなどのプラグインとして使えるEthereum用ウォレット
- メインネットやテストネット、プライベートネットへの接続が可能で切り替えも簡単
- これによりChromeとかから送金ができたり、Dappsの利用ができる
ざっと書きました。
もし、これ大事なのに忘れてるとか意味間違ってるとかあればご指摘ください。
あとは上にあげた中のツールを開発時にどう活用していくかですね。
特に、開発環境は、Remixを使うのか、Ganacheを使うのか、一般的なIDEを使ってコンパイルとかは別でするのか等パターンがいろいろあります。
まずは色々触って自分にあった使い方を見つけていくしかないですね!
僕ももっと触っていきたいと思います。
ブログ始めました。
はじめまして Paoといいます。
はてなブログを始めました! エンジニア系のブログです。
簡単にブログを書く目的と自己紹介を書いておきます。
自己紹介
- 30歳前後
- 関東在住
- 情報通信系の大企業勤務
- もうすぐ子持ち
- 関わってきた分野:VR(大学の修士)、Webアプリ開発(上流工程)、画像認識・機械学習の研究開発
- 趣味:麻雀(天鳳というネット麻雀ずっとやってます。)
書く目的
一言で言うと「自身の技術を磨くため」ですが、主には以下の目的です。
技術的な勉強のモチベーションにする
このブログを書く前から個人的な勉強はしているのですが、ブログを書くことで、モチベーションの維持につながればと思います。
アウトプットする習慣を身に付ける
勉強を自分でしていてもインプットがほとんどで身につかないことも多いです。
そこでブログを通して、アウトプットをどんどんしていけたらと思います。
また、私は文章を書く能力も高くないので、ブログを書くというアウトプットでライティング力も身につけていけたらと思ってます。
自身の技術力を公開する
最終的には、このブログで書いてきたことが自身の能力のアピールになれば良いと思っています。
これをきっかけに人のつながりや転職などにも、そのうちつながればと。
書く分野
興味が持ったこと何でも書いていく予定ですが、今のところは下記のような話が多いと思います。
その他
- 雑記のようなものも書くと思います。
- プログラム言語は色々扱うつもりですが、pythonが中心かもです。
- 本来の目的ではないですが、小遣い稼ぎ程度のアフィリンクなどは載せるつもりです。