Back to Blog

玫瑰中的荊棘:探索 Uniswap v4 新型 Hook 機制中的安全風險

Code Auditing
November 6, 2023
9 min read

Uniswap v4 即將到來!開發團隊擁有宏偉的計劃,將引入一系列新功能[1],包括(理論上)無限數量的資金池、每個交易對的動態費率、單例架構(singleton)、閃電記帳(flash accounting)、掛鉤(hooks)以及對 ERC1155 的支援。利用 EIP-1153 引入的暫態儲存(transient storage),預計 Uniswap v4 將在以太坊坎昆升級後推出。

在這些創新中,**掛鉤(hook)**機制因其強大的潛力而備受關注。它允許在資金池運作期間的特定節點執行特定的程式碼,大幅增強了資金池的可擴展性和靈活性。

然而,掛鉤機制也可能是一把雙面刃。雖然它功能強大且靈活,但安全利用它的挑戰亦不容忽視。這種複雜性不可避免地帶來了新的潛在攻擊向量。為了從安全角度為社群做出貢獻,我們的目標是呈現一系列文章,系統性地檢視與此機制相關的安全問題與隱憂。我們相信,這些見解將有助於建構更安全的 Uniswap v4 掛鉤。

本文是該系列的開篇之作,為讀者提供了全面的概述與基礎知識。敬請關注後續更多深入的討論!

Uniswap v4 的運作機制

在深入細節之前,我們需要對 Uniswap v4 的機制有基本的了解。根據官方公告[1]和白皮書[2],掛鉤、單例架構和閃電記帳是實現資金池客製化及多池高效路由的三大關鍵功能。

掛鉤 (Hooks)

v4 中的掛鉤旨在讓任何人透過 掛鉤 進行權衡決策,掛鉤是執行於資金池操作生命週期中各個時間點的合約。透過這種方式,可以客製化支援原生動態費率的資金池,新增鏈上限價單,或作為時間加權平均做市商(TWAMM)將大額訂單分攤到一段時間內執行。

目前共有 八個 掛鉤回調函數(callback),分為四組(每組包含一對回調):

  • beforeInitialize / afterInitialize
  • beforeModifyPosition / afterModifyPosition
  • beforeSwap / afterSwap
  • beforeDonate / afterDonate

下方展示了白皮書[2]中提供的交換(swap)掛鉤流程。

圖 1:交換掛鉤流程
圖 1:交換掛鉤流程

Uniswap 團隊提供了一些範例(例如 TWAMM Hook[3])來展示其用法,社群參與者也做出了貢獻。官方文件[4]連結到了 Awesome Uniswap v4 Hooks[5] 儲存庫,其中收集了更多的掛鉤範例。

單例架構、閃電記帳與鎖定機制

單例架構與閃電記帳旨在透過降低成本和確保效率來提升效能。具體而言,它引入了一個新的 singleton 合約,所有的資金池都位於這單一智慧合約中。這種單例設計依賴 PoolManager 來儲存和管理所有 資金池 的狀態。

與 Uniswap 協議早期版本中交換或新增流動性涉及直接代幣轉帳的操作不同,Uniswap v4 引入了 閃電記帳鎖定機制

具體而言,鎖定機制的運作方式如下:

  1. 鎖定合約(locker contract)請求 PoolManager 的鎖定。
  2. PoolManager 將鎖定合約地址加入 lockData 佇列,並呼叫其 lockAcquired 回調。
  3. 鎖定合約在回調中執行其邏輯。在執行期間,它與資金池的互動可能會產生非零的貨幣增量(currency deltas)。然而,在執行結束時,所有增量必須結算為零。此外,如果 lockData 佇列不為空,則只有最後一個鎖定合約可以執行操作。
  4. 之後,PoolManager 會檢查 lockData 佇列的狀態與貨幣增量。確認無誤後,PoolManager 將移除該鎖定合約。

總而言之,鎖定機制防止了併發存取並確保了結算。鎖定合約排隊等待鎖定,然後透過 lockAcquired 回調執行。在資金池操作前後,會呼叫指定的掛鉤回調。最後,PoolManager 會檢查狀態。

這種方法意味著操作會調整內部的淨餘額(即 增量),而不是執行即時轉帳。任何修改都會記錄在資金池的內部餘額中,實際轉帳在操作結束時(即 鎖定)進行。此過程確保了沒有未結清的代幣,從而維持償付能力。

由於鎖定機制,外部帳戶(EOA)無法直接與 PoolManager 互動,所有互動都必須透過合約進行。合約作為中間的鎖定合約,在進行任何資金池操作前請求鎖定。 主要有兩種合約互動場景:

  • 鎖定合約來自官方儲存庫或由使用者部署。在這些情況下,我們可以將互動視為透過 路由(router) 進行。

  • 鎖定合約與掛鉤整合在同一個合約中,或由第三方實體控制。對於這種場景,我們可以將互動視為透過 掛鉤 進行。因此,掛鉤同時扮演了鎖定合約與回調處理器的雙重角色。

威脅模型

在討論相應的安全問題之前,需要先確定威脅模型。 基本上,使用掛鉤時會出現以下考量:

  • 威脅模型 I:掛鉤本身是良性的但存在漏洞。
  • 威脅模型 II:掛鉤本身是惡意的。

在隨後的章節中,我們將基於威脅模型討論潛在的安全問題與隱憂。

威脅模型 I 中的安全隱憂

威脅模型 I 專注於與掛鉤本身相關的漏洞。顯然,此威脅模型假設開發者及其掛鉤是良性的。 然而,智慧合約現有的已知漏洞也可能發生在掛鉤中。例如,如果掛鉤是以可升級合約的方式實作,它可能會遭受類似於 OpenZeppelin 函式庫中的 UUPSUpgradeable 漏洞[6]。

鑑於這些考量,我們選擇專注於 Uniswap v4 特有的潛在漏洞。具體而言,在 Uniswap v4 中,掛鉤是可客製化的智慧合約,能夠在核心資金池操作(包括 初始化修改部位交換捐贈)前後執行邏輯。掛鉤預期實作標準掛鉤介面,但也允許納入客製化邏輯。 因此,我們的範圍限制在與標準掛鉤介面相關的邏輯上。我們總結了掛鉤如何利用這些標準回調函數,以查明漏洞的潛在來源。

廣義來說,掛鉤可分為兩類:

  • 作為使用者資金監管者的掛鉤。在這些情況下,攻擊者可能會針對掛鉤進行轉帳操作,導致資產損失。
  • 儲存使用者或其他協議所依賴之關鍵狀態資料的掛鉤。對於這些掛鉤,攻擊者可能會蓄意竄改關鍵狀態。錯誤的狀態若被其他使用者或協議使用,會引入潛在風險。

請注意,不屬於這兩類的掛鉤不在我們的討論範圍之內。

儘管範圍有限,但在本文撰寫時,由於尚未有實際應用,我們決定檢視 Awesome Uniswap v4 Hooks 儲存庫以獲取一些見解。

在徹底檢查該儲存庫(commit hash: 3a0a444922f26605ec27a41929f3ced924af6075)後,我們發現了幾個關鍵漏洞。這些漏洞主要源於 掛鉤、PoolManager 與外部第三方之間危險的互動。漏洞分為兩大類:存取控制缺陷不當的輸入驗證。研究結果總結如下表:

存在缺陷的專案數量 存取控制缺陷數量 不當輸入驗證數量
8 6 2

總體而言,我們識別出 22 個相關專案(排除了一些看似與 Uniswap v4 無關的專案)。其中,8 個(即 36%)被認為存在漏洞。具體來說,在這些存在漏洞的專案中,有 6 個發現了存取控制缺陷,而 2 個容易受到不受信任的外部呼叫攻擊。

存取控制缺陷

在此討論中,我們重點關注與 v4 相關的存取控制問題,這些問題源自 v4 的回調函數(包括 8 個掛鉤回調和鎖定回調)。當然,其他情況也可能需要驗證。然而,這些情況取決於具體設計,超出了我們上述討論的範圍。

這些函數只能由 PoolManager 呼叫,而不應由其他地址(包括 EOA 和合約)呼叫。例如,考慮到由資金池金鑰分配獎勵的情境,如果對應的函數可以被任意帳戶呼叫,那麼獎勵可能會被錯誤地提取。

因此,為掛鉤建立強大的存取控制機制至關重要,特別是因為它們可能被資金池以外的各方呼叫。透過嚴格管理存取權限,資金池可以大幅降低未經授權或惡意互動的風險。

不當的輸入驗證

在 Uniswap v4 中,由於鎖定機制的緣故,使用者必須先透過合約獲取鎖定,然後才能執行任何資金池操作。這確保了當前互動的合約是最新版本的鎖定合約。

儘管如此,仍存在潛在的攻擊場景,即由於部分脆弱掛鉤實作中存在 不當輸入驗證 而導致的 不受信任的外部呼叫

  • 首先,掛鉤沒有驗證使用者打算互動的資金池。這可能是一個帶有偽造代幣並執行惡意邏輯的惡意資金池。
  • 其次,一些關鍵的掛鉤函數允許任意外部調用。

不受信任的外部呼叫極其危險,因為它可能導致各種類型的攻擊,包括眾所周知的重入(reentrancy)問題。

為了利用這些脆弱的掛鉤,攻擊者可以為其偽造代幣註冊一個惡意資金池,然後呼叫掛鉤來執行操作。當與資金池互動時,惡意代幣邏輯會劫持控制流以促進不當行為。

針對威脅模型 I 的緩解措施

為了規避這些掛鉤問題,務必透過適當強化敏感外部/公開函數的存取控制,以及對輸入參數進行驗證來確保互動安全性。此外,使用重入防護(reentrancy guard)可能有助於確保掛鉤無法在標準邏輯流中被重複執行。透過部署適當的防護措施,資金池可以降低與此類威脅相關的風險。

威脅模型 II 中的安全隱憂

在此威脅模型中,假設開發者及其掛鉤是惡意的。鑑於可能性範圍廣泛,我們選擇主要關注與 v4 相關的問題。因此,關鍵考量在於所提供的掛鉤是否能夠處理使用者已轉帳或授權的加密資產。

根據存取掛鉤的方式(這決定了賦予掛鉤的潛在權限),我們可以將掛鉤分為兩類:

  • 託管型掛鉤 (Managed Hooks):掛鉤不是入口點。使用者必須透過路由(通常由 Uniswap 提供)與掛鉤互動。
  • 獨立型掛鉤 (Standalone Hooks):掛鉤是入口點,允許使用者直接與其互動。
圖 2:惡意掛鉤範例
圖 2:惡意掛鉤範例

託管型掛鉤

對於託管型掛鉤,使用者的加密資產(包括原生代幣等)會被轉帳或授權給 路由。由於 PoolManager 強制執行餘額檢查,惡意掛鉤無法直接輕易地收割這些資產。

然而,潛在的攻擊面依然存在。例如,v4 中的費用管理機制可能會被攻擊者透過掛鉤進行操縱。

獨立型掛鉤

當掛鉤以獨立型掛鉤的形式作為入口點時,情況會變得更加複雜。在此場景下,掛鉤擁有了更強大的權限,因為使用者可以直接與其互動。理論上,掛鉤可以透過這種直接互動執行任何想做的操作。

在 v4 場景下,程式碼邏輯的驗證是一個關鍵點。主要的隱憂在於程式碼邏輯是否能夠被操控。掛鉤可以被實作為可升級的,這意味著一個看起來「安全」的掛鉤未來可能會被升級為惡意的,從而產生巨大風險。這包括:

  • 可升級代理合約,這些合約可被直接利用;
  • 具備自毀邏輯,由於 selfdestructcreate2 的結合使用而可能被利用。

針對威脅模型 II 的緩解措施

評估掛鉤是否惡意至關重要。具體而言,對於託管型掛鉤,我們應關注其費用管理的行為;而對於獨立型掛鉤,主要的考量則是它們是否可升級。

結論

在本文中,我們初步簡要概述了與 v4 掛鉤安全問題相關的 Uniswap v4 核心機制。隨後,我們定義了兩個威脅模型,並對其各自的安全問題進行了高層級的討論。在本系列的後續文章中,我們將針對每個威脅模型下的安全問題提供詳細分析。敬請期待!

參考資料

[1] 我們對 Uniswap V4 的願景

[2] Uniswap v4 白皮書草案

[3] Uniswap v4 TWAMM 掛鉤

[4] 掛鉤範例

[5] Awesome Uniswap v4 Hooks

[6] UUPSUpgradeable 漏洞事後分析

閱讀本系列的其他文章

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit