
Uma opção ND IPv6 com comprimento zero. Uma verificação ausente. O daemon anda para trás e vive no loop. Reportado ao OpenBSD, corrigido, CVE atribuído.
Um pacote ICMPv6 malicioso da rede local trava permanentemente o slaacd e/ou o rad. A autoconfiguração de endereço SLAAC IPv6 morre até que alguém reinicie manualmente o daemon. Sem autenticação, sem privilégios, 18 bytes de payload ICMPv6.
| CVE | CVE-2026-41285 |
| Classe do bug | Loop infinito via underflow de inteiro |
| Causa raiz | O parser de opções ND faz nd_opt_len * 8 - 2 sem verificar len==0 |
| Componente | sbin/slaacd/engine.c, usr.sbin/rad/engine.c |
| Impacto | DoS permanente do SLAAC IPv6 (slaacd) ou serviço RA (rad) |
| Requerido | Qualquer dispositivo no mesmo segmento de rede L2 |
| Testado | OpenBSD 7.8 GENERIC amd64 |
RFC 4861 §4.6 diz que opções ND com comprimento zero são inválidas e devem ser descartadas silenciosamente. O próprio nd6_options() do kernel em sys/netinet6/nd6.c faz isso corretamente. Mas o pacote ICMPv6 bruto ainda é entregue aos sockets de userland antes que essa verificação importe.
slaacd e rad ambos analisam as opções ND por conta própria. O loop se parece com:
while (len > 0) {
// ...
optlen = nd_opt->nd_opt_len * 8 - 2; // nd_opt_len is uint8_t
if (optlen > len)
break;
len += 2;
// advance pointer by optlen... which is (uint32_t)-2 promoted from int
}
Quando nd_opt_len == 0: a expressão 0 * 8 - 2 promove para int → -2. A guarda (-2 > len) é sempre falsa (comparação com sinal, len é positivo). Então len += 2 e o ponteiro retrocede 2 bytes. O loop nunca avança. CPU atinge 100%. Para sempre.
O kernel validou sua própria cópia. O daemon de userland recebeu o original bruto. Ninguém avisou o slaacd.
rcctl restart slaacd manual ou reinicializaçãopython3 poc/kill_slaacd.py <interface>
Requer scapy. Envia um Router Advertisement com uma única opção ND onde nd_opt_len = 0. Só isso. O tipo da opção não importa (PoC usa tipo 200 / desconhecido).
Para a prova completa de ponta a ponta (mostra SLAAC funcionando -> exploit -> SLAAC morto):
python3 poc/prove_dos.py
── Antes do exploit ──
Endereços SLAAC: 2001:db8:1:0:df6f:edeb:6e3a:2640, ...
CPU do motor: 0.0%
── Após um pacote ──
CPU do motor: 23.1% → 43.3% (subindo)
Novo RA com 2001:db8:2::/64 enviado → nenhum endereço configurado
slaacd está morto. Autoconf IPv6: MORTO.
Mesma superfície de ataque que CVE-2022-27881 e CVE-2022-27882 (loops infinitos anteriores do slaacd em engine.c, também parsing de opções ND). Esta é uma nova instância da mesma classe de bug - as correções anteriores não cobriram todos os loops de parsing.
Verificar nd_opt_len == 0 antes de fazer aritmética nele. Sair do loop. Isso é o que o kernel já faz em nd6_options():
if (nd_opt->nd_opt_len == 0)
break; // or: goto bad;
Patch sugerido para parse_ra() do slaacd, debug_log_ra() e parser de RS do rad - todos os três loops precisam da mesma guarda de uma linha.
Isso é um DoS de rede local. Se você estiver no mesmo segmento L2 de uma máquina OpenBSD executando slaacd, um pacote congela seu IPv6. Não o envie para redes que você não possui. Se você usa OpenBSD, verifique se há um patch ou adicione a guarda len==0 você mesmo.
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online