
길이가 0인 IPv6 ND 옵션 하나. 누락된 검사 하나. 데몬은 뒤로 걸으며 루프 안에 살아 있다. OpenBSD에 보고되었고, 수정되었으며, CVE가 할당되었다.
로컬 네트워크에서 보낸 조작된 ICMPv6 패킷 하나가 slaacd 및/또는 rad를 영구적으로 중단(hang)시킵니다. 누군가 수동으로 데몬을 재시작할 때까지 IPv6 SLAAC 주소 자동 구성이 중단됩니다. 인증도, 권한도 필요 없으며 ICMPv6 페이로드는 18바이트에 불과합니다.
| CVE | CVE-2026-41285 |
| 버그 유형 | 정수 언더플로우를 통한 무한 루프 |
| 근본 원인 | ND 옵션 파서가 len==0을 확인하지 않고 nd_opt_len * 8 - 2 수행 |
| 구성 요소 | sbin/slaacd/engine.c, usr.sbin/rad/engine.c |
| 영향 | IPv6 SLAAC(slaacd) 또는 RA 서비스(rad)의 영구 DoS |
| 필요 조건 | 동일한 L2 네트워크 세그먼트에 있는 모든 장치 |
| 테스트 환경 | OpenBSD 7.8 GENERIC amd64 |
RFC 4861 §4.6에 따르면 길이가 0인 ND 옵션은 유효하지 않으므로 조용히 폐기되어야 합니다. 커널 자체의 sys/netinet6/nd6.c에 있는 nd6_options()는 이를 올바르게 처리합니다. 그러나 해당 검사가 적용되기 전에 원시 ICMPv6 패킷은 여전히 사용자 공간(userland) 소켓으로 전달됩니다.
slaacd와 rad는 모두 ND 옵션을 자체적으로 파싱합니다. 루프는 다음과 같습니다:
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
}
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 옵션이 포함된 Router Advertisement 하나를 전송합니다. 그게 전부입니다. 옵션 타입은 중요하지 않습니다(PoC는 타입 200 / unknown 사용).
전체 종단 간(end-to-end) 증명을 위해(SLAAC 정상 동작 → 익스플로잇 → SLAAC 중단을 보여줌):
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.
CVE-2022-27881 및 CVE-2022-27882와 동일한 공격 표면입니다(engine.c의 초기 slaacd 무한 루프로, 역시 ND 옵션 파싱 관련). 이는 동일한 버그 클래스의 새로운 사례입니다 - 이전 수정 사항이 모든 파싱 루프를 포함하지 않았기 때문입니다.
nd_opt_len에 대해 산술 연산을 수행하기 전에 nd_opt_len == 0인지 확인하고 루프에서 빠져나오세요. 커널이 이미 nd6_options()에서 수행하는 방식입니다:
if (nd_opt->nd_opt_len == 0)
break; // or: goto bad;
slaacd의 parse_ra(), debug_log_ra(), 그리고 rad의 RS 파서에 대한 제안 패치 - 세 루프 모두 동일한 한 줄 가드가 필요합니다.
이것은 로컬 네트워크 DoS입니다. slaacd를 실행 중인 OpenBSD 시스템과 동일한 L2 세그먼트에 있다면, 패킷 하나로 해당 시스템의 IPv6가 얼어붙습니다. 소유하지 않은 네트워크에는 보내지 마세요. OpenBSD를 운영 중이라면 패치가 있는지 확인하거나 직접 len==0 가드를 추가하세요.
Daniel Wade - GitHub · Twitter/X · Bluesky · nadsec.online