⬢ DragonFlyBSD Kernel Audit
DF-4569 / dfioctl_rep.c
← back to finding ↓ download raw
/*
 * dfioctl_rep.c -- kernel replica of the DragonFly DRM ioctl dispatch
 * semantics that matter for DF-4569.
 *
 * The QEMU guest has no Intel GPU, so /dev/dri/card0 cannot exist and
 * sys/dev/drm/drm_ioctl.c:drm_ioctl() itself cannot run there.  This module
 * reproduces EXACTLY the two dispatch properties under test:
 *
 *   (1) drm_ioctl() dispatches on nr = DRM_IOCTL_NR(cmd) == (cmd & 0xff)
 *       only.  The group byte, the IOC direction bits and -- critically --
 *       the IOCPARM_LEN(cmd) size bits of the user-supplied command are
 *       never examined (sys/dev/drm/drm_ioctl.c:809, 819-831, 847, 854).
 *
 *   (2) The kernel argument buffer (`data') handed to the handler is the
 *       buffer allocated by mapped_ioctl() in sys/kern/sys_generic.c,
 *       sized from the USER-controlled size bits:
 *         sys/kern/sys_generic.c:668  size = IOCPARM_LEN(com);
 *         sys/kern/sys_generic.c:674  kmalloc(size) only when size > 128
 *                                    and (com & IOC_VOID) == 0;
 *                                    otherwise data = 128-byte stack buf.
 *
 * It registers /dev/dfioctl with two handlers that are byte-for-byte
 * faithful replicas of the corresponding DRM core handlers:
 *
 *   nr 0x02 (DRM_IOCTL_GET_MAGIC / drm_getmagic, drm_auth.c:58-76):
 *          writes 4 bytes at offset 0, returns 0 -- like drm_getmagic,
 *          which only writes auth->magic and returns 0 on success.
 *   nr 0x06 (DRM_IOCTL_GET_STATS / drm_getstats, drm_ioctl.c:215-224):
 *          memset(data, 0, sizeof(struct drm_stats) == 248) and return 0.
 *
 * DF-4569: DragonFly port of drm_ioctl() dropped the upstream
 * canonical-size protection (Linux v4.7 drivers/gpu/drm/drm_ioctl.c:
 * "drv_size = _IOC_SIZE(ioctl->cmd); usize = _IOC_SIZE(cmd);
 *  asize = max(usize, drv_size); cmd = ioctl->cmd;").
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <sys/conf.h>

/* exact copies of the values used by the DRM core */
#define REP_GET_MAGIC_NR	0x02	/* DRM_IOCTL_GET_MAGIC  */
#define REP_GET_STATS_NR	0x06	/* DRM_IOCTL_GET_STATS  */
#define REP_DRM_STATS_SIZE	248	/* sizeof(struct drm_stats) on LP64:
					 * 8 + 15 * {unsigned long; enum} = 248 */

static int
dfioctl_rep_ioctl(struct dev_ioctl_args *ap)
{
	unsigned long cmd = ap->a_cmd;
	caddr_t data = ap->a_data;

	/* drm_ioctl(): "unsigned int nr = DRM_IOCTL_NR(cmd)" -- low byte only.
	 * NOTHING in drm_ioctl() looks at the group, direction or size bits. */
	switch (cmd & 0xff) {
	case REP_GET_MAGIC_NR:
		/* drm_getmagic() writes auth->magic (int, offset 0) and
		 * returns 0.  It reads no other input field. */
		*(int *)data = 0x5a5a5a5a;
		return 0;

	case REP_GET_STATS_NR:
		/* drm_getstats(): "memset(stats, 0, sizeof(*stats))" into the
		 * buffer mapped_ioctl() sized from the user's size bits. */
		memset(data, 0, REP_DRM_STATS_SIZE);
		return 0;

	default:
		return EINVAL;	/* like a hole in the drm_ioctls[] table */
	}
}

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

static struct dev_ops dfioctl_ops = {
	{ "dfioctl", 0, D_MPSAFE },
	.d_open = dfioctl_rep_open,
	.d_ioctl = dfioctl_rep_ioctl,
};

static cdev_t dfioctl_dev;

static int
dfioctl_rep_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		/* world-accessible so the unprivileged PoC can run */
		dfioctl_dev = make_dev(&dfioctl_ops, 0, UID_ROOT, GID_WHEEL,
				       0666, "dfioctl");
		kprintf("dfioctl_rep: loaded (DRM dispatch replica)\n");
		return 0;
	case MOD_UNLOAD:
		destroy_dev(dfioctl_dev);
		kprintf("dfioctl_rep: unloaded\n");
		return 0;
	default:
		return 0;
	}
}

DEV_MODULE(dfioctl_rep, dfioctl_rep_modevent, NULL);