⬢ DragonFlyBSD Kernel Audit
DF-3274 / df3274.c
← back to finding ↓ download raw
/*
 * DF-3274 harness -- sppp TX accounting reads m->m_pkthdr.len AFTER
 * m_freem(m) on the control-queue-full branch.
 *
 * VERBATIM if_spppsubr.c (all three sites, e.g. sppp_cp_send :1429-1438):
 *	if (IF_QFULL (&sp->pp_cpq)) {
 *		IF_DROP (&sp->pp_fastq);
 *		m_freem (m);
 *		IFNET_STAT_INC(ifp, oerrors, 1);
 *	} else
 *		IF_ENQUEUE (&sp->pp_cpq, m);
 *	ifsq = ...; (*ifp->if_start) (ifp, ifsq);
 *	IFNET_STAT_INC(ifp, obytes, m->m_pkthdr.len + 3);   <-- read after free
 *
 * A peer fills pp_cpq (IFQ_MAXLEN=50 deep) and any further control
 * transmit takes the free branch; the obytes accounting then reads the
 * freed mbuf header (stale-value read feeding a userspace-visible
 * counter: netstat -i).  Guard-page demonstration of the post-free read.
 */
#include "guard.h"

struct pkthdr_h { int len; };
struct mbufh { struct pkthdr_h m_pkthdr; };
static struct mbufh *m;
static long obytes;
static int qfull = 1;

static void IFNET_STAT_INC_OBYTES(int v);
static void cp_send_tail(void);


static void
m_freem_(void *p)
{
	/* emulate: chunk returned to the slab free list; guard it so the
	 * post-free read is caught (in-kernel: stale/recycled memory) */
	mprotect((void *)((unsigned long)p & ~4095UL), 4096, PROT_NONE);
}

/* verbatim tail of sppp_cp_send (state/logging dropped) */
static void
cp_send_tail(void)
{
	if (qfull) {
		/* IF_DROP */
		m_freem_(m);			/* m_freem (m); */
		/* IFNET_STAT_INC(ifp, oerrors, 1); */
	} /* else IF_ENQUEUE */
	/* (*ifp->if_start) (ifp, ifsq); */
	IFNET_STAT_INC_OBYTES(m->m_pkthdr.len + 3);
}

static void
IFNET_STAT_INC_OBYTES(int v) { obytes += v; }

static void
qfull_path(int len_)			/* FIXED: length captured pre-free */
{
	m_freem_(m);
	IFNET_STAT_INC_OBYTES(len_ + 3);
}

int
main(int argc, char **argv)
{
	int uaf = (argc > 1 && argv[1][0] == 'u');

	printf("DF-3274 (obytes accounting reads freed mbuf, if_spppsubr.c"
	    " :1372-1380/:1429-1438/:4672-4681)\n");
	m = (struct mbufh *)guard_buf(sizeof(*m));
	m->m_pkthdr.len = 26;
	printf("  control queue full: m_freem(m), then obytes +="
	    " m->m_pkthdr.len + 3\n");
	if (!uaf) {
		/* control: read BEFORE the free (as the fix would) */
		int len = m->m_pkthdr.len;
		m_freem_(m);
		IFNET_STAT_INC_OBYTES(len + 3);
		printf("  ran (fixed order), obytes=%ld\n", obytes);
		return 0;
	}
	fflush(stdout);
#ifdef FIXED
	/* DF-3274 fix: capture the length before the free */
	{
		int len_ = m->m_pkthdr.len;
		printf("FIXED: length captured before free (len=%d)\n", len_);
		qfull_path(len_);
		return 0;
	}
#endif
	cp_send_tail();			/* verbatim order: free, then read */
	printf("  ran?!\n");
	return 0;
}