本文介紹了與 Uniswap v4 中 Hook 機制相關的概念,並概述了 Hook 機制存在的安全風險。本文源自 BlockSec 所著文章《Thorns in the Rose: Exploring Security Risks in Uniswap v4’s Novel Hook Mechanism》,由 PANews 整理、編譯及撰稿。
(前情提要: Uniswap v4的Hook,「截斷式預言機」如何改變 DeFi?)
(背景補充: Uniswap V4 重點性能「Hooks」是如何實現限價單交易的?)
相信 Uniswap v4 不久就會和大家見面了!
這一次 Uniswap 團隊可謂目標巨集大,計劃引入眾多全新功能 [1],包括每個交易對支援無限數量的流動性池和動態費用、單例設計、閃電記帳、Hook,以及支援 ERC1155 代幣標準。利用 EIP-1153 引入的瞬態儲存,Uniswap v4 預計將在以太坊坎昆升級之後釋出。
在諸多創新中,Hook 機制因其強大潛力引起了廣泛關注。Hook 機制支援在流動性池生命週期中的特定點執行特定程式碼,大大增強了池子的可擴充套件性和靈活性。
然而,Hook 機制也可能是一把雙刃劍。雖然它功能強大且靈活,但安全使用 Hook 同樣是一個不小的挑戰。Hook 的複雜性不可避免地帶來了新的潛在攻擊向量。因此,我們希望撰寫一個系列文章,來系統介紹與 Hook 機制相關的安全問題與潛在風險,以此推動社群的安全發展,相信這些見解將有助於構建安全的 Uniswap v4 Hook。
作為該系列文章的開篇之作,本文介紹了與 Uniswap v4 中 Hook 機制相關的概念,並概述了 Hook 機制存在的安全風險。
Uniswap V4 的機制
在深入探討之前,我們需要對 Uniswap v4 的機制有一個基本的瞭解。根據官方公告 [1] 和白皮書 [2],Hook、單例架構和閃電記帳是實現自定義流動性池和跨多個池子實現高效路由的三個重要功能。
Hook
Hook 指的是在流動性資金池生命週期的不同階段執行的合約,Uniswap 團隊希望通過引入 Hook 使任何人都能做出權衡決策。通過這種方式,可以實現原生支援動態費用、新增鏈上限價單、或者通過時間加權平均做市商 (TWAMM) 分散大訂單。
目前有八個 Hook 回撥,分為四組(每組包含一對回撥):
- beforeInitialize/afterInitialize
- beforeModifyPosition/afterModifyPosition
- beforeSwap/afterSwap
- beforeDonate/afterDonate
下文是白皮書 [2] 中介紹的交換 Hook 的流程。
Uniswap 團隊用一些示例(例如 TWAMM Hook [3])介紹了操作方法,社群參與者也做出了一些貢獻。官方文件 [4] 還連結到了 Awesome Uniswap v4 Hooks [5] 倉庫,該倉庫收集了更多的 Hook 示例。
單例、閃電記帳和鎖機制
單例(singleton)架構和閃電記帳(flash accounting)旨在通過降低成本和確保效率來提高效能。它引入了一種新的 singleton 合約,即所有流動性池都儲存在同一個智慧合約中。這個單例設計依賴一個 PoolManager 來儲存和管理所有池子的狀態。
在 Uniswap 協議的早期版本中,兌換或新增流動性等操作涉及直接代幣轉移,v4 版本則有所不同,在於其引入了閃電記帳和鎖機制(lock mechanism)。
鎖機制的運作方式如下:
- 某個 locker 合約在 PoolManager 上請求 lock。
- PoolManager 將該 locker 合約的地址新增到 lockData 佇列,並呼叫其 lockAcquired 回撥。
- 該 locker 合約在回撥中執行其邏輯。在執行過程中,locker 合約與池子的互動可能導致非零的貨幣增量。然而,在執行結束時,所有增量必須結算為零。另外,如果 lockData 佇列不為空,只有最後一個 locker 合約可以執行操作。
- PoolManager 檢查 lockData 佇列和貨幣增量的狀態。驗證後,PoolManager 將刪除該 locker 合約。
總結來說,鎖機制防止了併發訪問,並保證了所有的交易都能被清算。而 locker 合約按順序申請 lock,然後通過 lockAcquired 回撥執行交易。在每次池操作前後會觸發對應的 Hook 回撥。最後,PoolManager 會檢查狀態。
這種方法意味著操作調整的是內部淨餘額(即 delta),而不是執行即時轉帳。任何修改都會記錄在池子的內部餘額中,實際的轉帳則在操作(即 lock)結束時進行。這個過程保證了沒有未清算的代幣,從而維持了資金的完整性。
由於鎖機制的存在,外部所有帳戶 (EOA) 不能直接與 PoolManager 進行互動。相反,任何互動都必須通過一個合約進行。該合約作為一箇中間的 locker,在進行任何池操作之前都需要請求 lock。
主要存在兩種合約互動場景:
- 某個 locker 合約來自官方的程式碼庫,或者由使用者部署。在這種情況下,我們可以將互動視為通過路由器進行。
- 某個 locker 合約和 Hook 整合到同一個合約中,或由第三方實體控制。對於這種情況,我們可以將互動視為通過 Hook 進行。在這種情況下,Hook 既扮演了 locker 合約的角色,又負責處理回撥。
威脅模型
在討論相關的安全問題之前,我們需要確定威脅模型。我們主要考慮以下兩種情況:
- 威脅模型 I:Hook 本身是良性的,但存在漏洞。
- 威脅模型 II:Hook 本身就是惡意的。
在接下來的部分,我們將根據這兩種威脅模型討論潛在的安全問題。
威脅模型 I 中的安全問題
威脅模型 I 關注的是與 Hook 本身相關的漏洞。這個威脅模型假設開發者及其 Hook 是無惡意的。然而,智慧合約現有的已知漏洞也可能出現在 Hook 中。例如,如果 Hook 是作為可升級合約實現的,那麼它可能會遇到類似於 OpenZeppelin 的 UUPSUpgradeable 漏洞的相關問題。
鑑於以上因素,我們選擇聚焦於 v4 版本特有的潛在漏洞。在 Uniswap v4 中,Hook 是能夠在核心池操作(包括初始化、修改位置、交換和收集)之前或之後執行自定義邏輯的智慧合約。雖然 Hook 預計將實現標準的介面,但它也允許包含自定義邏輯。因此,我們的討論範圍將限制在涉及標準 Hook 介面的邏輯。然後,我們將嘗試找出可能的漏洞來源,例如,Hook 可能如何濫用這些標準 Hook 函式。
具體來說,我們將關注以下兩種 Hook:
第一種 hook, 保管使用者資金。在這種情況下,攻擊者可能會攻擊這個 hook 來轉移資金,造成資產損失。
第二種 hook, 儲存使用者或其他協議依賴的關鍵狀態資料。在這種情況下,攻擊者可能會試圖改變關鍵狀態。當其他使用者或協議使用錯誤狀態時,可能會帶來潛在風險。
請注意,這兩種範圍之外的 hook 不在我們的討論範圍內。
由於本文撰寫時還沒有 Hook 的真實用例,我們將從 Awesome Uniswap v4 Hooks 倉庫中獲取一些資訊。
在對 Awesome Uniswap v4 Hooks 倉庫(提交hash為 3a0a444922f26605ec27a41929f3ced924af6075)進行深入研究後,我們發現了幾個嚴重的漏洞。這些漏洞主要源於 hook、PoolManager 以及外部第三方之間的風險互動,主要可以分為兩類:訪問控制問題和輸入驗證問題。具體發現請見下表:
總的來說,我們發現了 22 個相關專案(排除與 Uniswap v4 無關的專案)。在這些專案中,我們認為有 8 個(36%)專案是存在漏洞的。在這 8 個有漏洞的專案中,6 個存在訪問控制問題,2 個容易受到不受信任的外部呼叫。
訪問控制問題
在這部分討論中,我們主要關注的是 v4 中的回撥函式可能導致的問題,包括 8 個 hook 回撥和 lock 回撥。當然,還有其他情況需要驗證,但這些情況因設計而異,暫不在我們的討論範圍之內。
這些函式應該只能被 PoolManager 呼叫,不能被其他地址(包括 EOA 和合約)呼叫。例如,在獎勵由資金池金鑰分發的情況下,如果相應的函式可以由任意帳戶呼叫,那麼獎勵可能會被錯誤地領取。
因此,對於 hook 來說,建立強大的訪問控制機制是至關重要的,尤其是它們可以被除了池子本身之外的其他方呼叫。通過嚴格管理訪問許可權,流動性池可以顯著降低與 hook 未授權互動或惡意互動的風險。
輸入驗證問題
在 Uniswap v4 中,由於存在鎖機制,使用者在執行任何資金池操作之前必須通過合約獲得一個 lock。這確保了當前參與互動的合約是最新的 locker 合約。
儘管如此,仍然存在一個可能的攻擊場景,即由於在一些易受攻擊的 Hook 實現中輸入驗證不當而導致的不受信任的外部呼叫:
首先,hook 並未驗證使用者打算互動的資金池。這可能是一個含有虛假代幣並執行有害邏輯的惡意資金池。
其次,一些關鍵的 hook 函式允許任意的外部呼叫。
不受信任的外部呼叫極其危險,因為它可能導致各種型別的攻擊,包括我們熟知的重入攻擊。
為了攻擊這些易受攻擊的 hook,攻擊者可以為自己的虛假代幣註冊一個惡意資金池,然後呼叫 hook 在資金池執行操作。在與資金池互動時,惡意代幣邏輯劫持控制流以便進行不良行為。
針對威脅模型 I 的防範措施
為了規避與 hook 相關的此類安全問題,通過適當執行對敏感的外部 / 公共函式的必要訪問控制,並對輸入引數進行驗證,從而對互動進行驗證是至關重要的。此外,重入保護可能有助於確保 hook 不會在標準邏輯流程中被重複執行。通過實施適當的安全防護措施,資金池可以降低與此類威脅相關的風險。
威脅模型 II 中的安全問題
在這個威脅模型中,我們假設開發者及其 hook 是惡意的。鑑於涉及範圍很廣,我們僅關注與 v4 版本相關的安全問題。因此,關鍵在於提供的 hook 是否能夠處理使用者轉帳或授權的加密資產。
由於訪問 hook 的方法決定了可能賦予 hook 的許可權,我們據此將 hook 分為兩類:
- 託管型 Hook(Managed Hooks):hook 不是入口點。使用者必須通過路由器(可能由 Uniswap 提供)與 hook 進行互動。
- 獨立型 Hook(Standalone Hooks):hook 是入口點,允許使用者直接與之互動。
託管型 Hook
在這種情況下,使用者的加密資產(包括原生代幣和其他代幣)被轉帳或授權給 router 。由於 PoolManager 執行了餘額檢查,惡意 hook 不容易直接竊取這些資產。然而,仍然存在潛在的攻擊面。例如,v4 版本的費用管理機制可能會被攻擊者通過 hook 進行操縱。
獨立型 Hook
當 Hook 被用作入口點時,情況就更加複雜。在這種情況下,由於使用者可以直接與 hook 進行互動,hook 獲得了更多的權力。理論上,hook 可以通過這種互動執行想要的任何操作。
在 v4 版本中,程式碼邏輯的驗證是非常關鍵的。主要問題在於是否可以操縱程式碼邏輯。如果 hook 是可升級的,這意味著一個看似安全的 hook 可能會在升級之後成為惡意的,從而構成重大風險。這些風險包括:
可升級的代理(可以被直接攻擊);
帶有自毀邏輯。在聯合呼叫 selfdestruct 和 create2 的情況下可能被攻擊。
針對威脅模型 II 的防範措施
至關重要的一點在於評估 hook 是否是惡意的。具體來說,對於託管型 hook,我們應該關注費用管理的行為;而對於獨立型 hook,主要的關注點在於它們是否可升級。
結論
在本文中,我們首先簡要概述了與 Uniswap v4 的 Hook 安全問題相關的核心機制。隨後,我們提出了兩種威脅模型並簡要概述了相關的安全風險。
在後續文章中,我們將對每種威脅模型下的安全問題進行深入分析。