2024年10月,Bitcoin Core 项目披露了一个由我编写的拒绝服务漏洞,该漏洞是由于 `inv-to-send` 集合增长过大导致的,影响了 v25.0 之前的 Bitcoin Core 版本。我想在这里记录下当时我调查的一些笔记和截图。2023年5月初,我的监控基础设施注意到这个漏洞影响了主网节点,这让我能够准确地找出问题的根源。修复补丁的功劳归于 Anthony Towns。
2023年5月2日,我注意到我的一个监控节点上的入站连接数在大约两天内从大约 1901 降至仅 35 个。通常,节点会保持其已满的入站插槽,直到它重新启动或失去网络连接。
与其他贡献者交流后,我注意到该节点的 CPU 利用率达到了 100%。这影响了节点,使其无法与对等节点保持通信,导致入站连接超时并断开。在节点进程上使用 perf top
,我可以看到大量的 CPU 时间花费在 CTxMemPool::CompareDepthAndScore()
中的 b-msghand
线程中。我记录了以下火焰图,该图显示调用 CompareDepthAndScore()
的 make_heap()
占用了该进程超过 45% 的 CPU 时间。
与此同时,Bitcoin Core 的调试模式构建存在一个未解决的、不相关的 100% CPU 使用率问题。这让一些没有运行调试模式构建但注意到其节点上 CPU 使用率高的贡献者和用户感到困惑。虽然调试模式问题可能只影响了一些开发人员,但另一个高 CPU 使用率问题影响了整个网络。例如,包括 AntPool 和其他矿池在内的矿池报告了其挖矿操作的问题,因为他们的节点无法及时处理收到的区块。
观察整个网络的 ping 时间可以揭示这种拒绝服务的影响。由于 Bitcoin Core 的消息处理是单线程的,因此一次只能创建或处理一条消息,这意味着所有其他对等节点都必须等待。更长的等待时间会影响 ping 的响应时间。KIT DSN Bitcoin monitoring 拥有关于 ICMP 和 Bitcoin 协议 ping 的数据。比较这些数据可以让我们确定节点软件何时在消息处理方面遇到问题。数据显示,到主机的 ICMP ping 仍然不受影响,但是,从4月底到5月初,到 Bitcoin 节点软件的中位数 ping 几乎翻了一番,从大约 25ms 增加到 50ms 以上。5月8日,Bitcoin 中位数 ping 飙升至 200ms,而 ICMP ping 仍然不受影响。
通过查看 KIT DSN Bitcoin monitoring 收集的区块传播延迟数据也可以看到这种影响。在 2023 年 5 月 8 日左右,区块传播延迟出现了一个峰值。50% 的可达节点向其监控节点宣布区块所需的时间从不到一秒增加到超过五秒。同样,90% 的测量值从大约两秒飙升至超过 20 秒。
糟糕的区块传播也会导致更多的陈旧区块,因为矿池会在其过时的区块上挖掘更长时间,而他们尚未看到的新区块已经存在于网络中。根据我的 stale-blocks 数据集的数据,在 5 月 3 日(从陈旧区块 788016 开始)到 5 月 10 日(以区块 789147 结束)之间的一周内,观察到 10 个陈旧区块。这是大约每 1000 个区块 8.84 个陈旧区块的比率。相比之下,在区块 800000 到 900000 之间(大约两年),观察到 73 个陈旧区块。这是每 1000 个区块 0.73 个陈旧区块的比率。陈旧区块率的这种 10 倍增长很可能是由于区块传播受到严重影响造成的。
为什么函数 CTxMemPool::CompareDepthAndScore()
会使节点速度降低到难以处理 P2P 消息的程度?在 Bitcoin Core 中,b-msghand
线程处理 P2P 消息。例如,将新收到的区块传递给验证,响应 ping,向其他对等节点宣布交易等等。
函数 CTxMemPool::CompareDepthAndScore()
用于决定接下来向对等节点宣布哪些交易。在 Bitcoin P2P 协议中,交易通过 inv
(inventory)消息宣布。Bitcoin Core 向对等节点宣布的交易通常包含最多 35 个 wtxid
条目。为了跟踪接下来要向对等节点宣布哪些交易,存在一个每个对等节点的 m_tx_inventory_to_send
集合。它包含节点认为对等节点尚未看到的交易。在为对等节点构建 inventory 消息时,该集合按交易依赖性和手续费率排序,以优先考虑高手续费率的交易,并避免泄露节点了解交易的顺序。为此,使用了 CTxMemPool::CompareDepthAndScore()
比较函数。
2023 年 5 月初,与 BRC-20 tokens 相关的海量交易被广播。这意味着 m_tx_inventory_to_send
集合的增长速度比平时更快,规模也比平时更大。因此,对集合进行排序花费了更多时间。在 5 月 7 日晚上(UTC),VMPX BRC-20 token 的铸造开始,这导致在接下来的 6 小时内广播了超过 30 万笔交易,以及其他正在进行的 BRC-20 token 铸造。这导致了 5 月 8 日观察到的中位数 ping 和区块传播时间的峰值。
所谓的 spy nodes 只监听 inv
消息,从不自己宣布交易,这放大了影响。当对等节点向节点宣布交易时,节点可以将其从其 m_tx_inventory_to_send
集合中删除,因为它已被对等节点知道,因此不再需要宣布。这意味着 spy nodes 的集合甚至更大,并且由于它们被更缓慢地耗尽,因此需要花费更多时间进行排序。例如,LinkingLion 和其他 spy nodes 很常见,并且通常与节点并行打开多个连接。有时,我计算出的假定 spy nodes 比连接到我的节点的非 spy node 连接更多。
大量交易的广播,加上 spy nodes 的放大作用,以及 CTxMemPool::CompareDepthAndScore()
对大型 m_tx_inventory_to_send
集合的非最佳排序,导致节点花费大量时间创建新的 inventory 消息以进行交易中继。由于消息处理是单线程的,因此与其他对等节点的通信显着减慢。这达到了一个地步,即区块没有及时处理,并且某些连接超时。
修复是双重的。首先,所有要宣布的、已经挖掘的或由于某种原因不在内存池中的交易,都在 m_tx_inventory_to_send
集合排序之前被删除。以前,这些交易仅在集合排序后才被删除。这避免了花费时间对永远不会被宣布的交易条目进行排序,并减小了要排序的集合的大小。其次,当 m_tx_inventory_to_send
集合很大时,从集合中耗尽的条目数量会根据集合大小动态增加。这意味着当广播大量交易时,节点将向其对等节点宣布更多交易,直到集合再次变小。该修复已及时向后移植到 2023 年 5 月底的 v25.0 版本中。
虽然一组常规贡献者知道发生了什么,但这个问题并没有公开传达给公众。同时讨论的调试模式的 100% CPU 使用率问题 造成了混乱,即使在常规 Bitcoin Core 贡献者中也是如此。当时,我感觉这个问题可以而且也许应该悄悄地解决,暂时不需要大量的宣传。事后看来,也许更公开和透明地处理这个问题也可能奏效。大量的 BRC-20 广播只持续了大约一周(但这事先是不知道的),并且重新启动节点会有所帮助。为了缓解这个问题,例如,对于无法立即升级到带有修复版本的矿池(由于使用自定义补丁运行),准备了一个 spy nodes 黑名单,但我不知道它是否被使用过。
虽然没有针对此事件的专用通信渠道,但使用了与 P2P 贡献者的非公开 IRC 频道,并且通过直接消息邀请或告知感兴趣的贡献者有关这些事件。据我所知,没有事件响应渠道,我也不知道鉴于 Bitcoin 开发的临时性和去中心化性质,一个事件响应渠道是否有帮助。没有贡献者负责事件响应,但每个人都可以提供帮助。
就我个人而言,我很高兴我的监控证明对这件事很有用。虽然我当时没有设置针对连接断开的警报,只是通过查看仪表板才注意到它,但拥有它还是很有帮助的。为了查明问题,拥有一些可以用来运行的节点,例如,在上面运行 perf top
,这很有帮助。未来的监控应包括 ping 时间和连接断开的警报。