
Eine IPv6-ND-Option mit Länge null. Eine fehlende Prüfung. Der Daemon läuft rückwärts und bleibt in der Schleife. An OpenBSD gemeldet, behoben, CVE zugewiesen.
Ein einzelnes manipuliertes ICMPv6-Paket aus dem lokalen Netzwerk lässt slaacd und/oder rad dauerhaft hängen. Die IPv6-SLAAC-Adressautokonfiguration fällt aus, bis jemand den Daemon manuell neu startet. Keine Authentifizierung, keine Privilegien, 18 Bytes ICMPv6-Nutzlast.
| CVE | CVE-2026-41285 |
| Fehlerklasse | Endlosschleife durch Integer-Unterlauf |
| Grundursache | Der ND-Optionsparser führt nd_opt_len * 8 - 2 aus, ohne len==0 zu prüfen |
| Komponente | sbin/slaacd/engine.c, usr.sbin/rad/engine.c |
| Auswirkung | Dauerhafter DoS der IPv6-SLAAC (slaacd) oder des RA-Dienstes (rad) |
| Erforderlich | Ein beliebiges Gerät im selben L2-Netzwerksegment |
| Getestet | OpenBSD 7.8 GENERIC amd64 |
RFC 4861 §4.6 besagt, dass ND-Optionen mit der Länge null ungültig sind und stillschweigend verworfen werden müssen. Das eigene nd6_options() des Kernels in sys/netinet6/nd6.c macht das korrekt. Aber das rohe ICMPv6-Paket wird trotzdem an Userland-Sockets zugestellt, bevor diese Prüfung greift.
slaacd und rad parsen ND-Optionen beide selbst. Die Schleife sieht so aus:
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
}
Wenn nd_opt_len == 0: Der Ausdruck 0 * 8 - 2 wird zu int befördert → -2. Die Prüfung (-2 > len) ist immer falsch (vorzeichenbehafteter Vergleich, len ist positiv). Dann wird len += 2 ausgeführt und der Zeiger geht um 2 Bytes zurück. Die Schleife kommt nie weiter. Die CPU bleibt bei 100%. Für immer.
Der Kernel hat seine eigene Kopie validiert. Der Userland-Daemon bekam das rohe Original. Niemand hat es slaacd gesagt.
rcctl restart slaacd oder einen Neustartpython3 poc/kill_slaacd.py <interface>
Erfordert scapy. Sendet eine Router Advertisement mit einer einzigen ND-Option, bei der nd_opt_len = 0 ist. Das ist alles. Der Optionstyp spielt keine Rolle (PoC verwendet Typ 200 / unbekannt).
Für den vollständigen End-to-End-Beweis (zeigt SLAAC funktionsfähig -> Exploit -> SLAAC tot):
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.
Dieselbe Angriffsfläche wie CVE-2022-27881 und CVE-2022-27882 (frühere slaacd-Endlosschleifen in engine.c, ebenfalls ND-Option-Parsing). Dies ist eine neue Instanz derselben Fehlerklasse – die früheren Fixes haben nicht alle Parsing-Schleifen abgedeckt.
Prüfe nd_opt_len == 0, bevor du Arithmetik darauf ausführst. Brich aus der Schleife aus. Das macht der Kernel bereits in nd6_options():
if (nd_opt->nd_opt_len == 0)
break; // or: goto bad;
Vorgeschlagener Patch für slaacds parse_ra(), debug_log_ra() und rads RS-Parser – alle drei Schleifen benötigen dieselbe einzeilige Absicherung.
Dies ist ein DoS im lokalen Netzwerk. Wenn du dich im selben L2-Segment befindest wie eine OpenBSD-Kiste, die slaacd ausführt, friert ein einziges Paket dessen IPv6 ein. Sende es nicht an Netzwerke, die dir nicht gehören. Wenn du OpenBSD betreibst, suche nach einem Patch oder füge die len==0-Absicherung selbst hinzu.
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online