
Una opción ND de IPv6 con longitud cero. Una comprobación faltante. El demonio camina hacia atrás y vive en el bucle. Reportado a OpenBSD, corregido, CVE asignado.
Un paquete ICMPv6 manipulado desde la red local deja permanentemente colgados slaacd y/o rad. La autoconfiguración de direcciones IPv6 SLAAC muere hasta que alguien reinicie manualmente el demonio. Sin autenticación, sin privilegios, 18 bytes de carga útil ICMPv6.
| CVE | CVE-2026-41285 |
| Clase de error | Bucle infinito por desbordamiento de entero (integer underflow) |
| Causa raíz | El analizador de opciones ND hace nd_opt_len * 8 - 2 sin comprobar len==0 |
| Componente | sbin/slaacd/engine.c, usr.sbin/rad/engine.c |
| Impacto | DoS permanente de IPv6 SLAAC (slaacd) o del servicio RA (rad) |
| Requisito | Cualquier dispositivo en el mismo segmento de red L2 |
| Probado | OpenBSD 7.8 GENERIC amd64 |
RFC 4861 §4.6 dice que las opciones ND con longitud cero son inválidas y deben descartarse silenciosamente. El propio nd6_options() del kernel en sys/netinet6/nd6.c hace esto correctamente. Pero el paquete ICMPv6 bruto aún se entrega a los sockets de espacio de usuario antes de que esa comprobación tenga efecto.
Tanto slaacd como rad analizan las opciones ND por sí mismos. El bucle se ve así:
while (len > 0) {
// ...
optlen = nd_opt->nd_opt_len * 8 - 2; // nd_opt_len es uint8_t
if (optlen > len)
break;
len += 2;
// avanzar puntero por optlen... que es (uint32_t)-2 promovido desde int
}
Cuando nd_opt_len == 0: la expresión 0 * 8 - 2 se promueve a int → -2.
La comprobación (-2 > len) siempre es falsa (comparación con signo, len es positivo).
Luego len += 2 y el puntero retrocede 2 bytes. El bucle nunca avanza.
La CPU se satura al 100%. Para siempre.
El kernel validó su propia copia. El demonio de espacio de usuario recibió el original bruto. Nadie le dijo a slaacd.
rcctl restart slaacd o reinicio del sistemapython3 poc/kill_slaacd.py <interfaz>
Requiere scapy. Envía un único Router Advertisement con una sola opción ND donde nd_opt_len = 0. Eso es todo. El tipo de opción no importa (PoC usa tipo 200 / desconocido).
Para la prueba completa de extremo a extremo (muestra SLAAC funcionando -> exploit -> SLAAC muerto):
python3 poc/prove_dos.py
── Antes del exploit ──
Direcciones SLAAC: 2001:db8:1:0:df6f:edeb:6e3a:2640, ...
CPU del motor: 0.0%
── Después de un paquete ──
CPU del motor: 23.1% → 43.3% (subiendo)
Nuevo RA con 2001:db8:2::/64 enviado → ninguna dirección configurada
slaacd está muerto. Autoconf IPv6: MUERTO.
Misma superficie de ataque que CVE-2022-27881 y CVE-2022-27882 (bucles infinitos anteriores de slaacd en engine.c, también análisis de opciones ND). Esta es una nueva instancia de la misma clase de error: las correcciones anteriores no cubrieron todos los bucles de análisis.
Comprobar nd_opt_len == 0 antes de hacer aritmética sobre él. Salir del bucle. Esto es lo que el kernel ya hace en nd6_options():
if (nd_opt->nd_opt_len == 0)
break; // o: goto bad;
Parche sugerido para parse_ra() de slaacd, debug_log_ra() y el analizador RS de rad: los tres bucles necesitan la misma comprobación de una línea.
Esto es un DoS de red local. Si estás en el mismo segmento L2 que una máquina OpenBSD ejecutando slaacd, un paquete congela su IPv6. No lo envíes a redes que no te pertenezcan. Si ejecutas OpenBSD, busca un parche o añade la comprobación len==0 tú mismo.
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online