確保代碼與假設相匹配 別寫出給人驚嚇的代碼

2021-08-19 21:08:01

如果你也是軟件工程師,你應該聽過一些軟件工程的格言。雖然我並不主張嚴格遵守每一句格言,但有一些值得你放在心上。

今天我想講的是 “ 最少驚嚇原理 ”。名字挺別扭,但意思很簡單:在面對一份聲稱可以做某事的代碼時,大多數用戶都會對它的工作方式作一些假設;因此,你作為开發者,要做的事情就是確保自己的代碼與這些假設相匹配,這樣你的用戶就不會驚嚇連連。

這是個很棒的原則,因為开發者自己也要對一些東西作假設。如果你放出一個叫做 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

翻譯: 阿劍

鄭重聲明:本文版權歸原作者所有,轉載文章僅為傳播信息之目的,不構成任何投資建議,如有侵權行為,請第一時間聯絡我們修改或刪除,多謝。

推薦文章

btc日內再次下跌 短线應當如何處理?

盡管以太坊現貨ETF獲批是個好消息,但市場反應卻不如預期。在消息公布後,以太坊價格出現了小幅下跌,...

加密蓮
133 3個月前

7月23日、BTC(合約)ETH(合約)行情分析及操作策略

昨日收益還是不錯的,日內給出的現價空單分別止盈我們目標點位,恭喜跟上的朋友喫肉。時間一晃到月底了,...

倪老師
133 3個月前

幣圈院士:血與淚的教訓!交易者為何總是撞死在同一棵樹上?

幣圈院士談。交易市場中的幾種“死法” 在幣圈市場鱗次櫛比的海洋,風起雲湧,時常讓人感到驚手不及。在...

幣圈院士
138 3個月前

7月23:Mt. Gox 比特幣錢包在市場緊縮的情況下轉移了價值 28.2 億美元的 BTC

7月23:Mt. Gox 比特幣錢包在市場緊縮的情況下轉移了價值 28.2 億美元的 BTC一個引...

168超神
130 3個月前

悅盈:比特幣68000的空完美落地反彈繼續看跌 以太坊破前高看回撤

一個人的自律中,藏着無限的可能性,你自律的程度,決定着你人生的高度。 人生沒有近路可走,但你走的每...

我是周悅盈
111 3個月前

btc完美盈利 晚間波動較大注意

昨日btc空單完美給到,最大化走出一千七百點空間~ btc: 日內开盤下跌繼續測試66000一线,...

加密蓮
120 3個月前