Move語言安全性分析及合約審計要點之邏輯校驗漏洞
1、邏輯校驗漏洞
智能合約開發的業務相關邏輯設計復雜,涉及的經濟學計算和參數較多,不同項目和協議之間可組合性極其豐富,很難預測,非常容易出現安全漏洞。
在Solidity智能合約中,我們總結了4種類型的邏輯校驗漏洞:
(1)未校驗返回值
(2)未校驗相關計算數據公式
(3)未校驗函數參數
(4)未規范使用require校驗
同樣地,我們將從這4個方面分析Move合約中是否存在這些邏輯檢驗漏洞以及其可能性和危害。
1.1 未校驗返回值
不檢查消息調用的返回值,即使被調用的函數返回一個異常值,執行邏輯仍然會繼續進行,只是該函數的調用并沒有實現正確的邏輯,這會導致整個交易得不到正確的結果,甚至會威脅到數字資產的安全性。
比如,Solidity合約中的call函數,functionCallWithValue函數如下:
代碼中調用了call函數,如果call函數執行發生意外,比如轉賬失敗,則返回值success為false。如果沒有驗證該返回值,即使success為false,交易仍然會正常執行。只是交易中的這筆轉賬沒有成功。這里通過require對success進行了驗證,如果是false,交易就會回滾(revert)。
call函數是Solidity動態函數調用的一個關鍵函數,是Solidity語言層面的一個容易因為返回值而產生漏洞的典型代表。除了call函數之外,在業務層面,Solidity合約也經常使用返回值來判斷函數是否執行成功,比如ERC20合約中的函數:
對于這類函數,在實際應用的時候一般需要對返回值進行校驗,否則會產生漏洞,甚至會威脅到數字資產的安全性。
此外,根據實際的業務邏輯,函數會返回一些業務需要的數據,這些數據也需要根據業務進行驗證,進一步保證函數調用沒有發生意外,包括但不限于返回值的類型、長度、范圍等。比如上面的functionCallWithValue函數中,調動了verifyCallResultFromTarget函數對返回值進行校驗。其不僅對返回值success進行了檢查,還對retrundata的長度進行了校驗和處理。
在Move合約中,從語言層面來講,由于其靜態調用的特性,不存在類似于Solidity中的call函數需要校驗返回值的情況,即使有需要校驗函數是否執行正確,一般會在spec模塊使用規范語言在Move Prover中進行校驗,校驗失敗則交易會中止。
從業務層面來講,Move合約中的spec模塊同樣可以校驗函數對全局數據的修改。此外,還可以在合約中編寫單元測試函數對函數直接進行單元測試,來保證函數執行的正確性。因此,一般不會將表示函數執行是否成功的布爾變量作為返回值。因此,Move函數的返回值多是實際的業務數據,是否需要校驗,則需要根據實際業務需求來確定,比如需要根據返回值的不同,進入不同的函數邏輯分支,則需要對返回值進行判定和檢驗,比如DEX中的流動性函數:
X與Y的排序不同,需要訪問的balance也是不同的,還需要校驗order!=0。
總的來說,Move語言靜態調用特性、spec模塊以及單元測試等極大地提高了函數的安全性,這一點Solidity要好很多。但也不排除函數會因為沒有校驗返回值而產生漏洞的情況。因此,開發人員更需要對業務和實現邏輯熟悉,開發的時候需要謹慎而行。
1.2 未校驗相關計算數據
相關業務在合約實現過程中,考慮到情況不夠全面沒有正確校驗相應的業務經濟學公式和計算數據,導致合約對于特殊的計算數據容錯性差。比如:
(1)XCarnival安全事件
事件發生在2022年6月24日,NFT借貸協議XCarnival遭受到黑客攻擊,損失大約380萬美元。
根本原因是controller合約borrowAllowed函數調用的orderAllowed函數對數據結構order的校驗不完整,僅僅是校驗了訂單存在、地址正確并且沒有被清算,并沒有校驗訂單中的NFT是否被提取,即使訂單中的NFT已經被提取了,order的校驗仍然可以通過。
(2)Fortress Loans安全事件
事件發生在2022年5月9日,Fortress Loans遭到黑客攻擊,損失了1048.1 ETH以及40萬DAI。
根本原因是submit函數雖然校驗了signer的數量,但卻沒有對signer本身和計算的數據power進行校驗。
這使得攻擊者可以調用submit函數修改狀態變量fcds,最終修改了價格預言機中的價格。
最終,攻擊者利用該漏洞竊取了1048.1 ETH以及40萬DAI。
類似的安全事件還有不少,它們都是因為在函數內部缺少對經濟模型建立的數據結構或者計算的數據缺少校驗引起的漏洞。這類漏洞是由于項目設計與開發并沒有考慮到全部的情況造成的,其嚴重等級不一,嚴重的甚至會給項目帶來極大的經濟損失,就像上面的安全事件。
在Move合約實現各類項目時,同樣難以保證不會出現這類問題,尤其是新型項目。希望發生在Solidity智能合約中的這些安全事件能夠給Move開發者一些警示,在開發過程中,盡可能地避免安全漏洞。
1.3 未校驗函數參數
函數接收參數時,它不會自動地驗證輸入的數據屬性是否具有安全性和正確性。因此,函數在實現的時候需要根據業務需要對參數進行校驗,若缺少校驗后者校驗不符合業務需求,則會產生漏洞,甚至會威脅到數字資產的安全性。
以Superfluid.Finance安全事件為例。事件發生在2022年2月8日,以太坊上的DeFi協議Superfluid遭遇黑客攻擊,損失超1300萬美元。
根本原因在于,Superfluid合約存在嚴重的邏輯漏洞,callAgreement函數缺少對參數的校驗,使得攻擊者將合約構造的ctx數據替換為自定義ctx數據,這給攻擊者發起攻擊提供了機會。
在Move合約開發中更加需要對參數進行校驗。在Move中,函數的參數不僅僅是業務需求的數據,還包括了權限需要的數據,比如signer。Move沒有類似Solidity中的msg.sender這種全局變量,Move中對權限的鑒定是通過參數實現的。比如下面的函數:
該函數中的account參數是代幣鑄造的發起賬戶,它必須鑄幣的權限,即MintCapStore,類似于Solidity中的msg.sender必須是owner。如果缺失了這部分校驗,該代幣就是任何賬戶都可以鑄造的了。
此外,Move生態中的項目類型跟Solidity生態相同,只是實現的語言不同。因此,Solidity合約中存在的業務邏輯上的漏洞在Move合約中有很大的可能性依然存在。因此,Move開發者在開發項目時要注意這些在Solidity合約中已經出現過的漏洞。
1.4 未規范使用require
Solidity中的require旨在驗證函數的外部輸入,包括調用者輸入的參數、函數的返回值、函數執行前后狀態變化等。如果不能規范使用require,合約可能會產生漏洞,甚至威脅到數字資產的安全性,比如XDXSwap安全事件。
事件發生在2021年7月2日,火幣生態鏈(Heco)上DeFi項目XDXSwap受到閃電貸攻擊,損失約400萬美金。
根本原因就是閃電貸的功能實現合約,存在借出不還的嚴重漏洞,造成巨額損失,是項目方fork Uniswap合約代碼并修改時引入的嚴重漏洞,即缺少K值校驗的require語句。最根本的原因還是業務的不熟悉,導致實現存在漏洞。
在Move合約中, assert語句和spec模塊完成require類似的功能。同樣,很多Solidity生態的項目,包括DEX、借貸、農場等類型的項目在未來都將出現在Move的生態中。Move與Solidity原理以及機制是不同的,但項目的業務時相同的。鑒于Solidity生態項目踩坑無數,安全事件層出不窮,Move雖然安全性高,但是在實現各類項目時仍然要謹慎小心,盡量避免出現同類型的漏洞,希望同一個坑不要再踩一次了。
2、總結
當下Move仍處于發展階段,Move生態離成熟尚一定距離,開發者較少,開發者經驗欠缺,真正能夠熟練開發Move合約的不多,因此更容易出現業務層面的一些漏洞。這需要Move合約在設計和開發過程中對Move語言特性以及業務都要熟悉,才可能少出現業務漏洞。
另外,Solidity已經實現了大量的業務類型,比如去中心化交易所、去中心化借貸、收益聚合、杠桿借貸、杠桿挖礦、閃電貸、跨鏈交易等。這些典型的業務場景需要在Move生態足逐一實現,而且需要結合Move與Solidity的差異進行重新設計實現方案。在這個過程中,就比較容易出現一下漏洞,就像Solidity早期經歷了很多次攻擊和大量資產的損失才逐步走向成熟。Move雖然是一個安全性較高的語言,但誰也無法保證沒有漏洞,我們希望可以借鑒Solidity的發展過程,讓Move生態的發展少走一些彎路,少一些損失,更快更穩地走向成熟。