来自本地网络的一个精心构造的ICMPv6数据包会永久挂起slaacd和/或rad。IPv6 SLAAC地址自动配置将失效,直到有人手动重启守护进程。无需认证,无需权限,只需18字节的ICMPv6负载。
| CVE | CVE-2026-41285 |
| 漏洞类别 | 整数下溢导致的无限循环 |
| 根本原因 | ND选项解析器执行nd_opt_len * 8 - 2时未检查len==0 |
| 受影响组件 | sbin/slaacd/engine.c,usr.sbin/rad/engine.c |
| 影响 | IPv6 SLAAC(slaacd)或RA服务(rad)的永久拒绝服务 |
| 所需条件 | 同一L2网络段上的任意设备 |
| 测试环境 | OpenBSD 7.8 GENERIC amd64 |
RFC 4861 §4.6规定长度为0的ND选项无效,必须静默丢弃。内核自身的nd6_options()(位于sys/netinet6/nd6.c)能正确处理这一点。但在该检查生效之前,原始ICMPv6数据包仍然会被传递到用户态套接字。
slaacd和rad自身会解析ND选项。循环看起来像这样:
while (len > 0) {
// ...
optlen = nd_opt->nd_opt_len * 8 - 2; // nd_opt_len 是 uint8_t
if (optlen > len)
break;
len += 2;
// 将指针向前移动 optlen... 其值为从 int 提升而来的 (uint32_t)-2
}
当nd_opt_len == 0时:表达式0 * 8 - 2提升为int → -2。守卫条件(-2 > len)始终为假(有符号比较,len为正数)。然后执行len += 2,指针向后回退2字节。循环永远不会前进。CPU占用率飙升至100%。永远持续。
内核验证了自己的副本。用户态守护进程收到了原始的未处理数据。没有人告知slaacd。
rcctl restart slaacd或重启系统python3 poc/kill_slaacd.py <interface>
需要scapy。发送一个路由器通告,其中包含一个nd_opt_len = 0的ND选项。仅此而已。选项类型无关紧要(PoC使用类型200/未知)。
获取完整的端到端证明(展示SLAAC工作 -> 攻击 -> SLAAC死亡):
python3 poc/prove_dos.py
── 攻击前 ──
SLAAC地址: 2001:db8:1:0:df6f:edeb:6e3a:2640, ...
引擎CPU: 0.0%
── 发送一个数据包后 ──
引擎CPU: 23.1% → 43.3%(持续攀升)
发送带2001:db8:2::/64的新RA → 未配置地址
slaacd已死亡。IPv6自动配置:死亡。
与CVE-2022-27881和CVE-2022-27882(早期的slaacd引擎无限循环,同样是ND选项解析问题)相同的攻击面。这是同一类漏洞的新实例——之前的修复并未涵盖所有解析循环。
在执行算术运算前检查nd_opt_len == 0。跳出循环。这正是内核在nd6_options()中已经做的:
if (nd_opt->nd_opt_len == 0)
break; // 或:goto bad;
针对slaacd的parse_ra()、debug_log_ra()以及rad的RS解析器的建议补丁——所有三个循环都需要相同的单行守卫。
这是一个本地网络的拒绝服务攻击。如果你与运行slaacd的OpenBSD机器位于同一L2网段,一个数据包就能冻结其IPv6。不要向你未拥有的网络发送它。如果你运行OpenBSD,请检查补丁或自行添加len==0守卫。
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online