DF-5795 / mbuf_probe.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 | /* * mbuf_probe.c — pass-3 mbuf protocol invariant probe for * sys/kern/uipc_mbuf.c (DF-5795 / DF-5796). * * DF-5795: m_move_pkthdr() does not clear M_PKTHDR on the source mbuf, * contradicting the documented contract in sys/sys/mbuf.h * ("from no longer has a pkthdr after this operation") and * FreeBSD's macro semantics. The probe shows the source keeps * M_PKTHDR + a live stale pkthdr.len which m_adj() then mutates * on a mid-chain mbuf. * * DF-5796: _m_copyback2()/m_copyback() have no M_WRITABLE() gate: they * bcopy through shared (refcount>1) clusters. The probe makes * two mbufs share one cluster via m_copym(), writes through the * copy with m_copyback(), and reads the mutation through the * original. * * Build: make -m /usr/src/share/mk SYSDIR=/usr/src/sys * Run: kldload ./mbuf_probe.ko ; dmesg | tail * Needs: root to kldload (the production trigger is any network packet; * the KLD only makes the invariant break deterministic). */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/mbuf.h> #include <sys/malloc.h> #include <sys/systm.h> #include <sys/libkern.h> static void probe_5795(void) { struct mbuf *from, *from2, *to; int stale_before, bug; /* * Build a realistic 2-mbuf pkthdr chain: 10 + 20 = pkthdr.len 30. */ from = m_gethdr(M_WAITOK, MT_DATA); from2 = m_get(M_WAITOK, MT_DATA); to = m_gethdr(M_WAITOK, MT_DATA); from->m_len = 10; from2->m_len = 20; from->m_next = from2; from->m_pkthdr.len = 30; memset(mtod(from, void *), 0x11, 10); kprintf("DF5795: before move : from.flags=%08x from.pkthdr.len=%d " "to.flags=%08x\n", from->m_flags, from->m_pkthdr.len, to->m_flags); M_MOVE_PKTHDR(to, from); /* the documented "move" */ kprintf("DF5795: after move : from.flags=%08x from.pkthdr.len=%d " "M_PKTHDR-on-from=%d | to.flags=%08x to.pkthdr.len=%d\n", from->m_flags, from->m_pkthdr.len, !!(from->m_flags & M_PKTHDR), to->m_flags, to->m_pkthdr.len); bug = (from->m_flags & M_PKTHDR) != 0; kprintf("DF5795: %s: source mbuf still carries M_PKTHDR after " "M_MOVE_PKTHDR (contract: sys/sys/mbuf.h \"from no longer " "has a pkthdr\")\n", bug ? "REPRODUCED" : "NOT REPRODUCED"); /* * Consequence: the stale pkthdr is live mid-chain. m_adj() on the * moved-from mbuf now mutates a packet-header that officially does * not exist any more (FreeBSD: impossible, flag is cleared there). * This is exactly what icmp6.c:559 does (M_MOVE_PKTHDR then * m_adj(n0, off + sizeof(struct icmp6_hdr))). */ stale_before = from->m_pkthdr.len; m_adj(from, 8); kprintf("DF5795: m_adj(from, 8) mutated the 'moved' pkthdr.len: " "%d -> %d (%s)\n", stale_before, from->m_pkthdr.len, from->m_pkthdr.len != stale_before ? "MUTATED" : "unchanged"); to->m_next = from; /* reassemble: to -> from -> from2 */ m_freem(to); } static void probe_5796(void) { struct mbuf *a, *b; char evil[16]; u_char first; int sc; /* * a = pkthdr mbuf with its own cluster, filled with 0x41. * b = m_copym() deep-ish copy: shares a's cluster (ext_ref), the * standard read-only sharing used all over the network stack. */ a = m_getcl(M_WAITOK, MT_DATA, M_PKTHDR); if (a == NULL || (a->m_flags & M_EXT) == 0) { kprintf("DF5796: setup failed (no cluster)\n"); return; } memset(mtod(a, void *), 0x41, 32); a->m_len = 32; a->m_pkthdr.len = 32; b = m_copym(a, 0, M_COPYALL, M_WAITOK); if (b == NULL) { kprintf("DF5796: m_copym failed\n"); m_freem(a); return; } sc = m_sharecount(a); kprintf("DF5796: cluster shared, m_sharecount(a)=%d (M_WRITABLE=%d)\n", sc, M_WRITABLE(a)); /* * Write 16 bytes of 0x5A through the SHARED cluster via m_copyback. * No M_WRITABLE gate exists anywhere on this path * (uipc_mbuf.c:_m_copyback2 second loop). */ memset(evil, 0x5A, sizeof(evil)); m_copyback(b, 0, sizeof(evil), evil); first = *mtod(a, u_char *); /* read through the OTHER mbuf */ kprintf("DF5796: %s: m_copyback through shared cluster rewrote the " "other mbuf's data: a[0]=0x%02x (expected 0x41)\n", first == 0x5A ? "REPRODUCED" : "NOT REPRODUCED", first); m_freem(a); m_freem(b); } static int mbuf_probe_modevent(module_t mod __unused, int type, void *data __unused) { switch (type) { case MOD_LOAD: kprintf("mbuf_probe: loaded, probing mbuf core invariants\n"); probe_5795(); probe_5796(); kprintf("mbuf_probe: done\n"); return (0); case MOD_UNLOAD: kprintf("mbuf_probe: unloaded\n"); return (0); default: return (EOPNOTSUPP); } } static moduledata_t mbuf_probe_mod = { "mbuf_probe", mbuf_probe_modevent, NULL }; DECLARE_MODULE(mbuf_probe, mbuf_probe_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE); |