確保代碼與假設相匹配 別寫出給人驚嚇的代碼
如果你也是軟件工程師,你應該聽過一些軟件工程的格言。雖然我並不主張嚴格遵守每一句格言,但有一些值得你放在心上。
今天我想講的是 “ 最少驚嚇原理 ”。名字挺別扭,但意思很簡單:在面對一份聲稱可以做某事的代碼時,大多數用戶都會對它的工作方式作一些假設;因此,你作為开發者,要做的事情就是確保自己的代碼與這些假設相匹配,這樣你的用戶就不會驚嚇連連。
這是個很棒的原則,因為开發者自己也要對一些東西作假設。如果你放出一個叫做 calculateScore(GameState)
的函數(字面意思為 “根據遊戲狀態計算分數”),那么大部人都會正確地預期,這個函數只會從 “遊戲狀態” 中讀取數據。如果你這個函數同時也修改了遊戲的狀態,你就給了大部分用戶一個驚嚇,他們會很困惑,想搞清楚為什么遊戲的狀態會隨機地變掉。即使你在解釋文檔中說了,也沒法保證大家都看過文檔,所以,最好從一开始就保證你的代碼不會給人驚嚇。
- “調試代碼 6 個鐘,就能省下 5 分鐘閱讀文檔的時間喲” -
更安全就更好,對嗎?
回到 2018 年頭,ERC-721 還在草案階段,有一個建議是實現 “轉账安全性”,以確保 token 不會被困在(沒有設計好處理這種 token)接收方合約裏。為此,提案的作者修改了 transfer
函數的行動,檢查類接收方是否有能力支持這種 token 的轉账。這也引入了一個 unsafeTransfer
函數,它可以跳過這個檢查,如果發送者自己要求的話。
但是,因為擔心向後兼容的問題,這個函數在後續的修改中重新命名了。這使得 transfer
函數的表現在 ERC-20 和 ERC-721 上是完全相同的。但是,現在接收方的能力檢查就要挪到別的地方去了。因此, safe
函數被發明出來:safeTransfer
和 safeTransferFrom
。
這個問題是合理的,在此之前,已經有很多 ERC-20 代幣被意外轉入根本沒有預期會接收代幣的合約,導致這些 token 被鎖死的案例(一個很常見的錯誤是把代幣轉給它所屬的代幣合約,導致這些代幣完全鎖死)。毫不意外,當 ERC-1155 標准在草案階段時,他們吸收了 ERC-721 標准的啓發,不僅在轉账中引入了接收方檢查,在鑄造中也加入了檢查。
這些標准在接下來幾年中大部分都處於無人問津的狀態,而 ERC-20 標准在隔壁獨自精彩,直到最近,NFT 引發的 gas 價格暴漲表明,ERC-721 和 ERC-1155 標准迎來了开發者使用量上的暴漲。开發者的興趣卷土重來,而這些標准在設計時都考慮到了安全性,這當然是一件幸事,對吧?
再問一次,更安全,一定更好嗎?
OK,你們考慮是考慮到了,但這些函數如何能讓轉账或者鑄造變得安全呢?不同的團隊對 “安全” 的理解各有不同。對於一個开發者來說,安全的函數意味着這個函數裏面沒有 bug、不會引入額外的安全擔憂。對於用戶來說,安全性可能意味着程序做了充分的措施,可以保護他們不會不小心搬起石頭砸自己的腳。
事實證明,要按這樣來區分的話,這些函數更多是後者(保護用戶不受錯誤操作的困擾),而不是前者。因為,它給了开發者兩個選擇: transfer
和 safeTransfer
,為什么你不用 “安全” 的那個?名字裏面都寫好了嘛。
嗯,一個理由是我們的老朋友,可重入漏洞(我一直在盡最大努力希望它能重命名為 “不安全的外部調用”)。回想一下,任何外部調用都可能是不安全的,只要接受方账戶是由攻擊者控制的;因為攻擊者也許可以讓你的合約轉變成一種沒有得到定義的狀態。從設計上來說,這些 “safe” 函數扮演着對代幣接收方的一個外部調用,這個調用通常是由鑄造代幣或轉移代幣的發送者控制的。換句話說,這就是不安全調用的一個典型案例。
你可能會問,就是允許一個接收方拒絕一筆自己沒法處理的轉账而已,能有多大事呢?我們用兩個簡單的案例來回答這個問題。
Hashmasks
Hashmasks 是一種供給量有限的 NFT。用戶在單筆交易中最多可以購买 20 個 NFT,雖然這些 NFT 口罩在幾個月前就賣光了。這裏我們看看买口罩的函數
如果你沒有預設,那這個函數看起來非常完美,有理有據。但是,如果你是個有心人,那你就能看出 _safeMint
調用裏面藏着一些可怕的東西。
ENS 域名封裝器
最近,ENS 團隊的 Nick Johnson 聯系了我們,希望我們看看他們正在开發的一個 ENS 域名封裝器。這個封裝器允許用戶將手上的域名代幣化為一個新的 ERC-1155 代幣,以此支持更細粒度的權限和更一致的 API。
抽象地說,為了封裝任意的 ENS 域名(准確來說是任意並非二級域名的 .eth 域名),你必須先允許域名封裝器訪問你的 ENS 域名。然後你調用 wrap(bytes,address,uint96,address)
函數,一邊鑄造一個 ERC-1155 代幣,另一邊托管了底層的 ENS 域名。
這裏是封裝函數,可以說非常直接。首先,調用 _wrap
函數做一些邏輯計算,返回哈希化的域名名稱。然後保證交易的發送者就是這個 ENS 域名的擁有者,然後托管這個域名。注意,如果發送者並不擁有這個底層的 ENS 域名,那么整個交易應該回滾,取消 _wrap
函數造成的所有變更。
下面是 _wrap
函數,看起來沒有任何特別的。
不幸的是,_mint
本身可能會給不知情的开發者一個驚嚇。ERC-1155 的規範裏面聲明,在鑄造代幣時,應該咨詢接收方是否愿意接收這個代幣。在深入研究庫代碼(從 OpenZeppelin 的基礎上稍微修改而來),我們可以看到確實是這樣的。但是這對我們來說到底有什么用呢?這又是一個不安全的外部調用,我們可以用來激發重入的漏洞。具體來說,請注意,在 callback 執行期間,我們還持有表示這個 ENS 域名的 ERC-1155 代幣,但域名封裝器還沒驗證完成我們是這個 ENS 域名的所有者。這時候我們可以直接操作這個 ENS 域名而無需是其所有者。舉個例子,我們可以要求域名封裝器解封這個域名,燒掉這個我們鑄造出來的代幣,然後獲得它所代表的 ENS 域名。現在我們拿到了目標 ENS 域名了,可以為所欲為了,比如我可以注冊一個子域名,或者重設解析器。完成之後再退出 call back 函數。域名封裝器這時候會獲取這個 ENS 域名的所有者,也就是我們,發現匹配之後驗證完成,交易成功。就像這樣,我們可以暫時獲取向域名封裝器授權的任何 ENS 域名的所有權並執行任意的修改。
結論
給人驚嚇的代碼可能造成惡劣的後果,在兩個案例中,开發者都理性地假設了 safe
函數族(至少)會跟普通的函數一樣安全而不會增加攻擊面。隨着 ERC-721 和 ERC-1155 標准變得更加流行,這種攻擊可能會越來越頻繁。开發者需要考慮使用 safe
函數族的風險,並考慮外部調用會怎樣跟自己寫下的代碼交互。
原文鏈接:
https://samczsun.com/the-dangers-of-surprising-code/
作者: samczsun
翻譯: 阿劍
鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播信息之目的,不構成任何投資建議,如有侵權行為,請第一時間聯絡我們修改或刪除,多謝。
7月23:Mt. Gox 比特幣錢包在市場緊縮的情況下轉移了價值 28.2 億美元的 BTC
7月23:Mt. Gox 比特幣錢包在市場緊縮的情況下轉移了價值 28.2 億美元的 BTC一個引...
悅盈:比特幣68000的空完美落地反彈繼續看跌 以太坊破前高看回撤
一個人的自律中,藏着無限的可能性,你自律的程度,決定着你人生的高度。 人生沒有近路可走,但你走的每...