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