⬢ DragonFlyBSD Kernel Audit
DF-5796 / mbuf_probe.c
← back to finding ↓ download raw
/*
 * 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);