DF-3307 / pfildemo.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 | /* * pfildemo.c -- DF-3307 reproducer module. * * Registers an AF_INET6 PFIL_OUT hook that exercises the *documented* * pfil_run_hooks() mbuf-replacement path (sys/net/pfil.c): on the one * big probe packet it deep-copies the chain, frees the old chain and * hands the copy back with rv == 0 -- exactly what a normalizing * firewall hook does. ip6_output() keeps using its cached * exthdrs.ip6e_hbh/dest1/rthdr pointers afterwards, which now point * into the freed chain (fragment path, sys/netinet6/ip6_output.c). * * To make the use-after-free observable, the hook also: * - reclaims the just-freed hop-by-hop cluster (same cpu, LIFO * cluster cache) and plants a 0xAA canary in its first byte; * a stale READ of that byte by ip6_output() then shows up as * ip6f_nxt == 0xAA in the emitted fragments; * - keeps the cluster stashed so that the stale WRITE * (*nxt = IPPROTO_FRAGMENT, 44) can be read back live via * sysctl pfildemo.stash_byte after the send. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/mbuf.h> #include <sys/malloc.h> #include <sys/sysctl.h> #include <sys/systm.h> #include <net/if.h> #include <net/pfil.h> #include <netinet/in.h> #include <netinet/ip6.h> extern struct pfil_head inet6_pfil_hook; #define CANARY 0xAA static struct sysctl_ctx_list pfildemo_ctx; static struct sysctl_oid *pfildemo_root; static int pfildemo_hits; /* packets seen by the hook */ static int pfildemo_replaced; /* chains replaced (deep copy) */ static struct mbuf *pfildemo_stash; /* reclaimed hbh cluster */ static struct mbuf * pfildemo_deepcopy(struct mbuf *m0) { struct mbuf *top = NULL, **tp = &top, *n, *m; int first = 1; for (m = m0; m != NULL; m = m->m_next) { if (first) { n = m_gethdr(M_NOWAIT, m->m_type); if (n == NULL) goto fail; n->m_pkthdr = m->m_pkthdr; n->m_pkthdr.rcvif = NULL; n->m_flags = (n->m_flags & ~M_COPYFLAGS) | (m->m_flags & M_COPYFLAGS); } else { n = m_getcl(M_NOWAIT, m->m_type, 0); if (n == NULL) goto fail; } n->m_len = m->m_len; bcopy(mtod(m, caddr_t), mtod(n, caddr_t), m->m_len); *tp = n; tp = &n->m_next; first = 0; } return (top); fail: if (top != NULL) m_freem(top); return (NULL); } static int pfildemo_hook(void *arg, struct mbuf **mp, struct ifnet *ifp, int dir) { struct mbuf *m = *mp, *n, *tmp; struct mbuf *cands[192]; int ncand = 0, i, found = 0; u_char *d; pfildemo_hits++; /* Transform exactly one packet: the >60000-byte probe. */ if (pfildemo_replaced || (m->m_flags & M_PKTHDR) == 0 || m->m_pkthdr.len < 60000) return (0); pfildemo_replaced = 1; n = pfildemo_deepcopy(m); if (n == NULL) return (0); /* leave chain untouched */ m_freem(m); *mp = n; /* * Reclaim the freed hbh cluster: 2048-byte cluster whose * data[] begins with {IPPROTO_UDP(17), 255}. Plant canary. */ for (i = 0; i < 192; i++) { tmp = m_getcl(M_NOWAIT, MT_DATA, 0); if (tmp == NULL) break; d = mtod(tmp, u_char *); if (!found && d[0] == 17 && d[1] == 255) { found = 1; d[0] = CANARY; /* canary for the stale READ */ pfildemo_stash = tmp; continue; /* keep */ } cands[ncand++] = tmp; } for (i = 0; i < ncand; i++) m_free(cands[i]); return (0); } static int pfildemo_read_stash(SYSCTL_HANDLER_ARGS) { int val = -1; if (pfildemo_stash != NULL) val = *(u_char *)pfildemo_stash->m_data; return (sysctl_handle_int(oidp, &val, 0, req)); } static int pfildemo_modevent(module_t mod, int type, void *data) { int error = 0; switch (type) { case MOD_LOAD: sysctl_ctx_init(&pfildemo_ctx); pfildemo_root = SYSCTL_ADD_NODE(&pfildemo_ctx, &sysctl__children, OID_AUTO, "pfildemo", CTLFLAG_RW, 0, "DF-3307 demo hook"); SYSCTL_ADD_INT(&pfildemo_ctx, SYSCTL_CHILDREN(pfildemo_root), OID_AUTO, "hits", CTLFLAG_RD, &pfildemo_hits, 0, ""); SYSCTL_ADD_INT(&pfildemo_ctx, SYSCTL_CHILDREN(pfildemo_root), OID_AUTO, "replaced", CTLFLAG_RD, &pfildemo_replaced, 0, ""); SYSCTL_ADD_PROC(&pfildemo_ctx, SYSCTL_CHILDREN(pfildemo_root), OID_AUTO, "stash_byte", CTLTYPE_INT | CTLFLAG_RD, NULL, 0, pfildemo_read_stash, "I", "hbh cluster data[0]: 0xAA untouched, 44 = stale write"); error = pfil_add_hook(pfildemo_hook, NULL, PFIL_OUT, &inet6_pfil_hook); if (error) kprintf("pfildemo: pfil_add_hook: %d\n", error); break; case MOD_UNLOAD: pfil_remove_hook(pfildemo_hook, NULL, PFIL_OUT, &inet6_pfil_hook); if (pfildemo_stash != NULL) m_free(pfildemo_stash); sysctl_ctx_free(&pfildemo_ctx); break; default: error = EOPNOTSUPP; break; } return (error); } DECLARE_MODULE(pfildemo, pfildemo_modevent, SI_SUB_PROTO_DOMAIN, SI_ORDER_ANY); |