solidity智能合約的安全(二)

上次我們談到了由于solidity智能合約代碼的公開性和行業(yè)現(xiàn)狀,這一領(lǐng)域的安全狀況令人堪憂,所幸目前這種情況正在被重視,有些組織和個人已經(jīng)做了一些有意義的實踐與總結(jié)。下面是solidity社區(qū)總結(jié)出的安全方面的主要關(guān)注點與應(yīng)對思路。

智能合約的安全問題與最佳實踐

1. 重入


如果一個智能合約A調(diào)用了另一個智能合約B,那么控制權(quán)將從A完全傳遞給B,也就是說,如果B再回調(diào)回A也,然后再調(diào)用B,然后再調(diào)用A...這樣循環(huán)下去,A也是沒有辦法的。下面的合約將允許一個攻擊者多次得到退款,因為它使用了 call ,默認(rèn)發(fā)送所有剩余的 gas:

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;?

? ? ?}

}

作為改善措施,應(yīng)該使用使用“檢查-生效-交互”(Checks-Effects-Interactions)模式編寫函數(shù)代碼:

第一步,做檢查工作,例如參數(shù)是否合法,發(fā)送者是否合法...,這些檢查工作應(yīng)該首先被完成。

第二步,狀態(tài)變量修改。

第三步,與其他合約交互,這一步在任何合約中都應(yīng)該最后被調(diào)用。

由于對已知合約的調(diào)用反過來也可能導(dǎo)致對未知合約的調(diào)用,所以最好是一直保持使用這個模式編寫代碼。按照這一模式,以上代碼應(yīng)該這么寫:

pragma solidity ^0.4.11;

contract Fund { /// 合約中 |ether| 分成的映射。?

?????mapping(address => uint) shares; /// 提取你的分成。?

?????function withdraw() public {?

?????????var share = shares[msg.sender];?

?????????shares[msg.sender] = 0;?

?????????msg.sender.transfer(share);?

?????}

}

2.address.send(), address.transfer()? vs address.call.value()()


這三個調(diào)用都可以用來向一個智能合約轉(zhuǎn)賬,但仍然有以下區(qū)別:

address.send()和address.transfer()是重入安全(可以防止重入)的. 原因是,這兩種調(diào)用只分配到了2300 gas,這一數(shù)量一般只夠記錄一兩條log。所以,當(dāng)被調(diào)用的地址是一個合約,而且實現(xiàn)了可以被外界執(zhí)行的默認(rèn)函數(shù),那么這個默認(rèn)函數(shù)仍然會被調(diào)用到。address.transfer(y)效果等同于require(address.send(y));.

address.call.value(y)()將會把所有的gas發(fā)送到合約地址上并執(zhí)行默認(rèn)函數(shù). 所以這個默認(rèn)函數(shù)將會有足夠的gas執(zhí)行任何操作,包括重新調(diào)用原合約的接口,因此是重入不安全(不能防止重入)的。

因此,在用這三個接口轉(zhuǎn)賬時,一定要根據(jù)實際情況把安全狀況考慮清楚。一般來說,首選address.send()和 address.transfer()。其次選address.call.value(y)(),在使用address.call.value(y)()的時候最好限定所能使用的最多gas。

3.push模式 vs pull模式


在以上的討論中,盡管你使用address.call.value(y)()的時候限定了所能使用的最多gas,仍然不意味著就沒有問題了,因為也許一個你沒有想到的漏洞所需要的gas本來就低于你的限定量。我們最好將調(diào)用限制到智能運行其本身的transaction,因此,在付款相關(guān)操作中,拉取模式(pull)應(yīng)該優(yōu)先于推送模式(push), 讓我們看看以下例子:

// bad

contract auction {?

????address highestBidder;?

? ? uint highestBid;?

? ? function bid() payable {?

? ? ? ? require(msg.value >= highestBid);?

? ? ? ? if (highestBidder != 0) {?

? ? ? ? ? ? highestBidder.transfer(highestBid); // if this call consistently fails, no one else can bid

? ? ? ? }?

? ? ? ? highestBidder = msg.sender;?

? ? ? ? highestBid = msg.value;?

? ? }

}

以上例子中,退款采用的是push模式,如果有一個攻擊者在fallback中調(diào)用require(0);然后參與以上拍賣之后,其他人再參與拍賣時以上代碼highestBidder.transfer(highestBid);便會失敗。從而達(dá)到鎖定拍賣的目的。而如果退款時采取pull模式,則會避免這一缺陷:

// good

contract auction {?

?????address highestBidder;?

?????uint highestBid;?

?????mapping(address => uint) refunds;?

?????function bid() payable external {?

?????????require(msg.value >= highestBid);?

?????????if (highestBidder != 0) {?

?????????????refunds[highestBidder] += highestBid; // record the refund that this user can claim?

?????????}?

?????????highestBidder = msg.sender;?

?????????highestBid = msg.value;?

?????}?

?????function withdrawRefund() external {?

?????????uint refund = refunds[msg.sender];?

?????????refunds[msg.sender] = 0;?

?????????msg.sender.transfer(refund);

?????}

}

4.合約間調(diào)用處理


Solidity 開放了一些底層調(diào)用函數(shù)如 address.call(),address.callcode(),address.delegatecall()和 andaddress.send(). 這些底層調(diào)用的一個特點是:他們調(diào)用失敗時不會拋出異常,只是會返回失敗,而異常在合約間的調(diào)用中是可以傳遞的,返回的失敗結(jié)果卻不能。因此,當(dāng)調(diào)用這些底層函數(shù)時一定要考慮調(diào)用失敗的情形,例如,我們不能這么寫代碼:

// bad

someAddress.send(55);

someAddress.call.value(55)();?

someAddress.call.value(100)(bytes4(sha3("deposit()")));?

而應(yīng)該這么寫:

// good

if(!someAddress.send(55)) {?

?????// Some failure code

}

ExternalContract(someAddress).deposit.value(100);

5.不要假設(shè)智能合約里沒有eth或者其他代幣



攻擊者甚至可以在你的合約沒有創(chuàng)建之前向你的智能合約轉(zhuǎn)入一筆eth。當(dāng)然也有一些其他辦法巧妙的向你的智能合約存入eth,例如:攻擊者可以創(chuàng)建一個合約,向這個合約中轉(zhuǎn)入少量eth,然后調(diào)用selfdestruct(victimAddress),這個victimAddress填成你的合約地址,這樣eth就轉(zhuǎn)到你的合約里了,這種方法沒有人能阻止。


事實上,除了以上基本準(zhǔn)則,人們還在不斷總結(jié)經(jīng)驗并抽象出一些代碼模式。相信在人們的共同努力下,solidity智能合約會越來越安全。

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

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

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