Paoの技術力を磨くブログ

機械学習やブロックチェーン等の技術を身に付けていくブログです。

【Ethereum】ERC20トークンで発生した脆弱性「batchOverflow」について

つい最近話題になった「batchOverflow」についてホットなうちにまとめる。

参考
イーサリアム基盤ERC20トークン“重大バグ”発見|ポロニエックスほか複数の大手取引所取引停止 | 仮想通貨まとめ
まぁ重大バグではないが。。

要約

  • ERC20トークン自体の脆弱性ではない
  • 一部のERC20トークンが独自実装している「batchTransfer」というメソッドに問題がある
  • 影響範囲としては少数の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での記録をすることくらい。 それい以上のガスが必要な場合は、他の関数で呼び出されるような設計が必要

メソッド、変数のアクセス制限を明示しておく

メソッドや変数には、externalpublicinternalprivateを指定しておくこと

pragmaのバージョンを固定する

プラグマを固定すると、未知のバグのリスクが高い最新のコンパイラなどを使用して、誤った処理がされないようになる。

pragma solidity ^0.4.4;はよろしくなく、 pragma solidity 0.4.4;とすべきらしい

※世の中的に前者が割と多く、どこまで後者にすべきなのかはわからなかった。

関数とイベントの命名規則

関数とイベントの混乱の危険を避けるため、イベント名は大文字で開始し、大文字の前に接頭辞を付ける(Logを推奨)。 関数の場合はコンストラクタを除き、常に小文字で始める。

できるだけ新しい構造を使う

suicideよりもselfdestructを使うなど

組み込み関数をシャドーイング出来ることに注意

msgrevert()などの組み込み関数に対して、シャドーイング、すなわち同一名のメソッドを宣言することで
元の組み込み関数にアクセスできなくなる。

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

  • Decentralized Applicationsの略
  • ブロックチェーン・スマートコントラクト により非中央集権で分散化されたアプリケーション
  • 現時点での使われ方としては、ゲームが多く、後はDEX(分散型取引所)や予測市場(Augur, Gnosis)など
  • CryptoKittiesと呼ばれる仮想猫を育てたり交換するゲームが一番有名

ここから技術的なところ

Geth

  • EthereumのP2Pネットワークにアクセスするためのクライアント
  • 他のクライアントとしては「Parity」が有名だが、Gethが現状一番メジャー
  • Go Ethereumの略で、Go言語ベース
  • マイニングやブロック上の情報確認(特定アドレスの資産など)、デプロイ済みのコントラクトプログラムの実行が可能

Solidity

Web3.js

  • Ethereum用のjavascriptAPI
  • JSON RPCのラッパー
  • RPC経由でコントラクトの実行などをできる
  • Dappsのフロントエンドとして、htmlに組み込んで使う

Truffle

OpenZeppelin

  • セキュアなスマートコントラクト開発をするためのフレームワーク
  • トークンとして、ERC20、ERC721、ERC827の機能が実装されていたり、ICOの機能も実装されている
  • ユーザのトークンを扱うことになるスマートコントラクト/Dappsにおいて、セキュアなコーディングは必須なので重要
  • Truffleの中で使うことが可能

Remix

  • 旧名:browser-solidity
  • Ethereumの統合開発環境
  • コーディングだけでなく、コンパイルやデプロイも可能
  • オンライン上とローカルに立てる方法がある

Ganache

  • Truffleのチームが開発した開発環境
  • Gethなどのクライアントがなくても、Ethereumネットワークにアクセスできる
  • Remixの場合、Gethの起動が多分必要(RPCによる接続のため)

Mist

  • Ethereumのウォレット兼ブラウザ
  • 公式のウォレット
  • マイニングやDAppの操作が可能
  • GUIのため、わかりやすい

MetaMask

  • GoogleChromeFireFoxなどのプラグインとして使えるEthereum用ウォレット
  • メインネットやテストネット、プライベートネットへの接続が可能で切り替えも簡単
  • これによりChromeとかから送金ができたり、Dappsの利用ができる

ざっと書きました。
もし、これ大事なのに忘れてるとか意味間違ってるとかあればご指摘ください。

あとは上にあげた中のツールを開発時にどう活用していくかですね。
特に、開発環境は、Remixを使うのか、Ganacheを使うのか、一般的なIDEを使ってコンパイルとかは別でするのか等パターンがいろいろあります。
まずは色々触って自分にあった使い方を見つけていくしかないですね!
僕ももっと触っていきたいと思います。

ブログ始めました。

はじめまして Paoといいます。

はてなブログを始めました! エンジニア系のブログです。

簡単にブログを書く目的と自己紹介を書いておきます。

自己紹介

  • 30歳前後
  • 関東在住
  • 情報通信系の大企業勤務
  • もうすぐ子持ち
  • 関わってきた分野:VR(大学の修士)、Webアプリ開発(上流工程)、画像認識・機械学習の研究開発
  • 趣味:麻雀(天鳳というネット麻雀ずっとやってます。)

書く目的

一言で言うと「自身の技術を磨くため」ですが、主には以下の目的です。

技術的な勉強のモチベーションにする

このブログを書く前から個人的な勉強はしているのですが、ブログを書くことで、モチベーションの維持につながればと思います。

アウトプットする習慣を身に付ける

勉強を自分でしていてもインプットがほとんどで身につかないことも多いです。
そこでブログを通して、アウトプットをどんどんしていけたらと思います。
また、私は文章を書く能力も高くないので、ブログを書くというアウトプットでライティング力も身につけていけたらと思ってます。

自身の技術力を公開する

最終的には、このブログで書いてきたことが自身の能力のアピールになれば良いと思っています。
これをきっかけに人のつながりや転職などにも、そのうちつながればと。

書く分野

興味が持ったこと何でも書いていく予定ですが、今のところは下記のような話が多いと思います。

その他

  • 雑記のようなものも書くと思います。
  • プログラム言語は色々扱うつもりですが、pythonが中心かもです。
  • 本来の目的ではないですが、小遣い稼ぎ程度のアフィリンクなどは載せるつもりです。