
Un'opzione ND IPv6 con lunghezza zero. Un controllo mancante. Il demone procede all'indietro e rimane in loop. Segnalato a OpenBSD, corretto, assegnata CVE.
Un singolo pacchetto ICMPv6 appositamente costruito proveniente dalla rete locale blocca permanentemente slaacd e/o rad. L'autoconfigurazione degli indirizzi IPv6 SLAAC cessa finché qualcuno non riavvia manualmente il demone. Nessuna autenticazione, nessun privilegio, 18 byte di payload ICMPv6.
| CVE | CVE-2026-41285 |
| Bug class | Ciclo infinito dovuto a integer underflow |
| Root cause | Il parser delle opzioni ND esegue nd_opt_len * 8 - 2 senza controllare len==0 |
| Component | sbin/slaacd/engine.c, usr.sbin/rad/engine.c |
| Impact | DoS permanente di IPv6 SLAAC (slaacd) o del servizio RA (rad) |
| Required | Qualsiasi dispositivo sullo stesso segmento di rete L2 |
| Tested | OpenBSD 7.8 GENERIC amd64 |
La RFC 4861 §4.6 stabilisce che le opzioni ND con lunghezza zero sono invalide e devono essere scartate silenziosamente. La nd6_options() del kernel in sys/netinet6/nd6.c lo fa correttamente. Ma il pacchetto ICMPv6 grezzo viene comunque consegnato ai socket in userland prima che quel controllo abbia effetto.
Sia slaacd che rad analizzano le opzioni ND direttamente. Il ciclo è il seguente:
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: l'espressione 0 * 8 - 2 viene promossa a int → -2. Il controllo (-2 > len) è sempre falso (confronto con segno, len è positivo). Poi len += 2 e il puntatore torna indietro di 2 byte. Il ciclo non avanza mai. La CPU resta al 100%. Per sempre.
Il kernel ha validato la propria copia. Il demone in userland ha ricevuto l'originale grezzo. Nessuno ha avvisato slaacd.
rcctl restart slaacd manuale o un riavviopython3 poc/kill_slaacd.py <interface>
Richiede scapy. Invia un singolo Router Advertisement con una sola opzione ND in cui nd_opt_len = 0. Tutto qui. Il tipo di opzione non ha importanza (la PoC usa il tipo 200 / sconosciuto).
Per la prova end-to-end completa (mostra SLAAC funzionante -> exploit -> SLAAC morto):
python3 poc/prove_dos.py
── Before exploit ──
SLAAC addresses: 2001:db8:1:0:df6f:edeb:6e3a:2640, ...
Engine CPU: 0.0%
── After one packet ──
Engine CPU: 23.1% → 43.3% (climbing)
New RA with 2001:db8:2::/64 sent → no address configured
slaacd is dead. IPv6 autoconf: DEAD.
Stessa superficie d'attacco di CVE-2022-27881 e CVE-2022-27882 (precedenti cicli infiniti di slaacd in engine.c, anch'essi legati al parsing delle opzioni ND). Questa è una nuova istanza della stessa classe di bug: le correzioni precedenti non coprivano tutti i cicli di parsing.
Controlla nd_opt_len == 0 prima di fare qualsiasi operazione aritmetica su di esso. Esci dal ciclo. È quello che il kernel fa già in nd6_options():
if (nd_opt->nd_opt_len == 0)
break; // or: goto bad;
Patch suggerita per parse_ra() e debug_log_ra() di slaacd e per il parser RS di rad: tutti e tre i cicli necessitano della stessa guardia di una riga.
Questo è un DoS a livello di rete locale. Se ti trovi sullo stesso segmento L2 di una macchina OpenBSD che esegue slaacd, un singolo pacchetto congela il suo IPv6. Non inviarlo a reti che non possiedi. Se utilizzi OpenBSD, cerca una patch o aggiungi tu stesso la guardia len==0.
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online