DF-5258 / mtwprobe.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 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 | /* * mtwprobe.c - structural proof for DF-5258: * DragonFly sys/bus/u4b/wlan/if_mtwvar.h declares * struct mtw_endpoint_queue sc_epq[MTW_BULK_RX]; * where MTW_BULK_RX is the FIRST constant of the xfer enum == 0. * GCC treats this as a zero-length array: sizeof(struct mtw_softc) * therefore does NOT include the 6 endpoint queues, yet * mtw_init_locked() (if_mtw.c:4466-4467) calls mtw_setup_tx_list() * on sc->sc_epq[0..5] on every interface UP, stamping * 6 * sizeof(struct mtw_endpoint_queue) bytes starting at the * sc_epq offset - far past the end of the softc allocation. * * This module compiles the REAL if_mtwvar.h from the guest kernel * source, prints the real sizes/offsets, then re-runs the exact * mtw_setup_tx_list() stamping loop (copied verbatim from * if_mtw.c:949-965) against a kmalloc'd buffer the size of the * softc followed by a 0xAA guard region, and counts how many guard * bytes get clobbered - i.e. how far past the softc allocation the * writes land. No MT7601U hardware is needed for this proof. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/queue.h> #include <sys/types.h> #include <sys/bus.h> #include <sys/mbuf.h> #include <net/if_var.h> #include <net/if_media.h> #include <netproto/802_11/ieee80211_var.h> #include <netproto/802_11/ieee80211_radiotap.h> #include <bus/u4b/usb.h> #include <bus/u4b/usbdi.h> #include "opt_wlan.h" #include "if_mtwreg.h" #include "if_mtwvar.h" /* the real thing: /usr/src/sys/bus/u4b/wlan/if_mtwvar.h */ /* verbatim copy of mtw_setup_tx_list() - sys/bus/u4b/wlan/if_mtw.c:949-965 */ static void mtw_setup_tx_list(struct mtw_softc *sc, struct mtw_endpoint_queue *pq) { struct mtw_tx_data *data; memset(pq, 0, sizeof(*pq)); STAILQ_INIT(&pq->tx_qh); STAILQ_INIT(&pq->tx_fh); for (data = &pq->tx_data[0]; data < &pq->tx_data[MTW_TX_RING_COUNT]; data++) { data->sc = sc; STAILQ_INSERT_TAIL(&pq->tx_fh, data, next); } pq->tx_nfree = MTW_TX_RING_COUNT; } #define GUARD (256 * 1024) static void report(void) { size_t scsz = sizeof(struct mtw_softc); size_t pqsz = sizeof(struct mtw_endpoint_queue); size_t epq_off = __offsetof(struct mtw_softc, sc_epq); uint8_t *buf; struct mtw_softc *sc; size_t i, clobber = 0, firstbad = 0; int q; kprintf("mtwprobe: MTW_BULK_RX=%d MTW_EP_QUEUES=%d MTW_TX_RING_COUNT=%d\n", MTW_BULK_RX, MTW_EP_QUEUES, MTW_TX_RING_COUNT); kprintf("mtwprobe: sizeof(struct mtw_softc) = %zu\n", scsz); kprintf("mtwprobe: sizeof(struct mtw_endpoint_queue) = %zu\n", pqsz); kprintf("mtwprobe: sizeof(sc->sc_epq) as declared = %zu <-- zero-length array!\n", sizeof(((struct mtw_softc *)0)->sc_epq)); kprintf("mtwprobe: offsetof(struct mtw_softc, sc_epq) = %zu\n", epq_off); kprintf("mtwprobe: offsetof(rfswitch) [next member] = %zu (== sc_epq base)\n", __offsetof(struct mtw_softc, rfswitch)); kprintf("mtwprobe: 6 queues need %zu bytes from offset %zu -> end %zu; softc is only %zu bytes -> %zd bytes PAST the allocation\n", pqsz * MTW_EP_QUEUES, epq_off, epq_off + pqsz * MTW_EP_QUEUES, scsz, (ssize_t)(epq_off + pqsz * MTW_EP_QUEUES - scsz)); buf = kmalloc(scsz + GUARD, M_TEMP, M_WAITOK | M_ZERO); memset(buf + scsz, 0xAA, GUARD); sc = (struct mtw_softc *)buf; /* exactly what mtw_init_locked() does at if_mtw.c:4466-4467 */ for (q = 0; q != MTW_EP_QUEUES; q++) mtw_setup_tx_list(sc, &sc->sc_epq[q]); for (i = 0; i < GUARD; i++) { if (buf[scsz + i] != 0xAA) { if (clobber == 0) firstbad = i; clobber++; } } kprintf("mtwprobe: simulated ifconfig-up stamping clobbered %zu bytes past sizeof(struct mtw_softc) (first at +%zu)\n", clobber, firstbad); /* ---- alias mapping: which live softc fields fall inside the epq region? ---- */ struct { const char *n; size_t o; } fields[] = { {"rfswitch", __offsetof(struct mtw_softc, rfswitch)}, {"txpow1", __offsetof(struct mtw_softc, txpow1)}, {"lna", __offsetof(struct mtw_softc, lna)}, {"txpow20mhz", __offsetof(struct mtw_softc, txpow20mhz)}, {"sc_chan_group", __offsetof(struct mtw_softc, sc_chan_group)}, {"cmd_seq", __offsetof(struct mtw_softc, cmd_seq)}, {"sc_detached", __offsetof(struct mtw_softc, sc_detached)}, {"sc_mcu", __offsetof(struct mtw_softc, sc_mcu)}, {"rxq", __offsetof(struct mtw_softc, rxq)}, {"txq", __offsetof(struct mtw_softc, txq)}, {"ratectl_task", __offsetof(struct mtw_softc, ratectl_task)}, {"ratectl_ch", __offsetof(struct mtw_softc, ratectl_ch)}, {"sc_bssid", __offsetof(struct mtw_softc, sc_bssid)}, {"sc_rxtapu", __offsetof(struct mtw_softc, sc_rxtapu)}, {"sc_txtapu", __offsetof(struct mtw_softc, sc_txtapu)}, }; size_t epq_end = epq_off + pqsz * MTW_EP_QUEUES; for (i = 0; i < sizeof(fields)/sizeof(fields[0]); i++) { const char *where; if (fields[i].o < epq_off) where = "BEFORE epq region"; else if (fields[i].o < epq_end) { size_t rel = fields[i].o - epq_off; size_t q = rel / pqsz; size_t in = rel % pqsz; size_t ent = in / sizeof(struct mtw_tx_data); size_t off = in % sizeof(struct mtw_tx_data); kprintf("mtwprobe: LIVE FIELD %-14s @+%6zu INSIDE epq[%zu] tx_data[%zu] + %zu (%s)\n", fields[i].n, fields[i].o, q, ent, off, off < 16 ? "STAILQ next ptr" : off < 24 ? "m (mbuf*)" : off < 32 ? "sc ptr" : off < 40 ? "xfer ptr" : off < 44 ? "qid/ridx" : off < 48 ? "buflen" : off < 56 ? "ni (node*) !" : "desc[]"); continue; } else where = "after epq region (safe)"; kprintf("mtwprobe: live field %-14s @+%6zu %s\n", fields[i].n, fields[i].o, where); } kfree(buf, M_TEMP); /* ---- forge simulation with RAW byte addresses (no member access) ---- */ { uint8_t *p = (uint8_t *)sc; void **ni_p = (void **)(p + __offsetof(struct mtw_softc, sc_epq) + 3 * sizeof(struct mtw_tx_data) + 48); /* epq[0].tx_data[3].ni */ kprintf("mtwprobe: raw check: &sc_chan_group=%p epq[0].tx_data[3].ni at %p (same? %d)\n", (void *)(p + __offsetof(struct mtw_softc, sc_chan_group)), (void *)ni_p, (size_t)ni_p + 4 == (size_t)(p + __offsetof(struct mtw_softc, sc_chan_group))); /* idle case: post-stamp ni == NULL; mtw_set_chan() writes group */ kprintf("mtwprobe: idle: ni before = %p\n", *ni_p); sc->sc_chan_group = 2; /* mtw_set_chan(), if_mtw.c:3758 */ kprintf("mtwprobe: idle: ni after sc_chan_group=2 -> %p <-- forged node ptr\n", *ni_p); /* in-flight case: entry holds a real node pointer */ *ni_p = (void *)0xfffff800deadbeefULL; sc->sc_chan_group = 3; kprintf("mtwprobe: in-flight: ni was 0xfffff800deadbeef, after sc_chan_group=3 -> %p\n", *ni_p); kprintf("mtwprobe: this pointer is passed to ieee80211_tx_complete()/ieee80211_free_node() on TX completion.\n"); } kfree(buf, M_TEMP); } static int mtwprobe_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: report(); break; default: break; } return (0); } static moduledata_t mtwprobe_mod = { "mtwprobe", mtwprobe_modevent, NULL }; DECLARE_MODULE(mtwprobe, mtwprobe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |