安全考慮

翻譯原文

date:20170728

按照以往的經(jīng)驗(yàn)來開發(fā)軟件通常會(huì)比較簡單,但是如果用前無古人的方法來寫就會(huì)有些難度。

在Solidity中,這尤其重要,因?yàn)槟憧梢杂弥悄芎霞s來處理token或者更有甚者其他有價(jià)值的東西。另外,智能合約的每次執(zhí)行都是公開的,而且源碼通常是公開的。

當(dāng)然你總是會(huì)關(guān)心它的花費(fèi):你可以把智能合約跟網(wǎng)站服務(wù)作比較,他們都是面向公眾的(因此,也會(huì)有惡意的黑客)并且可能是開源的。如果你只想把你的購物清單保存在網(wǎng)站服務(wù)中,你不必關(guān)系很多,如果你用網(wǎng)站服務(wù)來管理你的銀行賬戶,你就要多加小心了。

這個(gè)章節(jié)會(huì)羅列出一些陷阱和常見安全建議,當(dāng)然,永遠(yuǎn)不會(huì)補(bǔ)充完整。所以,你也應(yīng)該心里有數(shù),盡管你的智能合約不會(huì)出現(xiàn)問題,但是編譯器或者平臺(tái)本身可能會(huì)出現(xiàn)問題。編譯器的安全相關(guān)問題都羅列在已知bug列表中,也是機(jī)器可讀的。注意,針對(duì)Solidity 編譯器的代碼生成器,有一個(gè)bug賞金程序。

也請(qǐng)你幫組我們來完善這個(gè)開源文檔(尤其是,多寫幾個(gè)例子)。

陷阱

私有信息和隨機(jī)性

你使用智能合約的所有事情,都是公共可見的,即使是局部變量和標(biāo)記為private的狀態(tài)變量。

如果你不想礦工作弊,在智能合約中使用隨機(jī)數(shù)是很好的技巧。

重入(RE-Entrancy)

任何合約A與合約B的交互以及任何發(fā)送以太幣都會(huì)把程序控制權(quán)交給合約B。這使得合約B在交互結(jié)束之前,可以回調(diào)A。舉個(gè)例子,下面的代碼有bug(這只是代碼片段,不是完整的合約代碼):

pragma solidity ^0.4.0;

// 這個(gè)合約包含bug,請(qǐng)不要使用
contract Fund {
    /// 映射合約的以太幣股份
    mapping(address => uint) shares;
    /// 取回你的股份
    function withdraw() {
        if (msg.sender.send(shares[msg.sender]))
            shares[msg.sender] = 0;
    }
}

這個(gè)問題不是很嚴(yán)重,因?yàn)?code>send也有g(shù)as限制,但是依舊暴露出一個(gè)問題:以太幣交易總是伴隨著代碼執(zhí)行,所以接收者可能是一個(gè)會(huì)執(zhí)行withdraw函數(shù)的合約。這會(huì)使得它可以多次取回金額,并且可以取回該合約的所有以太幣。

為了防止重入,你可以使用下面的檢查影響交互模式:

pragma solidity ^0.4.11;

contract Fund {
    /// 映射合約的以太幣股份
    mapping(address => uint) shares;
    /// 取回股份
    function withdraw() {
        var share = shares[msg.sender];
        shares[msg.sender] = 0;
        msg.sender.transfer(share);
    }
}

注意,重入不僅對(duì)以太幣交易有影響,而且可以是合約里的任何函數(shù)。另外,你必須考慮合約賬戶的多合約交互情況。一個(gè)被調(diào)用的合約可能會(huì)改變調(diào)用者的狀態(tài)。

gas限制和循環(huán)

循環(huán)不會(huì)有固定次數(shù)的遍歷,例如,循環(huán)依賴storage變量,一定要小心:因?yàn)間as的限制,交易只能消耗特定數(shù)量的gas。要么指定,要么只是執(zhí)行正常的操作,循環(huán)的遍歷次數(shù)必須在gas限制以內(nèi),gas不足會(huì)導(dǎo)致整個(gè)合約在某個(gè)時(shí)刻熄火。這不支持只是從區(qū)塊鏈中讀取數(shù)據(jù)的constant函數(shù)。這些函數(shù)仍然可能被其他合約調(diào)用作為on-chain操作的一部分,并且失敗。請(qǐng)?jiān)谀愕暮霞s中指出這種情況。

發(fā)送和接收以太幣
  • 目前,合約和外部賬戶,都不能阻止某人給他們發(fā)送以太幣。合約可以交互和拒絕常規(guī)的交易,但是可以有很多方式來移動(dòng)以太幣,而無需消息調(diào)用。一種方法是簡單的在某個(gè)合約地址上挖礦,第二種方法是使用selfdesruct(x)。
  • 如果一個(gè)合約接收以太幣(沒有調(diào)用函數(shù)),回調(diào)函數(shù)就會(huì)執(zhí)行。如果沒有回調(diào)函數(shù),以太幣就會(huì)被拒絕(通過拋出異常)。在執(zhí)行回調(diào)函數(shù)過程中,合約只依賴于當(dāng)時(shí)所需的“gas薪金”(2300gas)。但是該薪金不足以訪問storage。為了保證你的合約可以接受以太幣,要檢查回調(diào)函數(shù)所需的gas數(shù)目(例子在Remix的詳情章節(jié))。
  • 接收合約使用addr.call.value(x)()會(huì)傳遞更多的gas。這個(gè)函數(shù)只有在傳遞所有剩余的gs和對(duì)接受者開放其他更昂貴的操作(并且它在操作失敗的情況下只返回失敗代碼,并不會(huì)自動(dòng)傳遞錯(cuò)誤)的情況下,和addr.transfer(x)的表現(xiàn)一致。這可能包含對(duì)調(diào)用方的回調(diào)或者其他你不期望的狀態(tài)改變。所以這為誠實(shí)的或者惡意的用戶提供了很高的靈活性。
  • 如果使用address.transfer來發(fā)送以太幣,下面幾個(gè)要點(diǎn)要特別注意:
  1. 如果接收者是一個(gè)合約,這會(huì)引發(fā)回調(diào)函數(shù)的執(zhí)行,并且能夠,返過來回調(diào)調(diào)用者的函數(shù)。
  2. 如果棧深度超過1024,發(fā)送以太幣的操作會(huì)失敗。因?yàn)檎{(diào)用者完全控制了調(diào)用深度,他可以強(qiáng)制關(guān)閉交易。這個(gè)是合約的能力,或者使用send并且保證總是檢查他的返回值。更好的是,合約按照一定的模式來寫,使得接收者可以取回以太幣。
  3. 發(fā)送以太幣可能會(huì)失敗,因?yàn)閳?zhí)行接收者合約需要更多的gas(使用require,assert,revert,throw或者因?yàn)椴僮鞅緛砭秃馨嘿F)-它會(huì)返回“gas不足”(OOG)。如果你使用transfer或者send之后有對(duì)返回值進(jìn)行檢查,這會(huì)給接收者提供中斷交易的方法。再說一次,用取回模式替代發(fā)送模式是很好的練習(xí)。
回調(diào)深度

外部函數(shù)調(diào)用可能在任何時(shí)候都會(huì)失敗,應(yīng)為他們的最大調(diào)用棧深為1024.這種情況下,Solidity會(huì)拋出一個(gè)異常。惡意的黑客可能會(huì)在調(diào)用你的合約之前把棧深提高。

注意,send()函數(shù)在調(diào)用棧被耗盡的情況下,不會(huì)拋出異常,而是返回false。底層函數(shù).call(),.callcode().delegatecall()的行為都是一樣的。

tx.orgin

永遠(yuǎn)不要使用tx.orgin來驗(yàn)證。我們假定你有如下的錢包合約:

pragma solidity ^0.4.11;

// 這個(gè)合約包含bug - 不要使用
contract TxUserWallet {
    address owner;

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

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

現(xiàn)在有人欺騙你,讓你把以太幣發(fā)送到這個(gè)攻擊錢包的地址上:

pragma solidity ^0.4.11;

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

contract TxAttackWallet {
    address owner;

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

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

如果你的錢包對(duì)msg.sender進(jìn)行驗(yàn)證,它會(huì)獲取到攻擊的錢包地址,而不是所有者的地址。通過驗(yàn)證tx.orgin,他會(huì)得到初始地址,攻擊者會(huì)獲取到你的所有金額。

次要詳情
  • for (var i = 0; i < arrayName.length; i++) { ... }中,i的類型為uint8。因?yàn)樗怯脕肀4?code>0的最小的類型。如果數(shù)組有大于255個(gè)元素,循環(huán)會(huì)被終止。
  • 目前,對(duì)有constant修飾的函數(shù)編譯器不會(huì)強(qiáng)制要求不可變。另外EVM也沒有強(qiáng)制。所以雖然合約中聲明為靜態(tài),但是依舊會(huì)被改變。
  • 不占據(jù)完整32位字節(jié)的類型會(huì)包含“高位臟數(shù)據(jù)”。如果你訪問msg.data,這一點(diǎn)尤其重要-它暴露了一個(gè)可擴(kuò)展性問題:針對(duì)函數(shù)f(uint8 x),你可以使用參數(shù)0xff0000010x00000001來欺騙交易。這兩個(gè)參數(shù)傳進(jìn)函數(shù)的時(shí)候,x都會(huì)覺得是1,但是msg.data就會(huì)不同。如果任何地方使用keccak256(msg.data),就會(huì)有不同的結(jié)果。

建議

限制發(fā)送的以太幣數(shù)目

限制保存在智能合約里的以太幣(或者其他代幣)的數(shù)量。如果你的源碼,或者平臺(tái)或者編譯器出現(xiàn)問題,這些錢幣可能會(huì)丟失。如果你想要限制你的損失,那就限制錢幣數(shù)目。

保證它小型和模塊化

讓你的合約保持小巧和容易理解。把不相關(guān)的函數(shù)移到其他合約或者庫中。針對(duì)提高源碼質(zhì)量的通常的建議是:限制局部變量的數(shù)量,函數(shù)的長度等等。給你的函數(shù)提供文檔,那么其他人可以知道你的意圖,并且知道代碼是否是這么做了。

使用檢測交互效果模式

很多函數(shù)會(huì)首先檢查代碼(調(diào)用函數(shù)方的參數(shù)是否符合要求,是否發(fā)送了足量的以太幣,用戶是否有令牌等)。這些檢查應(yīng)該先執(zhí)行。

第二步,如果所有的檢測通過了,當(dāng)前合約就會(huì)修改這些狀態(tài)。和其他合約的交互在任何函數(shù)都應(yīng)該放在最后一步。

早期合約發(fā)布一些功能并等待外部函數(shù)調(diào)用來返回非錯(cuò)誤狀態(tài)。這通常是一個(gè)嚴(yán)重的錯(cuò)誤,因?yàn)樯厦嫠撌龅闹厝雴栴}。

注意,對(duì)已知合約的調(diào)用,也可能引起未知合約的調(diào)用。所以只使用這個(gè)模式可能會(huì)更好。

包含一個(gè)失敗安全模式

當(dāng)你的系統(tǒng)完全去中心化的時(shí)候,會(huì)移除所有的中間人。引入失敗安全機(jī)制可能是一個(gè)好的想法,尤其是對(duì)新的代碼:

你可以在智能合約中添加一個(gè)函數(shù),用來做“是否有以太幣泄露”的自檢,“是否所有花費(fèi)跟合約的余額一致”或者類似的事情。需要注意的是,你不能為這個(gè)檢查花費(fèi)太多的gas。所有這里可能需要off-chain計(jì)算。

如果自我檢查失敗了,合約會(huì)自動(dòng)轉(zhuǎn)換到安全模式,例如,關(guān)閉大部分功能,移交控制權(quán)來修復(fù)了bug的,并且信任的第三方或者只是把合約轉(zhuǎn)換為簡單的“把錢還給我”合約。

正規(guī)化校驗(yàn)

使用正規(guī)化校驗(yàn),他可能實(shí)現(xiàn)自動(dòng)數(shù)學(xué)驗(yàn)證你的源碼包含一種特定的正規(guī)要求。要求也是正規(guī)化的(就像源碼一樣),但是通常會(huì)更加簡單。

需要注意的是正規(guī)化校驗(yàn)本身只能幫助你理解“你做了什么”(要求)和“你怎么做”(你的實(shí)現(xiàn))之間的區(qū)別。你需要檢查要求是不是你所期望的并且沒有漏過任何不期望的效果。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 簡介 不管你們知不知道以太坊(Ethereum blockchain)是什么,但是你們大概都聽說過以太坊。最近在新...
    Lilymoana閱讀 3,995評(píng)論 1 22
  • 本文翻譯自:https://github.com/ConsenSys/smart-contract-best-pr...
    tolak閱讀 5,259評(píng)論 4 21
  • 原文鏈接date:20170710 Solidity中合約的概念和其他面向?qū)ο笳Z言中的類差不多。他們都有狀態(tài)變量來...
    gaoer1938閱讀 1,111評(píng)論 0 0
  • (獨(dú)享)城里大巴真的大,城市建設(shè)快速化。今天汽車人獨(dú)享,城市軌道四處達(dá)。一馬平川山那頭,一路奔弛到小康。人生無常心...
    甘朝武閱讀 178評(píng)論 0 0
  • 我想采一千片幸運(yùn)草 - 把我想給你的幸福攢夠 - 送給你 我愿采一千片幸運(yùn)草 - 放在星空下 - 祭奠我們流產(chǎn)的愛...
    潘林虎閱讀 384評(píng)論 1 1

友情鏈接更多精彩內(nèi)容