玫瑰中的刺:探索Uniswap v4新钩子机制中的安全风险

这是我们探索 Uniswap v4 钩子机制安全风险系列的第一篇文章!本文将为读者提供全面的概述和基础理解。

玫瑰中的刺:探索Uniswap v4新钩子机制中的安全风险

Uniswap v4 正在路上!该团队雄心勃勃的计划,引入了一系列新功能[1],包括(理论上)无限数量的池和每个交易对的动态费用率、单例模式、闪电记账、钩子(hooks)以及对 ERC1155 的支持。利用 EIP-1153 引入的临时存储,Uniswap v4 预计将在以太坊的 Cancun 升级后推出。

在这些创新中,**钩子(hook)**机制因其强大的潜力而备受关注。它允许在池操作的特定时间点执行特定代码,从而大大增强了池的可扩展性和灵活性。

然而,钩子机制也可能是一把双刃剑。虽然它强大且灵活,但安全使用它的挑战不容忽视。这种复杂性不可避免地会带来新的潜在攻击向量。为了从安全角度为社区做出贡献,我们的目标是系统地提出一系列文章,探讨与该机制相关的安全问题和担忧。我们相信,这些见解将有助于构建安全的 Uniswap v4 钩子。

本文是本系列的开篇,为读者提供全面的概述和基础理解。敬请期待更多富有洞察力的讨论!

Uniswap v4 的机制

在深入细节之前,我们需要对 Uniswap v4 的机制有一个基本的了解。根据官方公告[1]和白皮书[2],钩子、单例架构和闪电记账是实现池定制和跨多个池的高效路由的三项关键功能。

钩子(Hooks)

v4 中的钩子旨在允许任何人通过钩子做出权衡决策,钩子是合同,在池操作生命周期的各个时间点运行。通过这样做,可以定制原生支持动态费用的池,添加链上限价订单,或充当时间加权平均做市商(TWAMM)来分散大订单。

目前,有八个钩子回调,分为四组(每组包含一对回调):

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

下图显示了白皮书[2]提供的 swap 钩子的流程。

Figure 1: Swap Hook Flow

Uniswap 团队提供了一些示例(例如,TWAMM Hook[3])来演示其用法,社区成员也做出了贡献。官方文档[4]链接到 Awesome Uniswap v4 Hooks[5] 仓库,其中收集了更多钩子示例。

单例、闪电记账和锁定机制

单例架构和闪电记账旨在通过降低成本和确保效率来提高性能。具体来说,它引入了一个新的 singleton 合约,所有池都存在于一个智能合约中。这种单例设计依赖于 PoolManager 来存储和管理所有的所有状态。

与 Uniswap 协议的早期版本不同,早期版本中的交换或添加流动性等操作涉及直接的代币转账,Uniswap v4 引入了 flash accountinglock mechanism

具体来说,锁定机制的运行方式如下:

  1. 一个锁定的合约请求对 PoolManager 的锁定。
  2. PoolManager 将锁定者的地址添加到 lockData 队列并调用其 lockAcquired 回调。
  3. 锁定者在回调中执行其逻辑。在锁定者的执行期间,其与池的交互可能会导致非零的货币增量。但是,在执行结束时,所有增量必须结算为零。此外,如果 lockData 队列不为空,则只有最后一个锁定者可以执行操作。
  4. 之后,PoolManager 检查 lockData 队列和货币增量的状态。验证后,PoolManager 将移除锁定者。

总而言之,锁定机制可防止并发访问并确保结算。锁定者排队等待锁,然后通过 lockAcquired 回调执行。在池操作之前和之后,会调用指定的钩子回调。最后,PoolManager 检查状态。

这种方法意味着操作会调整内部净余额(即增量),而不是执行即时转账。任何修改都会记录在池的内部余额中,实际转账在操作结束时(即锁定)进行。此过程保证没有未偿付的代币,从而保持偿付能力。

由于锁定机制,EOA(外部拥有账户)无法直接与 PoolManager 交互。相反,任何交互都必须通过合约进行。该合约充当中间锁定者,在任何池操作之前请求锁定。 主要有两种合约交互场景:

  • 锁定者合约来自官方仓库或由用户部署。在这些情况下,我们可以将交互视为通过路由器进行。

  • 锁定者和钩子集成到同一个合约中,或由第三方实体控制。在这种情况下,我们可以将交互视为通过钩子进行。因此,钩子兼具锁定者和回调处理者的双重角色。

威胁模型

在讨论相应的安全问题之前,需要确定威胁模型。 基本上,使用钩子会产生一些考虑因素:

  • 威胁模型 I:钩子是良性的但存在漏洞。
  • 威胁模型 II:钩子是恶意的。

在接下来的部分中,我们将根据威胁模型讨论潜在的安全问题/担忧。

威胁模型 I 中的安全担忧

威胁模型 I 侧重于与钩子本身相关的漏洞。显然,此威胁模型假定开发人员及其钩子是良性的。 然而,智能合约已知的漏洞也可能出现在钩子中。例如,如果钩子实现为可升级合约,它可能会遭受与 OZ 库的 UUPSUpgradeable 漏洞[6]类似的漏洞。

考虑到这些因素,我们选择专注于v4 特有的潜在漏洞。具体来说,在 Uniswap v4 中,钩子是可定制的智能合约,能够在核心池操作(包括initializemodifyPositionswapdonate)之前或之后执行逻辑。钩子应实现标准的钩子接口,但也允许包含自定义逻辑。 因此,我们的范围仅限于与标准钩子接口相关的逻辑。然后,我们可以总结钩子可能如何使用这些标准钩子函数来确定潜在的漏洞来源。

广义上讲,钩子可以分为两类:

  • 充当用户资金托管者的钩子。在这些情况下,攻击者可能会以钩子为目标来转移资金,从而导致资产损失。
  • 存储用户或其他协议依赖的关键状态数据的钩子。对于这些钩子,攻击者可能会故意更改关键状态。当这些错误的状态被其他用户或协议使用时,会引入潜在风险。

请注意,不属于这两类范畴的钩子不在我们的讨论范围内。

尽管范围有限,但在此枚举所有可能性仍然不可行。由于截至撰写本文时没有实际应用程序,我们决定通过 Awesome Uniswap v4 Hooks 仓库获取一些见解。

在仔细审查了该仓库(提交哈希为 3a0a444922f26605ec27a41929f3ced924af6075)后,我们识别出了一些关键漏洞。这些漏洞主要源于钩子、PoolManager 和外部第三方之间的风险交互。漏洞主要分为两类:访问控制缺陷不当的输入验证。结果总结在下表中:

# of flawed projects # of flawed access control # of improper input validation
8 6 2

总的来说,我们确定了 22 个相关的项目(不包括一些似乎与 Uniswap v4 无关的项目)。其中 8 个(占 36%)被认为存在漏洞。具体来说,在这些有漏洞的项目中,有 6 个检测到访问控制缺陷,而 2 个容易受到不受信任的外部调用。

访问控制缺陷

在本次讨论中,我们侧重于与 v4 相关的访问控制问题,这些问题源于 v4 的回调函数,包括 8 个钩子回调和锁定回调。当然,也可能需要验证其他情况。然而,这些情况取决于设计,超出了我们先前讨论的指定范围。

这些函数只能由 PoolManager 调用,而不能由其他地址(包括 EOA 和合约)调用。例如,考虑一个由池密钥分发奖励的情况。如果相应的函数可以被任意账户调用,则可能会错误地领取奖励。

鉴于此,为钩子建立强大的访问控制机制至关重要,特别是因为它们可以被池本身以外的方调用。通过仔细管理访问权限,池可以大大降低与钩子进行未经授权或恶意交互的风险。

不当的输入验证

在 Uniswap v4 中,由于锁定机制,用户在执行任何池操作之前必须通过合约获得锁定。这确保了当前交互的合约是最新的锁定者。

尽管如此,由于某些易受攻击的钩子实现中不当的输入验证,仍然存在潜在的攻击场景,即不受信任的外部调用

  • 首先,钩子不验证用户打算与之交互的池。这可能是一个包含假代币并执行有害逻辑的恶意池。
  • 其次,一些关键的钩子函数允许任意的外部调用。

不受信任的外部调用非常危险,因为它可能导致各种类型的攻击,包括众所周知的重入攻击。

要利用这种易受攻击的钩子,攻击者可以注册一个包含其假代币的恶意池,然后调用钩子在该池上执行操作。在与池交互时,恶意代币逻辑会劫持控制流以方便不当行为。

威胁模型 I 的缓解措施

为规避钩子中的此类问题,必须通过适当强制执行敏感外部/公共函数的必要访问控制以及验证输入参数来验证交互。此外,重入保护程序可能有助于确保钩子不能在标准逻辑流中被重复执行。通过实施适当的保护措施,池可以减轻与此类威胁相关的风险。

威胁模型 II 中的安全担忧

在此威胁模型中,假定开发人员及其钩子是恶意的。鉴于可能性范围广泛,我们选择主要关注与 v4 相关的问题。因此,关键考虑因素是提供的钩子是否能够处理用户已转账或已批准的加密资产。

根据访问钩子的方法,该方法决定了授予钩子的潜在权限,我们可以将钩子分为两个不同的类别:

  • 托管钩子:钩子不是入口点。用户必须通过路由器(很可能是 Uniswap 提供的)与钩子交互。
  • 独立钩子:钩子是入口点,允许用户直接与它们交互。
Figure 2: Examples of Malicious Hooks

托管钩子

对于托管钩子,用户的加密资产(包括本地代币和其他代币)会被转账或批准给router。由于 PoolManager 强制执行的余额检查,恶意钩子很难直接掠夺这些资产。

然而,潜在的攻击面仍然存在。例如,v4 中的费用管理机制可能会被攻击者通过钩子操纵。

独立钩子

当钩子以独立钩子的形式用作入口点时,情况会变得更加复杂。在这种情况下,钩子会获得更大的权力,因为用户可以直接与它们交互。理论上,钩子可以通过这种交互执行任何它们想要的操作。

在 v4 情境下,代码逻辑的验证是一个关键点。主要担忧在于代码逻辑是否会被操纵。钩子可以实现为可升级的,这意味着一个看似安全的钩子以后可能会被升级为一个恶意的钩子,从而构成重大风险。这包括:

  • 可直接利用的可升级代理;
  • 配备自毁逻辑,可利用selfdestructcreate2的组合使用。

威胁模型 II 的缓解措施

评估钩子是否是恶意的至关重要。具体来说,对于托管钩子,我们应该关注费用管理的行为;而对于独立钩子,主要担忧在于它们是否可升级。

结论

在本文中,我们首先简要总结了 Uniswap v4 的核心机制,这些机制与 v4 钩子的安全问题相关。在此之后,我们定义了两个威胁模型,并对其各自的安全问题进行了高层次的讨论。在接下来的系列文章中,我们将详细分析每个威胁模型下的安全问题。敬请关注!

参考

[1] Our Vision for Uniswap V4

[2] Uniswap V4 whitepaper draft

[3] Uniswap V4 TWAMM Hook

[4] Hook Examples

[5] Awesome Uniswap V4 Hooks

[6] UUPSUpgradeable Vulnerability Post-mortem

阅读本系列的另一篇文章

Sign up for the latest updates