DF-5259 / beacon_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 111 112 113 114 115 116 117 118 119 120 121 122 | /* * DF-5259 beacon mbuf UAF race - userland interleaving harness. * * Mirrors the exact code regions and lock domains of if_mtw.c: * * mtw_update_beacon() if_mtw.c:3829-3868 - touches rvp->beacon_mbuf * under ONLY the net80211 ic lock (modelled: icl), * calls ieee80211_beacon_update() which WRITES into * the mbuf (modelled: mb_write()). * mtw_newstate() RUN if_mtw.c:1802-1803 - m_freem(beacon_mbuf) under * ONLY the driver sc lock (modelled: scl) after * explicitly releasing the ic lock (if_mtw.c:1771). * mtw_vap_delete() if_mtw.c:890-892 - m_freem(beacon_mbuf) under * only scl. * * No common lock exists between the free path (scl) and the update path * (icl) - FreeBSD's run_update_beacon() wraps the same region in * RUN_LOCK(sc) which is exactly what the DragonFly mtw port dropped. * * The harness counts how many beacon-update writes land in an mbuf that the * free path has already "freed" (poison-flagged so the process survives). */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include <sys/time.h> static pthread_mutex_t icl = PTHREAD_MUTEX_INITIALIZER; /* IEEE80211_LOCK(ic) */ static pthread_mutex_t scl = PTHREAD_MUTEX_INITIALIZER; /* MTW_LOCK(sc) */ struct mbuf { volatile int freed; char data[2048]; }; static struct mbuf *beacon_mbuf; static volatile unsigned long updates, frees, uaf_writes; static volatile int stop; /* model of ieee80211_beacon_update() writing TIM/capinfo/seq into the mbuf */ static void mb_write(struct mbuf *m) { memset(m->data + 64, 0x5a, 32); /* TIM update style write */ if (__atomic_load_n(&m->freed, __ATOMIC_ACQUIRE)) __sync_fetch_and_add(&uaf_writes, 1); } /* thread A: mtw_update_beacon() - ic lock domain only */ static void * update_beacon(void *arg) { while (!stop) { pthread_mutex_lock(&icl); /* ic lock */ if (beacon_mbuf == NULL) { /* 3855 */ beacon_mbuf = calloc(1, sizeof(*beacon_mbuf)); if (beacon_mbuf == NULL) { pthread_mutex_unlock(&icl); continue; } } /* 3860: the pointer is now a LOCAL (argument evaluation); * ieee80211_beacon_update() then walks the mbuf updating * seq/capinfo/TIM - substantial work during which the free * path (which needs only the sc lock) can run concurrently. * Model that traversal window: */ { volatile int j; for (j = 0; j < 20000; j++) ; } mb_write(beacon_mbuf); /* write */ pthread_mutex_unlock(&icl); __sync_fetch_and_add(&updates, 1); } return (NULL); } /* thread B: mtw_newstate(S_RUN) 1802 / mtw_vap_delete() 891 - sc lock domain */ static void * free_beacon(void *arg) { struct mbuf *m; while (!stop) { pthread_mutex_lock(&scl); /* sc lock */ m = beacon_mbuf; beacon_mbuf = NULL; if (m != NULL) { __atomic_store_n(&m->freed, 1, __ATOMIC_RELEASE); /* m_freem() */ free(m); } pthread_mutex_unlock(&scl); __sync_fetch_and_add(&frees, 1); usleep(50); /* rate-limit; real trigger: ifconfig down/destroy */ } return (NULL); } int main(int argc, char **argv) { int secs = argc > 1 ? atoi(argv[1]) : 10; pthread_t ta, tb; struct timeval t0, t1; gettimeofday(&t0, NULL); pthread_create(&ta, NULL, update_beacon, NULL); pthread_create(&tb, NULL, free_beacon, NULL); sleep(secs); stop = 1; pthread_join(ta, NULL); pthread_join(tb, NULL); gettimeofday(&t1, NULL); printf("beacon updates=%lu frees=%lu UAF writes=%lu in %ld.%06lds\n", updates, frees, uaf_writes, t1.tv_sec - t0.tv_sec, t1.tv_usec - t0.tv_usec); return (uaf_writes > 0 ? 0 : 1); } |