通过转发功能使Transfer v2通道无法升级的漏洞分析

38 阅读3分钟

漏洞概述

该漏洞存在于Cosmos IBC协议的Transfer v2通道升级机制中。一方面,升级功能包含特定的HasInflightPackets检查,它会查找通道的已提交包,如果存在任何未完成的包,就不会将通道设置为FLUSHCOMPLETE状态。这在handleFlushStateWriteUpgradeAckChannelWriteUpgradeConfirmChannel函数中实现。如果通道不处于FLUSHCOMPLETE状态,升级就无法完全完成,在ChanUpgradeOpen中会报错,阻止进入WriteUpgradeOpenChannel函数(该函数会将通道重新设置为开放状态并更新升级字段)。

包承诺可以通过两种方式删除:确认(acknowledgement)和超时(timeout)。

另一方面,转发功能在收到转发目标通道的确认之前,不会为转发的包写入确认。这种转发方法引入了一种情况:合法通道可能变得依赖于恶意通道。如果恶意通道不确认包,合法通道似乎无法转换到FLUSHCOMPLETE状态,因此无法完成升级(无论是当前还是未来的升级)。

复现步骤

POC首先创建合法的transfer v2通道:在ibc-0上的channel-0 <-> 在ibc-1上的channel-0。这部分由中继器处理。

然后在应用程序中打开另外两个通道:在ibc-0上的channel-1 <- 我的本地链ibc-1 || 我的本地链ibc-0 -> 在ibc-1上的channel-1。

并提交如下接收包:

ibc0Cw.ReceivePacketV2(ibc0Path, types.FungibleTokenPacketDataV2{
    Tokens: []types.Token{
        {
            Amount: "1",
            Denom:  types.Denom{Base: "x"},
        },
    },
    Memo:     "",
    Sender:   ibc1Cw.Address(),
    Receiver: ibc0Cw.Address(),
    Forwarding: types.ForwardingPacketData{Hops: []types.Hop{
        {ChannelId: legitChannel, PortId: port}, {ChannelId: ibc0Path.Dest.ChanId, PortId: port},
    }, DestinationMemo: ""},
})

包在ibc-0上的channel-1被接收,由于转发功能,它通过channel-0提交到ibc-1上的channel-0,然后再次转发到我的ibc-1上的channel-1。因此,它不会写入任何确认,但会写入收据。这意味着我们将无法使其超时。

最终结果是:ibc-0上的合法channel-0将持续拥有已提交(传输中)的包,直到我的ibc-1上的channel-1收到确认为止(而这不会发生)。受害者通道是ibc-0上的channel-0,最终将持有这个承诺。

POC本身并不是一个新场景,它只是展示了可以在合法通道上创建这些永久承诺的场景。

技术细节

POC中使用的方法在github.com/h1uf/ibc-to…

复现环境要求:

应用程序通过我的代理链建立channel-1到channel-1的连接。在gist.github.com/unknownfeat…

影响评估

通过转发功能,Transfer v2通道可能变得无法升级,恶意通道可以在合法通道上创建将保持未确认状态的承诺。这似乎不是预期行为,因为承诺不应该永远保持已提交状态(除非除了提到的两个地方之外还有其他删除位置)。通过转发功能的这一部分引入的对潜在恶意通道的依赖似乎没有得到充分认识。

解决方案

从目前来看,似乎没有其他方法可以删除承诺,而且通道似乎无法在不刷新所有包的情况下变为FLUSHCOMPLETE状态(尽管可能遗漏了某些细节)。

最终,Transfer v2功能被撤回,通道可升级性被移除,从而解决了这个问题。该问题被归类为低严重性问题,并获得了2,000美元的赏金。