漏洞概述
2018年6月,研究人员Braydon Fuller 在Bitcoin Core 中发现了一个安全漏洞——INVDoS 漏洞,CVE编号为CVE-2018-17145
。该漏洞为Inventory 内存溢出DoS漏洞,属于DoS攻击漏洞的一种。攻击者利用该漏洞可以创建伪造的比特币交易,当比特币区块链节点处理该交易时回引发服务器内存资源不受控制的消耗,最终使得系统奔溃。
漏洞修复后研究人员选择不公开漏洞细节,以预防潜在的漏洞利用。上周,有其他研究人员独立发现了该漏洞,随后研究人员将漏洞细节公开。
攻击技术细节
恶意节点可以通过快速发送多个含有随机哈希值的交易inv消息来发起攻击,最多发起49999
项,但不发送对应的tx
数据。当多个节点同时发起攻击时可以加速攻击并升级为DDoS 攻击。当连接速度为1Gbps (125 MB/s) 时,每秒钟可以发送含有49999
项的83
个inv
消息,也就是说攻击的最大速度为每秒钟 4166584 inv
项。
Bcoin细节
比特币中的漏洞来源于用来追踪节点inventory 的Map
的大小可以没有限制的增加,但是有一个基于时间的限制在节点拖延和没有响应时可以清空txMap。如果有足够的inventor
哈希值在timeout
检查前被发送,内存就会快速增长并引发进程奔溃。P2P 网络消息也可以通过暴露的代码路径 handlePacket -> handleInv -> handleTXInv -> ensureTX -> getTX
在没有限制的情况下增加txMap
内存:
getTX(peer, hashes) {
...
for (const hash of hashes) {
if (this.txMap.has(hash))
continue;
this.txMap.add(hash);
peer.txMap.set(hash, now);
...
}
...
}
该问题可以通过增加对节点txMap 大小的限制和移除超过限制的节点来解决。Christopher Jeffrey 在commit 05c38853d7f50fb4ad87e28fa7b46017f78e2955 中修复了该问题。
Bitcoin Core细节
Bitcoin Core 中的问题更加隐蔽,位于限制类似map增长的代码中,参见src/net processing.cpp
中的ProcessMessage
:
if (vInv.size() > MAX_INV_SZ)
{
LOCK(cs_main);
Misbehaving(pfrom->GetId(), 20,
strprintf("message inv size() = %u",
vInv.size()));
return false;
}
但是程序的真实行为有所不同,从程序的行为来安似乎并没有限制,而且内存可以无限增长。该问题很容易检查,对src/net processing.cpp
的ProcessMessage
的GetMainSignals().Inventory(inv.hash)
没有保护的限制会使得m callbacks pending
的大小比用来追踪事件的SingleThreadedSchedulerClient
的处理以更快的速度增长。漏洞源代码参见src/validationinterface.cpp
:
void CMainSignals::Inventory(const uint256 &hash) {
m internals->m schedulerClient.AddToProcessQueue([hash, this] {
m internals->Inventory(hash);
});
}
该漏洞通过移除有漏洞的代码修复,具体参见commit beef7ec4be725beea870a2da510d2817487601ec。
Btcd细节
Btcd将接收到的交易inventory
项目保存在peer.knownInventory (peer/peer.go)
中,peer.knownInventory
是一个MRU map (mruInventoryMap)
,用来限制大小。但是peer.knownInventory
的实例使用的MRU map
暴露了一个引发内存泄露的漏洞。mruInventoryMap.Add
中到wire.InvVect
的指针在之后的SyncManager.handleInvMsg
中进行了修改。在netsync/manager.go
中:
if peer.IsWitnessEnabled() {
iv.Type = wire.InvTypeWitnessTx
}
当mruInventory.Add
尝试从invList
中清除LRU
项目时,而且不再匹配invMap key
,因为该对象已经改变了。因为lru.Type != iv.Type
所以请除会失败。因为内存使用不断的增长,在peer/mruinvmap.go
中:
if uint(len(m.invMap))+1 > m.limit {
node := m.invList.Back()
lru := node.Value.(*wire.InvVect)
// Evict least recently used item.
delete(m.invMap, *lru)
...
}
除了mruInventoryMap
外,还有2个map会保存接收到的inventory
哈希作为peerSyncState: requestedTxns
和 requestedBlocks
的密钥。这些map会用来预防内存泄露。Btcd 通过转向修复的 LRU实现或转向更新版的map来修复该问题。该漏洞在7月17日通过commit 4b3f7f3c7a490151801c0aaf117befeae1c6bc1b修复。
更多漏洞技术细节参见:https://invdos.net/#