LoginSignup
6
6

More than 5 years have passed since last update.

SolidityのSecurity Consideration をチェックする。

Posted at

前回、sendやtransferについて書いたのですが、これを機に一通り security considerationを網羅しておこう、と思い立ち一通り読んだのでまとめます。

Pit falls (落とし穴)

なんか気をつけた方が良いよ的なことがまとめてあるので、サクッと日本語に直していきます。(直訳ではなくて意訳なので、解釈間違ってたら指摘してください。)

Private Information and Randomness (個人情報とランダム性について)

1.Smart Contract の中で用いる全てのローカル変数、状態変数はたとえ private 修飾子をつけたとしても誰でも見ることができます。(だから個人情報を載せるときは気をつけてね)

2.Smart Contract でランダムな数字を生成するとき、マイナーにズルされないようにするために、少しトリッキーなことをします。(ランダムな数字の生成方法なんかも以前調べたので、気が向いたら書いてみます)

Re-Entrancy(再入?)

コントラクトからコントラクトにEtherを送信したり、他のコントラクトの関数を呼び出すときに、そのcallback関数のなかで元の関数が何度も呼ばれてEtherを何回も送信させされたり、色々予想外の処理をされたりするから気をつけて!という趣旨でした。

サンプルコードがいくつか載ってます

pragma solidity ^0.4.0;

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
    /// Mapping of ether shares of the contract.
    mapping(address => uint) shares;
    /// Withdraw your share.
    function withdraw() public {
        if (msg.sender.send(shares[msg.sender]))
            shares[msg.sender] = 0;
    }
}

pragma solidity ^0.4.0;

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract Fund {
    /// Mapping of ether shares of the contract.
    mapping(address => uint) shares;
    /// Withdraw your share.
    function withdraw() public {
        if (msg.sender.call.value(shares[msg.sender])())
            shares[msg.sender] = 0;
    }
}

pragma solidity ^0.4.11;

contract Fund {
    /// Mapping of ether shares of the contract.
    mapping(address => uint) shares;
    /// Withdraw your share.
    function withdraw() public {
        var share = shares[msg.sender];
        shares[msg.sender] = 0;
        msg.sender.transfer(share);
    }
}

たとえば、⑴では、 <address>send を使ってETHを送信しているのですが、⑵では、 この場合、関数の呼び出し元は、fallback 関数の中で再度withdraw関数を呼び出す処理を書くことができます。
しかし、実際は、sendのcallback内では、sendに対して割り当てられた限られたgasしか使うことができないため、さほど問題になることはありません。

しかし、⑵のように <address>call.value を利用した場合、呼び出し側は、callback関数の中で 残った全てのgasを利用することができるため、呼び出し元が何度も関数を呼び出し、送金させることができる可能性があります。

このように、ETHを送金する処理をするときは、re-entryに気をつけなければいけないのですが、をれを解決するために、 Checks-Effects-Interactions Pattern を使いましょう。
これは、⑶のような形で、share[msg.sender] = 0 としておくことで、もしこの関数が再度呼び出されたとしても、2回目以降のmsg.sender.transferでは、share = 0なので、最初に想定した額以上に送金されることはありません。
このように送金した値を随時記録していくような実装パターンを取りましょう。

Gas Limit and Loops

Ethereumには、block gas limit といって、1block あたりに消費できるgasの限界が決まっています。そのため、変化する状態変数を利用したループ処理などでは、その処理で消費されるgasの量がblock gas limit を超え、contractの処理が途中で止まってしまう場合があります。
また、constant 修飾子のついた関数では、データの変更を伴わないため、block gas limit は適用されない。また、constant 関数は他のコントラクトのチェーン上の処理の一部として呼び出される場合があり、そのような場合はドキュメントにその旨記載してください。

Sending and Receiving Ether

これはちょうど前回書いたので、そちらを参照ください。

Callstack Depth

external 関数は、常に悪意のある攻撃によって、callstack depthが1024以上になり、処理に失敗する可能性があります。
また、send()は、call stack が不足した際に、exceptionではなく、falseを返します。これは低レベル関数である.call()や.delegatecall() 、callnode()も同じ挙動を示します。

tx.origin

tx.origin は、トランザクションの送信元のアドレスを返します。しかし、tx.originはユーザーの認証に使わないでください。

例を示します。
仮に、下記のようなWallet Contractをあなたが持っていると仮定します。

pragma solidity ^0.4.11;

// THIS CONTRACT CONTAINS A BUG - DO NOT USE
contract TxUserWallet {
    address owner;

    function TxUserWallet() public {
        owner = msg.sender;
    }

    function transferTo(address dest, uint amount) public {
        require(tx.origin == owner);
        dest.transfer(amount);
    }
}

そして攻撃者がこんな感じのWallet Contractを持っているとします。

pragma solidity ^0.4.11;

interface TxUserWallet {
    function transferTo(address dest, uint amount) public;
}


contract TxAttackWallet {
    address owner;

    function TxAttackWallet() public {
        owner = msg.sender;
    }

    function() public {
        TxUserWallet(msg.sender).transferTo(owner, msg.sender.balance);
    }
}

このとき、もし攻撃者が、あなたを騙して、あなたのTxUserWallet→TxAttackWalletにETHを(少しだけ)送金させた場合、受け取ったTxAttackWalletのfallback関数が呼ばれ、TxAttackWalletのownerにあなたのETHを全て(msg.sender.balance のとこ)送るような関数を呼び出します。
そして、このときtx.originはあなたであるため、tx.origin == ownerが成立してしまい、TxUserWalletのtransferTo関数は実行され、あなたのETHは全て攻撃者のWalletに送信されてしまいます。

ここでもし、msg.senderを利用していれば、msg.senderは攻撃者のaddressになり、ETHを盗み取られる心配はありません。

Minor Details

原文そのまま載せて訳す感じにしてみます。そっちの方がわかりやすい気がしてきたので。


In for (var i = 0; i < arrayName.length; i++) { ... }, the type of i will be uint8, because this is the smallest type that is required to hold the value 0. If the array has more than 255 elements, the loop will not terminate.


for (var i = 0; i < arrayName.length; i++) { ... } において、i はuint8 型なので、ループが255回を越えると、ループは最後まで到達できません。uint8は0~255しか取れないので。


The constant keyword for functions is currently not enforced by the compiler. Furthermore, it is not enforced by the EVM, so a contract function that “claims” to be constant might still cause changes to the state.


constant 修飾子は現状コンパイラによっても、EVMによっても強制されてはおらず、constant 修飾子がついていてもstateを変更する可能性があります。


types that do not occupy the full 32 bytes might contain “dirty higher order bits”. This is especially important if you access msg.data - it poses a c risk: You can craft transactions that call a function f(uint8 x) with a raw byte argument of 0xff000001 and with 0x00000001. Both are fed to the contract and both will look like the number 1 as far as x is concerned, but msg.data will be different, so if you use keccak256(msg.data) for anything, you will get different results.


完全な32バイトを占めていないタイプは'汚れた最上位ビット'を含むことがあります。これは、msg.dataを利用する場合に特に重要です。展性リスクがあり、もしこのような最上位ビットを含む場合、function f(uint8 x)を生のバイト列0xff000001で現すことも、0x00000001で表すこともできます。どちらもxを使用する限りにおいては、1に見えますがmsg.data(keccak256)をしようする場合は、別の結果が得られてしまいます。

Recommendations レコメンデーション

Restrict the Amount of Ether

コントラクトに保持できるETHやトークンの量を制限しておくことで、何らかのバグがあった場合に損失が生じるリスクを減らしましょう。

Keep it Small and Modular 小さく、モジュールに

コントラクトは小さく、シンプルにわかりやすい単位にしましょう。
推奨事項
-ローカル変数の数や関数の長さを制限する
-何を意図した関数で、実際のコードはどうなっているのかを書いておく

Use the Checks-Effects-Interactions Pattern

最初の方でも出てきたやつですね。他のコントラクトの関数を呼ぶときや、呼ばれる場合の注意。

手順としては
1.まずrequire や assert などで、送信者や、引数が不正でないかチェックする
2.状態変数に加える変化を記録
3.最後に他のコントラクトとの関係を実行する。

という感じで、予想外の処理が行われたりすることを防止することが大事。

Include a Fail-Safe Mode

安全装置的な機能を用意しておきましょう。
ETHは流出していないか、トークンとETHの量は合致しているか、など。
もし不具合や不正が見つかった場合に、自動的に関数を停止するような仕組みを用意しておくとよいでしょう。

Formal Verification

あなたの書いたコードが、何らかの仕様を満たしているか、自動で検証することができます。らしいです。ここよくわかんないです。

ざっくりまとめてみました。
今、Ethereumのアプリ作っていて、色々調べているのですが、何かアイデアとかある人、jiroyamamoto.addwis@gmail.com までご連絡ください。お仕事の依頼でも、一緒に作ってみたい、勉強する相手が欲しいとかでも何でも歓迎です。

よろしくです。

6
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6